1. 从JSON到CSV:Web Scraper的终极指南
爬虫工程师每天都要面对各种数据格式转换的挑战。JSON和CSV作为两种最常用的数据交换格式,在实际项目中经常需要互相转换。最近接手了一个电商价格监控项目,原始数据通过API获取为JSON格式,但最终分析工具只接受CSV输入。这个看似简单的转换过程,在实际操作中却遇到了字符编码、嵌套结构处理、大数据量分块等一系问题。本文将分享我在Web Scraper项目中积累的JSON转CSV完整解决方案。
2. 核心工具与技术选型
2.1 为什么选择Python作为处理工具
Python的pandas库在数据处理领域几乎成为行业标准。测试对比了三种方案:
- 纯Python标准库(json+csv模块):处理简单结构时内存占用最低(约比pandas少30%),但缺少高级功能
- pandas:处理嵌套JSON优势明显,平均转换速度比纯Python快2-3倍
- jq命令行工具:适合流式处理超大型文件,但Windows兼容性较差
实测数据(处理10MB JSON文件):
| 工具 | 耗时(s) | 内存峰值(MB) |
|---|---|---|
| Python标准库 | 3.2 | 85 |
| pandas | 1.1 | 120 |
| jq | 2.8 | 65 |
提示:当JSON超过100MB时,建议使用ijson库进行流式解析,避免内存溢出
2.2 必备Python库详解
python复制# 基础工具栈
import pandas as pd
import json
from csv import DictWriter
# 处理特殊情况的扩展库
import numpy as np # 处理空值
import chardet # 编码检测
import ijson # 大文件流处理
3. 完整转换流程解析
3.1 数据预处理阶段
从Web Scraper获取的原始JSON通常需要清洗:
- 编码检测与转换(特别是中文网站)
python复制with open('raw.json', 'rb') as f:
encoding = chardet.detect(f.read(1024))['encoding']
data = json.loads(open('raw.json', encoding=encoding).read())
- 嵌套结构展平处理
python复制# 处理嵌套字典的典型方案
def flatten_dict(d, parent_key='', sep='_'):
items = []
for k, v in d.items():
new_key = f"{parent_key}{sep}{k}" if parent_key else k
if isinstance(v, dict):
items.extend(flatten_dict(v, new_key, sep=sep).items())
else:
items.append((new_key, v))
return dict(items)
3.2 核心转换实现
3.2.1 基础转换方法
python复制# 最简实现(适合小型数据集)
df = pd.read_json('input.json')
df.to_csv('output.csv', index=False, encoding='utf-8-sig')
3.2.2 处理复杂结构的进阶方案
当JSON包含数组嵌套时:
python复制from pandas import json_normalize
with open('nested.json') as f:
data = json.load(f)
df = json_normalize(
data,
record_path='products', # 展开数组
meta=['store_id', 'timestamp'], # 保留元字段
errors='ignore'
)
3.3 输出优化技巧
- 分块写入大文件
python复制chunk_size = 10000
for i in range(0, len(df), chunk_size):
df[i:i+chunk_size].to_csv(
f'output_{i//chunk_size}.csv',
mode='a' if i>0 else 'w',
header=i==0
)
- 特殊字符处理
python复制df.to_csv('output.csv',
quoting=csv.QUOTE_NONNUMERIC,
escapechar='\\',
doublequote=False)
4. 实战问题解决方案
4.1 中文乱码问题全方案
不同环境下的编码处理:
| 环境 | 推荐编码 | 备注 |
|---|---|---|
| Windows Excel | utf-8-sig | 添加BOM头 |
| Linux系统 | utf-8 | 最通用 |
| 老旧系统 | gbk | 兼容性优先 |
4.2 内存优化策略
处理5GB以上JSON文件的方案:
- 使用ijson流式解析
python复制def stream_json_to_csv(input_path, output_path):
with open(output_path, 'w', newline='', encoding='utf-8') as csvfile:
writer = None
for item in ijson.items(open(input_path, 'rb'), 'item'):
if not writer:
writer = DictWriter(csvfile, fieldnames=item.keys())
writer.writeheader()
writer.writerow(item)
- 分片处理配合数据库
python复制import sqlite3
conn = sqlite3.connect(':memory:')
chunks = pd.read_json('large.json', lines=True, chunksize=50000)
for chunk in chunks:
chunk.to_sql('temp', conn, if_exists='append')
pd.read_sql('SELECT * FROM temp', conn).to_csv('final.csv')
5. Web Scraper集成方案
5.1 主流爬虫框架对接
Scrapy中间件示例:
python复制class JsonToCsvPipeline:
def open_spider(self, spider):
self.csvfile = open(f'{spider.name}.csv', 'w', encoding='utf-8')
self.writer = None
def process_item(self, item, spider):
if not self.writer:
self.writer = DictWriter(self.csvfile, fieldnames=item.keys())
self.writer.writeheader()
self.writer.writerow(dict(item))
return item
5.2 浏览器自动化方案
配合Playwright的实时转换:
javascript复制// 在Node.js环境中
const { parse } = require('json2csv');
const fs = require('fs');
async function scrapeAndSave(page) {
const data = await page.evaluate(() =>
Array.from(document.querySelectorAll('.product')).map(el => ({
name: el.querySelector('.name').innerText,
price: el.querySelector('.price').innerText
}))
);
const csv = parse(data);
fs.writeFileSync('products.csv', '\uFEFF' + csv, 'utf-8');
}
6. 性能优化实测数据
不同规模数据的处理方案选择:
| 数据规模 | 推荐方案 | 内存控制技巧 |
|---|---|---|
| <10MB | pandas直接转换 | 单次加载 |
| 10MB-1GB | chunks分块处理 | 控制chunksize在10000-50000 |
| >1GB | 流式处理+数据库 | 使用ijson/sqlite3组合 |
实测对比(i7-11800H, 32GB内存):
| 方案 | 100MB文件 | 1GB文件 | 10GB文件 |
|---|---|---|---|
| 直接pandas | 1.2s | 内存溢出 | - |
| chunks=50000 | 1.8s | 18s | 内存溢出 |
| ijson流式 | 2.5s | 25s | 4m12s |
7. 企业级应用扩展
7.1 自动化监控系统集成
在Airflow中创建转换DAG:
python复制from airflow import DAG
from airflow.operators.python import PythonOperator
def convert_json_to_csv():
df = pd.read_json('/data/input.json')
df.to_csv('/data/output.csv', index=False)
dag = DAG('json_conversion', schedule_interval='@daily')
task = PythonOperator(
task_id='convert_task',
python_callable=convert_json_to_csv,
dag=dag
)
7.2 数据质量检查方案
转换后建议执行的检查:
python复制# 记录数比对
assert len(df) == original_json_count
# 关键字段非空检查
assert df['id'].isna().sum() == 0
# 类型一致性验证
assert pd.api.types.is_numeric_dtype(df['price'])
# 数据快照比对
assert df['name'].str.contains('测试').sum() == 0
8. 特殊场景处理技巧
8.1 处理JSON Lines格式
对于每行一个JSON对象的特殊格式:
python复制# 方法1:pandas直接读取
df = pd.read_json('multiline.json', lines=True)
# 方法2:逐行处理
with open('output.csv', 'w') as csvfile:
writer = None
with open('multiline.json') as f:
for line in f:
data = json.loads(line)
if not writer:
writer = DictWriter(csvfile, fieldnames=data.keys())
writer.writeheader()
writer.writerow(data)
8.2 动态字段处理
当JSON字段不固定时的解决方案:
python复制def dynamic_converter(json_path, csv_path):
all_keys = set()
# 第一遍扫描收集所有可能字段
with open(json_path) as f:
for line in f:
data = json.loads(line)
all_keys.update(data.keys())
# 第二遍处理确保字段一致
with open(csv_path, 'w') as csvfile:
writer = DictWriter(csvfile, fieldnames=sorted(all_keys))
writer.writeheader()
with open(json_path) as f:
for line in f:
data = json.loads(line)
writer.writerow({k: data.get(k) for k in all_keys})
9. 转换后的CSV优化
9.1 列顺序重排技巧
python复制# 按业务重要性排序
column_order = ['id', 'name', 'price', 'category', 'create_time']
df = df[column_order + [col for col in df.columns if col not in column_order]]
9.2 类型转换最佳实践
python复制# 自动识别并转换类型
def auto_convert(df):
for col in df.columns:
# 尝试转为数字
try:
df[col] = pd.to_numeric(df[col])
continue
except:
pass
# 尝试转为日期
try:
df[col] = pd.to_datetime(df[col])
continue
except:
pass
return df
10. 逆向转换:CSV转JSON
虽然主题是JSON转CSV,但逆向转换也常有需求:
python复制# 保持原始数据结构
df = pd.read_csv('input.csv')
df.to_json('output.json', orient='records', force_ascii=False)
# 生成嵌套结构
nested = df.groupby('category').apply(
lambda x: x[['id', 'name']].to_dict('records')
).to_json()
在实际项目中,JSON和CSV的转换远不是表面看起来那么简单。处理过的一个电商数据项目,原始JSON包含6层嵌套结构,字段名包含特殊字符,还有动态变化的字段。通过组合使用json_normalize、自定义展平函数和类型推断,最终生成的CSV文件才能被下游BI工具正确识别。记住关键原则:先理解数据结构,再选择工具方法,最后考虑性能优化。
