Python 处理 10GB 超大 CSV 文件,我踩过的 5 个内存爆炸坑及终极解决方案
·
引言
上周接到一个需求,要处理一个 12GB 的用户行为日志 CSV 文件。一开始我想当然地用了pd.read_csv(),结果电脑直接卡死,内存飙升到 99%,强制重启后还丢了半天的工作。
后来花了整整两天时间,试了 N 种方法,踩了无数坑,终于找到了一套稳定高效的处理方案。今天把这些经验分享出来,希望能帮到同样遇到大文件处理问题的同学。
坑 1:直接用 pandas.read_csv () 一次性加载
这是最容易犯的错误。pandas 默认会把整个文件加载到内存中,对于超过可用内存的文件,直接 OOM(内存不足)。
错误代码:
python
运行
import pandas as pd
# 12GB文件,直接GG
df = pd.read_csv("user_behavior_12gb.csv")
解决方案:分块读取
python
运行
# 每次只读取10万行
chunk_size = 100000
chunks = pd.read_csv("user_behavior_12gb.csv", chunksize=chunk_size)
for i, chunk in enumerate(chunks):
print(f"处理第{i+1}块,共{len(chunk)}行")
# 在这里处理每一块数据
process_chunk(chunk)
坑 2:分块读取但没有指定数据类型
pandas 会自动推断数据类型,这不仅慢,还会占用更多内存。比如一个全是整数的列,可能会被推断为 float 类型,内存占用翻倍。
优化方案:显式指定 dtype
python
运行
dtype_dict = {
"user_id": "int32",
"item_id": "int32",
"behavior_type": "category",
"timestamp": "int64"
}
chunks = pd.read_csv(
"user_behavior_12gb.csv",
chunksize=chunk_size,
dtype=dtype_dict,
usecols=["user_id", "item_id", "behavior_type"] # 只读取需要的列
)
坑 3:使用 list.append () 累积结果
很多人会在循环中用 list.append () 来收集每块的处理结果,当结果量很大时,这同样会导致内存爆炸。
解决方案:边处理边写入
python
运行
# 不要这样做
# results = []
# for chunk in chunks:
# results.append(process_chunk(chunk))
# final_result = pd.concat(results)
# 正确做法:边处理边写入文件
with open("result.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["user_id", "click_count"])
for chunk in chunks:
chunk_result = process_chunk(chunk)
writer.writerows(chunk_result.values)
坑 4:忽略了 CSV 文件的编码问题
很多时候大文件是 GBK 编码或者其他编码,直接用默认的 utf-8 读取会报错,而且错误信息很不友好。
解决方案:先检测编码,再读取
python
运行
import chardet
# 先读取一小部分检测编码
with open("user_behavior_12gb.csv", "rb") as f:
raw_data = f.read(10000)
result = chardet.detect(raw_data)
encoding = result["encoding"]
print(f"检测到文件编码:{encoding}")
# 然后用检测到的编码读取
chunks = pd.read_csv("user_behavior_12gb.csv", chunksize=chunk_size, encoding=encoding)
坑 5:没有使用更高效的文件格式
CSV 是文本格式,读写都很慢,而且占用空间大。如果需要多次处理同一个文件,强烈建议转换成更高效的二进制格式。
终极优化:转换为 Parquet 格式
python
运行
# 第一次处理时转换
for i, chunk in enumerate(chunks):
chunk.to_parquet(f"temp/chunk_{i}.parquet", index=False)
# 后续处理直接读取Parquet文件,速度快10倍以上
import pyarrow.parquet as pq
table = pq.read_table("temp/")
df = table.to_pandas()
总结
处理大文件的核心思想就是 "分而治之" 和 "按需加载"。记住这几个原则:
- 永远不要一次性加载超过可用内存的数据
- 显式指定数据类型,只读取需要的列
- 边处理边写入,不要在内存中累积结果
- 优先使用二进制格式存储数据
最后给大家一个小建议:如果经常需要处理 GB 级别的数据,还是升级一下电脑内存吧,16G 是底线,32G 会舒服很多。
更多推荐
所有评论(0)