【python】批量读取CSV文件资源不足处理解决思路与方式

“”"
背景:每月读取指定文件夹下的批量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世界中。

感觉中间有些还可以再调整调整,后面空的时候再来补充完善吧

路漫漫其修远兮,吾将上下而求索~~

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值