Python处理百万级CSV/Excel文件:告别卡顿,实战Pandas高效读写技巧

77次阅读
没有评论

共计 5887 个字符,预计需要花费 15 分钟才能阅读完成。

上周帮同事处理一份几十万行的日志数据,他还在用 open() 然后一行行split(),跑了半个小时还没结果。我一看这架势,赶紧推荐他用 Pandas。今天就带大家看看,面对大量数据时,Python 如何优雅高效地处理 CSV/Excel 文件,告别漫长等待。

为什么原生 Python 处理大文件会慢?

在咱们日常处理数据时,如果文件不大,直接用 Python 内置的文件操作确实方便快捷。但当数据量飙升到几十万、上百万行甚至更大时,纯 Python 的字符串操作、循环解析就会成为性能瓶颈。

主要原因有两点:

  1. I/ O 操作耗时:文件越大,从磁盘读取数据到内存的时间就越长。
  2. Python 解释器开销:Python 的动态特性和全局解释器锁(GIL)使得它在处理大量纯计算或字符串操作时效率不如 C /C++ 等底层语言。而 Pandas 底层是用 C /C++ 实现的,能充分利用多核优势,对数据操作进行了大量优化。

所以,面对大数据,咱们得找个“重型武器”——Pandas。

方案一:Python 原生文件操作 (基础但低效)

这种方法对于几千行的小文件来说没问题,但超过几万行就得考虑效率了。

import time

def process_small_csv_native(filepath):
    start_time = time.time()
    data = []
    with open(filepath, 'r', encoding='utf-8') as f:
        # 这里加 try-except 是之前处理一些“脏数据”文件时,经常遇到 decode 错误,踩过坑才知道要防一手
        try:
            # 假设 CSV 是逗号分隔,第一行是标题
            header = f.readline().strip().split(',')
            for line in f:
                parts = line.strip().split(',')
                # 这里可以对 parts 进行一些基本处理
                data.append(parts)
        except UnicodeDecodeError as e:
            print(f"文件编码错误: {e}")
            return None
    end_time = time.time()
    print(f"原生 Python 处理 {len(data)} 行耗时: {end_time - start_time:.2f} 秒")
    return data

# 假设有个 large_data.csv 文件
# process_small_csv_native('large_data.csv')

小提醒: 这种方法简单直观,但如果你处理的文件有几十 MB 甚至上 GB,那可能就要等上半天了。它的主要问题在于每次迭代都是纯 Python 对象操作,没有底层优化。

方案二:CSV 模块 (略有改善,仍不够)

Python 内置的 csv 模块比手动 split() 更健壮,能更好地处理逗号、引号等特殊字符。但本质上,它仍然是一行一行地读取和解析,内存效率和处理速度提升有限。

import csv
import time

def process_csv_module(filepath):
    start_time = time.time()
    data = []
    with open(filepath, 'r', encoding='utf-8') as f:
        # csv.reader 能自动处理 CSV 的复杂格式,比如字段内的逗号和引号
        reader = csv.reader(f)
        header = next(reader) # 读取标题行
        for row in reader:
            data.append(row)
    end_time = time.time()
    print(f"CSV 模块处理 {len(data)} 行耗时: {end_time - start_time:.2f} 秒")
    return data

# process_csv_module('large_data.csv')

小提醒: csv模块确实比原生 split 更“智能”,处理标准 CSV 文件不容易出错。但对于内存和计算效率的提升,依然有限。它更适合中小型、格式相对复杂的 CSV 文件。

方案三:Pandas 高效处理 (重点推荐)

终于轮到我们的主角 Pandas 登场了!Pandas 是 Python 数据科学领域的核心库之一,它提供了高性能、易用的数据结构(如 DataFrame)和数据分析工具。对于大数据处理,Pandas 简直是神器。

第一步:高效读取大数据文件

Pandas 的 read_csvread_excel函数是处理大文件的利器,但你得学会用它们的“高级参数”。

读取 CSV 文件:

import pandas as pd
import time
import os

