SerpBase 采集脚本慢在哪?cProfile + py-spy 性能剖析实录
「脚本太慢了」是采集项目最常见的抱怨,但相当多的「优化」都优化错了方向:有人在优化 JSON 解析,实际瓶颈是每条记录 commit 一次;有人在调正则,实际时间全花在等接口响应上。慢分两种——「在等」和「在算」,先分清,再动手。 这篇用两个剖析工具(cProfile + py-spy)把一次批量采集剖开看。
第 0 步:先看「等」还是「算」
在剖析工具之前,先做最朴素的分段计时:
import time
t = time.perf_counter(); data = fetch(kw); net = time.perf_counter() - t
t = time.perf_counter(); rows = parse(data); cpu1 = time.perf_counter() - t
t = time.perf_counter(); store(rows); db = time.perf_counter() - t
print(f"net={net:.2f}s parse={cpu1:.2f}s db={db:.2f}s")
跑 20 个关键词汇总。这一步就能定性:net 占大头 = 在等(用并发/缓存/批量);parse/db 占大头 = 在算(上剖析工具找热点)。
第 1 步:cProfile 找 CPU 热点
import cProfile, pstats
def main():
for kw in keywords:
rows = parse(fetch(kw))
store(rows)
cProfile.run("main()", "prof.out")
p = pstats.Stats("prof.out")
p.sort_stats("cumulative").print_stats(20) # 先看 cumulative
p.sort_stats("tottime").print_stats(20) # 再看 tottime(自身耗时)
读法两条:
- cumulative(累计):这个函数连同它调用的子函数花了多少——用来定位「大块头」
- tottime(自身):只算函数自己——用来揪出「自己就很贵」的热点(正则、序列化、日志格式化常见于此)
关键提醒:cProfile 不统计 I/O 等待。 如果你的时间花在等请求响应上,profile 出来会「一片平坦、没有热点」——这个「没有热点」本身就是最重要的线索:瓶颈在等,不在算。
第 2 步:py-spy 剖运行中的进程
cProfile 要改代码、有开销(跑一遍慢 2-3 倍),适合离线。生产里用 py-spy——不修改代码、不用重启:
pip install py-spy
py-spy top --pid 12345 # 实时看各函数占用
py-spy record -o profile.svg --pid 12345 # 采一段生成火焰图
火焰图里横轴越宽 = 占用时间越多,一层层往下看就能从「main」追到具体热点。
第 3 步:一次真实案例的数字
批量采集 200 个关键词的一次剖析(数字为这次实测):
| 阶段 | 优化前 | 发现 | 优化后 |
|---|---|---|---|
| 总墙钟 | 6m05s | — | 3m10s |
| 网络等待 | ~4m40s | I/O 主导 → 并发 4 + 命中缓存 | ~2m10s |
| CPU 合计 | ~40s | JSON 解析 + 正则占 60% | ~25s(减少正则,直取字段) |
| 入库 | ~35s | 每条一次 commit → 批提交 | ~6s |
三个发现按「先等后算」的顺序处理:先解决等待(并发+缓存),再削 CPU 热点(解析),最后砍入库次数(批提交)。
第 4 步:优化的顺序原则
- 先看构成:等 vs 算(第 0 步)
- 等的部分:并发、缓存、批量化——收益大、风险低
- 算的部分:用 cumulative/tottime 找热点,一次改一处
- 每次改动后复测:同样的输入跑一遍,看数字变没变——没有测量就没有优化
踩坑记录
坑 1:拿 cProfile 找 I/O 瓶颈。 等待时间在 profile 里不显示,越看越迷惑。先分段计时。
坑 2:用小数据剖析。 10 个词的样本里「热点」是玄学。用真实量级跑。
坑 3:生产环境直接跑 cProfile。 开销 2-3 倍,把线上拖慢。生产用 py-spy。
坑 4:凭直觉优化。 「这行看起来慢」经常错——实测热点常是正则、序列化、循环里的日志格式化。
坑 5:忘了日志开销。 循环里每轮 print/logger 是真实热点(尤其写文件)。批量场景降日志级别或缓冲输出。
坑 6:优化完不复测。 改了三处感觉快了,实际互相抵消。一次一处、每次复测。
小结
性能剖析的路径:分段计时定性(等/算)→ cProfile 找 CPU 热点 → py-spy 看生产 → 按「等→算→入库」顺序优化 → 每次复测。这套下来,你优化的是「真实热点」,而不是「看起来慢的代码」。对采集脚本这类 I/O 混 CPU 的负载,「先分清在等还是在算」这一句话,能省掉一半无效工作。
响应信封里的 elapsed_ms(网关侧耗时)与本地分段计时对照,可以快速判断「慢在接口还是慢在本地」——字段说明见 SerpBase 官方文档。你们的采集脚本剖析过吗?最大的热点是什么?评论区聊聊。

浙公网安备 33010602011771号