20 个平台热榜:8 个有接口,一次静默失败 12 天
公告:铁柱资源网为用户提供最新的原创技术教程,还有电脑技巧以及其他日常信息 游戏资讯等 让我们的生活更加精彩有乐趣!铁柱网原先域名:qq8m.com,部分地区打不开,新域名:qq8m.com,qq8m.com,qq8m.com重要的事情说三遍
铁柱资源网网站源码20 个平台热榜:8 个有接口,一次静默失败 12 天

20 个平台热榜:8 个有接口,一次静默失败 12 天

网站源码傲轩小编2026-10-29 18:16:421.77 KA+A-

我有 20 个平台的热榜,每天凌晨自动抓、自动翻译、自动构建上线。这篇文章写的是那些数据源本身——每一类是怎么定的,哪条路看着更正规但走不通,以及我在上面踩到的三个坑和一次整整一天的数据全丢。

先说结论

20 个平台里,只有 8 个有能直接用的接口。剩下 12 个要么读 RSS,要么得绕一道。

采集脚本最会骗人的状态是「跑成功」。单个平台坏了,任务照样成功,榜单上那块照常显示昨天的内容。

每条路都是试出来的。今天凌晨 04:30:18 起跑,20 个平台全部拿到数据,一共 480 条,采集加翻译加构建加部署,64 秒跑完。

一次完整的运行长什么样

这是今天那份运行报告,一个字没改:

代码块
== fetch     04:30:18 ==
OK  zhihu: 29 items
OK  bili: 30 items
OK  v2ex: 7 items
OK  hupu: 30 items
OK  maoyan: 30 items
OK  hn: 30 items
OK  lobsters: 25 items
OK  github: 30 items
OK  reddit: 25 items
OK  yt: 30 items
OK  kr36: 30 items
OK  tc: 20 items
OK  sspai: 10 items
OK  ifanr: 20 items
OK  ithome: 30 items
OK  solidot: 14 items
OK  verge: 10 items
OK  ars: 20 items
OK  cnbeta: 30 items
OK  arxiv: 30 items
TR  translating hn (30 titles)…
(另外 7 个英文平台同理)
TR  translating github descriptions (28)…
SAVE zhihu.json: 29 items
(另外 19 个平台同理)
done: 20/20 platforms collected (2026-09-28)
== summarize 04:31:15 ==
summarize: 19 platforms, 450 items total, 95 top entries fed to model
summarize OK: 10 focus items (3 merged across platforms) -> summary.json
== build     04:31:18 ==
== git       04:31:19 ==
DONE in 64s

一分钟里做完这些:

  • 20 个平台各抓 7 到 30 条,合计 480 条
  • 其中 8 个英文平台的 189 条标题送去翻译,另外 28 条英文描述也翻
  • 首页那块「今日焦点」是另一步:把各平台前 5 条(arxiv 除外)喂给模型,让它挑 10 条并合并同题,今天有 3 条是跨平台合并出来的
  • 构建静态站点,提交,推上去,线上自动部署

注意那个 done: 20/20。脚本最后是按「成功平台数」结账的,只有同一个平台连续挂掉太多,任务才会算失败。

第一类:有现成接口的,8 个

知乎、B 站、V2EX、虎扑、猫眼、Hacker News、Lobsters、GitHub。

看着最省事,实际上每家的脾气都不一样:

  • 知乎官方 CLI 每天只给 2 次热榜额度,跑两次就没了。换成第三方 CLI,走 cookie,没有次数限制。
  • B 站、虎扑、猫眼在国内直连就行,挂上代理反而抓不到。这三个都属于「越正规的通道越不给用」,走的是移动端接口和 dashboard 接口。
  • 虎扑那个接口一次只回十来条,要翻 5 页才凑够 30 条,而且它给的热度是加权流,不是纯数值排序,只能照单全收。
  • 猫眼必须带桌面 UA 和 Referer,不带直接 403。
  • V2EX 有个完全公开、不用 key 的接口,但它一次只回 7 到 10 条。这是平台的天花板,不是你的 bug。今天它给了 7 条。
  • HN 和 Lobsters 最简单,直连就通,一个用 Algolia 的检索接口,一个直接是热门 JSON。
  • GitHub 走 Search API,条件是「最近 7 天新建的仓库,按星数排」。

第二类:只能读 RSS 的,10 个

少数派、爱范儿、IT之家、Solidot、cnBeta、The Verge、Ars Technica、TechCrunch、arXiv、Reddit。

这一类最好维护:不用申请 key,格式统一,一段正则就能解析。代价是它给什么你就拿什么。Solidot 一天就十几条,少数派和 Verge 各给 10 条,Reddit 只给标题和链接,分数和评论数是拿不到的(后面细说)。

第三类:得绕的,2 个

36 氪和 YouTube。

36 氪的频道页是纯客户端渲染,直接请求拿回来是个空壳,只能用抓取服务去渲染,而且那个服务有个档位参数必须开到最高,用默认档位返回体永远是空的。

YouTube 官方 trending 对我这种机房出来的 IP 只回空壳,所以我用的是第三方整理的页面。

坑一:连续 12 天的静默失败

