3. 文本数据读取:从TXT到PDF,大文件也不怕
数据清洗的第一步是什么?说白了,你得先把数据请进来。我见过不少新手,一上来就写清洗逻辑,结果文件都读不对,白忙活半天。这一章,咱们就把几种常见的文本格式——TXT、CSV、JSON、PDF——的读取方法捋一遍。最后再聊聊大文件怎么处理,这可是实战中的硬骨头。
核心要点:读取是清洗的前提。选对工具、注意编码、处理异常,这三件事做好了,后面就顺了。
3.1 TXT文件读取:最基础,也最容易踩坑
TXT文件看起来简单,但坑不少。我遇到过最典型的问题——编码。有一次客户给的数据,打开全是乱码,折腾了半天才发现是GBK编码,而我默认用了UTF-8。
Python里读取TXT,最常用的是内置的open()函数。记住,一定要指定编码。
# 基础读取
with open('data.txt', 'r', encoding='utf-8') as f:
content = f.read() # 一次性读完,适合小文件
# 按行读取,适合中等文件
with open('data.txt', 'r', encoding='utf-8') as f:
for line in f:
print(line.strip()) # strip()去掉换行符
# 读取所有行到列表
with open('data.txt', 'r', encoding='utf-8') as f:
lines = f.readlines() # 注意:大文件别用这个
我曾经踩过的坑:用readlines()读了一个2GB的日志文件,结果内存直接爆了。后来改用迭代器逐行处理,才解决问题。大文件千万别一次性读入内存。
3.2 CSV文件读取:别自己写解析,用标准库
CSV看起来简单,但自己写解析容易出问题。比如字段里包含逗号、引号怎么办?我见过有人用split(',')处理CSV,结果数据里有个字段值是"hello, world",直接裂开了。
Python标准库csv模块就是干这个的。它处理引号、转义、换行符都很靠谱。
import csv
# 读取CSV,返回列表
with open('data.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
header = next(reader) # 跳过表头
for row in reader:
print(row) # row是一个列表
# 读取为字典,表头自动作为key
with open('data.csv', 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
print(row['name'], row['age']) # 按列名访问
| 方法 | 返回类型 | 适用场景 |
|---|---|---|
csv.reader |
列表 | 不需要列名,按位置访问 |
csv.DictReader |
字典 | 有表头,按列名访问,代码更可读 |
我的习惯:只要CSV有表头,我优先用DictReader。代码可读性高,后面清洗时按列名操作,不容易搞错索引。
3.3 JSON文件读取:结构清晰,但小心嵌套
JSON是API接口最常用的格式。Python的json模块用起来很顺手。但有一点要注意——JSON里的null、true、false会被自动转成Python的None、True、False。
import json
# 读取JSON文件
with open('data.json', 'r', encoding='utf-8') as f:
data = json.load(f) # 解析为Python字典或列表
# 处理嵌套JSON
def flatten_json(data, parent_key='', sep='_'):
"""递归展开嵌套JSON"""
items = []
for k, v in data.items():
new_key = f"{parent_key}{sep}{k}" if parent_key else k
if isinstance(v, dict):
items.extend(flatten_json(v, new_key, sep=sep).items())
else:
items.append((new_key, v))
return dict(items)
# 使用示例
nested_data = {"user": {"name": "Alice", "address": {"city": "Beijing"}}}
flat = flatten_json(nested_data)
print(flat) # {'user_name': 'Alice', 'user_address_city': 'Beijing'}
实战经验:嵌套JSON在清洗时很麻烦。我一般先把它展平成一维结构,再转成DataFrame处理。上面的flatten_json函数是我项目里常用的工具函数。
3.4 PDF文件读取:PyPDF2的实战用法
PDF是最难搞的格式之一。它本质上是排版文件,不是纯文本。PyPDF2是目前比较靠谱的库。但说实话,PDF解析没有100%完美的方案,尤其是扫描件。
import PyPDF2
# 读取PDF文件
with open('document.pdf', 'rb') as f: # 注意是'rb'模式
reader = PyPDF2.PdfReader(f)
num_pages = len(reader.pages)
print(f"总页数: {num_pages}")
# 提取每一页的文本
for page_num in range(num_pages):
page = reader.pages[page_num]
text = page.extract_text()
print(f"--- 第{page_num + 1}页 ---")
print(text)
# 提取特定页面
with open('document.pdf', 'rb') as f:
reader = PyPDF2.PdfReader(f)
page = reader.pages[0] # 第一页
text = page.extract_text()
注意:PyPDF2对某些PDF(尤其是扫描件、图片型PDF)提取效果很差。遇到这种情况,可以考虑OCR方案(比如pytesseract),但那已经超出本章范围了。我的建议是:先试PyPDF2,不行再换方案。
3.5 大文件处理技巧:别让内存爆炸
这是实战中最常见的场景。我处理过最大的日志文件有50GB,一次性读入内存?想都别想。核心思路就一个——分块处理。
下面这张图展示了大文件处理的核心流程:
具体怎么做?我总结了三种常用方法:
# 方法1:逐行读取(适合TXT、CSV)
with open('large_file.txt', 'r', encoding='utf-8') as f:
for line in f:
process(line) # 处理每一行
# 方法2:按块读取(适合二进制文件、不确定行数)
def read_in_chunks(file_path, chunk_size=1024*1024): # 1MB一块
with open(file_path, 'r', encoding='utf-8') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
yield chunk
for chunk in read_in_chunks('large_file.txt'):
process(chunk)
# 方法3:使用pandas分块读取CSV
import pandas as pd
chunk_size = 10000 # 每次读1万行
for chunk in pd.read_csv('large_data.csv', chunksize=chunk_size):
# 对每个chunk做清洗
chunk_clean = chunk.dropna()
# 可以追加到文件或数据库
chunk_clean.to_csv('clean_data.csv', mode='a', header=False, index=False)
| 方法 | 适用格式 | 内存占用 | 处理速度 |
|---|---|---|---|
| 逐行读取 | TXT、CSV | 极低 | 中等 |
| 按块读取 | 任意文本 | 可控(取决于chunk_size) | 较快 |
| pandas分块 | CSV | 可控 | 快(底层C优化) |
我的建议:处理CSV大文件时,pandas的chunksize参数是最省心的。它自动帮你管理内存,而且清洗操作可以直接用DataFrame的API。但如果是非结构化文本(比如日志),逐行读取更灵活。
3.6 实战:混合格式读取与编码检测
现实中的数据往往不是单一格式。我遇到过这样一个场景:一个压缩包里同时有TXT、CSV、JSON,而且编码还不统一。这时候就需要一个通用的读取函数。
import json
import csv
import chardet # 自动检测编码
def detect_encoding(file_path):
"""检测文件编码"""
with open(file_path, 'rb') as f:
raw = f.read(10000) # 读前1万字节
result = chardet.detect(raw)
return result['encoding']
def smart_read(file_path):
"""智能读取:根据扩展名选择解析方式"""
encoding = detect_encoding(file_path)
print(f"检测到编码: {encoding}")
if file_path.endswith('.txt'):
with open(file_path, 'r', encoding=encoding) as f:
return f.read()
elif file_path.endswith('.csv'):
with open(file_path, 'r', encoding=encoding) as f:
reader = csv.DictReader(f)
return list(reader)
elif file_path.endswith('.json'):
with open(file_path, 'r', encoding=encoding) as f:
return json.load(f)
else:
raise ValueError(f"不支持的文件格式: {file_path}")
# 使用示例
data = smart_read('unknown_format.csv')
print(type(data)) # 自动识别为列表
我曾经踩过的坑:有一次客户给的CSV文件,前100行是正常的UTF-8,但中间突然混了一段GBK编码。用chardet检测前1万字节没发现问题,结果读到一半就报错了。后来我改用errors='replace'参数,把无法解码的字符替换成占位符,至少保证程序不崩溃。
嗯,到这里,文本数据读取的几种常见格式就讲完了。记住一个原则:先读对,再清洗。编码、格式、大文件处理,这三关过了,后面的清洗工作才能顺利进行。