在 Hacker NewsHN首页每隔一段时间就会出现一条从标题到正文都很“完整”的帖子三段式背景、几十条要点、结尾再来一句总结。很多读者会下意识怀疑它是 AI 生成的但很少有人认真统计过HN 头条里到底有多少帖子属于 AI 内容。原作者围绕这个问题做了两次抽样调查得到的答案并不是“几乎没有”也不是“到处都是”而是一个需要放在分类口径下仔细解释的比例。本文不打算停留在结论转述上而是把这次调查的方法拆开如何从 HN 获取头条样本、如何给帖子做 AI 属性标注、两次抽样在时间窗口和分类规则上有哪些差异、最终比例应该怎么计算和解读以及哪些因素会让结果失真。整条链路可以复现你也可以用同一套脚本采集自己指定时段的 HN 首页数据验证不同时间点上的差异。1. 先搞清楚“HN 头条里的 AI 内容”到底指什么1.1 为什么不能用“我扫了一眼首页”做结论HN 的首页头条不是一个固定列表。topstories接口返回的是实时热度排序几分钟内就可能发生替换一条帖子能不能留在首页取决于点赞速度、评论热度、时间衰减和 HN 的排名算法和这条帖子的内容是否由 AI 生成没有任何直接关系。如果用个人观感来估计 AI 内容比例很容易出现两类偏差。第一类是幸存者偏差一条标题明显带有 AI 风格、正文像综述的帖子更容易被记住而大量普通工具发布帖、招聘帖和问答帖会被忽略最后高估比例。第二类是时段偏差工作日白天 HN 上技术讨论更密集周末和深夜则可能被更多博客投稿占据不同时段看到的内容成分完全不同。两次抽样调查所做的本质上就是把这种印象转化为可计数、可复核的样本统计。1.2 需要把内容分成四类要回答“HN 头条有多少是 AI 内容”不能只做一个“是 AI / 不是 AI”的二分类。因为“AI 内容”这个词至少包含两个维度内容是否由 AI 生成以及内容是否在讨论 AI。如果把两个维度混在一起统计结果会失去解释力。建议按照下表分成四类类别通俗解释典型信号AI 生成帖帖子主体由大模型自动生成作者只做发布或简单编辑模板化结构、无个人经验、泛泛的综合介绍、缺少可复现细节AI 辅助帖人类提供数据、代码或经验由 AI 帮助改写和润色有具体内容和真实参数但段落间带有明显的总结式过渡AI 主题帖内容在讨论 AI但帖子本身是人类写的个人观点、系统设计、采访、代码仓库、评测非 AI 帖与 AI 无关内容主要由人类完成常规技术博客、招聘、工具发布、硬件项目这个分类很重要。如果把“AI 主题帖”当成“AI 内容”那么在 2023 年之后的 HN 首页上大量关于大模型的技术讨论都会被误算进去比例会明显偏高但结论和“AI 在代替人类发帖”完全不是一回事。1.3 两次抽样调查的统计口径统计口径直接决定最终百分比。建议在调查开始时同时计算两个口径严格口径只有“AI 生成帖”计入分子。宽松口径“AI 生成帖”和“AI 辅助帖”都计入分子。原作者两次抽样调查的正确读法必须带上口径一起看。如果第一次用严格口径、第二次用宽松口径两次百分比之间的差异并不能说明 AI 内容变多了只能说明统计规则变了。这也是后续在社区传播时最容易引起误解的地方。注意本文中出现的两次抽样参数和结果表都用于说明方法不是原作者调查的原始数据。想要下结论必须基于自己的采集记录和标注表。2. 数据采集用 HN 官方 API 拉取头条而不是靠肉眼截图2.1 数据链路和字段设计HN 提供公开的 Firebase API不需要 API Key。核心请求只有两步请求https://hacker-news.firebaseio.com/v0/topstories.json得到一篇story的 ID 数组。对每个 ID 请求https://hacker-news.firebaseio.com/v0/item/{id}.json得到标题、链接、分数、评论数、作者、时间等字段。为什么要保存原始 JSON而不是只保存整理后的百分比因为 HN 的topstories接口是实时榜单不是历史存档。事后无法通过当前 API 恢复某个时间点的首页如果采集脚本跑完只留下了两个百分比后续发现问题时没有任何办法回查。保存原始 JSON 是最低成本的审计手段。采集时建议保留以下字段字段含义采集备注id帖子 ID去重主键title标题标注时需要url外链地址可能为空Ask HN 没有外链text正文 HTML需要清洗成纯文本score当前分数快照值非最终值descendants评论数数值不一定等于实际评论总数by作者名后续可做作者画像time发布时间Unix 时间戳需要转 ISO 格式type类型只保留 story过滤 job2.2 拉取前 50 条头条的 Python 脚本下面脚本适合学习环境和小规模调查。它做了三件事拉取topstories前 50 条、逐条获取详情、保存原始 JSON。import requests import time import json from datetime import datetime, timezone API_BASE https://hacker-news.firebaseio.com/v0 def fetch_json(path, timeout10, retries3): url f{API_BASE}/{path} for attempt in range(retries): try: resp requests.get(url, timeouttimeout) resp.raise_for_status() return resp.json() except requests.RequestException: if attempt retries - 1: raise time.sleep(1 attempt * 2) top_ids fetch_json(topstories.json)[:50] rows [] for idx, item_id in enumerate(top_ids): item fetch_json(fitem/{item_id}.json) if not item or item.get(type) ! story: continue rows.append({ id: item.get(id), title: item.get(title), url: item.get(url), score: item.get(score), descendants: item.get(descendants, 0), by: item.get(by), time: item.get(time), time_iso: datetime.fromtimestamp( item.get(time, 0), tztimezone.utc ).isoformat(), text: (item.get(text) or )[:2000], type: item.get(type), }) time.sleep(0.1) with open(hn_headline_sample.json, w, encodingutf-8) as fp: json.dump(rows, fp, ensure_asciiFalse, indent2) print(fsaved {len(rows)} rows)为什么取前 50 而不是全部 500 条HN 的topstories接口会返回较长的候选列表但首页实际可见的位置有限。前 50 条已经可以覆盖首页高热度区域也能容纳一些排名临界波动的替代帖子。如果调查目标是“首页那一屏”取前 30 更贴近真实展示效果如果希望样本更稳定可以取前 50。无论取多少都要在报告里写清楚。2.3 清洗正文字段并转成 CSVHN 的text字段是 HTML 片段直接写入标注表会有一堆标签影响人工阅读。需要先转成纯文本import re def clean_html(text): if not text: return text re.sub(r[^], , text) return re.sub(r\s, , text).strip()清洗后把 JSON 转成 CSV 或 Excel 标注表更符合人工标注习惯。下面代码生成一个最简单的标注模板import csv with open(hn_headline_sample.json, r, encodingutf-8) as fp: rows json.load(fp) with open(hn_label_template.csv, w, encodingutf-8, newline) as fp: writer csv.DictWriter(fp, fieldnames[ id, title, url, time_iso, category, confidence, notes, labeler ]) writer.writeheader() for row in rows: writer.writerow({ id: row[id], title: row[title], url: row[url] or , time_iso: row[time_iso], category: , confidence: , notes: , labeler: , })2.4 API 调用限流和网络可达性HN API 是公共服务但没有任何限流承诺。不加等待地连续请求可能遇到超时或 429 响应。生产级采集至少要做到三点请求之间sleep(0.1)到sleep(0.5)小样本不需要激进并发。对超时和 5xx 做指数退避重试而不是立刻失败退出。定期保存中间结果脚本中断时可以从最近一次保存点继续。注意运行采集脚本前先确认当前运行环境可以正常访问 HN 的公共 API。如果脚本长时间卡在requests.get第一件事不是改代码而是确认目标接口可达。3. 抽样设计和人工标注两次抽样的差别在哪里3.1 抽样时间窗口和样本规模两次抽样调查的核心价值在于比较趋势。如果两次抽样都在同一个时段的同一类日子进行差异才更可能反映真实变化如果一次在深夜、一次在工作日早晨差异可能只是活跃用户构成不同。以下表是一个记录第一次和第二次抽样差异的模板维度第一次抽样第二次抽样抽样日期工作日示例周某 22:00间隔数周后同一时段采集范围topstories前 30 条topstories前 50 条去重方式按 story_id 去重按 story_id 和 URL 去重排除类型排除 job 帖排除 job 帖标注方式单人预标注 人工复核双人标注 仲裁这里的日期和范围只是示例。实际两次抽样以原作者调查记录为准。但无论怎样调整都要把“采集时间、取前多少条、是否去重、是否排除 job 帖”写进报告否则百分比没有任何可比较性。3.2 人工标注判断清单人工标注是这次调查中最关键、也最主观的一步。为了避免只看标题就下结论建议对每条帖子按以下清单顺序判断标题是否像“Overview”“Guide”“Comprehensive”这类模板组合。正文有没有作者自己的实测数据、代码片段、踩坑记录。外链指向的博客页面是否也是相同风格的大段综述。作者是否在 HN 评论区回应读者质疑还是发完帖后完全消失。作者历史发帖是否横跨多个无关主题且频率高、段落完整。帖子是否有明确的个人或团队身份信息。一条帖子如果“正文没有个人经验、结构高度模板化、链接指向一个同样模板化的博客”那么即便标题看起来不像 AI 写的也应当归入 AI 生成帖。判断依据是内容生产痕迹而不是个别关键词。3.3 用规则做弱信号预筛而不是直接下结论完全靠人工逐条读几十条样本还可以接受样本扩大到几百条后效率会明显下降。可以用关键词规则把“疑似 AI 生成帖”筛出来优先复核。下面只做一个弱信号示例AI_PATTERNS [ in conclusion, comprehensive overview, here is a breakdown, it is important to note that, delve into, in todays digital landscape, ] def weak_signal(title, text): combined f{title} {text}.lower() hits [p for p in AI_PATTERNS if p in combined] return hits这套规则的误报率很高。一个长期写英文技术博客的人类作者完全可能写in conclusion而真正的 AI 生成文本也可能会刻意避开这些模板词。因此弱信号只能用于排序和优先复核不能作为分类依据。3.4 双人标注与不一致处理单次人工标注的一致性只能代表个人标准。更可靠的做法是让两人独立标注同一批帖子然后对比结果两人分类一致时直接通过。两人分类不一致时先协商协商不成就由第三人仲裁。记录不一致的数量可以简单计算一致率例如一致条数 / 总条数。标注表至少应包含story_id、title、url、category、confidence、notes、labeler。其中notes必须写明判断依据不能只写一个 category否则后期复检时完全想不起来当初为什么这样分类。4. 两次抽样结果怎么算占比公式、结果表和置信区间4.1 计算严格口径和宽松口径样本标注完成后计算比例本身很简单。设样本总数为n其中 AI 生成帖数量为aAI 辅助帖数量为b那么严格口径p_strict a / n宽松口径p_loose (a b) / n下面是一个最小计算公式counts { ai_generated: 4, ai_assisted: 5, ai_topic: 8, non_ai: 33, } total sum(counts.values()) strict counts[ai_generated] / total loose (counts[ai_generated] counts[ai_assisted]) / total print(f样本量: {total}) print(f严格口径: {strict:.1%}) print(f宽松口径: {loose:.1%})这段代码里的数字只是演示。真正使用时counts应来自你的人工标注表统计而不是随手填写的示例值。4.2 用表格呈现两次抽样的差异结果表建议按下表格式组织把口径、样本量、占比放在一起口径第一次抽样第二次抽样说明严格口径AI 生成帖占比8.0%示例12.0%示例示例数据用于演示表格结构宽松口径AI 生成 AI 辅助帖占比16.0%示例20.0%示例示例数据不是原调查结论样本量3050实际以标注表为准这里必须再次强调上面的数字是方法示例不是原作者两次抽样调查的原始结果。写博客或做报告时表格下方要补一句话说明数据来源例如“数据来自 2025 年某日两次采集并人工标注完整标注表见附录”。这样后续引用时才不会产生误导。4.3 不要只看百分比要计算置信区间样本量 30 到 50 时点估计的波动非常大。样本量 50、严格口径 8%表观上是一条帖子变化就是 2 个百分点。这样的差异可能只是噪声。可以用正态近似粗算 95% 置信区间import math def ci(p, n, z1.96): if n 0: return (0, 0) se math.sqrt(p * (1 - p) / n) return (max(0, p - z * se), min(1, p z * se)) print(ci(0.08, 50)) print(ci(0.10, 100))样本量为 50、比例为 8% 时95% 置信区间大约从接近 0 到 15.5%样本量为 100、比例为 10% 时区间大约是 4.1% 到 15.9%。如果两次抽样的点估计差异还在彼此的置信区间内就不能贸然下“比例上升了”的结论。小样本调查的正确写法是给区间而不是只给一个百分比。5. 调查里最容易踩的 5 个坑5.1 把“讨论 AI 的帖子”当成“AI 写的帖子”HN 是技术社区大量高质量帖子都在讨论大模型、Agent、RAG 和应用落地。这些帖子的标题和正文会频繁出现 ChatGPT、GPT、LLM 等词但它们往往是人类工程师写的系统设计或实测记录。如果统计口径把这类帖子计入“AI 内容”比例会虚高。解决方式在标注前先定义清楚“内容是否由 AI 生成”让它独立于“内容是否与 AI 相关”。AI 主题帖单独成一类不计入严格口径。5.2 重复计数导致样本不独立连续多次拉取topstories时同一条高热度帖子会在多个时间点出现。如果直接把所有快照合在一起热门帖子会被重复计数某种类型的帖子占比可能被放大。解决方式按story_id去重并在报告中写明去重前的快照次数、去重后的独立样本量。重复快照可以用于观察帖子留存时长但不能混进分类统计。5.3 只看标题不看正文和链接标题是最容易被 AI 风格影响的字段但也是最容易伪装的部分。一条标题平平无奇的帖子正文可能是一篇完整的 AI 综述一条标题很“AI”的帖子也可能是作者刻意幽默或使用工具润色后的结果。解决方式每条帖子至少检查正文或外链页面并把检查结果写入notes。样本量不大时不建议隔着 RSS 标题做分类。5.4 采样时段过于特殊重大技术发布日、长假、周末等时段HN 首页的信息构成会和普通工作日明显不同。例如某模型发布当天首页几乎全是相关讨论AI 主题帖占比暴涨但 AI 生成帖占比未必同步变化。解决方式固定采样时段并记录采样日是否出现重大事件。多次抽样最好拉长到几周以上而不是连续两天。5.5 单人标注的主观偏差一个人标注时标准可能在前十条和后十条之间漂移。刚开始觉得“这段像 AI”看了二十条模板化帖子之后对 AI 风格的敏感度会下降分类标准就变了。解决方式双人独立标注、第三人对不一致点做仲裁。标注规则提前写好标注过程中不随意改要改就回头重新过一遍已有样本。6. 从调查结论延伸读者、运营者各自该怎么做6.1 对 HN 读者快速识别一条疑似 AI 生成帖如果不想做完整调查只想在日常刷 HN 时保持判断力可以按三步走先看正文有没有具体数据、代码、复现步骤而不是只看标题。再点进外链博客看是否存在大量“无信息密度的综述段落”。最后看评论区如果有老用户质疑“这看起来像 AI 生成”参考价值很高。不要用“出现没出现某个词”来判断。AI 写作在快速演进模板词越来越不明显但“没有个人经验、没有具体边界、没有可复现结果”这个弱点仍然存在。6.2 对内容运营把“AI 内容占比”做成可监控指标内容平台如果关心 AI 内容占比可以借鉴这套方法但需要把流程工程化固定采集频率例如每小时抓一次榜单。先做规则预筛再由人工抽样复核。用分类模型做全量打标但持续用人工标注集评估准确率。输出指标时区分严格口径和宽松口径。比例指标只有定义固定后才有趋势价值。今天算“AI 生成帖”明天算“AI 主题帖”后天把辅助润色也算进去最后画出来的曲线没有任何指导意义。6.3 扩展方向更大样本和作者画像这一次调查的是“HN 头条里有多少 AI 内容”下一步可以继续拆解作者维度发布 AI 生成帖的作者账号年龄、发帖频率、发帖时间分布有什么特征。链接维度AI 生成帖主要来自哪些域名是否集中在低质量博客平台。时间维度AI 内容占比是否在特定时段更高例如人类活跃度低的深夜时段。评论区信号评论中是否有用户指出 AI 特征这些信号能否作为弱监督数据。这些扩展不需要推倒现有脚本只需要在采集脚本中补齐作者历史、域名、正文长度等字段。7. 完整可复现实验的环境准备和操作清单7.1 环境依赖建议使用以下环境适合运行本文示例脚本依赖版本建议用途Python3.10 及以上运行采集和统计脚本requests最新稳定版请求 HN APIpandas可选统计分析文本编辑器/CSV 工具任意人工标注如果原始环境网络访问公共 API 受限需要先解决网络可达性问题再继续采集。这是一切后续步骤的前提。7.2 操作步骤清单固定采样协议确定日期、时段、topstories取前多少条、是否排除 job 帖。运行采集脚本保存原始 JSON。执行 HTML 清洗生成hn_label_template.csv。由两人独立完成标注记录category、confidence、notes、labeler。对比两份标注表处理不一致条目。统计严格口径和宽松口径比例。计算 95% 置信区间。输出报告附上采样时间、样本量、分类规则和标注表。7.3 验证输出脚本运行成功时终端应输出类似saved 50 rows的信息。检查点如下hn_headline_sample.json中每条记录都有id、title、time_iso。hn_label_template.csv中每个category字段最终不为空。统计结果里样本量和标注表的行数一致。如果统计比例时发现分母和标注表行数不一致优先回查去重逻辑。7.4 生产环境还需要补充什么如果要把这套流程做成周期监控而不只是一次性调查需要额外补四件事调度用 cron 或定时任务固定时间抓取避免手工漏抓。存储把原始 JSON 存入数据库或对象存储而不是覆盖本地文件。监控采集失败时要有告警避免一个月后才发现数据链路早已中断。版本管理标注规则和脚本代码纳入版本管理规则变更时保留旧版本否则历史结果无法回溯。回到最初的问题HN 头条有多少是 AI 内容真正重要的并不是某一个具体百分比而是这个百分比背后有没有固定的采样窗口、清晰的分类口径和可回溯的原始数据。下一次再评价某个平台内容被 AI 覆盖时可以先问一句你用的是严格口径还是宽松口径能把你标注过的原始数据和结果表发出来吗