Trang kho lưu trữ trên GitHub hiển thị pushed_at, một mốc thời gian có thể thay đổi khi bất kỳ nhánh nào nhận được push. Vì vậy, nó có thể lệch khỏi commit mới nhất trên nhánh mặc định. Ngày của nhánh mặc định là một tín hiệu về tình trạng bảo trì repo; nó không nhất thiết phản ánh đúng mã mà trình quản lý gói sẽ cài đặt.
Thông thường, trình quản lý gói sẽ phân giải artifact từ registry hoặc phiên bản module. Đó là lý do bài kiểm tra này tách riêng hoạt động của repo và artifact đã phát hành: một bên có thể còn mới trong khi bên kia đã cũ.
Vì thế, tôi đã chọn ra 35 repo vẫn còn xuất hiện trong các khuyến nghị, rồi đọc con số mà GitHub không đưa ngay trên phần đầu trang: ngày của commit mới nhất trên nhánh mặc định.
Sai lệch là có thật: 14 trong số 35 repo có pushed_at đi trước commit mới nhất trên nhánh mặc định hơn 180 ngày, và mức lệch cao nhất lên tới 1.802 ngày. Bot được xác nhận rõ ràng ở ba trong số 14 trường hợp đó; sau khi lọc các repo đã lưu trữ và lọc hoạt động của con người, còn lại hai trường hợp bot đã được xác nhận trong chín ứng viên. Phát hiện hữu ích hơn là: độ mới của repo và độ mới của artifact đã phát hành có thể đi chệch nhau.
Đã đo cái gì, và đo trên cơ sở nào

