CSV(Comma-Separated Values)作为数据科学领域的"通用货币",其重要性怎么强调都不为过。我在金融数据分析岗位工作的五年间,处理过的CSV文件体积从几KB的配置表到上百GB的交易记录不等。这种以纯文本形式存储表格数据的简单格式,因其跨平台、易读写的特性,至今仍是数据交换的首选载体。
初学者常误以为CSV处理就是简单的字符串分割,实则暗藏玄机。比如欧洲地区常用分号作为分隔符,某些包含逗号的字段需要用引号包裹,还有各种字符编码问题。上周我还遇到一个案例:某电商平台的订单CSV里,用户留言字段包含换行符导致解析失败。这些实际场景中的"坑",正是本教程要重点攻克的对象。
Python内置的csv模块就像瑞士军刀——小巧但功能完备。先看最基础的写入操作:
python复制import csv
with open('products.csv', 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
writer.writerow(['商品ID', '名称', '价格']) # 表头
writer.writerow([1001, 'Python编程书', 89.9])
writer.writerows([
[1002, '机械键盘', 299],
[1003, '无线鼠标', 159]
])
关键细节说明:
newline=''参数在Windows下避免出现空行utf-8-sig编码可生成带BOM头的文件,方便Excel直接识别writerows()比循环调用writerow()效率高30%以上读取时更要注意异常处理:
python复制def safe_read_csv(filepath):
try:
with open(filepath, encoding='utf-8-sig') as f:
return list(csv.reader(f))
except UnicodeDecodeError:
with open(filepath, encoding='gbk') as f: # 尝试中文常见编码
return list(csv.reader(f))
当处理不同来源的CSV时,csv.register_dialect()能解决90%的格式兼容问题:
python复制csv.register_dialect('european', delimiter=';', quoting=csv.QUOTE_ALL)
with open('euro_data.csv', newline='') as f:
reader = csv.reader(f, dialect='european')
for row in reader:
process_data(row)
常见方言配置组合:
quoting=csv.QUOTE_NONNUMERICdelimiter='\t'escapechar='\\'当CSV超过100MB时,就需要Pandas的高级功能了。这个300MB销售数据的处理案例很典型:
python复制chunk_size = 100000 # 10万行一个块
result = []
for chunk in pd.read_csv('sales.csv',
chunksize=chunk_size,
dtype={'product_id': 'str'}, # 防止ID被误转为数字
parse_dates=['order_time']):
chunk = chunk[chunk['amount'] > 0] # 过滤无效数据
result.append(chunk.groupby('product_id').sum())
final_df = pd.concat(result)
内存优化技巧:
dtype避免类型推断耗内存usecols只加载必要列category类型Pandas的链式方法非常适合数据清洗:
python复制df = (pd.read_csv('dirty_data.csv')
.rename(columns=str.lower)
.assign(create_date=lambda x: pd.to_datetime(x['date']))
.drop(columns=['date'])
.query('price > 0')
.pipe(lambda x: x.fillna(x.mean()))
)
使用10万行测试数据对比(单位:秒):
| 操作 | csv模块 | Pandas | Dask |
|---|---|---|---|
| 读取 | 1.2 | 0.8 | 0.5 |
| 过滤 | 3.1 | 0.3 | 0.2 |
| 聚合计算 | 4.5 | 0.4 | 0.3 |
| 写入 | 1.5 | 1.0 | 0.8 |
UnicodeDecodeError
encoding='utf-8'/gbk/latin1ParserError
error_bad_lines=False跳过或预处理文件内存不足
chunksize或dask.dataframe批量导入MySQL的优化方案:
python复制from sqlalchemy import create_engine
engine = create_engine('mysql://user:pass@localhost/db')
chunksize = 10000
for chunk in pd.read_csv('big_data.csv', chunksize=chunksize):
chunk.to_sql('target_table', engine, if_exists='append', index=False)
这个日志分析脚本我用了三年:
python复制import pandas as pd
from pathlib import Path
def process_logs(dir_path):
dfs = []
for f in Path(dir_path).glob('*.csv'):
df = pd.read_csv(f, parse_dates=['timestamp'])
df['source_file'] = f.name
dfs.append(df)
full_df = pd.concat(dfs)
error_stats = (full_df[full_df['level'] == 'ERROR']
.groupby(['date', 'module'])
.size()
.unstack())
error_stats.to_csv('error_report.csv')
编码检测技巧
chardet库检测未知编码:python复制import chardet
with open('unknown.csv', 'rb') as f:
result = chardet.detect(f.read(10000))
print(result['encoding'])
内存监控方法
python复制import psutil
def mem_usage():
return psutil.Process().memory_info().rss / 1024 / 1024
性能优化黄金法则
最后分享一个真实案例:曾处理过包含2000万行的CSV,原始脚本需要2小时,通过优化后的方案(Pandas分块+指定dtype+多进程)最终只需8分钟。这让我深刻体会到——CSV处理不是简单的IO操作,而是需要系统化思维的数据工程。