数据落地:CSV、JSON 与 Excel

采集程序的输出格式决定了后面数据能怎么用:CSV 方便丢给 Excel 与 BI 工具,JSONL 适合流式处理与入库,Excel 适合交付给不做技术的同事。选错格式的代价往往在数据量涨到几十万条时才显现——内存爆掉、文件打不开、中文乱码。

三种格式的取舍

格式优点缺点适用场景
CSV通用、体积极小、可流式追加无嵌套结构、类型靠约定、编码易踩坑字段固定、要给 Excel/BI 看
JSONL(每行一个 JSON)保留嵌套与类型、可逐行解析不适合人眼直读、体积较大入库前的落地层、字段会变
Excel(xlsx)多 sheet、可加样式、交付友好单表上限 1048576 行、内存占用高一次性报表、给业务方交付

经验规则:管道内部用 JSONL,交付用 Excel,长期存档用 CSV 或数据库。不要用 CSV 存嵌套对象列表,那只会得到一列 [object Object] 式的字符串。

CSV:csv.DictWriter 与中文编码

# writer_csv.py —— Python 3.10+
import csv
from pathlib import Path

def write_csv(rows: list[dict], path: Path, fields: list[str]) -> int:
    """utf-8-sig 让 Excel 双击打开不出现中文乱码"""
    path.parent.mkdir(parents=True, exist_ok=True)
    # newline="" 是官方要求:否则 Windows 上每行之间会多出一个空行
    with path.open("w", encoding="utf-8-sig", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
        writer.writeheader()
        for row in rows:
            # 去掉换行,避免一条数据被撑成多行
            clean = {k: str(row.get(k, "")).replace("\n", " ") for k in fields}
            writer.writerow(clean)
    return len(rows)

if __name__ == "__main__":
    data = [{"title": "示例文章一", "url": "https://example.com/1", "pub_time": "2024-05-01 09:00"}]
    print(write_csv(data, Path("output/2024-05-01/news.csv"), ["title", "url", "pub_time"]))
编码写法是否带 BOM谁认得建议
utf-8Python、Linux 工具链、现代 Excel给程序读
utf-8-sig是(\ufeffExcel 双击也能正确显示中文给人用 Excel 打开
gbk老版 Windows Excel仅在必须兼容老环境时用

若下游程序把 utf-8-sig 的第一列名读成 \ufefftitle,改成 utf-8 即可——这是「格式没错但字段名对不上」最常见的原因。

JSONL:追加写与 ensure_ascii=False

# writer_jsonl.py
import json
from pathlib import Path

def append_jsonl(rows: list[dict], path: Path) -> None:
    """每行一个 JSON,可追加;ensure_ascii=False 保证中文按原样存储"""
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("a", encoding="utf-8") as f:
        for row in rows:
            # separators 去掉多余空格;default=str 兜住 datetime 等类型
            line = json.dumps(row, ensure_ascii=False, separators=(",", ":"), default=str)
            f.write(line + "\n")

def read_jsonl(path: Path):
    """逐行读取,内存占用与文件大小无关,适合几十万行以上"""
    with path.open("r", encoding="utf-8") as f:
        for line in f:
            if line.strip():
                yield json.loads(line)

Excel:pandas + openpyxl 多 sheet 与样式

# writer_excel.py
import pandas as pd
from openpyxl.styles import Alignment, Font, PatternFill
from openpyxl.utils import get_column_letter

def write_excel(frames: dict[str, pd.DataFrame], path: str) -> None:
    """一个 dict 一个 sheet,写完再统一设置列宽与表头样式"""
    with pd.ExcelWriter(path, engine="openpyxl") as writer:
        for sheet_name, df in frames.items():
            df.to_excel(writer, sheet_name=sheet_name, index=False)   # 关掉索引列
        for sheet_name in frames:
            ws = writer.sheets[sheet_name]
            ws.freeze_panes = "A2"                                    # 冻结首行
            for idx, column in enumerate(ws.iter_cols(min_row=1, max_row=1), start=1):
                cell = column[0]
                cell.font = Font(bold=True, color="FFFFFF")
                cell.fill = PatternFill("solid", start_color="4472C4")
                cell.alignment = Alignment(horizontal="center")
                letter = get_column_letter(idx)
                # 中文按两个字符宽度估算列宽
                width = max((len(str(c.value or "")) * 2 for c in ws[letter]), default=10)
                ws.column_dimensions[letter].width = min(max(width + 4, 10), 60)

if __name__ == "__main__":
    write_excel({"明细": pd.DataFrame({"标题": ["示例文章一"]})}, "output/日报.xlsx")

超过 5 万行不要用 to_excel 一次写:先在 pandas 里聚合或分片,再逐片写不同 sheet;xlsx 单表 1048576 行是硬上限,to_excel 超限时不会给出友好错误。

目录规范与常见坑

层级建议写法原因
站点output/<site>/多站点互不覆盖
日期output/<site>/2024-05-01/按天分区,便于补跑与清理
文件名<站点>-<类型>-<时间戳>.jsonl可排序、不重名覆盖
临时文件先写 .tmpos.replace 改名避免下游读到半个文件
现象原因处理
Excel 打开中文乱码CSV 用了 utf-8 无 BOMutf-8-sig,或用「数据 → 从文本」导入
每行之间多一个空行未设置 newline=""open(..., newline="")
一条数据变成好几行字段里含 \n写之前统一替换换行符
JSON 里中文变 \u4e2densure_ascii 默认 Trueensure_ascii=False
大文件读入内存 OOMjson.load 读整个数组改 JSONL 并逐行 json.loads

合规提示:落地的数据只应来自公开数据源,遵守站点 robots.txt 与服务条款,控制采集频率与并发;不采集手机号、身份证号、简历个人信息等隐私数据,不绕过登录、验证码与付费墙。对外发布或再分发前,需确认《个人信息保护法》《数据安全法》《著作权法》相关要求并取得授权,优先使用官方 API 与开放数据集。

小结:把格式选择当成架构决策——管道内部用 JSONL 逐行追加,交付用 Excel 并关闭索引、设置列宽与表头样式,长期存档用 CSV 并注意 utf-8-signewline="" 这两个必写参数;文件按站点与日期分目录,先写临时文件再改名。

笔记加载中…