Tài liệu tham chiếu chính thức: GitHub repository API.
Toàn bộ số liệu ở đây được đọc từ phản hồi API trực tiếp trong khoảng 15:44 đến 15:53 UTC vào 2026-07-27 và được lưu cache. Bộ dữ liệu 35 dòng, danh sách repo, các dòng đã dựng, cùng script fetch/build trong artifacts/ lưu giữ toàn bộ đầu vào và mã chuyển đổi của audit này.
Đã thu thập bốn nhóm dữ liệu; các yêu cầu registry và activity được gọi có điều kiện, không phải theo một chuỗi bốn lần gọi đồng nhất:
GET /repos/{owner}/{repo}— số sao,archived,pushed_at, license,default_branch.GET /repos/{o}/{r}/commits?sha={default_branch}&per_page=1— commit mới nhất trên nhánh mặc định, dùng như tín hiệu bảo trì repo.GET /repos/{o}/{r}/activity?per_page=30— với mọi repo có hai giá trị trên lệch nhau, xem điều gì thực sự đã làmpushed_atthay đổi.GET /repos/{o}/{r}/releasescùng registry của PyPI và npm — xem artifact được phát hành lần cuối khi nào, và hóa ra điều này còn quan trọng hơn cả hai chỉ số kia.
staleness_days là khoảng thời gian trôi qua từ commit trên nhánh mặc định đến thời điểm chốt dữ liệu. Khoảng cách giữa pushed_at và commit đó là “ảo giác”, tính theo ngày. Bất kỳ thứ gì vượt quá 180 ngày đều được gắn cờ.
Có hai nguyên tắc cần nói rõ vì chúng đã làm thay đổi kết quả.
Việc quy nguồn package luôn được xác minh, không suy đoán. Một package mà README có nhắc đến một repo không có nghĩa đó là package của repo ấy. Mọi ánh xạ đều phải được xác nhận bằng một trường có cấu trúc — mục repository/project_urls của chính registry, hoặc một manifest được commit ngay trong repo. Sáu ánh xạ trông có vẻ hợp lý nhưng không qua được bài kiểm tra này, và số lượt tải của chúng không được quy về repo đó.
Một trong những trường hợp bị loại này đủ để giải thích toàn bộ quy tắc. curl-cffi có 35.763.529 lượt tải mỗi tháng và nhìn từ xa rất giống binding Python cho lwthiker/curl-impersonate — một repo đã lạnh suốt 875 ngày. Nếu quy nó về đó, con số sẽ lớn gấp 44 lần newspaper3k, nhưng sẽ là sai: metadata PyPI của curl-cffi trỏ tới lexiforest/curl_cffi, một dự án riêng, vẫn được bảo trì và phát hành lần cuối vào 2026-04-03. Con số ngoạn mục nhất ở đây lại là con số sai.
Trường hợp thứ bảy còn lạ hơn: steel-dev/steel-mcp-server tự khai báo @steel-dev/mcp-server trong package.json, nhưng npm trả về 404. Nó chưa từng được phát hành, nên không thể nói là “vẫn đang được cài đặt”.
Ở đâu không lấy được con số, thì ghi đúng như vậy. Các công cụ viết bằng Go, JVM, .NET và PHP không có mặt trên PyPI hay npm, nên được ghi là N/A (no PyPI/npm package) — không bao giờ là số 0. Mười bảy trong số 35 repo không phát hành GitHub Releases; điều đó được ghi là none, chứ không phải thiếu dữ liệu.
Các trường này cố ý không bị gộp lại thành một “điểm sức khỏe”. Một nhánh mặc định cũ, một nhánh phụ mới, một GitHub Release bị thiếu, và một artifact registry đã cũ đều trả lời những câu hỏi khác nhau. Bằng chứng ở cấp hàng có trong bộ dữ liệu 35 dòng, cùng với đầu vào repo và bản ghi đã dựng. Hãy xem chúng như tín hiệu để phân luồng kiểm tra tiếp theo, chứ không phải bốn lá phiếu quyết định một dự án còn sống hay đã chết.
Lưu ý về cách chọn mẫu, nói trước luôn
Đây là một danh sách được chọn thủ công từ những công cụ mà tôi nghi là đang “ăn may” nhờ danh tiếng. Nó không phải mẫu ngẫu nhiên của hệ sinh thái scraping, và câu “31/35 đã cũ” không phải là tỷ lệ toàn hệ sinh thái — nó gần hơn với thước đo việc tôi chọn mẫu có trúng hay không. Kết quả thú vị không nằm ở số lượng repo cũ. Điều đáng chú ý là ngay cả trên một mẫu được chọn vì hiện tượng đó, cơ chế cụ thể mà tôi đang kiểm tra cũng chỉ giải thích được thiểu số trường hợp, và chỉ xác nhận chắc chắn được ít hơn nữa.
Ảo giác là có thật, và đây là trường hợp tệ nhất
sjdirect/abot, một crawler .NET có 2.308 sao. GitHub báo có một lần push vào 2026-07-17, tức chỉ mười ngày trước thời điểm chốt dữ liệu. Nhưng nhánh mặc định của nó lần cuối được chạm tới là 2021-08-09.
Đó là khoảng cách 1.802 ngày. Năm năm. Phần đầu trang thì bảo là tuần trước.
14 trong số 35 repo có khoảng cách trên 180 ngày:
| Repo | Gap (days) | Default branch last moved | 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 |
Mười bốn trên ba mươi lăm. Có thật, đáng để biết, và chỉ là thiểu số trong một mẫu được chọn để có hiện tượng đó.
Bot được xác nhận ở ba repo bị gắn cờ — và còn hai sau khi lọc
Phiên bản thường được kể của câu chuyện này luôn nhắc đến dependabot. Tôi đã kiểm tra bằng cách lấy activity feed của từng repo bị gắn cờ và phân loại mọi ref được push sau commit cuối trên nhánh mặc định. Chữ “sau” rất quan trọng: các event trước commit cuối không nói gì về thứ đã làm phình pushed_at, và nếu đếm cả feed thì câu hỏi đã bị đổi mất.
Xác nhận do bot, theo phương pháp hậu-commit: ba. scrapinghub/splash (4/4 event sau commit nằm trên dependabot/pip/*), geziyor/geziyor (5/5 trên dependabot/go_modules/*), apache/any23 (16/16 trên dependabot/maven/*). Một trong ba repo này, any23, đã được lưu trữ chính thức, nên không bao giờ lọt vào tập đã lọc — còn lại hai trường hợp được xác nhận và cũng thỏa mọi điều kiện khác.
Sai hoàn toàn: hai, và cả hai còn thú vị hơn câu chuyện bot.
Khoảng cách 1.520 ngày của dragnet-org/dragnet đến từ việc một người push vào một nhánh tên mp/py3.10 — một bản port Python 3.10 chưa được merge. Có người đã cố kéo nó tiến lên rồi dừng lại. Đó không phải là tiếng ồn tự động làm phình timestamp; đó là một dấu vết hữu hình, có ngày tháng, của một nỗ lực cứu vãn thất bại. Có thể nói đây còn là tín hiệu hữu ích nhất trong toàn bộ bộ dữ liệu, và khung diễn giải “dependabot làm” sẽ xóa sạch nó.
Pha trộn, và còn lớn hơn cả hai: hai. crawlab-team/crawlab có 12.250 sao — là repo đứng thứ hai về sao trong mẫu — và khoảng cách 488 ngày trên main. Feed của nó có cả các nhánh từ dependabot lẫn 24 lần push sau commit từ con người, tất cả đều vào develop và test. Người đọc phần đầu trang sẽ thấy tháng 2/2026 và cho rằng nó còn sống; người đọc main sẽ thấy tháng 10/2024 và cho rằng nó đã chết. Cả hai đều sai. Việc phát triển đã chuyển khỏi nhánh mặc định, và GitHub không có cách nào thể hiện rõ điều đó trong phần tóm tắt. sjdirect/abot là trường hợp pha trộn còn lại: lần push làm pushed_at nhảy lên thật sự là dependabot, nhưng có một lần người dùng push upgrade1 trong năm 2024, nên nó bị loại khỏi tập sau khi lọc.
Rhizome-Conifer/conifer là trường hợp mơ hồ, và ban đầu tôi đã đọc sai. Nhánh mặc định của nó là main, không phải master, và main đứng yên từ 2023-10-12 — event duy nhất trên main trong feed là lúc tạo nhánh vào tháng 1/2025, phù hợp với một lần đổi tên. Trong khi đó, một tài khoản duy nhất đã push vào conifer-twilight và twilight/read-only vào 2026-07-22, tức năm ngày trước thời điểm chốt dữ liệu. Đó là hoạt động của con người có thật, nhưng “đang được phát triển tích cực” là điều mà các ref này chưa chứng minh đủ: chỉ một người đóng góp, lại trên các nhánh tên read-only, ít nhất cũng phù hợp với một giai đoạn hạ dần hơn là phát triển tiếp. Điều có thể khẳng định hẹp hơn nhưng vẫn đáng nói là: khoảng cách 1.013 ngày không phải là tiếng ồn bot, và cũng chưa đủ để coi là bằng chứng bỏ bê.
Không xác định: bảy. php-html-parser, simhash-py, internetarchive/wayback, boilerpipe, waybackurls, wpull và crawler4j đều có khoảng cách đã xác minh nhưng activity feed lại trả về rỗng.
Giải thích dễ nảy ra nhất là retention — feed của GitHub không thể ngược về vô hạn. Nhưng cache đã bác bỏ điều đó với đa số trường hợp. Event cũ nhất trong 123 phản hồi là 2023-03-10, và năm trong số bảy repo có pushed_at nằm khá thoải mái trong cửa sổ đó: php-html-parser 2024-08-09, wpull 2024-04-29, waybackurls 2024-05-01, internetarchive/wayback 2024-03-01, simhash-py 2023-05-15. Bất cứ thứ gì làm dịch các timestamp ấy lẽ ra phải xuất hiện trong feed nhưng lại không có. Retention chỉ giải thích được boilerpipe (2018) và crawler4j (2021).
Vì vậy, phát biểu trung thực phải ngắn hơn một lời giải thích gọn gàng: với bảy repo, khoảng cách là факт đã xác minh nhưng nguyên nhân chưa được xác lập — endpoint trả về không gì cả, và với năm repo trong số đó tôi không thể nói vì sao. “Do dependabot” chỉ là giả định cho cả bảy.
Tổng hợp lại trong 14 repo bị gắn cờ:
Cause of the inflated pushed_at | Repos | Which, and on what evidence |
|---|---|---|
| Confirmed bot-driven | 3 | scrapinghub/splash (4 of 4 post-commit events on dependabot/pip/*), geziyor/geziyor (5 of 5 on dependabot/go_modules/*), apache/any23 (16 of 16 on dependabot/maven/*) — any23 is archived, leaving two that also satisfy every other condition |
| Mixed, bot and human | 2 | crawlab-team/crawlab (dependabot branches plus 24 post-commit pushes from humans, all to develop and test), sjdirect/abot (the push that set its headline pushed_at really was dependabot, but a human pushed upgrade1 in 2024) |
| Outright wrong — human work, zero bot branches | 2 | dragnet-org/dragnet (3 post-commit events, 0 on a bot branch) — a human pushing mp/py3.10, an unmerged Python 3.10 port. Rhizome-Conifer/conifer (30 post-commit events, 0 on a bot branch) — a single account pushing conifer-twilight and twilight/read-only on 2026-07-22. Whether conifer is abandoned stays ambiguous, as above; what is not ambiguous is that no bot inflated its pushed_at |
| Unestablished — activity feed empty | 7 | php-html-parser, simhash-py, internetarchive/wayback, boilerpipe, waybackurls, wpull, crawler4j |
Các hàng này cộng lại đúng 14. Chúng phân loại thứ gì đã làm pushed_at thay đổi; chúng không tự chứng minh một dự án đã bị bỏ rơi.
Điều gì còn lại sau khi áp đủ bộ lọc, và “còn lại” nghĩa là gì
Mệnh đề ban đầu cần bốn điều cùng lúc: cũ hơn một năm, pushed_at bị đẩy lệch quá 180 ngày, không archived, và không có dấu hiệu cho thấy sự phình lên đó là do công việc của con người. Có chín trong 35 ứng viên vượt qua cả bốn điều, được liệt kê dưới đây cùng với hai trường hợp loại trừ quan trọng nhất:
| Repo | Clears all four? | Positive evidence that bots drove the inflation |
|---|---|---|
splash | yes | yes — 4 of 4 post-commit events on dependabot/pip/* |
waybackurls | yes | none in either direction |
crawler4j | yes | none in either direction |
geziyor | yes | yes — 5 of 5 on dependabot/go_modules/* |
php-html-parser | yes | none in either direction |
boilerpipe | yes | none in either direction |
wpull | yes | none in either direction |
internetarchive/wayback | yes | none in either direction |
simhash-py | yes | none in either direction |
any23 | no — archived | yes — 16 of 16 on dependabot/maven/*, the single strongest confirmation in the whole audit |
abot | no — its record contains a human push (upgrade1, 2024) | mixed — the push that set its headline pushed_at really was dependabot |
Con số này cần một điều kiện bổ sung mà bộ lọc không thể tự mang theo. Chỉ có hai trong số chín — splash và geziyor — có bằng chứng tích cực cho thấy bot đã làm phình pushed_at. Bảy trường hợp còn lại vượt qua điều kiện thứ tư vì không có bằng chứng theo cả hai hướng. Chúng là những case mà mệnh đề vẫn sống sót, chứ không phải case đã được xác nhận. Và trường hợp xác nhận mạnh nhất trong toàn bộ audit, any23 với 16/16 event là dependabot, lại bị loại vì repo đã archived.
Cũng lưu ý rằng abot, case 1.802 ngày, không nằm trong chín repo đó. Hồ sơ của nó có một lần push từ người thật, nên nó không đạt điều kiện thứ tư — tức case ấn tượng nhất trong bộ dữ liệu lại không phải là ví dụ sạch cho cơ chế mà nó minh họa.
Với 21/35 repo, GitHub báo tình trạng cũ một cách thẳng thắn
Đây là phát hiện đã làm tổn hại nhiều nhất đến giả thuyết của tôi. Có 14 repo có khoảng cách đúng bằng 0, và bảy repo nữa thấp hơn 180 ngày. Với 21 trong số 35 ứng viên, pushed_at chính là commit cuối trên nhánh mặc định. GitHub không hề che giấu gì cả.
Bao gồm cả vài thứ “chết” nhất trong mẫu:
| Repo | Stars | Stale (days) | Gap |
|---|---|---|---|
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 có 22.374 sao và phần đầu trang của nó vẫn nói y hệt từ 2025-07-07. Không có gì bị che giấu, và số lượt cài đặt vẫn tiếp tục.
Điều đó buộc mệnh đề phải thu hẹp lại: GitHub che mờ tình trạng cũ trong một thiểu số trường hợp, còn trong đa số trường hợp nó nói rất rõ, trong khi số lượt cài đặt vẫn tiếp tục. Vì sao chúng vẫn tiếp tục là điều mà dữ liệu này không trả lời được — các con số ở đây đếm lượt cài, không phải quyết định. Nhưng thay đổi giao diện cũng không xử lý được nhóm thứ hai, và đó mới là nhóm lớn hơn.
Hoạt động của repo và artifact đã phát hành có thể lệch nhau
Hàng đáng chú ý nhất trong bộ dữ liệu phá vỡ toàn bộ khung diễn giải.
codelucas/newspaper — 15.126 sao — vẫn còn hoạt động. Commit cuối trên nhánh mặc định của nó có ngày 2026-07-21, so với ngày chốt dữ liệu 2026-07-27, và chính maintainer là người viết commit đó. Mọi kiểm tra ở cấp repo đều đạt.
Package mà mọi người cài là newspaper3k 0.2.8, phát hành vào 2018-09-28. Nó đã cũ 2.858 ngày, nhưng vẫn kéo về 813.513 lượt tải mỗi tháng.
Nhánh mặc định thì mới, còn artifact trên PyPI thì đã không được phát hành lại từ năm 2018. Điều này chỉ chứng minh có khoảng trễ giữa các lần phát hành, chứ không cho biết vì sao package chưa được phát hành hay pipeline có bị hỏng hay không. Đây chính là loại rủi ro mà kiểm tra chỉ dựa trên repo sẽ bỏ sót, vì artifact trong registry mới là thứ thực sự được chạy sau khi pip install newspaper3k.
Khi nhìn vào package thay vì repo, mô hình này xuất hiện ở khắp nơi. Trong 17 package có quy nguồn repo đã được xác minh, 16 package gần nhất đã phát hành cách đây hơn một năm, và 16 package đó chiếm khoảng 2,28 triệu lượt cài mỗi tháng trên tổng 2,30 triệu:
| Package | Installs/month | Last published | Package age (days) |
|---|---|---|---|
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 xứng đáng được nhắc riêng: 790.305 lượt cài mỗi tháng từ một repo đã lạnh 905 ngày, trong một hạng mục mà việc luôn cập nhật là nhiệm vụ cốt lõi. Hành vi TLS của trình duyệt thay đổi; một thư viện ngừng theo kịp từ đầu 2024 sẽ dựa trên giả định của đầu 2024.
Hai lưu ý cho bảng này. Số lượt tải từ registry bao gồm cả chạy CI và mirror, không loại trừ trùng lặp, nên chúng đo lưu lượng cài đặt chứ không đo con người. Và các cửa sổ trượt không cùng chung ngày kết thúc — npm gần với 2026-07-24, còn pypistats thì tính tương đối so với thời điểm lấy dữ liệu — nên tổng cộng này là tổng của những tháng lệch nhau một chút và nên được hiểu là “khoảng 2,28 triệu”, không phải con số chính xác đến từng đơn vị.
Một trùng tên rất đáng biết
internetarchive/wayback là OpenWayback viết bằng Java đã chết, đã lạnh suốt 1.916 ngày. Còn wayback trên PyPI là một dự án hoàn toàn khác — edgi-govdata-archiving/wayback — và nó vẫn khỏe mạnh, đã phát hành 0.5.1 vào 2026-06-19, tức năm tuần trước thời điểm chốt dữ liệu. Cùng tên, trạng thái trái ngược, không có quan hệ gì. Đây là một trong sáu ánh xạ bị loại, và cũng là cái dễ làm người dùng thật vấp nhất: tìm theo tên thì thấy cả hai, còn trên bất kỳ trang nào cũng không nói rõ bạn đang nhìn nhầm cái nào.
Đã archived, đã deprecated, mà vẫn được cài 127.232 lần mỗi tháng
Sáu repo trong mẫu được gắn archived: true, và GitHub hiển thị điều này bằng một banner toàn chiều rộng. Lúc đầu tôi đọc và nghĩ rằng vậy là đủ chứng minh người ta phớt lờ cảnh báo lớn. Cache cho thấy câu chuyện còn tệ hơn thế.
Tài liệu tham chiếu chính thức: npm download-count API documentation.
Tài liệu tham chiếu chính thức: npm's deprecation documentation.
@modelcontextprotocol/server-puppeteer kéo về 127.232 lượt cài mỗi tháng từ modelcontextprotocol/servers-archived. Nhưng trường repository trên npm của nó là null — không có liên kết nào từ trang package quay về repo, nên banner không phải thứ mà người cài có thể bỏ qua. Phần lớn người lấy package này thậm chí còn không có đường nào để thấy repo đó.
Thứ npm thật sự công bố là trạng thái deprecation. Phiên bản mới nhất của package mang trường deprecated: "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info." — npm sẽ in nguyên dòng này ra terminal khi cài đặt. Vậy là cảnh báo có được gửi đi, đúng nơi người dùng đang đứng, và 127.232 lượt cài mỗi tháng vẫn diễn ra. Đây là phát hiện mạnh hơn cả banner, và nó chỉ ra một điều khác: tín hiệu không hề bị thiếu, mà đang xuất hiện trong một biển output khi cài đặt mà không ai bị buộc phải đọc hết.
browserbase/mcp-server-browserbase cho thấy một cách “đóng cửa” gọn gàng: commit cuối trên nhánh mặc định của nó, vào 2026-07-20, chính là "Mark repository as archived and unmaintained (#198)". Maintainer đã thông báo, ghi ngày, và đánh dấu trong API. Tuy nhiên, 20.389 lượt cài của nó cần được đọc cẩn thận — cửa sổ npm là 2026-06-25 đến 2026-07-24, nên 26 trong số 30 ngày đó xảy ra trước commit lưu trữ. Con số này chủ yếu là nhu cầu trước thông báo, chứ không phải sự phớt lờ nó. Điều gì xảy ra tiếp theo thì thật sự chưa biết từ ảnh chụp này, và tôi sẽ muốn đọc lại sau một tháng trước khi kết luận gì thêm.
57 sao, 204.025 lượt cài mỗi tháng
Janpot/microdata-node có 57 sao và nhận 204.025 lượt tải mỗi tháng từ một bản phát hành ngày 2020-05-11.
Với 57 sao, microdata-node có mức hiện diện trên repo khá thấp so với lưu lượng ở registry. Tỷ lệ cài đặt trên sao 3.579:1 là dấu hiệu phù hợp với việc dùng gián tiếp, chạy CI lặp lại, mirror, hoặc tiêu thụ trực tiếp bởi máy. Audit này không thu thập đồ thị phụ thuộc nên không thể chọn giữa các khả năng đó.
Hàng này là lời nhắc rằng cần kiểm kê mức độ phơi nhiễm gián tiếp, nhưng muốn chứng minh việc dùng gián tiếp thì cần bằng chứng reverse-dependency hoặc lockfile mà audit này không thu được.
Cũ không đồng nghĩa với hỏng
Một cuộc audit trung thực phải nói rõ: không điều nào ở đây đo xem có cái gì bị hỏng hay không. Nó chỉ đo xem có ai còn ở nhà hay không.
Một số thứ ở đây đơn giản là đã hoàn tất. SetSimilaritySearch triển khai các thuật toán đo tương đồng giữa tập hợp; loại này không “mục nát”. simhash là một bài báo từ năm 2007. Thuật toán trích xuất nội dung của boilerpipe hoạt động ở 2026 cũng giống như ở 2015 — bất kể độ chính xác trước các trang web hiện đại ra sao, mã của nó không trượt khỏi dưới chân bạn.
Cái dễ mục nát là bất cứ thứ gì có mục tiêu thay đổi ở đầu bên kia:
- Tự động hóa trình duyệt — mỗi bản Chrome mới đều có thể làm nó gãy.
- Hành vi HTTP client mô phỏng trình duyệt thật — trình duyệt đổi, thư viện bị đóng băng sẽ không còn khớp;
tls-clientnằm đúng ở đây. - Parser theo từng site và quy tắc trích xuất riêng cho từng website — mỗi lần site đổi giao diện là một lỗi.
- Bất cứ thứ gì bọc một API bên thứ ba — nhà cung cấp đổi schema và bạn chỉ biết khi chạy production.
- Bất cứ thứ gì bọc LLM — chu kỳ deprecate của model diễn ra nhanh hơn tất cả những thứ này.
Vì vậy, “cũ 1.606 ngày” là báo động cấp cứu cho một nhóm, nhưng gần như không quan trọng với một tiện ích băm. Audit này không kiểm thử lỗi và cũng không đưa ra tuyên bố nào về lỗi; việc tự phân loại dependency theo nhóm nào tốn gần như không đáng kể và hữu ích hơn rất nhiều so với việc chỉ nhìn con số độ cũ.
Bốn kiểm tra thực sự trả lời câu hỏi

Không cái nào là phần đầu trang repo.
| # | Check | Where to read it | What it catches |
|---|---|---|---|
| 1 | Commit cuối trên nhánh mặc định | GET /repos/{owner}/{repo}/commits?sha={default_branch}&per_page=1 | Con số mà bạn không được thấy ngay. |
| 2 | Lần cuối artifact được phát hành | pypi.org/pypi/{pkg}/json hoặc registry.npmjs.org/{pkg} → version mới nhất và ngày upload | Đây là thứ bắt được newspaper3k, còn kiểm tra 1 thì không bao giờ làm được. Khi đang ở đó, hãy đọc thêm trường deprecated của npm, vì đó là cách @modelcontextprotocol/server-puppeteer tự thông báo về mình. |
| 3 | Cờ archived | Một trường trong phản hồi repo, rõ ràng và miễn phí | Lưu ý rằng nó chỉ có ích nếu bạn đã chạm được tới repo; các package có repository: null thì không cho bạn làm điều đó. |
| 4 | Khoảng cách giữa 1 và 2 | — (suy ra từ hai mục trên) | Một repo còn commit mới nhưng release đã ba năm cũ là một kiểu thất bại khác với một repo chỉ đơn giản là lạnh: nó có nghĩa là maintainer vẫn còn hiện diện nhưng không phát hành. Đó là quyết định cần đưa ra khi đã mở mắt ra nhìn, chứ không phải một cờ đỏ tự thân. Logic tương tự cũng áp dụng ngược lại với crawlab: hãy kiểm tra xem công việc đã chuyển sang nhánh không mặc định hay chưa rồi hãy kết luận. |
Hãy chạy các kiểm tra 1, 2 và 3 như bước sàng lọc nhanh. Sau đó nâng cấp kiểm tra khi metadata repo không có, ánh xạ package sang repo mơ hồ, hoạt động chuyển sang nhánh không mặc định, hoặc artifact trong registry lệch khỏi repo. Giới hạn của GitHub khi chưa xác thực và độ trễ của registry khiến một lời hứa “trong một giây” trở nên không thực tế.
Nếu kiểm tra phát hiện một thứ đã lạnh trong một hạng mục đang thay đổi, các lựa chọn được duy trì tốt trong mảng này đã được chúng tôi thử nghiệm và ghi lại: Trafilatura cho kiểu trích xuất nội dung mà dragnet và boilerpipe từng làm, Scrapy hoặc Crawlee cho framework crawling, Crawl4AI và Firecrawl cho trích xuất theo hướng LLM, và Scrapling khi độ bền là quan trọng. Bài pillar về scraper mã nguồn mở của chúng tôi theo dõi bộ công cụ rộng hơn. Đó là các bài đánh giá từ chính chúng tôi; hãy tự chạy bốn kiểm tra trước khi tin bất kỳ khuyến nghị nào, kể cả của chúng tôi.
Giới hạn của dữ liệu này
- Cách chọn mẫu. Chọn thủ công vì nghi ngờ đã bị bỏ bê. Không thể suy ra tỷ lệ của toàn hệ sinh thái từ đó.
- Không kiểm thử lỗi. Không một trong 35 công cụ này được chạy trên website thực. Độ cũ là tín hiệu bảo trì, không phải phán quyết chức năng.
- Số lượt tải bao gồm cả máy. CI, mirror, không loại trừ trùng lặp, và các cửa sổ không chung ngày kết thúc. Đây là lưu lượng cài đặt, không phải người dùng, càng không phải quyết định.
- Bảy nguyên nhân chưa xác định vẫn chưa xác định. Activity feed trả về không gì cả, và với năm trong số bảy, retention không giải thích được. Để ô trống còn tốt hơn nhét vào đó một phỏng đoán phổ biến.
staleness_daysdùng ngày của committer. Lịch sử bị viết lại hoặc backdate sẽ làm méo số này. Không phát hiện thấy điều đó, nhưng không có nghĩa là nó không tồn tại.- Chỉ có một ngày chốt dữ liệu. 2026-07-27. Nhiều repo này sẽ đã thay đổi khi bạn đọc đến đây — đặc biệt là
newspaper, vốn commit đều đặn. Hãy chạy lại bốn kiểm tra; đừng trích dẫn ngày của tôi.
Khi dịch vụ được quản lý thay đổi cấu trúc bài toán
Mọi kiểm tra ở đây tồn tại vì với thư viện tự host, độ cũ là thứ bạn tự chịu trách nhiệm. Nếu một HTTP client bị đóng băng không còn cư xử như trình duyệt hiện đại, đó là sự cố của bạn, xảy ra vào bất kỳ giờ nào nó lộ ra.
Ghi chú của tác giả: Thunderbit là sản phẩm scraping được quản lý của chúng tôi. Một dịch vụ được quản lý sẽ chuyển một phần trách nhiệm bảo trì sang nhà cung cấp, nhưng độ phủ, thời gian phản hồi, ràng buộc nhà cung cấp và tính liên tục của nhà cung cấp sẽ trở thành một phần của mô hình rủi ro. Thunderbit không được đánh giá trong cuộc audit kho lưu trữ này.
Đổi lại một cách trung thực: bạn mất khả năng đọc source, ghim phiên bản, và tự sửa lúc 2 giờ sáng. Với một đội vốn đã duy trì scraper, hướng mã nguồn mở thường vẫn là lựa chọn đúng — bốn kiểm tra ở trên là cách để biến điều đó thành một quyết định chứ không phải một giả định.
Dùng thử Thunderbit để trích xuất dữ liệu web
Bản tóm tắt ngắn
Tôi lập một danh sách để chứng minh GitHub che giấu tình trạng bỏ bê. Ảo giác là có thật ở 14 trong 35 repo, và rất ngoạn mục ở một trường hợp: sjdirect/abot cho thấy một lần push vào tuần trước trong khi nhánh mặc định đã đóng băng từ năm 2021, lệch tới 1.802 ngày.
Nhưng cơ chế này hẹp hơn câu chuyện. Chín repo vượt qua mọi điều kiện mà mệnh đề yêu cầu, và chỉ hai trong số chín — splash và geziyor — có bằng chứng tích cực rằng bot đã làm phình con số; phần còn lại chỉ vượt qua vì không có bằng chứng ngược lại. Hai repo bị gắn cờ là chuyện con người cố gắng nhưng không cứu nổi dự án. Một repo khác, crawlab, có 12.250 sao và đơn giản là đã chuyển phát triển sang develop. Với bảy repo, activity feed trả về rỗng và retention không giải thích được năm trường hợp trong số đó, nên nguyên nhân vẫn chưa xác lập chứ không phải bị giả định sẵn. Và với 21 trong số 35 repo, GitHub báo tình trạng cũ rất chính xác.
Trường hợp tệ nhất trong bộ dữ liệu lại vượt qua mọi kiểm tra ở cấp repo. codelucas/newspaper được commit vào 2026-07-21; newspaper3k, lần phát hành gần nhất là 2018-09-28, vẫn được tải 813.513 lần trong tháng đó. Trên toàn bộ mẫu, 16 package có release đã hơn một năm tuổi cộng lại chiếm khoảng 2,28 triệu lượt cài mỗi tháng.
Hãy kiểm tra commit trên nhánh mặc định, ngày phát hành từ registry, cờ archived, và trường deprecation của npm như bước sàng lọc ban đầu. Sau đó xác minh ánh xạ package-repo và kiểm tra phát triển ở nhánh không mặc định trước khi kết luận.
Dùng thử Thunderbit để trích xuất dữ liệu web Get Started Free
Câu hỏi thường gặp
pushed_at là gì và vì sao nó không có nghĩa là “lần cập nhật cuối”?
pushed_at là trường trong GitHub API đứng sau mốc hoạt động ở trang repo, và nó được làm mới khi có bất kỳ thứ gì được push vào bất kỳ nhánh nào. Commit mới nhất trên nhánh mặc định là một tín hiệu về bảo trì repo, trong khi trình quản lý gói thường cài artifact từ registry hoặc phiên bản module đã được giải quyết. Trong audit này, 14 trong 35 repo cho thấy hai ngày GitHub lệch nhau hơn 180 ngày.
Có phải lúc nào cũng là dependabot làm phình timestamp đó không?
Không, và đó hóa ra là phần yếu nhất của câu chuyện dân gian. Nếu chỉ đếm các event xảy ra sau commit cuối trên nhánh mặc định, bot được xác nhận ở 3 trong 14 repo bị gắn cờ (splash 4/4, geziyor 5/5, any23 16/16). Với 2 repo thì kết luận ngược hẳn lại: khoảng cách của dragnet đến từ việc một người push một bản port Python 3.10 chưa merge. Hai repo nữa là pha trộn, bao gồm crawlab, nơi có 24 lần push từ con người vào develop và test trong lúc main đứng yên. Và với 7 repo, activity feed không trả về gì cả, nên nguyên nhân vẫn chưa xác lập — retention chỉ giải thích được 2 trong số 7 trường hợp đó.
Repo cũ có nghĩa là tool bị hỏng không?
Không theo bằng chứng ở đây — không công cụ nào trong số này được chạy trên website thực. Độ cũ quan trọng tùy theo tốc độ thay đổi của mục tiêu: tự động hóa trình duyệt, HTTP client mô phỏng hành vi browser, parser theo site, và wrapper API/LLM đều nhanh chóng lỗi thời, trong khi các thư viện thuật toán như SetSimilaritySearch hay simhash có thể đã nhiều năm tuổi mà vẫn hoàn toàn ổn. tls-client là trường hợp sắc nhất trong mẫu, với 790.305 lượt cài mỗi tháng từ một repo đã lạnh 905 ngày.
Làm sao repo vẫn hoạt động nhưng package lại đã chết?
Đó là codelucas/newspaper, và cũng là điều quan trọng nhất mà audit này phát hiện. Nhánh mặc định của nó được commit vào 2026-07-21, chỉ vài ngày trước ngày chốt dữ liệu, nhưng newspaper3k trên PyPI lần cuối phát hành 0.2.8 vào 2018-09-28 — 2.858 ngày — và vẫn nhận 813.513 lượt tải mỗi tháng. Các kiểm tra ở cấp repo đều đạt; artifact mà bạn cài đã tám năm tuổi. Luôn kiểm tra ngày phát hành mới nhất của registry riêng biệt với lịch sử commit.
Repo archived có giải quyết được việc này không? GitHub có banner mà.
Không đáng tin cậy, và @modelcontextprotocol/server-puppeteer cho thấy vì sao. Trường repository trên npm của nó là null, nên không có liên kết từ package đến repo archived và cũng không có banner nào để lướt qua. Thứ npm thực sự gửi ra là chuỗi deprecated của package — "Package no longer supported" — được in khi cài đặt, và 127.232 lượt cài mỗi tháng vẫn đi qua đó. browserbase/mcp-server-browserbase đã thông báo đóng cửa đúng cách trong commit cuối của nó; 20.389 lượt cài của nó chủ yếu xảy ra trước commit đó, nên nói được rất ít điều theo cả hai hướng.
Làm sao tôi kiểm tra nhanh dependency của mình?
Hãy bắt đầu bằng ngày commit trên nhánh mặc định, ngày version/upload từ registry, trường deprecation của npm, và cờ archived của repo. Sau đó xác minh ánh xạ package sang repo, kiểm tra các nhánh không mặc định khi hoạt động lệch đi, và dùng dependency graph hoặc lockfile trước khi gọi việc phơi nhiễm là gián tiếp. Các trường hợp trùng tên như hai dự án wayback khác nhau giữa Java và PyPI cho thấy vì sao bước nâng cấp đó là cần thiết.


