kp-011

数据清洗与持久化:CSV、SQLite 与 Parquet

核心 ≈ 25 分钟 清洗存储SQLiteCSVParquetpandas

前置知识

本文基于模型知识整理,建议核对官方文档(见 参考资料)。

一句话定义

清洗把「网页上的人类可读值」变成「机器可算的字段」,持久化则按用途选择 CSV(交换)、JSONL(流式落盘)、SQLite(查询)或 Parquet(分析)。

为什么重要

「¥1,299.00」存成字符串,kp-027 的价格分析就寸步难行。采集脚本的价值不在于抓了多少,而在于落库的数据能直接被查询与计算;清洗是采集与挖掘(kp-024)之间的合同边界。

前置知识

kp-010(有结构化产出);pandas 基础用法。

核心概念

  • 类型规整:金额→float、数量→int、时间→ISO 8601、布尔→0/1。
  • 单位剥离:货币符号、千分位、单位后缀("万"、"kg")。
  • 空值语义:区分「页面没显示」「接口返回 null」「解析失败」三种空。
  • 存储选型:CSV 通用可读;JSONL 追加友好、保字段灵活;SQLite 单文件可 SQL 查询;Parquet 列存压缩、分析快。

原理与机制

清洗函数模板(与采集代码解耦,可独立单测):

import re
from datetime import datetime

PRICE_RE = re.compile(r"[-+]?\d[\d,]*\.?\d*")

def clean_price(raw: str | None) -> float | None:
    if not raw:
        return None
    m = PRICE_RE.search(raw.replace(",", ","))
    return float(m.group().replace(",", "")) if m else None

def clean_time(raw: str | None) -> str | None:
    if not raw:
        return None
    for fmt in ("%Y-%m-%d %H:%M", "%Y年%m月%d日", "%Y-%m-%d"):
        try:
            return datetime.strptime(raw.strip(), fmt).isoformat()
        except ValueError:
            continue
    return None          # 统一失败语义,而不是抛异常中断全量任务

存储选型对照:

CSV      人读/交换, 类型全丢失, Excel 打开中文需 utf-8-sig
JSONL    追加写入/流式, 字段可演化, 但查询要全量扫描
SQLite   单文件 + SQL, 适合 < 10GB 采集库, WHERE/JOIN/GROUP BY 随手可用
Parquet  列存 + 压缩, pandas/polars 读取极快, 适合交给 kp-027~029 分析

落库最小示例(采集脚本里只做「收集 + 批量写」):

import sqlite3

def save(items):
    conn = sqlite3.connect("data.db")
    conn.executemany(
        "INSERT OR REPLACE INTO products(id,title,price,crawled_at) VALUES(?,?,?,?)",
        [(it["id"], it["title"], it["price"], it["crawled_at"]) for it in items],
    )
    conn.commit(); conn.close()

实例或案例

电商价格监控的库表设计:prices(sku_id, ts, price, source) 每条价格一行(长表),而非「每个 SKU 一列」——长表天然支持 kp-027 的按时间聚合与对比分析。这是「为下游查询设计 schema」的直接体现。

常见误区

  • 误区一:CSV 乱码。 Windows Excel 打开 UTF-8 CSV 全是乱码;导出时指定 encoding="utf-8-sig"(带 BOM)。
  • 误区二:在采集循环里塞满清洗逻辑。 抓取与清洗耦合导致重跑成本高;正确姿势是先原样落盘(原始层),清洗作为独立可重跑步骤(见 kp-025)。
  • 误区三:所有空值都填 0。 「没抓到价格」填 0 会让均价分析严重失真;空就是空,让分析层决定如何处理。

自测题

  1. 为什么价格字段必须转 float 而不是存字符串?

答:字符串无法参与聚合比较排序,下游时序与统计会全部失败;「¥1,299.00」需剥离符号千分位。

  1. 四种存储各自的适用场景?

答:CSV 交换、JSONL 流式追加、SQLite 单机查询、Parquet 列式分析。

  1. 采集层与清洗层为什么要分离?

答:原始数据只落一次,清洗规则可反复迭代重跑;且采集崩溃不影响已清洗数据,回溯成本最低。

与其他知识点的关系

kp-025 把本篇的清洗函数扩成质量管线;kp-024 定义了层间契约;kp-027/029 消费本篇产出的规整数据。

延伸阅读

pandas 官方「io」章节(read_csv/to_sql);SQLite 官方文档的数据类型一节。

相关知识点

学习进度