# 创建一个大型 CSV 文件用于测试 (可选,如果已有大文件可跳过)
# 我以前做数据分析时,经常自己生成模拟数据来测试代码性能,很实用
if not os.path.exists('test_large_data.csv'):
    print("生成测试文件...")
    data_size = 1_000_000 # 100 万行
    df_test = pd.DataFrame({'col1': range(data_size),
        'col2': [f'text_{i}' for i in range(data_size)],
        'col3': [i * 1.23 for i in range(data_size)],
        'col4': [True if i % 2 == 0 else False for i in range(data_size)]
    })
    df_test.to_csv('test_large_data.csv', index=False)
    print("测试文件生成完毕: test_large_data.csv")

def read_csv_with_pandas(filepath):
    start_time = time.time()
    # 最基本的读取,不推荐用于大文件
    # df = pd.read_csv(filepath)

    # 优化读取:指定 dtype,减少内存占用和读取时间
    # 如果不指定 dtype,Pandas 会尝试推断每一列的类型,这会消耗大量时间和内存。# 我刚开始用的时候就因为没指定类型,处理一个千万级文件直接爆内存,调试了半天。# 提前知道列的数据类型,能大大提升效率。dtype_dict = {
        'col1': int,
        'col2': str,
        'col3': float,
        'col4': bool
    }

    # 使用 chunksize 分块读取,避免一次性加载所有数据到内存,尤其适合超大文件
    # 比如你的文件有 10GB,内存只有 8GB,分块读取就能解决
    chunks = []
    for chunk in pd.read_csv(filepath, dtype=dtype_dict, chunksize=100_000): # 每批读取 10 万行
        # 在这里可以对每个 chunk 进行独立处理或筛选,减少最终合并的数据量
        chunks.append(chunk)
    df = pd.concat(chunks, ignore_index=True) # 最后再合并所有 chunk

    end_time = time.time()
    print(f"Pandas 处理 {len(df)} 行耗时: {end_time - start_time:.2f} 秒")
    print(f"数据帧内存占用: {df.memory_usage(deep=True).sum() / (1024**2):.2f} MB")
    return df

df_csv = read_csv_with_pandas('test_large_data.csv')

小提醒: dtype参数是性能优化的关键!它能告诉 Pandas 每列的数据类型,省去了类型推断的时间,并能分配更精确的内存。chunksize则是处理超大文件,防止内存溢出的法宝。咱们不能一口气吃成个胖子,分批吃更健康。

读取 Excel 文件:

Excel 文件通常比 CSV 更复杂,因为它们可以有多个工作表、格式、公式等。Pandas 也能读,但处理超大 Excel 文件(比如几百 MB)时,性能可能会比 CSV 差不少。

# Excel 文件通常不适合百万级行,但如果非要处理,思路类似
# 这里就不生成大 Excel 文件了,因为它会非常慢且占用大量磁盘空间
# 如果你有一个大的 Excel 文件,比如 'large_data.xlsx'

# def read_excel_with_pandas(filepath):
#     start_time = time.time()
#     # engine='openpyxl' 是默认引擎,但对于非常大的文件,它可能会比较慢。#     # 如果你只需要部分列,用 usecols 会大大节省内存。#     # 如果只需要部分行,用 nrows 也很有用。#     df = pd.read_excel(filepath, engine='openpyxl', usecols=['ColumnA', 'ColumnB'])
#     end_time = time.time()
#     print(f"Pandas 读取 Excel {len(df)} 行耗时: {end_time - start_time:.2f} 秒")
#     return df

# read_excel_with_pandas('large_data.xlsx')

小提醒: Excel 文件本身结构复杂,不如 CSV 高效。若 Excel 文件真的非常大,我的经验是:能转 CSV 处理就转 CSV。如果实在不行,usecolsnrows 参数能有效减少加载到内存的数据量。若 Excel 有合并单元格,记得加header=None,不然会丢数据(别问我怎么知道的,全是泪)。

第二步:内存优化与数据处理

数据读进来之后,如何高效地进行处理也是一门学问。

