用 Python 构建 HN S-Tier 链接筛选与归档方案

用 Python 构建 HN S-Tier 链接筛选与归档方案 十年里每天读两次 Hacker News然后把自己认为最值得反复阅读的链接整理成一份“S-Tier”清单。这件事听起来不复杂但真正坚持下来的人很少。难点不在“读”而在“筛”和“存”每天都有几千条新内容涌进来哪些值得收藏、哪些看一遍就够了、哪些过几个月还要翻出来重读这本身就是一套需要长期打磨的方法。这篇文章不谈所谓的“神级链接合集”本身而是拆解这套方法。我们会先讲清楚 S-Tier 链接应该具备什么特征再给出一套可复现的 HN 链接采集、过滤、归档方案。整套方案基于 Hacker News 官方公开 API 和 Algolia 搜索 API用 Python 就能跑起来不依赖 GPU不依赖重型服务适合个人知识库、技术周报整理和日常信息沉淀。如果你平时也逛 HN、喜欢收集技术文章但收藏夹越来越乱、想找某篇文章时翻不到这篇文章可以直接读完再动手。1. 核心能力速览能力项说明内容主题Hacker NewsHN优质链接筛选、采集与长期归档数据来源HN 官方 Firebase API、Algolia HN Search API主要功能获取热帖、获取文章详情、按评分/域名/关键词过滤、生成 Markdown 清单、批量归档技术门槛Python 基础即可不需要机器学习知识运行环境Windows / macOS / Linux 均可普通办公电脑即可启动方式命令行运行 Python 脚本是否支持 API支持HN 官方接口本身公开可用是否支持批量任务支持可一次拉取多个时间段、多个分类显存需求无纯文本接口不涉及本地模型推理磁盘占用极小主要存放 JSON 缓存和 Markdown 归档文件适合场景个人知识库、技术周报、信息筛选、调研素材收集从能力表能看出这套方法的核心不是“做出一个别人没有的工具”而是把 HN 上分散的信息流转变成自己有沉淀、可检索、能复用的链接库。下面的内容会按“筛选标准 - 环境准备 - 脚本实现 - 批量归档 - 排错”顺序展开。2. 适用场景与使用边界先说适合谁。如果你每天打开 HN 只是刷首页刷完就关那这篇内容最多给你一个 API 调用范例。如果你平时要做技术调研、写技术文章、整理团队周报或者单纯想建立一个长期更新的个人技术阅读库这套方法的价值就体现出来了。它适合这几类人技术博主和内容作者。需要持续跟踪 HN 上的高质量讨论作为选题和背景材料来源。独立开发者和技术管理者。关注系统设计、编程语言、数据库、AI 等领域的高分讨论希望把散落的信息沉淀成可检索的库。信息管理爱好者。喜欢用 Markdown、JSON、Git 这类纯文本方案管理个人知识资产的人。对 HN API 感兴趣想拿真实数据练手的开发者。这个项目的数据结构简单很适合做接口调试和批处理练习。再说边界。这套方案不做“自动判断内容优劣”评分筛选只能基于 HN 现有的 score、评论数、发布时间等公开数据。S-Tier 的最终判断仍然需要你人工点开链接、阅读正文、做一次主观评分。另外HN 接口没有官方认证机制没有付费套餐也没有 SLA 承诺只适合低频率的个人使用不适合做大流量产品依赖。合规层面也要注意HN 上的内容是第三方用户提交的链接指向的文章版权属于原作者。收藏、摘录链接和摘要没问题但如果要把文章正文搬运到自己的博客或商用产品里必须获得原作者授权。涉及个人技术博客、公司内部文档、未公开项目等敏感内容时不要过度抓取和存档。官方 API 也明确要求合理使用频率批量采集前要控制请求速率。3. 如何理解一份 S-Tier HN 链接清单作者坚持十年每天读两次 HN最后留下的“S-Tier”清单并不是简单的“高分链接 Top 100”。高分的本质是“当时有很多人觉得有用”但不代表“过两年还有重读价值”。真正能进入长期清单的链接通常具备几个共同特征。第一个特征是技术深度。HN 上分数很高的帖子有时只是某个知名项目发布的新闻稿信息价值在当天就消耗完了。但真正值得进 S-Tier 的往往是那些解释底层原理、复盘系统演进、对比技术选型的深度文章。这类内容的特点是你读第一遍能理解作者的结论读第二遍还能看到当时忽略的细节。第二个特征是长期有效性。一篇讲“如何用 Redis 实现分布式锁”的老文章只要里面的方案还有参考意义放在今天依然值得读。反过来一篇“某框架发布 v1.0”的新闻因为版本演进而快速过时就不适合放进长期清单。第三个特征是可操作性。S-Tier 链接往往能直接指导实践。比如一篇完整的性能调优案例、一份详尽的故障复盘报告、一个带完整代码的架构设计方案这类内容可以当工具书反复翻阅。第四个特征是稀缺性。HN 上大量内容是对其他媒体内容的转发真正原创的、只有作者自己才能写出来的经验帖更值得收藏。尤其是那些“公司内部架构文章”“独立开发者收入复盘”“底层库作者的设计笔记”一旦被删或链接失效就很难再找到第二份。理解了这些特征你就能明白对外部链接进行 S-Tier 筛选不能交给简单的公式必须加入个人判断。脚本能帮你缩小范围但不能替你判断。4. 环境准备与前置条件这套链接采集方案不需要 GPU也不需要安装大型框架环境要求非常低。建议配置操作系统Windows 10/11、macOS、Ubuntu 等主流系统均可。Python 版本建议 3.8 及以上3.9/3.10/3.11 都行。网络环境能正常访问 HN API 即可。不同地区访问国际接口的延迟不同超时时间可以按自己网络条件调整。磁盘空间脚本和归档文件加起来通常不到 100MB完全可以忽略。依赖包主要用到requests可选pandas。Python 环境没有的先装 Python然后在项目目录里创建虚拟环境mkdir hn-s-tier cd hn-s-tier python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate安装依赖pip install requests如果你希望后面导出 CSV 或做复杂过滤可以顺手把 pandas 也装上pip install pandas安装完可以先验证一下依赖是否正常python -c import requests; print(requests.__version__)能输出版本号说明环境准备完成。没有 GPU、没有 CUDA、没有大模型依赖这套流程在轻量云服务器、树莓派、旧笔记本上都能跑。5. 用 HN API 构建自己的链接清单5.1 理解 HN 的公开接口HN 官方提供了基于 Firebase 的只读 API基础地址是https://hacker-news.firebaseio.com/v0/常用的接口有三个https://hacker-news.firebaseio.com/v0/topstories.json https://hacker-news.firebaseio.com/v0/askstories.json https://hacker-news.firebaseio.com/v0/showstories.jsontopstories.json返回一个 JSON 数组里面是最新热门文章的 ID。这个数组默认有几百个 ID按热度排列但没有直接附带文章标题和链接需要根据 ID 再去item/{id}.json拉详情。拿到单个文章详情后返回的 JSON 结构大致如下{ id: 123456, title: Example HN Story, url: https://example.com/article, score: 320, by: author_name, time: 1720000000, type: story }其中score是点赞数title是标题url是外链地址。如果没有外链就是纯粹的 HN 讨论帖url字段可能不存在。除了官方 APIAlgolia 还维护了一套 HN 搜索 API适合做关键词检索https://hn.algolia.com/api/v1/search?querypythontagsstory这套接口支持按关键词、按时间范围、按标签筛选批量调研时很好用。5.2 写一个最小可运行的采集脚本新建fetch_hn.py内容如下import json import time import requests BASE https://hacker-news.firebaseio.com/v0 HEADERS {User-Agent: hn-s-tier-script/0.1} def fetch_topstories(limit30): resp requests.get(f{BASE}/topstories.json, headersHEADERS, timeout30) resp.raise_for_status() article_ids resp.json() return article_ids[:limit] def fetch_item(item_id): resp requests.get(f{BASE}/item/{item_id}.json, headersHEADERS, timeout30) resp.raise_for_status() return resp.json() def build_markdown(items): lines [# S-Tier HN Links, ] lines.append(| 标题 | 评分 | 发布时间 | 链接 |) lines.append(| --- | ---: | --- | --- |) for item in items: title item.get(title, Untitled).replace(|, \\|) score item.get(score, 0) ts item.get(time, 0) time_str time.strftime(%Y-%m-%d, time.localtime(ts)) url item.get(url) or fhttps://news.ycombinator.com/item?id{item.get(id)} lines.append(f| {title} | {score} | {time_str} | {url} |) return \n.join(lines) if __name__ __main__: story_ids fetch_topstories(20) stories [] for sid in story_ids: try: story fetch_item(sid) if story and story.get(type) story: stories.append(story) except requests.RequestException as e: print(ffetch item {sid} failed: {e}) time.sleep(0.3) output build_markdown(stories) print(output)运行python fetch_hn.py脚本会拉取当前 top 20 条热门文章输出一个 Markdown 表格。第一次运行建议先跑 5 到 10 条确认网络和接口都正常。这里有一个重要设计每次请求之间加了time.sleep(0.3)也就是约每秒 3 个请求。这个频率对个人脚本来说是合理的也避免因为请求过快被 HN 限流。5.3 加入评分阈值过滤热门文章不等于高质量文章。要靠近 S-Tier可以先加一个最低评分过滤。把build_markdown之前加一个函数def filter_by_score(stories, min_score100): return [s for s in stories if s.get(score, 0) min_score]调用时filtered filter_by_score(stories, min_score150)门槛怎么定高分时段和低分时段差异很大工作日 HN 首页的帖子分数通常更高周末则偏低。固定阈值只是第一步更合理的做法是设置一个相对阈值比如“当天 top 50 里的前 20%”这需要你按自己的数据情况调整。5.4 加入关键词过滤有时候你只关心某个领域比如 Rust、数据库或者 AI Agent。可以加一个关键词系统KEYWORDS [rust, postgres, llm, agent, database] def filter_by_keywords(stories, keywordsKEYWORDS): result [] for story in stories: text f{story.get(title, )} {story.get(url, )}.lower() if any(kw in text for kw in keywords): result.append(story) return result注意关键词过滤会把一部分相关但标题里没有关键词的文章漏掉。比如一篇讲数据库性能调优的文章标题里可能没有 database 字样。所以关键词过滤适合做初步筛选不适合做最终判断。6. 功能测试与效果验证6.1 测试 1确认 API 连通性先跑通最基础的一步拉取 topstories 数组。curl https://hacker-news.firebaseio.com/v0/topstories.json如果返回结果是一个 JSON 数组比如[123456, 123457, 123458]说明 API 连通正常。如果返回 404 或者null说明请求地址写错或者当前网络环境访问该接口异常。判断成功的标准接口返回一个包含多位数字 ID 的数组。不需要依赖特定 ID因为 HN 内容是实时变化的。6.2 测试 2拉取单条文章详情从上面拿到的一个 ID 去测试详情接口curl https://hacker-news.firebaseio.com/v0/item/123456.json这里把123456替换成你实际拿到的 ID。返回 JSON 里应该包含title、score、by、time这些字段。判断成功的标准请求能返回完整的 JSON 对象并且type字段为story。如果返回null说明这个 ID 对应的内容已被删除或不存在这在 HN 上是正常现象。6.3 测试 3Python 脚本生成 Markdown运行前面写的fetch_hn.py观察输出。第一次跑建议把limit改成 5减少请求数量。判断成功的标准终端输出一个 Markdown 表格表格第一行是表头下面每一行代表一篇文章包含标题、评分、发布时间和链接。失败时优先检查三件事终端有没有报connection timeout或NameError前者是网络问题后者是代码问题。脚本能不能正常导入requests不行就重新安装依赖。输出的表格里标题是否包含|字符这个字符在 Markdown 表格里必须转义脚本里已经加了一部分处理。6.4 测试 4评分过滤和关键词过滤改一下脚本把 top 50 篇文章拉下来然后分别用min_score200和KEYWORDS过滤观察输出数量。判断成功的标准过滤后输出数量明显少于过滤前且留下的文章和你的预期一致。如果过滤后输出为空可能是阈值设得过高或者关键词太少需要调整。6.5 测试 5Algolia 搜索接口Algolia 接口适合做主题调研。测试一下curl https://hn.algolia.com/api/v1/search?queryllmtagsstoryhitsPerPage10返回的 JSON 结构里hits数组包含了匹配结果。每条结果还有points、num_comments、created_at等字段。判断成功的标准hits数组非空并且可以看到title字段。如果接口超时可以适当缩小查询范围。7. 接口 API 与批量任务7.1 批量归档到 Markdown手工跑一次脚本只能拿到当前时刻的快照。真正长期维护 S-Tier 清单需要的是一套可以每天自动执行的批量归档流程。最简单的批量任务就是按日期归档。新建archive.pyimport json import time import datetime import pathlib import requests BASE https://hacker-news.firebaseio.com/v0 HEADERS {User-Agent: hn-s-tier-batch/0.1} OUTPUT_DIR pathlib.Path(archive) def fetch_topstories(limit100): resp requests.get(f{BASE}/topstories.json, headersHEADERS, timeout30) resp.raise_for_status() return resp.json()[:limit] def fetch_item(item_id): resp requests.get(f{BASE}/item/{item_id}.json, headersHEADERS, timeout30) resp.raise_for_status() return resp.json() def generate_archive_file(stories, date_str): lines [f# HN Archive {date_str}, ] lines.append(| 标题 | 评分 | 评论数 | 链接 |) lines.append(| --- | ---: | ---: | --- |) for story in stories: title story.get(title, ).replace(|, \\|) score story.get(score, 0) comments story.get(descendants, 0) url story.get(url) or fhttps://news.ycombinator.com/item?id{story.get(id)} lines.append(f| {title} | {score} | {comments} | {url} |) return \n.join(lines) def main(): OUTPUT_DIR.mkdir(exist_okTrue) today datetime.date.today().isoformat() story_ids fetch_topstories(100) stories [] for sid in story_ids: try: story fetch_item(sid) if story and story.get(type) story: stories.append(story) except requests.RequestException: pass time.sleep(0.3) md_content generate_archive_file(stories, today) output_path OUTPUT_DIR / f{today}.md output_path.write_text(md_content, encodingutf-8) print(farchived {len(stories)} stories to {output_path}) if __name__ __main__: main()运行python archive.py运行结束后会在archive目录下生成一个带有当天日期的 Markdown 文件。这样可以做到每天一个文件方便回溯。保留当天 top 100 的原始快照即使 HN 上内容更新了你手里仍然有一份历史记录。如果一条热帖第二天分数掉了很多你反而能观察到 HN 上的评分变化过程。7.2 批量抓取多个分类除了 topstories还可以扩展拉取 askstories 和 showstories。修改main函数把三类都跑一遍CATEGORIES { top: topstories.json, ask: askstories.json, show: showstories.json, }这样每天的归档文件就能覆盖“热门文章”“Ask HN 讨论”“Show HN 作品展示”三个维度。Ask HN 里经常出现值得长期收藏的经验讨论Show HN 则是发现新项目的重要渠道。7.3 失败重试与增量更新批量任务最怕中间断掉。推荐在fetch_item上做统一重试def fetch_item_with_retry(item_id, retries3, wait2): for attempt in range(retries): try: return fetch_item(item_id) except requests.RequestException as e: if attempt retries - 1: raise print(fretry {item_id} after {wait}s: {e}) time.sleep(wait)增量更新的思路是先读取昨天的归档文件或本地的已处理 ID 集合只拉新的 ID。简单做法是把已处理的 ID 存到一个processed_ids.json文件里processed set() if pathlib.Path(processed_ids.json).exists(): processed set(json.loads(pathlib.Path(processed_ids.json).read_text())) new_ids [i for i in story_ids if i not in processed] # 只对 new_ids 拉详情 # 处理完成后更新 processed_ids.json这样重复运行时不会反复请求全部 ID减少接口压力。7.4 通用 API 调用模板如果你不是要跑整套脚本只是想在自己项目里调 HN 接口下面这个模板可以直接参考import requests def get_hackernews_item(item_id: int) - dict: url fhttps://hacker-news.firebaseio.com/v0/item/{item_id}.json resp requests.get(url, timeout30) resp.raise_for_status() return resp.json()import requests def search_hackernews(query: str, hits_per_page: int 20): url https://hn.algolia.com/api/v1/search params {query: query, tags: story, hitsPerPage: hits_per_page} resp requests.get(url, paramsparams, timeout30) resp.raise_for_status() return resp.json().get(hits, [])注意HN 接口没有认证机制属于免费开放接口。个人项目和低流量场景用起来没问题但不要拿它做高频数据采集。更稳妥的做法是控制每秒请求数在 1 到 3 次以内并且给自己留一个本地缓存层。8. 资源占用与性能观察这个方案不涉及 GPU不涉及大模型资源占用主要集中在网络请求和本地文件写入上。8.1 网络请求耗时HN API 的响应大小很小单条topstories.json只有几 KB单条item详情也是 KB 级。耗时主要来自网络延迟而不是数据处理。国内访问国际接口的延迟通常比较高所以脚本里所有请求都建议设置timeout30避免因为单次请求卡住整个任务。观察方法在脚本里加一个简单计时start time.time() # 业务逻辑 print(felapsed: {time.time() - start:.2f}s)如果拉 100 条详情耗时超过几分钟多半是网络延迟导致的可以考虑调小每批数量或者放宽sleep间隔。8.2 CPU 和内存占用脚本本身没有复杂计算CPU 占用可以忽略不计。内存方面100 条文章详情的 JSON 数据总量也就几 MB普通办公电脑运行毫无压力。8.3 避免长时间运行如果归档脚本要每天定时执行建议配合操作系统的定时任务。Linux 和 macOS 用 cronWindows 用任务计划程序。定时任务执行时要注意上次任务是否还在运行避免两个进程同时写同一个归档文件。简单做法是在脚本开头加一个锁文件检查import pathlib LOCK_FILE pathlib.Path(hn_archive.lock) if LOCK_FILE.exists(): print(another instance is running, exit) exit(0) LOCK_FILE.touch() try: main() finally: LOCK_FILE.unlink()8.4 Markdown 文件体积每天的归档文件大小通常在 20KB 到 50KB 之间一年的归档文件加起来也不到 20MB。哪怕积累十年这个方案都不需要额外的存储服务放本地或者 GitHub 仓库里都很合适。9. 常见问题与排查方法问题现象可能原因排查方式解决方案接口返回null请求的 item 不存在或被删除换一个已知 ID 测试HN 内容有删除机制属正常现象跳过该 ID 即可请求超时网络延迟高或临时不稳定用浏览器打开 API 地址看能否返回 JSON调大timeout增加重试机制降低单批数量topstories.json返回 404接口地址拼写错误检查 URL 是否包含/v0/修正为https://hacker-news.firebaseio.com/v0/topstories.jsonMarkdown 表格显示错乱标题中包含|或换行符打印原始标题检查替换|为\|去掉换行符脚本运行一次后被限流请求频率过高查看日志中是否有 429 或连接重置加大sleep间隔建议不低于 0.5 秒归档文件内容为空拉取详情时全部失败检查异常输出增加重试逻辑把失败的 ID 单独记录重复链接过多热门文章长期停留在首页按日期归档后做标题去重保存最近 N 天的标题集合遇到重复跳过Algolia 搜索返回结果少关键词过于具体用更宽泛的关键词测试拆分多个关键词分别搜索定时任务没有执行cron 或任务计划程序配置错误手动执行脚本确认正常检查定时任务日志使用绝对路径运行 Python9.1 关于接口请求频率HN 官方 API 没有公开的硬性限流文档但社区普遍认为请求频率过高会触发临时封禁。个人使用场景下控制每秒 1 到 3 个请求是稳妥做法。如果看到 HTTP 429 或者连接被重置就停下来等几分钟再继续不要尝试暴力重试。9.2 关于内容丢失HN 上的链接可能随时失效。今天在首页的热门文章下周原网站可能就下线了。这也是为什么要做本地归档的原因。但要注意归档本地文件里保存的是标题和链接不是文章正文。如果链接真的失效你从归档里也只能看到标题。所以真正重要的深度文章建议手动保存正文或者截图并注意版权合规。10. 最佳实践与使用建议10.1 建立两级筛选流程第一级是脚本自动过滤用评分和关键词把几百条内容压缩到几十条。第二级是人工阅读从这几十条里挑出真正符合 S-Tier 标准的链接打上标签写入长期清单。两步分开做效率比直接刷首页高很多。10.2 用标签而不是文件夹如果你积累的 S-Tier 链接超过几百条单纯按日期归档就不够用了。推荐在 Markdown 文件里给每条链接打标签| 标题 | 标签 | 评分 | 链接 | | --- | --- | ---: | --- | | Example Article | database, performance | 320 | https://example.com/article |标签可以随时改文件夹结构一旦定了就很难调。后面想按主题导出清单用 Python 做一次标签过滤就行。10.3 定期回顾并重新评分十年前觉得惊艳的文章现在可能已经过时当时没看懂的文章现在可能是宝藏。建议每隔半年或一年回看一遍自己的 S-Tier 清单把过时的内容移到 Archive把依然有价值的置顶。这套“定期复核”机制比单纯收藏重要得多。10.4 用 Git 管理归档归档文件是纯文本非常适合放进 Git 仓库。这样你可以看到自己筛选标准的变化过程也能在误删后找回历史版本。GitHub 仓库、GitLab、Gitea 都可以存按天提交一个仓库跑十年也没什么压力。10.5 控制采集范围不要尝试把 HN 上所有内容都抓下来。个人知识库的目的不是备份 HN而是沉淀自己真正需要的内容。建议每天只拉 top 100 加上 Ask HN 和 Show HN足够了。拉全站数据既消耗资源也超出了个人使用场景的合理边界。11. 总结与下一步这个项目最值得尝试的地方不是 API 调用本身而是“用脚本缩小人工筛选范围”的流程思路。作者坚持十年每天读两次 HN最后沉淀出的 S-Tier 链接清单本质上不是靠工具而是靠一套稳定的、可以长期执行的筛选和归档习惯。如果你打算照着搭一套建议顺序是第一步先跑通最小脚本拉一次 top 20 生成 Markdown 表格。第二步加入评分阈值和关键词过滤跑一周看效果。第三步加上按日期归档和 Git 管理形成稳定习惯。最容易踩的坑有两个。一个是请求频率没控制好导致 IP 被临时限流遇到这种情况不用慌降低频率等一段时间就好。另一个是只顾着抓数据、不做人工筛选最后归档了一堆没有价值的链接和没归档也没什么区别。下一步可以继续扩展的方向包括用 GitHub Actions 做定时触发、把归档文件接入自己的博客系统、根据标签生成月度技术周报、甚至结合本地知识库工具做全文检索。HN 的内容每天都在变化但一套稳定的筛选流程可以用很久。建议先跑一周再看 Archive 里留下了多少值得回看的内容。