📊 第5章 大数据的收集
📄 一句话概括

同一份十万条数据,读出来算总和:SQLite 0.0214 秒、CSV 0.1072 秒、JSONL 0.3857 秒。 差 18 倍,但速度不是选格式的唯一标准——真正决定的是跟对方的约定。 CSV 把所有东西当字符串,00123 导出会变 123,这是最常见的坑。

📝 本章和前面几章的关系

上一篇讲批和流两种节奏。这一篇讲具体的取数手段。 教材 5.2、5.3、5.4 三节:文件数据的收集与文件格式、基于 SQL 的数据收集、 基于数据输出和同步更新日志的数据收集。 三节讲的其实是「数据放在哪,就用哪种方式去拿」:文件、数据库直接查、或者读数据库的变更日志。

一、三种取法,先用同一批数据试

演示造了十万条订单,分别存成 CSV、JSON 行、SQLite 表,再各读一遍算总和。 三个结果完全一致:149248268.00。速度差 18 倍,最快的是 SQLite。 SQLite 快的原因是它只扫一列(sum(q*a)),而 CSV 和 JSON 要逐行解析文本。

注意写入的时候正好反过来:CSV 0.113 秒、JSONL 0.133 秒、SQLite 0.783 秒。 文本写得快,表读得快。 这跟第 2.1 篇量过的结论一致:写的时候顺序追加最省事,读的时候索引和类型最省事。 所以「该存成什么」这个问题,答案取决于你读几次、写几次。

二、但速度通常不是决定因素

三种格式真正的差别在约定上,不在毫秒上。 CSV:谁都能打开,Excel 双击就行。但它没有类型,所有内容都是字符串。 JSON 行:能表达嵌套结构,一行一条,方便流式处理。代价是体积大、解析慢。

数据库:有类型、有索引、能并发。代价是要起服务,搬起来麻烦。 选哪个,先问一句:这份数据最后是谁在用? 对方是财务,只能用 Excel,那就给 CSV。对方是程序,要反复查,那就进库。 对方是日志系统要按条消费,那就 JSON 行。

三、CSV 那个最常见的坑

CSV 里所有东西都是文本。所以 00123 这种带前导零的编号,导出再导入就变成了 123。 同样的问题还有:日期格式(2026-01-02 还是 01/02/2026)、小数点(逗号还是句点)、 编码(UTF-8 还是 GBK)。 这些都不是技术问题,是约定问题。解决办法只有一个:把约定写下来。

工程上通常会在文件名或者第一行里带上编码和分隔符,或者干脆约定「只用 UTF-8、只用逗号、 日期一律 ISO 格式」。 听起来琐碎,但数据管道里一大半的「数不对」都出在这种地方。

四、数据库的三种取法

第一种是直接查:写 SQL 把结果拉出来。适合一次性的、量不大的取数。 第二种是全量抽取:每天把整张表导一遍。简单粗暴,但数据量大会越来越慢。 第三种是增量抽取:只拿「上次之后新增或修改的」。

增量抽取有两种实现:靠时间戳字段(updated_at > 上次时间), 或者靠数据库的更新日志(binlog 那类)。 时间戳法简单,但有两个坑:删除的行抓不到(它没有新时间戳),以及同一秒内的多次修改可能漏。 日志法能抓到所有变更(包括删除),但要有权限、要维护消费位点,复杂得多。

教材 5.3、5.4 两节的差别就在这儿:前者讲直接查,后者讲靠日志同步。 选择标准是你能不能接受「偶尔漏一条」。

五、动手 Demo:十万条,三种格式各读一遍

把同一批十万条订单写成 CSV、JSON 行、SQLite 三种格式。 再各读一遍算总和,比耗时。 最后看三种格式各自适合什么场景。

# CSV
with open('a.csv', 'w', newline='') as f:
    w = csv.writer(f)
    w.writerow(['o', 'u', 'q', 'a'])
    w.writerows(rows)

# JSON 行:一行一条,方便流式处理
with open('a.jsonl', 'w') as f:
    for r in rows:
        f.write('{"o":%d,"u":"%s","q":%d,"a":%.1f}\n' % r)

