首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >串行抓书死了两次,换并发一次跑通——后怕的不是失败,是我根本不知道它为什么死 #WorkBuddy#

串行抓书死了两次,换并发一次跑通——后怕的不是失败,是我根本不知道它为什么死 #WorkBuddy#

原创
作者头像
用户12824395
发布于 2026-10-19 23:13:27
发布于 2026-10-19 23:13:27
340
举报

先把结论放前面:那天晚上,一个更保守的方案失败了两次,一个看起来更激进的方案一次跑通。到今天我也不知道第一次为什么死。

这件事让我后怕的地方,不是失败本身,是我差点就给自己编了一个原因,然后把它当真事记下来。

起因:我要给 AI 垫一本书

10 月 16 日傍晚,我在做一件后来写进上篇文章的事:把吴恩达那本免费公开的《机器学习策略》全文抓下来,给 AI 当背景材料。

书的来源很干净:作者本人在 GitHub 上开源了中文版,我直接走 API 取 Markdown 原文,不下载 PDF,不做 OCR。共 58 章。

抓取这种事,我的第一反应是稳妥起见:一章一章来,串行,慢就慢点,稳要紧。

这个决定看起来没有任何问题。事后证明,问题恰恰出在这个"看起来没有任何问题"上。

第一次翻车:进程被掐了

串行脚本跑起来没多久,进程直接没了。终端里剩下一行:

代码语言:javascript
复制
exit code 137

我当时的反应,说出来不怕你笑:我以为是我这边网络抖了一下。

重跑。又死了。还是 137。

两次。同一个地方,同一个退出码。

到这里我停手了。不是因为我懂了,是因为我意识到再跑第三次大概也是这个结果——同一个输入、同一个输出,这不是运气问题,是某个我不知道的东西在系统性地拦截我。

转折:换并发,一次跑通

我换了写法:线程池,8 个 worker 并发抓。这个改动方向上跟"稳"是反着来的——同时开 8 个请求,听起来更容易出事。

结果:58 章,一次跑完,零失败。成品文件 144,407 字节,落盘时间 19:02,我核过

。

更稳的死了,更激进的活了。 这个结果跟我脑内的直觉完全拧着。正常人这时候会干什么?会找一个解释,把这件事圆过去。我也一样。而且我确实找到了一个特别顺口的——

我给自己编的解释,差点就信了

第一版解释是这样的:串行是一个进程从头跑到尾,内存越攒越多,最后被系统杀掉;并发每个任务小,用完就释放,所以活下来了。

这个解释听起来很有道理,对吧?我当时已经打算把它写进文章了。

然后我干了一件事后看特别值的事:我去核了数字。

全书 58 章加起来,144,407 字节。144 KB。 一张手机照片的零头。

一个进程跑一个总量 144 KB 的任务,攒内存攒到被系统杀掉?这个解释撑不住了。数据量根本不够格。 那一刻我有点冒汗——如果我顺手把这个解释写出去了,它现在就挂在一篇公开发表的文章里,看起来还挺像回事。

那到底为什么死?我不知道

诚实的答案是:不知道。我能列出来的候选解释有三个,每一个都没有证据:

  1. 系统内存压力——macOS 内存紧张时确实会杀进程。但 144 KB 的任务量让这个解释很勉强;
  2. 运行环境的资源守卫——我的脚本跑在一个有资源限制的沙箱里,守卫可能因为别的原因(请求数、运行时长)动手。有可能,但我没有它的日志;
  3. 串行脚本自身的问题——比如某个我没意识到的写法缺陷。有可能,但原脚本已经删了,我想复盘都没得复盘。

第三个是我最懊悔的。出事那晚我要是留一份现场——把退出码、时间点、系统日志抓下来存个档——今天这篇文章就不用写"我不知道"这四个字。

失败不可怕,失败现场没留档才可怕。因为那意味着你连"不知道"的资格都没有——你只有"永远不知道"。