Reddit 我修得最久,也栽得最狠。

先说结果:9 月 1 日到 9 月 12 日,连续 12 天,Reddit 那一栏一直是 8 月 31 日的数据。页面照常渲染,任务状态是成功,我什么都没发现。

那 12 天里,每天的报告里都躺着一行:

代码块
KEEP reddit.json: fetch failed, keeping previous data

它确实在报告里,问题是它混在 20 行 OK 中间,而任务的状态还是成功。我每天是扫一眼报告有没有红字,KEEP 这个词当时对我来说更像是「它保留了旧数据,挺稳」。

真正让这件事暴露的,是一次完全不相关的事故。9 月 12 日那天,构建日志的文件权限不对,脚本退出了非零,于是错误输出第一次被带进了报告,我才看到那一行真正的报错:

代码块
ERR reddit: reddit: no thing blocks found (proxy changed?)

发现它的不是监控,是另一个碰巧发生的权限错误。

原因也不难理解:Reddit 那条路本来依赖谷歌翻译的代理页面,9 月初那东西改成了 302 跳转,直接废了。我换成了 Reddit 官方的 Atom RSS,有两条必须记住:

  • 地址必须带 geo_filter=GLOBAL。不带的话它会按你出口 IP 的国家给你本地化内容。我的代理出口在德国,不带这个参数就是整页德文。
  • 必须走代理。直连拿不到。

顺带说一件我放弃的事:这个 RSS 里没有分数和评论数。我试了三条路想拿回来。免登录的 .json 接口我试了三种打法:直连、走代理、连 Chrome 的 TLS 指纹伪装,一律 403。第三方的存档接口,一个改成付费限流了,一个只回 2015 年的老帖。唯一真能拿到分数的是复用我自己浏览器的登录 cookie,实测返回 200,分数也真拿到了。但那是没被批准的自动化访问,我没采用。

所以现在这一栏就固定是官方 RSS,没有分数。这条写进了项目文档,标的是「已定案,别再折腾」。

坑二:看着正常,其实少了一半

arXiv 这条坏的姿势很特别。

先是接口挂了:官方 API 整段回一个 14 字节的响应体,内容就是 Rate exceeded.。上代理、直连都一样。换成官方 RSS 就好了,200,不用 key。

真正麻烦的是后来一次代码审计翻出来的问题。我那个 RSS 是三个分类一起抓的(人工智能、机器学习、自然语言处理),代码写成了一个循环,但里面有一句提前返回:等第一个人工智能分类抓满 30 条,函数就直接返回了。结果就是后两个分类从来没被请求过,机器学习方向和大模型方向的论文整体从榜单上消失。

这件事难发现的地方在于它完全不影响「正常运行」:任务成功,条数满 30,页面正常,只有你专门去数分类,才会发现少了两个方向。

今天是 30 条,人工智能、机器学习、自然语言处理各 6 条,另外 12 条是机器人、安全、数据库这些(RSS 里带交叉分类,混进来了)。

坑三:抓回 0 条和抓失败,是两种坑

36 氪属于第三类。它不报错,它抓回来 0 条。

原因在抓取服务的返回体上。档位不够高的时候,它不报错,只回一个空的结果列表加一句失败原因。我的代码直接去取第一个结果,于是抛了个数组越界的异常,被外层兜住了,走了「保留旧数据」那条分支。

结果和 Reddit 一样,平台静默停在昨天。区别只是原因从网络问题变成了参数问题。

那天一条数据都没更新

9 月 15 日,这条管线整条卡死了。

那天的报告全文就这几行:

代码块
# Cron Job: daily-hub 每日更新
**Run Time:** 2026-09-15 05:30:25
**Mode:** no_agent (script)
**Status:** script failed
Script timed out after 3600s: …/daily-hub-update.sh

249 个字节。除了这一行什么都没有:没有报错、没有进度,连一句「开始采集了」都没有。输出走的是管道加块缓冲,进程被杀的时候,缓冲区里的话全丢了。

那天线上一条数据都没更新。我是第二天早上打开页面才发现的,因为页面显示的还是前一天的日期。

现在的护栏是四条,都是那次之后加的:

  1. exec </dev/null,让任何子进程读标准输入立刻拿到结束符,不会无声挂死
  2. 每一步都套 timeout:采集 20 分钟、摘要 7 分钟、构建 5 分钟,超了就让它死,别拖着
  3. 用 python3 -u 跑,关掉缓冲,被杀的时候至少能看到进行到哪一行
  4. 每一步往一个日志文件里写一行带时间戳的记号,事后一眼能看出卡在哪一步

后来又补了两个预算,因为审计发现还有同一类隐患:翻译这一步有 420 秒的总预算,Reddit 的重试有 300 秒的总预算。原来那套退避重试最坏情况能睡足十分钟,跟 9 月 15 日那次卡死属于同一类问题。

我的取舍

