“”"
背景:每月读取指定文件夹下的批量csv文件,然后经过业务逻辑的处理最后调度到数据库中
问题:服务器资源有限,多次在调度的时候程序崩溃,或者直接超内存,运行多次才有那么一两次成功。
这种情况下必须优化代码才能将程序成功运行起来,不然人工处理极为繁琐。遂最近每天回家后上网查资料,想解决思路。
解决思路:
1、del 掉一些业务接口中冗余变量
2、使用gc模块在程序运行过程中主动进行垃圾回收
3、引入生成器,对于批量的csv文件分批读取再做业务处理
4、分析内存,合理设置内存占用,测试运行的临界区间
5、避免过量的cpu占用,防止单个进程占因占用超阈值而被服务器策略kill掉
注:业务代码逻辑就多种多样了,此处只提供整体处理思路
其中sys、psutil、tracemalloc在这次使用中都差不多,依据个人喜好来进行选择,
在下面的代码实际使用中依据场景进行删减或增加
“”"
import os # 用于取路径下文件
import sys # 用于测变量大小
import csv # 用于读csv文件
import gc # 回收机制
import psutil # 查看进程内存与cpu占用量
import tracemalloc # 跟踪内存 测大小
import numpy as np
import pandas as pd
def deal():
# 查看当前的垃圾回收设置
print('gc settings:', gc.get_threshold())
# 启动内存追踪
tracemalloc.start()
# 获取到当前进程对象
ps = psutil.Process()
# 设置文件放置路径
# path = r'/home/xxx/xxx' # linux服务器路径
path = r'D:\Study\python_pycharm\pydata-book\test\csv' # 假设该路径下存放每个月爬取到的某些公开网站信息
# 看看路径是否存在
if not os.path.exists(path):
return '该路径不存在,请检查该路径'
# 获取指定路径下文件
files_list = os.listdir(path)
files = pd.DataFrame(files_list,columns=['file_name'])
"""这里之所以引入pandas是因为我这里文件实际放置的不止我所需要的最新的那些文件,所以我会做一些筛选
实际应用场景中可以依据时间、后缀名、格式、名称关键字等等做剔除,再通过时间函数与split等等做抓取
此处不过多叙述"""
'''引入生成器'''
files_all = files['file_name']
def generator_csv(files_all):
# 遍历文件
for file in files_all.values:
print('file:',file)
# 获取完整绝对路径
file_path = path + '/' + file
csv_list = []
# 将csv内容获取出来
"""关于为什么不直接用pd.read_csv,其实这里是有一个坑的,一旦csv文件中存在某些奇奇怪怪的特殊字符,
那么就会报错,但是用csv去读的话其实是可以读出来的。读出来后,根据实际来处理即可"""
with open(file_path, encoding='utf-8') as csv_file:
reader = csv.DictReader(csv_file)
for row in reader:
csv_list.append(row)
# 组装成DataFrame形式利于我们做逻辑处理
df = pd.DataFrame(csv_list).iloc[:,:]
"""查看占用大小
此处可以灵活配置,根据服务器上你实际能够使用的来
如果内存不大,那么可以尝试一次多读几个csv文件,那么改下当前代码即可"""
df_size_sys = sys.getsizeof(df)
df_size_ps = ps.memory_info().rss
print(f'此处csv集占{df_size_sys/1024/1024} m')
print(f'此时内存占用{df_size_ps/1024/1024} m')
"""可以通过获取的数据内存大小,来作为临界阈值,
从而让每次读的文件刚刚到达时,就吐出数据,
同样是改代码,加内存判断,此处不做样例,只提供思路,实现并不难"""
yield df
# 打开自动垃圾回收功能,禁用调试输出
gc.enable()
gc.set_debug(False)
# new一个生成器
csv_files = generator_csv(files_all)
print('生成器csv_files大小:',sys.getsizeof(csv_files),'KB')
result = pd.DataFrame([])
for csv_file in csv_files:
# 获取程序当前内存分配的快照
sna = tracemalloc.take_snapshot()
print('单集csv_file大小:',sys.getsizeof(csv_file))
print(ps.memory_info().rss/1024/1024)
"""这中间执行业务逻辑代码,涉及各自复杂逻辑计算,复杂筛选"""
"""这中间执行业务逻辑代码,涉及各自复杂逻辑计算,复杂筛选"""
b = [11]
a = 'hello'
print(a)
# 删除不再使用的变量
del a,b
print('执行中csv_file值为:',csv_file)
res_np = pd.DataFrame(np.arange(12).reshape(3,4),columns=list('abcd'))
res = csv_file.copy().append(res_np)
# 查看cpu占用量,如果占用过多,则需要减少一次性处理的数据量
print(ps.cpu_percent(interval=1))
"""这中间执行业务逻辑代码,涉及各自复杂逻辑计算,复杂筛选"""
"""这中间执行业务逻辑代码,涉及各自复杂逻辑计算,复杂筛选"""
"""这中间执行业务逻辑代码,涉及各自复杂逻辑计算,复杂筛选"""
print(ps.memory_info().rss/1024/1024)
# 查看当前Python进程中对象的数量,以及对应的内存占用情况
print('Objects count:', len(gc.get_objects()))
print('Memory usage:', gc.get_stats()[0]['collected'] / (1024 * 1024), 'MB')
print(ps.memory_info().rss/1024/1024)
top = sna.statistics('lineno')
# top = sna.statistics('filename')
# 查看本次代码运行过程中内存占用前十的
for stat in top[:10]:
print(stat)
# 查看内存分配总量(也可以对我们的代码优化做一定的参考)
size = tracemalloc.get_traced_memory()[0]
print("Memory usage: {} bytes".format(size))
# 查看cpu占用量,如果占用过多,则需要减少一次性处理的数据量
print(ps.cpu_percent(interval=1))
result = result.append(res)
# 显式执行一次垃圾回收
gc.collect()
# 暂停
tracemalloc.stop()
# 关闭自动垃圾回收功能
# gc.disable()
return result
def etl_data(result):
"""调度又是另一些方法了,要连接数据库要用SQLAlchemy 以及一些专门连接不同数据库的包
具体的情况已经自己的情况来定。这个后面有空再梳理出来,
无非就是建立session(最好用jdbc模式,安全些)获取cursor
然后建模取表执行语句。注意如果一次性写入过多,可以拆分写入,这样可以避免内存不足"""
print('result值为:', result)
print("调度代码执行完毕,成功写入表xxx共xxx条")
if __name__=='__main__':
result = deal()
etl_data(result)
过程中有很多时候是人工看cpu占用与内存占用,看完之后自己再评估调试,大小并非一层不变,要根据实际的资源占用与控制台打印出来的数据监控,来控制数据处理的量。
以上就是一些思路与解决方式了,如有不完善或者有更好的思路,欢迎各位大大指出,我也还在努力探索python世界中。
感觉中间有些还可以再调整调整,后面空的时候再来补充完善吧
路漫漫其修远兮,吾将上下而求索~~