退出码其实在说话,是我没听

这次逼我停下来的,是那个 137。我之前对它的态度是"反正重跑就行",从来没想过去解码它。这回我认真查了,还在自己机器上做了个十秒钟的实验:

杀进程的信号

信号编号

进程退出码

SIGKILL

9

137(= 128 + 9)

SIGTERM

15

143(= 128 + 15)

规则很简单:正常退出的码是程序自己给的;被信号杀掉的,退出码 = 128 + 信号编号。 137 意味着进程是被 SIGKILL 干掉的——就是那种不给任何收拾遗言机会的、直接从内核层面掐死

。

写到这里我要做一个不太舒服的更正。

一篇旧文的更正

我之前发过一篇讲同一晚蒸馏那本书的文章,里面把 exit 137 解释成了"SIGTERM,内存回收"。这个说法是错的。 SIGTERM 对应的退出码是 143,137 是 SIGKILL。这个错误是从我当晚的笔记一路带进文章的——我当时没做实验,抄了自己没验证的记录。

两件事让我把这条更正写出来:

一是退出码是命令行世界最基础的语言,我一个天天跟终端打交道的人把它读错了,还被平台审核顺利放行——错误不会因为平台没拦住就不存在。

二是它恰好证明了这篇文章的主题:我在没有证据的时候,随手给自己编了个解释。 上一篇文章里那个"内存回收",跟这次我差点写出去的"内存越攒越多",是同一个毛病的两次发作。

线上那篇我会走编辑入口改掉。这篇文章算是立此存照。

这件事改了我的三个习惯

第一,退出码要解码,不要背。 128 + N 这个公式两秒就能查到,但它把"进程被杀了"和"进程自己退了"区分开——这两件事的下一步动作完全不同。前者去查谁杀的,后者去查程序逻辑。我之前把两者混成一团,所以才会用"重跑"去应对一个系统性的拦截。

第二,失败现场当场留档。 现在我的规矩是:进程异常死掉,先抓三样东西再动手重跑——退出码、时间戳、系统日志(macOS 上是 log show 的最近片段)。三十秒的事,换的是"以后还能复盘"的资格。

第三,"更稳"是个需要验证的判断,不是免检标签。 串行比并发稳,这话单拎出来谁都觉得对。但至少在我那个场景里,事实是反的。我不打算从这里推出"并发永远更稳"——那就是用一个反直觉故事换一个新教条,没意思。我只想记住:"稳"是我当时的假设,不是我当时的数据。

边界声明

这篇的结论在什么条件下成立,超出范围我不知道:

  • 样本量 n=1:一次串行失败 ×2、一次并发成功,没有做交叉重复实验。"串行死、并发活"在我当晚的场景里是真的,它是不是规律,我没有数据;
  • "为什么死"三个候选解释全部未验证,列出来说明的是"我查过哪些方向",不是"我确认了哪个";
  • 退出码 128+N 是 POSIX 信号语义,跨 Unix 系统通用;但"谁发的 SIGKILL"跟具体运行环境有关,我的排查结论不能搬到你机器上直接用;
  • 书的抓取走的是作者本人开源的 GitHub 仓库,这个前提不成立的话(比如抓有版权墙的站点),整个讨论换一套规则。

文中数字来源:58/58 章、144,407 字节、成品落盘时间 2026-10-16 19:02,来自本机文件实测;串行两次 exit 137 记录自当晚工作日志;SIGKILL→137 / SIGTERM→143 为 2026-10-19 本机实跑对照(Python subprocess,返回 -9 与 -15,对应 shell 退出码 137 与 143)。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 起因:我要给 AI 垫一本书
  • 第一次翻车:进程被掐了
  • 转折:换并发,一次跑通
  • 我给自己编的解释,差点就信了
  • 那到底为什么死?我不知道
  • 退出码其实在说话,是我没听
  • 一篇旧文的更正
  • 这件事改了我的三个习惯
  • 边界声明
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档