写到这儿,几个我明确选了的做法:

  • 单个平台失败,不阻断另外 19 个。 代价是失败会静默,所以我盯的是「今天几个平台拿到了数据」,而不是任务的成功状态。
  • 抓不到就留旧数据,并且给它打一个「过期」的标记,前端会标出来。不拿空数据覆盖掉能看的内容。
  • 翻译一次只送 15 条。 一次送 30 条,模型的输出会被截断,JSON 解析失败,整批白翻。
  • 首页摘要那一步,模型只允许返回条目编号。 标题和链接由脚本从原始数据里回填,模型没有机会生成一个不存在的链接。
  • 不去为了拿 Reddit 的分数走没被批准的路,也不去排开发者平台那套申请。收益和成本不成比例。

想照抄的话,三步

第一步,给每一步套 timeout,并且在脚本开头加 exec </dev/null。 这一步花五分钟,能省掉我那次整整一天的数据。

第二步,每一步写一行步进日志。 不用复杂,mark 一个函数,把时间和步骤名追加到一个文件里。出事的时候,这一行就是最快的线索。

第三步,想清楚单平台失败之后干什么:是整条任务失败,还是保留旧数据、把成功的平台数记下来。我选的是后者,因为一个平台挂掉不该让另外 19 个白跑。

我踩过的坑

  • 国内平台别挂代理,国外平台必须挂。同一个脚本里两种网络要求并存,不是统一的开关。
  • 每个平台条数不一样是正常的。V2EX 7 条、少数派 10 条,那是平台给的天花板。
  • 「抓回 0 条」和「抓失败」要分开处理,前者不会抛异常,只会让你以为一切正常。
  • 构建日志别写系统临时目录。有一次那里有个别的身份创建的旧文件,权限不对,整个任务失败。
  • cron 环境里的 HOME 不是你家目录。脚本第二行就 export HOME,不然读配置和凭据全是找不到。
  • 记录时间戳的那个文件,每个月会攒下几千行,别忘了定期清。

文末:那个脚本

40 行,注释里标了哪几处要按自己环境改。

代码块
#!/bin/bash
# 每日更新: 采集 → 翻译 → 构建 → 提交推送
# 每一步都有 timeout 兜底 + exec </dev/null:单步挂起不能拖垮整个 job。
# 教训:某一步静默挂死 → 整条脚本卡到任务上限被杀,当天数据全丢
# (输出走管道 + 行缓冲被块缓冲吞掉,报告里一个字都没有)。
exec </dev/null                 # 任何子进程读 stdin 立刻 EOF,不会无声挂死
export HOME=/home/你的用户名      # cron 环境里这里往往是 /root,必须显式指定
cd /path/to/your/repo || { echo "REPO NOT FOUND"; exit 1; }

STEPS_LOG="$HOME/.logs/steps.log"
mkdir -p "$(dirname "$STEPS_LOG")"
mark() { echo "== $1 =="; printf '%s %s
' "$(TZ=Asia/Shanghai date '+%F %T')" "$1" >> "$STEPS_LOG"; }

start=$(date +%s)

mark "fetch $(TZ=Asia/Shanghai date +%H:%M:%S)"
timeout -k 30 1200 python3 -u scripts/fetch.py || {
  rc=$?; echo "FETCH FAILED (rc=$rc; 124=单步超时)"; exit 1; }

mark "summarize $(TZ=Asia/Shanghai date +%H:%M:%S)"
timeout -k 30 420 python3 -u scripts/summarize.py || echo "SUMMARIZE FAILED (non-fatal)"

mark "build $(TZ=Asia/Shanghai date +%H:%M:%S)"
BUILDLOG="$HOME/.logs/build.log"
mkdir -p "$(dirname "$BUILDLOG")"
timeout -k 30 300 npm run build > "$BUILDLOG" 2>&1 || {
  rc=$?; echo "BUILD FAILED (rc=$rc)"; tail -5 "$BUILDLOG"; exit 1; }

mark "git $(TZ=Asia/Shanghai date +%H:%M:%S)"
git add -A
if git diff --cached --quiet; then
  echo "no changes to commit (data unchanged)"
else
  timeout 180 git commit -m "daily update $(TZ=Asia/Shanghai date +%Y-%m-%d)"     || { echo "COMMIT FAILED"; exit 1; }
  timeout 180 git push origin main || { echo "PUSH FAILED"; exit 1; }
fi

echo "DONE in $(( $(date +%s) - start ))s"

关键点只有四条:第一行的 exec </dev/null、每个 timeout 的数字、python3 -u 的无缓冲、还有那个 mark 函数。剩下的按自己环境改路径就行。


利益相关说明:这篇文章没有赞助方,文中的管线是我自己搭的。我另外运营一个付费社群「AI落地实录」,每周写一篇这类深挖——我是星主,以上属于自我推荐性质的自我宣传。对这类内容感兴趣的可以看:AI落地实录(新用户 25 元/年)。

本文内容整理自公开网络资讯,仅供参考,版权归原作者所有。

点击这里复制本文地址 以上内容由铁柱网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,版权违规,投诉请联系我们,谢谢!


扫码关注“铁柱网”微信公众号
更多精彩,等你来撩

有好的文章或资源希望【铁柱网】帮助分享推广,猛戳这里我要投稿

支持Ctrl+Enter提交
qrcode

铁柱资源网 © All Rights Reserved.  

嘿,欢迎咨询