GitHub 的仓库页面会暴露 pushed_at 这个时间戳,只要任意分支有 push,它就可能更新。但这个时间未必等于默认分支上的最新提交时间。默认分支日期只能说明仓库维护状况,并不一定代表包管理器最终安装的那份代码。
包管理器通常解析的是注册表制品或模块版本。也正因如此,这次审计把仓库活跃度和已发布制品分开检查:两者可能一个很新、另一个却已经过时。
于是我挑了 35 个仍然出现在推荐里的仓库,去读 GitHub 标题里不会直接展示的那个数字:默认分支上的最新提交日期。
结果很明显:35 个里有 14 个的 pushed_at 比默认分支最新提交时间晚了 180 天以上,最长甚至达到 1,802 天。其中 3 个已确认是机器人行为;在去掉归档仓库和人工活动后,9 个候选里还剩 2 个确认由机器人触发。更有价值的发现是:仓库更新时间和已发布制品更新时间,确实可能完全不一致。
我们测了什么,以及依据是什么

官方参考:GitHub repository API。
这里的所有数据都来自 2026-07-27 UTC 15:44 到 15:53 之间的实时 API 响应,并已缓存。用于审计的 35 行数据集、仓库列表、生成后的记录,以及 artifacts/ 中的抓取和构建脚本,都保留了审计输入与转换逻辑。
我们采集了四类数据;其中注册表和活跃度请求并不是统一的四连调用,而是按需触发:
GET /repos/{owner}/{repo}— stars、archived、pushed_at、许可证、default_branch。GET /repos/{o}/{r}/commits?sha={default_branch}&per_page=1— 默认分支上最新的提交,用来判断仓库维护是否活跃。GET /repos/{o}/{r}/activity?per_page=30— 对于两种时间不一致的仓库,追查到底是什么在推动pushed_at。GET /repos/{o}/{r}/releases以及 PyPI 和 npm 注册表 — 查看 制品 最后一次发布的时间,这一项后来证明比前两者都更关键。
staleness_days 表示从默认分支最新提交到基准时点之间经过的时间。pushed_at 与该提交之间的差值,就是这个“看起来还活着”的幻觉,用天数表示。超过 180 天就会标记出来。
有两条原则需要提前说明,因为它们直接改变了结果。
包归属只做验证,不做推测。 README 里提到某个仓库,并不代表那个包真的属于该仓库。每一条映射都必须有结构化字段确认——要么来自注册表自己的 repository / project_urls,要么来自仓库内提交的清单文件。6 条看起来很像的映射没有通过这项检查,因此它们的下载量故意不计入归属。
其中有一条拒绝,足以说明这条规则的必要性。curl-cffi 每月有 35,763,529 次下载,从表面看像是 lwthiker/curl-impersonate 的 Python 绑定——而后者的仓库已经 875 天没有动静。若把它算进去,数字会比 newspaper3k 高出 44 倍,但那会是错的:curl-cffi 自己的 PyPI 元数据指向的是 lexiforest/curl_cffi,这是一个独立且仍在维护的项目,最后一次发布是 2026-04-03。这里最惊人的数字,恰恰是错误的那个。
第七个案例更离奇:steel-dev/steel-mcp-server 在自己的 package.json 里声明了 @steel-dev/mcp-server,但 npm 返回 404。它根本从未发布过,所以“仍在被安装”这件事对它并不成立。
拿不到数字的地方,就老老实实写明。 Go、JVM、.NET 和 PHP 工具没有 PyPI 或 npm 记录,因此统一标为 N/A (no PyPI/npm package),绝不会写成 0。35 个仓库里有 17 个完全没有 GitHub Releases;这记作 none,而不是缺失数据。
这些字段刻意没有合并成一个“健康分数”。默认分支陈旧、侧分支近期有动静、GitHub Release 缺失、注册表制品过旧,这四件事回答的是不同问题。每一行的证据都可以在 35 行数据集 中查看,旁边还有 仓库输入 和 生成后的记录。把它们当作分诊信号来读:它们告诉你下一步该查什么,而不是在回答“项目是否还活着”这个问题上投四票。
先说明采样限制
这是一份我手工挑选出来、怀疑已经靠名气续命的工具清单。它不是爬虫生态的随机样本,所以“35 个里有 31 个过时”不能解释成整个生态的比例——这更接近于我选题能力的体现。真正有意思的不是陈旧数量,而是:即使在一个专门为这个现象挑选的样本里,我要验证的那个具体机制,也只解释了少数案例,而且能被明确确认的更少。
幻觉确实存在,而且这个案例最夸张
sjdirect/abot 是一个 .NET 爬虫,拥有 2,308 个 stars。GitHub 显示它在 2026-07-17 有一次 push,也就是基准日期前 10 天。但默认分支上最后一次改动其实是在 2021-08-09。
这中间隔了 1,802 天。整整五年。标题却像是在说“上周还在更新”。
35 个仓库里,有 14 个的时间差超过 180 天:
| 仓库 | 时间差(天) | 默认分支最后变动 | pushed_at |
|---|---|---|---|
sjdirect/abot | 1,802 | 2021-08-09 | 2026-07-17 |
dragnet-org/dragnet | 1,520 | 2021-05-09 | 2025-07-08 |
paquettg/php-html-parser | 1,376 | 2020-11-01 | 2024-08-09 |
seomoz/simhash-py | 1,159 | 2020-03-12 | 2023-05-15 |
internetarchive/wayback | 1,039 | 2021-04-27 | 2024-03-01 |
Rhizome-Conifer/conifer | 1,013 | 2023-10-12 | 2026-07-22 |
kohlschutter/boilerpipe | 856 | 2015-08-30 | 2018-01-03 |
scrapinghub/splash | 819 | 2022-05-05 | 2024-08-02 |
tomnomnom/waybackurls | 756 | 2022-04-05 | 2024-05-01 |
geziyor/geziyor | 689 | 2024-08-12 | 2026-07-02 |
crawlab-team/crawlab | 488 | 2024-10-09 | 2026-02-10 |
ArchiveTeam/wpull | 468 | 2023-01-16 | 2024-04-29 |
yasserg/crawler4j | 396 | 2020-10-03 | 2021-11-04 |
apache/any23 | 381 | 2022-06-03 | 2023-06-20 |
14/35。这个现象是真实的,值得知道,但它仍然只是在一个刻意挑选出该现象的样本里,占少数的案例。
3 个被标记的仓库已确认是机器人所致——但筛选后只剩 2 个
这个故事的民间版本总会提到 dependabot。我逐个拉取被标记仓库的 activity feed,并把最后一次默认分支提交之后推送的所有 ref 都分类了一遍。这里的“之后”很关键:发生在最后一次提交之前的事件,无法解释时间差是怎么被拉大的;如果把整个 feed 都算进去,其实已经悄悄换了问题。
按“提交之后”的方法确认是机器人驱动的:3 个。 scrapinghub/splash(4/4 个 post-commit 事件都在 dependabot/pip/* 上)、geziyor/geziyor(5/5 个都在 dependabot/go_modules/* 上)、apache/any23(16/16 个都在 dependabot/maven/* 上)。其中 any23 这一个是正式归档的,所以不会进入筛选后的集合——最后只剩 2 个同时满足其他所有条件的确认案例。
明显不对:2 个,而且这两个都比“机器人故事”更有意思。
dragnet-org/dragnet 的 1,520 天差距来自一个人推送了名为 mp/py3.10 的分支——一个尚未合并的 Python 3.10 移植。有人尝试推进它,然后停了下来。这不是自动化噪音把时间戳抬高了,而是一段清晰、带日期的失败修复记录。可以说,它是整个数据集中最有价值的信号之一;如果只套用“dependabot 干的”这个说法,就会把它抹掉。
混合情况,而且规模更大:2 个。 crawlab-team/crawlab 有 12,250 个 stars——是样本里 stars 第二多的仓库——它在 main 上的时间差是 488 天。它的 activity feed 里既有 dependabot 分支,也有 24 次来自人工的 post-commit push,而且全部推到了 develop 和 test。只看标题的读者会看到 2026 年 2 月,以为项目很健康;只看 main 的读者会看到 2024 年 10 月,以为项目已经死了。两种判断都不对。开发工作已经转移到了默认分支之外,而 GitHub 的摘要视图并没有办法表达这一点。sjdirect/abot 也是同类情况:给它制造标题时间戳的那次 push 确实是 dependabot,但 2024 年又有人推了一个 upgrade1 分支,因此它后来会从过滤后的集合中被剔除。
Rhizome-Conifer/conifer 是一个模糊案例,我最开始也读错了。它的默认分支是 main,不是 master,而 main 自 2023-10-12 之后就没再更新——feed 里的唯一 main 事件是 2025 年 1 月的一次分支创建,与重命名一致。与此同时,一个账号在 2026-07-22 向 conifer-twilight 和 twilight/read-only 推送了内容,也就是基准日期前 5 天。这确实是人工活动,但“正在积极开发”并没有证据支持得那么强:只有一个贡献者、而且分支名里带着 read-only,这至少和“项目在有管理地收尾”一样说得通。能确定的只有一件事,而且仍然值得说明:这 1,013 天的时间差不是机器人噪音,但也不能据此就认定项目已被放弃。
未知:7 个。 php-html-parser、simhash-py、internetarchive/wayback、boilerpipe、waybackurls、wpull 和 crawler4j 都有明确的时间差,但 activity feed 返回的是空结果。
人们很容易想到一个解释:GitHub 的 activity feed 不会无限往前保留。缓存数据已经把这个解释推翻了大半。123 个响应里,最早的事件日期是 2023-03-10,而这 7 个里有 5 个的 pushed_at 完全落在这个窗口内:php-html-parser 是 2024-08-09,wpull 是 2024-04-29,waybackurls 是 2024-05-01,internetarchive/wayback 是 2024-03-01,simhash-py 是 2023-05-15。按理说,推动这些时间戳的动作应该出现在 feed 里,但实际上没有。保留期只能解释 boilerpipe(2018)和 crawler4j(2021)。
所以,诚实的结论比一个漂亮解释要窄得多:对这 7 个仓库来说,时间差是已验证事实,但其原因并未建立——端点什么都没返回,而其中 5 个我也无法告诉你为什么。把它们一律归因于“dependabot 干的”,只是猜测。
汇总 14 个被标记的仓库后:
pushed_at 被放大的原因 | 仓库数 | 哪些仓库,以及依据 |
|---|---|---|
| 已确认由机器人驱动 | 3 | scrapinghub/splash(4/4 个 post-commit 事件都在 dependabot/pip/* 上)、geziyor/geziyor(5/5 个都在 dependabot/go_modules/* 上)、apache/any23(16/16 个都在 dependabot/maven/* 上)——any23 已归档,因此最后只剩 2 个同时满足其他所有条件的案例 |
| 混合:机器人 + 人工 | 2 | crawlab-team/crawlab(dependabot 分支 + 24 次人工 post-commit push,全部推到 develop 和 test)、sjdirect/abot(制造标题 pushed_at 的那次 push 确实是 dependabot,但 2024 年又有人推了 upgrade1) |
| 明显不是机器人——纯人工工作,没有任何 bot 分支 | 2 | dragnet-org/dragnet(3 个 post-commit 事件,0 个在 bot 分支上)——一个人推了 mp/py3.10,一个未合并的 Python 3.10 移植。Rhizome-Conifer/conifer(30 个 post-commit 事件,0 个在 bot 分支上)——一个账号在 2026-07-22 推了 conifer-twilight 和 twilight/read-only。至于 conifer 是否真的被遗弃,如上所述仍然模糊;但可以确定的是,没有 bot 抬高它的 pushed_at |
| 原因未建立——activity feed 为空 | 7 | php-html-parser、simhash-py、internetarchive/wayback、boilerpipe、waybackurls、wpull、crawler4j |
这些行加起来正好是 14。它们分类的是谁在推动 pushed_at;并不能单独证明项目是否已经放弃。
通过完整筛选的是什么,以及“通过”到底意味着什么
原始判断需要同时满足四件事:过时超过一年、pushed_at 被拉高超过 180 天、未归档、并且没有迹象表明这个拉高来自人工工作。35 个候选里有 9 个同时满足这四项,下面列出它们,以及最重要的两个排除项:
| 仓库 | 是否满足全部四项? | 有机器人推动时间戳膨胀的正向证据 |
|---|---|---|
splash | 是 | 是——4/4 个 post-commit 事件都在 dependabot/pip/* 上 |
waybackurls | 是 | 双向都没有证据 |
crawler4j | 是 | 双向都没有证据 |
geziyor | 是 | 是——5/5 个都在 dependabot/go_modules/* 上 |
php-html-parser | 是 | 双向都没有证据 |
boilerpipe | 是 | 双向都没有证据 |
wpull | 是 | 双向都没有证据 |
internetarchive/wayback | 是 | 双向都没有证据 |
simhash-py | 是 | 双向都没有证据 |
any23 | 否——已归档 | 是——16/16 个都在 dependabot/maven/* 上,这是整份审计里最强的确认案例 |
abot | 否——记录里出现过一次人工 push(upgrade1,2024) | 混合——给它制造标题 pushed_at 的那次 push 确实是 dependabot |
但这个数字还需要一个过滤器本身无法表达的说明。这 9 个里,只有 2 个——splash 和 geziyor——有正向证据证明是机器人推动了时间戳膨胀。 其余 7 个只是因为没有反向证据,所以才通过了第四项。它们是“这个判断仍然成立”的案例,不是“已经被证实”的案例。而整份审计里最强的确认案例 any23,虽然 16/16 都是 dependabot 事件,却因为仓库已归档而被排除。
另外要注意,1,802 天那个案例 abot 并不在这 9 个里面。它的记录里出现过人工 push,因此没有通过第四项——也就是说,数据集中最夸张的那个幻觉,并不是它想说明的机制的一个干净样本。
35 个里有 21 个,GitHub 其实把陈旧状态说得很直白
下面这个发现,对我的假设打击最大。14 个仓库的时间差恰好是 0,另外 7 个也都低于 180 天。对 35 个候选中的 21 个来说,pushed_at 就是默认分支的最后提交时间。GitHub 并没有在遮掩什么。
包括样本里一些最“老”的项目:
| 仓库 | Stars | 过时天数 | 时间差 |
|---|---|---|---|
Janpot/microdata-node | 57 | 1,866 | 0 |
1e0ng/simhash | 1,037 | 1,606 | 21 |
ekzhu/SetSimilaritySearch | 603 | 1,384 | 0 |
GerbenJavado/LinkFinder | 4,431 | 834 | 0 |
hakluke/hakrawler | 5,099 | 582 | 0 |
lavague-ai/LaVague | 6,388 | 551 | 0 |
my8100/scrapydweb | 3,411 | 522 | 0 |
getomni-ai/zerox | 12,258 | 432 | 0 |
scrapinghub/frontera | 1,332 | 415 | 0 |
BuilderIO/gpt-crawler | 22,374 | 384 | 0 |
BuilderIO/gpt-crawler 有 22,374 个 stars,而且它的标题自 2025-07-07 以来就没变过。没有任何隐藏,安装量还在继续。
这迫使结论收缩成更窄的一句:GitHub 会在少数情况下掩盖陈旧性,但在多数情况下,它把陈旧性说得很明白,而安装仍然继续。 为什么还会继续安装,这组数据无法回答——这里统计的是安装量,不是用户决策。但 UI 的变化并不能解决第二类问题,而第二类恰恰是更大的那一类。
仓库活跃度和已发布制品,可能完全不是一回事
数据集中最醒目的那一行,直接打破了前面的叙述框架。
codelucas/newspaper —— 15,126 个 stars —— 是活跃的。它默认分支最后一次提交日期是 2026-07-21,距离基准日期 2026-07-27 只差几天,而且提交者就是维护者本人。仓库层面的所有检查都通过了。
但大家真正安装的包是 newspaper3k 0.2.8,发布于 2018-09-28。它已经 2,858 天没更新了,却每月还有 813,513 次下载。
默认分支是最新的,而 PyPI 制品自 2018 年以来就没再发过。这证明的是发布断层,而不是为什么没继续发布,或者流水线是否坏了。这正是单看仓库维护状态会漏掉的风险,因为 pip install newspaper3k 真正执行的,是注册表里的制品。
一旦你开始看包而不是只看仓库,这种情况到处都是。17 个经过验证归属的包里,16 个最后一次发布都已超过一年,而这 16 个合计占了大约 230 万月安装量中的 228 万左右:
| 包 | 每月安装量 | 最后发布 | 包年龄(天) |
|---|---|---|---|
newspaper3k | 813,513 | 2018-09-28 | 2,858 |
tls-client | 790,305 | 2024-02-02 | 905 |
simhash | 317,615 | 2022-03-03 | 1,606 |
microdata-node | 204,025 | 2020-05-11 | 2,267 |
@modelcontextprotocol/server-puppeteer | 127,232 | 2025-05-12 | 440 |
extract-thinker | 10,927 | 2025-06-09 | 412 |
SetSimilaritySearch | 7,984 | 2022-10-11 | 1,384 |
frontera | 4,709 | 2019-04-05 | 2,669 |
zerox | 3,303 | 2025-05-20 | 432 |
scrapydweb | 1,163 | 2025-02-16 | 525 |
lavague | 606 | 2024-08-05 | 720 |
splash | 333 | 2020-06-16 | 2,231 |
dragnet | 213 | 2019-04-16 | 2,658 |
@builder.io/gpt-crawler | 137 | 2025-01-23 | 549 |
lmnr-index | 120 | 2025-06-05 | 416 |
simhash-py | 113 | 2017-03-22 | 3,413 |
tls-client 值得单独点名:它来自一个已经冷了 905 天的仓库,却还能带来每月 790,305 次安装,而且这个类别里“保持跟进”就是核心工作。浏览器 TLS 行为会变化;一个在 2024 年初就停止跟踪变化的库,运行的还是 2024 年初的假设。
这个表有两点需要提醒。注册表下载量包含 CI、镜像,而且不会去重,所以它衡量的是安装量,不是人类用户数。其次,滚动窗口的截止日期并不一致——npm 接近 2026-07-24,pypistats 则是按抓取时间回溯——因此总量是若干略有错位的月份之和,应该理解为“大约 228 万”,而不是精确到个位数的数字。
一个值得知道的同名冲突
internetarchive/wayback 是已经死掉的 Java OpenWayback,已经冷了 1,916 天。PyPI 上的 wayback 是完全不同的项目——edgi-govdata-archiving/wayback——而且状态很好,在 2026-06-19 发布了 0.5.1,比基准日期早了五周。同名,状态相反,彼此无关。这是被我拒绝归属的 6 个案例之一,也最容易让真实用户踩坑:搜同一个名字会出现两个结果,但任意一个页面都不会告诉你你找到的是哪一个。
已归档、已弃用,但每月仍有 127,232 次安装
样本里有 6 个仓库带着 archived: true,GitHub 会把它显示成一条横幅。我最初以为,这至少说明用户会忽略大字警告。但缓存数据告诉我,事情比这更糟。
官方参考:npm download-count API documentation。
官方参考:npm 的 deprecation 文档。
@modelcontextprotocol/server-puppeteer 每月还有 127,232 次安装,来源是 modelcontextprotocol/servers-archived。但它的 npm repository 字段是 null——包页面根本没有指回仓库的链接,所以安装者甚至不会先看到那条横幅。多数下载这个包的人,压根就没有一条明确路径能走到那个仓库。
npm 真正会发布出来的是弃用信息。这个包的最新版本包含 deprecated: "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",npm 在安装时会把这句话打印到终端。所以警告其实已经送达了,而且是出现在用户真正所在的位置;但每月 127,232 次安装仍照常发生。这个发现比“有横幅但没人看”更强,也指向不同的问题:信号并不是没发出,而是被塞进了安装输出的长墙里,没有任何东西强制用户去读。
browserbase/mcp-server-browserbase 则展示了一个体面关闭的样子:它最后一次默认分支提交,也就是 2026-07-20 的那次,提交信息就写着 “Mark repository as archived and unmaintained (#198)”。维护者明确公告、写上日期,并在 API 里做了标记。它的 20,389 次安装也值得仔细看——npm 的窗口是 2026-06-25 到 2026-07-24,所以这 30 天里的 26 天都在归档提交之前。这个数字更多反映的是公告前的需求,而不是对公告的无视。公告之后发生了什么,从这张快照里无法直接得知;如果要下结论,我更愿意一个月后再看一次。
57 个 stars,204,025 次月安装
Janpot/microdata-node 只有 57 个 stars,但从 2020-05-11 的发布版本中拉来了每月 204,025 次下载。
57 个 stars 与如此高的注册表体量相比,仓库可见度几乎可以忽略。3,579:1 的安装/星标比,说明它很可能被间接依赖、在 CI 中反复使用、被镜像拉取,或者直接被机器消费。此次审计没有抓取依赖图,因此无法在这些解释里选一个。
这条记录提示我们要去排查间接暴露,但要证明“被间接依赖”,还需要反向依赖或 lockfile 证据,而这次审计没有收集。
过时,不等于坏了
如果要诚实审计,就必须说清楚:这些数据并不能衡量功能是否失效。 它们衡量的是,有没有人在维护。
其中一些项目只是做完了。SetSimilaritySearch 实现的是集合相似度算法;这类算法不会“腐烂”。simhash 是 2007 年的论文。boilerpipe 的内容抽取算法在 2026 年和 2015 年的行为本质上没变——无论它对现代网页的准确率如何,它的代码都没有悄悄漂移到你手上。
真正会变质的,是另一端有持续变化目标的东西:
- 浏览器自动化——每次 Chrome 更新都可能把它弄坏。
- 模仿真实浏览器行为的 HTTP 客户端——浏览器在变,而冻结的库会越来越不像浏览器;
tls-client就属于这一类。 - 站点特定解析器和针对单站点的抽取规则——每次网站改版都可能变成 bug。
- 任何封装第三方 API 的东西——供应商改 schema,问题通常是在生产环境里才爆出来。
- 任何封装 LLM 的东西——模型弃用的节奏比上面这些都快。
所以,“1,606 天未更新”对某些类别来说是五级火警,对另一些哈希工具来说却几乎无关紧要。这次没有测试它是否会坏,也没有做出这类断言;但把自己的依赖按类别排序,几乎不花成本,却比单看陈旧天数有用得多。
真正回答问题的四个检查

它们都不是仓库标题栏。
| # | 检查项 | 去哪里看 | 能发现什么 |
|---|---|---|---|
| 1 | 默认分支上的最后一次提交 | GET /repos/{owner}/{repo}/commits?sha={default_branch}&per_page=1 | 那个不会直接展示给你的数字。 |
| 2 | 制品最后一次发布的时间 | pypi.org/pypi/{pkg}/json 或 registry.npmjs.org/{pkg} → 最新版本及其上传日期 | 这就是能抓出 newspaper3k 的地方,而检查 1 永远抓不到。顺便读一下 npm 的 deprecated 字段,@modelcontextprotocol/server-puppeteer 就是靠它说明自己的。 |
| 3 | archived 标记 | 仓库响应里的一个字段,明确且直接 | 但前提是你得先真的能拿到那个仓库,而 repository: null 的包根本不给你这条路。 |
| 4 | 1 和 2 之间的差距 | —(由前两项推导) | 一个仓库如果提交很新,但发布版本已经三年没动,这和单纯“冷掉”是两回事:这说明维护者还在,但没有发版。你要睁大眼睛自己判断,而不是把它当成一个红旗。反过来,crawlab 也是同理:先确认工作是不是转移到了非默认分支,再下结论。 |
先快速跑完检查 1、2、3 做初筛。然后再在这些情况下升级处理:仓库元数据缺失、包与仓库的映射不明确、活跃工作转移到了非默认分支,或者注册表制品和仓库状态不一致。GitHub 未认证访问有限流,注册表也有延迟,所以别指望“一秒出结果”。
如果某项检查发现某个在高变化类别里已经“冷掉”的工具,现代可替代方案在我们的测试里也有记录:Trafilatura 可替代 dragnet 和 boilerpipe 这类内容抽取,Scrapy 或 Crawlee 适合爬虫框架,Crawl4AI 和 Firecrawl 适合面向 LLM 的抽取,而 Scrapling 则适合重视抗变性的时候。我们的 开源爬虫总览 也覆盖了更大的范围。这些都是第一方评测;在相信任何推荐之前,包括我们的,也请先自己跑一遍这四项检查。
这份数据的局限性
- 样本选择。 这是按“怀疑已废弃”人工挑选出来的,不能据此读出生态比例。
- 没有做功能失效测试。 这 35 个工具没有一个在真实网站上跑过。陈旧只是维护信号,不是功能判决。
- 下载量包含机器。 CI、镜像、没有去重,而且窗口的截止日期也不一致。它衡量的是安装量,不是用户数,也不是决策数。
- 7 个未知原因仍然未知。 activity feed 返回空结果,而这 7 个里有 5 个,保留期并不能解释为什么为空。留白比填上流行猜测更好。
staleness_days使用的是提交者日期。 如果历史被重写或回填,结果会失真。我们没有检测到这种情况,但“没检测到”不等于“绝对没有”。- 只有一个基准日期。 2026-07-27。等你读到这里时,其中一些仓库已经变化了——尤其是
newspaper,它本来就提交很频繁。请重新跑这四项检查,不要引用我的日期。
托管服务会怎样改变这件事
这里的每一项检查之所以存在,是因为如果你用的是自托管库,陈旧性就是你自己的责任。如果一个冻结的 HTTP 客户端不再像当前浏览器,那就是你的事故,发生在它暴露出来的那个时间点。
作者说明:Thunderbit 是我们的托管爬取产品。托管服务会把一部分维护责任转给供应商,但覆盖范围、响应时间、锁定效应和供应商持续性都会变成风险模型的一部分。本次仓库审计没有评估 Thunderbit。
真实的取舍是:你放弃了查看源码、固定版本以及在凌晨两点自己修掉它的能力。对于已经在维护爬虫的团队来说,开源方案往往更合适——而这四项检查,就是把它变成一个决策,而不是一个假设的方法。
简短结论
我做这份名单,本来是想证明 GitHub 会掩盖项目弃用。幻觉确实存在:35 个仓库里有 14 个受影响,而且一个案例特别夸张——sjdirect/abot 显示上周还有 push,但默认分支自 2021 年后就没动过,时间差足足 1,802 天。
但机制比故事窄得多。9 个仓库同时满足该判断所需的全部条件,其中只有 2 个——splash 和 geziyor——有正向证据证明是机器人在放大时间戳;其余只是因为没有反证才过关。另有 2 个被标记仓库是人为尝试恢复项目却失败。还有一个 crawlab,有 12,250 个 stars,只是把开发转到了 develop。再有 7 个,activity feed 直接为空,其中 5 个连保留期都解释不了,所以原因不能被武断归类。并且在 35 个里,有 21 个,GitHub 其实把陈旧状态说得很准确。
数据集中最糟糕的案例,连仓库层面的检查都全部通过了。codelucas/newspaper 在 2026-07-21 还有提交;而 newspaper3k 最后一次发布是 2018-09-28,却在那个月仍然被下载了 813,513 次。整个样本里,有 16 个发布已超过一年的包,合计每月大约 228 万次安装。
请先检查默认分支提交日期、注册表发布日期、归档标记和 npm 的弃用字段,作为初筛。然后再确认包与仓库的归属关系,以及是否存在非默认分支开发,再做结论。
试用 Thunderbit 做网页数据提取 Get Started Free
常见问题
什么是 pushed_at,为什么它不等于“最后更新”?
pushed_at 是 GitHub API 中对应仓库页活动时间戳的字段,只要任何分支有 push,它就会刷新。默认分支最新提交只是仓库维护状态的一个信号;而包管理器通常安装的是注册表制品或已解析的模块版本。在这次审计里,35 个仓库中有 14 个的这两个 GitHub 日期相差超过了 180 天。
是不是总是 dependabot 把这个时间戳拉高?
不是,而且这恰好是民间说法里最薄弱的一环。只统计发生在默认分支最后一次提交之后的事件,14 个被标记仓库里有 3 个被确认是机器人所致(splash 4/4、geziyor 5/5、any23 16/16)。有 2 个则完全不是:dragnet 的时间差来自人工推送的一个未合并 Python 3.10 移植。另有 2 个是混合情况,包括 crawlab,它在 develop 和 test 上有 24 次人工 push,而 main 一直没动。还有 7 个 activity feed 直接为空,所以原因无法建立——其中只有 2 个能被保留期解释。
仓库过时,就说明工具坏了吗?
就这份证据来看,不能这么说——这里没有任何一个工具在真实网站上跑过。陈旧与否,要看目标变化有多快:浏览器自动化、模仿浏览器行为的 HTTP 客户端、站点特定解析器以及 API/LLM 封装,都会很快过时;而像 SetSimilaritySearch 或 simhash 这样的算法库,即使很多年没动,也可能完全没问题。tls-client 是样本里最典型的案例:仓库冷了 905 天,但每月还有 790,305 次安装。
仓库活跃,但包却已经死了,这怎么可能?
这正是 codelucas/newspaper 的情况,也是这次审计最重要的发现。它的默认分支在 2026-07-21 还有提交,离基准日期只差几天;但 PyPI 上的 newspaper3k 最后一次发布还是 2018-09-28 的 0.2.8,已经 2,858 天了,而且每月仍有 813,513 次下载。仓库层面的检查全都通过了,但你真正安装的制品却已经八年没更新。一定要把注册表的最新发布时间和提交日志分开看。
归档仓库不是已经解决了吗?GitHub 不是会显示横幅?
不可靠,而 @modelcontextprotocol/server-puppeteer 就说明了原因。它的 npm repository 字段是 null,所以包页面根本没有指向已归档仓库的链接,也就不存在一个能“跳过”的横幅。npm 真正会给你的,是包里的 deprecated 字符串——“Package no longer supported”——它会在安装时打印出来,而 127,232 次月安装依然照常发生。browserbase/mcp-server-browserbase 则是在最后一次提交里体面地宣布了关闭;它的 20,389 次安装大多发生在那次提交之前,因此无论怎么读都不能说明太多。
我怎样快速检查自己的依赖?
先看默认分支提交日期、注册表版本/上传日期、npm 的弃用字段,以及仓库的 archived 标记。然后确认包和仓库的归属关系,查看活动是否转移到了非默认分支;如果还要判断是否有间接依赖,就得进一步看依赖图或 lockfile。像 Java 和 PyPI 上无关的 wayback 项目这种同名冲突,也说明这一步必须做。