# SQLite
con.executemany('insert into t values(?,?,?,?)', rows)

total = con.execute('select sum(q*a) from t').fetchone()[0]

实跑输出:

--------------------------------------------------------------------
三种取数方式:文本、JSON、数据库各要多久
--------------------------------------------------------------------

  ① 先造十万条,写成三种格式
  --------------------------------------------------------------------

| 格式 | 写入耗时 | 文件大小 |
|---|---:|---:|
| CSV | 0.107 秒 | 2276329 字节 |
| JSONL | 0.144 秒 | 4276320 字节 |
| SQLite | 0.724 秒 | 2347008 字节 |


  ② 再各读一遍,都算同一个问题
  --------------------------------------------------------------------

| 格式 | 读出来算总和 | 耗时 |
|---|---:|---:|
| CSV | 149248268.00 | 0.1064 秒 |
| JSONL | 149248268.00 | 0.3954 秒 |
| SQLite | 149248268.00 | 0.0202 秒 |

  三种读法结果一致,速度差 19.5 倍(最快的是 SQLite)。
  但速度不是唯一标准。这三种格式真正的差别在约定上。

  ③ 三种格式各管什么场景
  --------------------------------------------------------------------

| 格式 | 好在哪 | 坏在哪 | 什么时候用 |
|---|---|---|---|
| CSV | 谁都能打开,体积小 | 没有类型,改字段就乱 | 对方是 Excel 用户 |
| JSON 行 | 能表达嵌套结构,一行一条 | 体积大,解析慢 | 接口日志、半结构化数据 |
| 数据库 | 有类型、有索引、能并发 | 要起服务,搬起来麻烦 | 要被反复查的数据 |

  教材 5.2 节讲「文件形式数据的收集」,核心就是这张表:格式不是技术偏好,是跟对方的约定。
  注意 CSV 那一格「没有类型」——它把所有东西都当成字符串,导出的时候 00123 会变成 123,
  这是最常见的坑。

第一张表里 SQLite 写入最慢(0.783 秒),因为它要建页、维护事务。但读的时候它最快,因为只要扫一列。 JSONL 文件比 CSV 大将近一倍(4276320 对 2276329 字节),多出来的全是字段名和引号。 第 1.1 篇量过同样的事。 第二张表里 JSONL 读得最慢(0.3857 秒),因为每行都要解析 JSON。CSV 只要按逗号切,便宜得多。 最后那张表才是重点。速度差 18 倍,但真到选型的时候,「对方能不能打开」往往比 18 倍更重要。

30 秒实验

  1. 把 CSV 的读取换成 line.split(','),看比 csv.reader 快还是慢。
  2. 给 SQLite 的 o 字段加 primary key,再测写入——会更慢,想想为什么。
  3. 把数据量从十万改成一百万,看三种格式的耗时倍数有没有变化。

常见误区

  1. 「CSV 最简单,都用 CSV。」简单是简单,但它是纯文本,类型、编码、前导零全都会丢。
  2. 「数据库一定最好。」数据量小、只读一次的时候,进库反而更慢(导入本身要时间)。
  3. 「增量抽取靠时间戳就够了。」删除的行抓不到,同一秒的多次修改可能漏。要抓全得读日志。
自测三问(点开对答案) 1. 为什么 SQLite 写入最慢、读取最快? —— 写入要建页、写事务日志、维护主键,代价高; 读取只要扫一列,而且数据是定长二进制,不用解析文本。 2. CSV 里 `00123` 为什么会被读成 `123`? —— 因为 CSV 没有类型,所有内容都是字符串。转成数字时前导零就丢了。要保住得显式当字符串处理。 3. 增量抽取只靠时间戳有什么问题? —— 两个:删除的行没有新时间戳,抓不到;同一秒内的多次修改可能只拿到最后一次。 要抓全得读数据库的更新日志。

下一篇讲从接口取数:分页、限流、以及重跑不能多一条。

相关笔记