def process_data_with_pandas(df):
    print("n--- 数据处理与优化 ---")
    # 查看当前数据帧的内存占用和数据类型
    print("原始数据帧信息:")
    df.info(memory_usage='deep')

    # 优化列类型:将能用更小类型表示的列进行转换
    # 比如一个 int 列所有值都在 0 -127 之间,可以转成 int8
    df['col1'] = df['col1'].astype('int32') # 假设 col1 范围不大,可以用更小的 int32
    df['col4'] = df['col4'].astype('category') # 布尔值或有限类别的数据,转 category 能省很多内存

    # 筛选数据,只保留我们关心的部分
    df_filtered = df[df['col1'] > 500_000]

    # 选择需要的列,进一步减少内存
    df_selected = df_filtered[['col1', 'col3']]

    print("n 优化后数据帧信息:")
    df_selected.info(memory_usage='deep')

    # 进行一些简单的计算
    df_selected['col3_squared'] = df_selected['col3'] ** 2
    return df_selected

df_processed = process_data_with_pandas(df_csv)

小提醒: 读进来不代表所有数据都要用。usecols在读取时就筛选,astype可以进一步压缩数据类型。特别是 category 类型,对于重复度高的字符串列(比如省份、性别),能大幅减少内存占用。df.info(memory_usage='deep')是检查内存占用的好帮手。

第三步:高效写入结果

处理完数据后,我们通常需要将结果保存下来。Pandas 的 to_csvto_excel同样提供了高效的写入方式。

def write_processed_data(df, output_filepath):
    print("n--- 写入处理后的数据 ---")
    start_time = time.time()
    # index=False 避免将 Pandas 生成的默认索引列也写入文件,这是我每次 to_csv 必加的参数
    # compression='gzip' 可以压缩输出文件,对于大文件非常有用,节省磁盘空间
    df.to_csv(output_filepath, index=False, compression='gzip')
    end_time = time.time()
    print(f"Pandas 写入 {len(df)} 行到'{output_filepath}'耗时: {end_time - start_time:.2f} 秒")
    print(f"压缩后的文件大小: {os.path.getsize(output_filepath) / (1024**2):.2f} MB")

write_processed_data(df_processed, 'processed_data.csv.gz')

小提醒: index=False是个好习惯,能避免写入不必要的索引列。写入大文件时,考虑 compression='gzip' 或其他压缩格式可以省下不少磁盘空间,同时读取时 Pandas 也能自动解压,非常方便。

常见误区

在我的开发生涯中,遇到或自己踩过不少关于 Pandas 处理大文件的坑,这里总结几个大家容易犯的错误:

  1. 误区一:不指定 dtype 类型,盲目让 Pandas 推断

    • 后果: 导致内存爆炸和读取缓慢,尤其数值和字符串混淆的列,Pandas 可能将其全部推断为object(字符串),占用大量内存。
    • 建议:read_csv 时,尽可能为已知列指定 dtype。不确定时,可以先读一小部分数据用df.info() 观察,或者在读取后用 df.convert_dtypes() 进行转换。
  2. 误区二:不了解 chunksizeusecols,盲目全量加载

    • 后果: 对于 GB 级别的文件,一次性加载到内存容易导致内存溢出,程序崩溃。
    • 建议: 如果文件大小远超你的内存,务必使用 chunksize 参数分块处理。如果只需要部分列,usecols参数能在读取时就筛选掉不必要的列,节省内存。
  3. 误区三:Excel 文件直接硬刚

    • 后果: Excel 文件因为其复杂的结构和格式信息,即使是同样的数据量,处理速度和内存占用都远高于 CSV。
    • 建议: 当遇到大尺寸 Excel 文件时,我的经验是:如果可能,先将 Excel 文件另存为 CSV 格式,再用 Pandas 处理 CSV。这样会高效得多。如果必须处理 Excel,那也要善用 usecolsnrows

经验总结

面对 Python 大数据处理,Pandas 是咱们的得力助手。通过熟练掌握 read_csvdtypechunksize,以及to_csvindex=Falsecompression 等参数,就能让效率飞跃。它就像一把瑞士军刀,用好了能省下大量时间。

大家在处理大文件时还遇到过哪些有意思的坑?欢迎在评论区分享你的经验!

正文完
 0
评论(没有评论)