用新闻API打造关键词每日简报:从抓取到定时推送的完整实践 你每天用在刷新闻上的时间有多少半小时一小时如果是做投资研究、产品调研或者行业观察这个时间还要翻倍。我决定做一个关键词新闻简报小工具输入一个关键词比如AI创业通过调用新闻API自动抓取最新行业新闻按时间排序后清洗、去重、抽摘要最后生成一份可直接在手机上读的每日简报。跑了一个多月每天稳定输出省下来的时间非常可观。这篇文章把整个思路、选型、代码实现和踩过的坑都整理出来适合想批量跟踪行业动态又不想天天泡在资讯流里的开发者、自媒体编辑和研究者参考。先说结论这个工具的技术门槛并不高真正的难点在于新闻源的质量控制、关键词的误召回处理以及简报的可读性设计。如果只做一个爬接口打印标题的脚本十分钟就能写完但那种东西基本没法坚持用。我折腾了几版最后沉淀下来的方案是API抓取 多级过滤 摘要生成 定时投递下面按照实际开发顺序拆开讲。1. 整体设计与思路拆解1.1 核心需求与适用场景标题里写的调用新闻API输入关键词抓取最新行业新闻生成每日新闻简报落点其实是三个动作定向采集、时效过滤和结构化输出。我最开始只想要一个简单的关键词搜索工具用一次调接口返回几十条结果就够了。但用几天后发现零散查询解决不了持续跟踪的问题。真正的需求往往是这样我想连续追踪AI创业这个概念关注它每周的新获投融资、新产品发布、头部公司的起落偶尔还要留意政策变化。这种需求靠人肉在搜索框里点状态很难保持靠搜索引擎的Alert邮件噪音又大邮件容易被淹没。于是做每日定时简报就成了最合理的形态。适用人群也很明确做竞品分析和行业研究的人每天需要扫一遍某垂直领域自媒体团队需要一个低成本的选题雷达个人投资者跟踪特定赛道还有像我这种收藏了一堆资讯App但从来不打开的人。本质上这个工具解决的是信息筛选的重复劳动把每天花一小时看资讯压缩成每天用五分钟读简报。1.2 方案选型为什么用新闻API而不是自己爬这是第一个必须想清楚的问题。有人在设计阶段可能会说直接去目标站点爬不就行了又不要钱。我确实也走过这条路简单说几个实测教训头部新闻站的反爬策略更新很快今天能用的请求头明天就可能返回验证页维护成本远超预期。新闻网站的结构频繁调整昨天能解析的HTML今天可能改了一个class名称就全部失效。爬虫在法律风险上相对模糊个人学习用还好一旦做成团队工具或内部服务版权和合规问题就要认真对待。新闻API的优势在于数据的可获得性和规范性。它把新闻源聚合、结构化解析、时间戳统一这些脏活都替你做了返回的JSON里通常包含了标题、正文摘要、发布时间、来源、作者、分类等清晰字段。你只需要做业务逻辑不需要处理网页解析。代价是收费和服务限制但现在不少厂商都有免费额度个人使用完全够。这里只分享选型原则不点名具体服务商因为接口服务变化太快今天好用的明天可能就被收购改名了读者只要拿着挑选标准去对比就行。1.3 整体流程与各环节的取舍整个简报生成流水线长这样关键词定义与过滤规则配置 → 定时触发抓取任务 → 调用新闻API获取新闻列表 → 数据清洗与去重 → 相关性打分 → 提取摘要 → 渲染成简报模板 → 输出或推送这个流程里有几个容易被忽略的取舍点抓取频率定为每天一次而不是实时。行业新闻不是股票行情用户真正需要的是今天发生的重要事情而不是这几分钟内的突发事件。一天一次可以把API用量降到最低免费额度完全覆盖。时间窗口设为过去24小时而不是过去1小时或3小时。考虑到各新闻网站发布时间和API收录之间往往有几个小时的延迟如果只取最近1小时早上看简报时可能漏掉昨晚黄金时段发的新闻。24小时窗口比较稳妥。先把摘要生成放在第三步而不是第一步。很多新闻API的返回内容里已经附带一小段摘要先用这段原始摘要做粗筛如果足够就用原文用大模型做二次摘要只是为了解决那些摘要缺失或标题党的条目。我把整个流程总结成一张职责表后面每个模块都按这个职责去写。模块职责输出形态定时调度每天在设定的时间点触发任务触发信号抓取器组装参数、调用API、处理分页原始新闻条目清洗器去重、过滤噪音、过滤旧闻干净的新闻列表打分器计算关键词相关度、综合排序带分数的新闻列表摘要生成提取核心信息、压缩成简报摘要文本渲染器按模板输出Markdown/HTML最终简报这个架构的好处是模块之间解耦今天用这个API明天换另一个API只需要改抓取器其他部分全都不用动。我在后续扩展多关键词的时候也体会到了这个好处一个关键词一套Filter规则共用同一套渲染模板加一个关键词只是加一行配置的事。2. 核心细节解析与实操要点2.1 新闻API选型不看品牌看这几个指标市面上的新闻API五花八门有的主打全球新闻聚合有的专注财经快讯有的其实是通用搜索引擎的新闻模式接口。我建议按五个维度去评估内容覆盖范围这个接口到底聚合了哪些新闻源能不能覆盖你关心的区域和语种拿AI创业来说国内项目和海外项目完全是两个圈层如果你不想漏可能需要配两个接口源。时间精度与收录延迟新闻发布时间字段是否精确到分钟API端收录新闻的延迟通常不是实时的有些轻松平台的数据要晚一两个小时。测试方法很简单抓一个结果打开源文章链接对一下发布时间。字段完整性除了标题和链接是否返回正文摘要、作者、关键图片、来源Logo字段越全后面做渲染就越省事。最影响体验的字段其实是摘要没有摘要就没法做高密度简报。配额与限流策略免费次数是一次性赠送还是每天重置并发限制多高在开发调试阶段这个限制决定了你能不能反复试错。建议选按天重置的而不是总量赠送的。是否支持关键词组合查询有些接口只支持简单的短语匹配有些支持 AND/OR 逻辑、站内源过滤、排除词。组合查询能力越大生成日报的准确率越好。2.2 请求参数设计不是只传一个关键词就完事如果调用新闻API只传一个qAI创业通常回来的结果是没法直接用的。原因是关键词匹配太粗很容易召回到某AI创业大赛报名、高校举办AI创业论坛这种边缘内容而且不同新闻源的发布时间格式不统一后面的排序会乱。我在实际配置中至少会传这几类参数参数作用我的建议值q关键词或短语根据过滤规则扩展如AI创业 OR AI 创业公司time_range时间过滤24hlanguage语言过滤zh或ensort_mode排序方式date或recencypage_size每页条数50offset分页偏移第一次为0循环拉取source_include/exclude限定源过滤掉明显低质或不可靠的来源这里有一个非常关键的点关键词要做正向扩展反向排除。比如追踪AI创业正面是AI创业、人工智能创业、AI startup等反面要排除创业大赛、创业培训、创业孵化器活动报名等因为这些关键词里虽然带创业但和AI创业公司的产业动态不是一回事。很多新闻API支持布尔查询尽量用起来(AI创业 OR AI 创业 OR AI startup) AND NOT (创业大赛 OR 创业孵化 OR 创业培训)实际编码时如果API不支持复杂语法就在本地做二次过滤规则放配置文件里慢慢积累。2.3 去重与噪音过滤简报质量的生命线这是我做了三版之后觉得最值得花时间的地方。新闻API返回的数据经常存在三个问题同一新闻被多家媒体转载标题只改了几个字链接完全不同但内容几乎一样。老新闻被翻出来重发时间戳被更新看起来像是最新消息其实内容已是几周前的旧闻。关键词是泛匹配AI创业会命中大量AI创业但不相关的周边新闻。我的处理方案是三段式第一步标题归一化去重。把标题里的中英文标点、多余空格、独家、快讯等前后缀清掉再把AI人工智能创业和人工智能AI创业这类词序不同的标题做字符集归并转成小写、去掉空格和标点然后按MD5算指纹在当天已经收集的新闻里查重。第二步正文片段查重。因为标题去重无法拦截改头换面的转载我把API返回的摘要取前60个字和已有新闻做SimHash或Jaccard相似度计算相似度超过0.68就判定为重复保留来源权重较高的那一条。我自己写的简化版是把摘要切词后取集合交集除以并集简单粗暴但是有效。第三步白名单和黑名单。小成本做法是只保留allowed_domains白名单内的来源比如你自己信任的几家主流媒体和行业垂直媒体再维护一个blocked_keywords黑名单比如广告、招聘、会议报名等。这个名单一开始只有七八条跑一周后慢慢扩充到几十条新闻噪音明显下降。2.4 相关性打分别把热度当一切API返回结果里通常会提供一个热度值或相似度分数许多人直接按它排序。我的经验是接口的热度分大多基于全网搜索指数和点击量呈现明显的头部马太效应——大热点排前面小而重要的行业事件会被淹没。于是我自己加了一个轻量级相关性打分公式综合三部分关键词命中位置标题命中加3分摘要前80字命中加2分正文命中加1分。时效权重发布至今在6小时内的加2分12小时内加1分超过24小时不加分。这个权重防止分时段新闻大量堆在日期排序前列。来源权重行业垂直媒体和权威媒体有基础分3分一般媒体1分被列入黑名单的直接排除。总分计算score 0.6 * 关键词命中得分 0.3 * 时效得分 0.1 * 来源基础分最终按分数从高到低取前2030条进入简报。用这种方式排出来的结果比单纯按API默认排序更贴近行业观察者想看到的东西。3. 实操过程与核心环节实现3.1 工程目录与模块划分我把代码按职责拆成了以下结构。这套结构不复杂但每个文件职责清楚单测也好写news_digest/ ├── config.py # 关键词、API配置、黑白名单 ├── fetcher.py # 调用新闻API、解析JSON、处理分页 ├── cleaner.py # 去重、过滤、归一化 ├── ranker.py # 相关性打分 ├── summarizer.py # 摘要生成优先用API自带摘要 ├── reporter.py # 渲染Markdown/HTML简报 ├── scheduler.py # 定时任务入口 ├── output/ # 生成的简报文件 └── data_cache.json # 每日去重指纹缓存config.py用来保存所有易变的配置思路是把变化留在配置文件里而不是代码里。今天追踪AI创业明天追踪光储充只需要改配置项脚本主干都不用动。这样一个脚本可以同时服务多个关键词配置结构大概长这样# config.py KEYWORDS_CONFIG [ { query: AI创业 OR AI 创业 OR AI startup, exclude_keywords: [创业大赛, 创业孵化, 创业培训, 报名], language: zh, time_range: 24h, page_size: 50, max_items: 20, output_name: ai_startup_daily }, { query: 光储充 OR 光储一体化, exclude_keywords: [光伏安装工, 招聘], language: zh, time_range: 24h, page_size: 50, max_items: 15, output_name: ess_daily } ]3.2 抓取器处理分页和时区的关键代码写抓取器时最容易翻车的三件事分页循环条件写错、时间字段时区没有转换、响应结构变化没有兜底。我直接贴一个可用版本关键点写在注释里。# fetcher.py import requests import time from datetime import datetime, timedelta, timezone def build_params(query: str, language: str, time_range: str) - dict: 组装请求参数注意查重、排除词可以放进query # 这里用的是占位域名实际项目替换为自己的API端点 return { q: query, language: language, time_range: time_range, sort_mode: recency, page_size: 50, offset: 0, apikey: API_KEY, } def fetch_all_news(query: str, language: str, time_range: str, max_pages: int 3): 拉取入关键词相关的新闻列表带分页保护 all_items [] offset 0 for _ in range(max_pages): params build_params(query, language, time_range) params[offset] offset resp requests.get( https://api.example.com/news/v1/search, paramsparams, timeout15, ) data resp.json() items data.get(articles, []) if not items: break # 把时间字段统一转成北京时间简版适配两种常见格式 for it in items: raw_time it.get(published_at) or it.get(timestamp) it[pub_time] normalize_datetime(raw_time) all_items.extend(items) # 如果回传总量已经大于当前列表说明还有下一页 total data.get(total, 0) offset len(items) if offset total or offset 100: break time.sleep(0.3) return all_items def normalize_datetime(raw_time): 把不同格式的发布时间统一为 UTC8 时间字符串 if isinstance(raw_time, (int, float)): return datetime.fromtimestamp(raw_time, tztimezone(timedelta(hours8))) # 字符串形式按ISO格式解析成UTC再转北京时间 text str(raw_time) dt datetime.fromisoformat(text.replace(Z, 00:00)) return dt.astimezone(timezone(timedelta(hours8))).strftime(%Y-%m-%d %H:%M)分页这里有个细节不要把max_pages设太大。我试过把前500条全部拉回然后发现大量长尾噪音会让打分器无所适从而且免费API的配额会被一次任务消耗一大半。现在策略是最多翻3页每页50条即最多拉150条足够覆盖一天的关键新闻。3.3 清洗与过滤指纹去重和关键词排除清洗器负责把原始数据变成真正进入排行榜的候选。去重我用了一个每日指纹缓存文件data_cache.json这样跨天任务之间也能共享历史指纹避免同一新闻在连续两天的简报里重复出现。实现思路是这样的# cleaner.py import hashlib import json import re def normalize_title(title: str) - str: 去掉标题中的符号和语气词生成归一化文本 title title.lower() title re.sub(r[\s\-\|\·\:_()【】\[\]{}], , title) title re.sub(r^(独家|快讯|最新|重磅), , title) return title def make_fingerprint(item: dict) - str: 优先用标题归一化后的指纹去重 norm normalize_title(item.get(title, )) return hashlib.md5(norm.encode(utf-8)).hexdigest() def is_duplicate(item: dict, cache_data: dict) - bool: fp make_fingerprint(item) if fp in cache_data: return True # 再判断摘要相似度用来拦截改标题的转载 summary item.get(summary, )[:100] for old_fp, old_summary in cache_data.get(summary_fingerprints, []): if jaccard_similarity(summary, old_summary) 0.68: return True return False def jaccard_similarity(text_a: str, text_b: str) - float: set_a set(text_a.split()) set_b set(text_b.split()) if not set_a or not set_b: return 0.0 inter set_a set_b union set_a | set_b return len(inter) / len(union)这里最值得说的是jaccard_similarity的阈值。我一开始定0.5结果很多正常新闻被误杀后来调到0.8重复转载又漏得厉害。反复测试之后0.68是个不错的中点。如果你的新闻源转载现象特别多推荐用SimHash做64位指纹Hamming距离小于3认为是重复误判率会更低不过开发成本也高一些。能被黑名单排除词命中的条目直接在清洗阶段扔掉。比如标题里出现报名、开奖、倒计时大概率是活动通告而不是产业新闻这在行业观察简报里属于噪音。3.4 摘要生成先用原摘要再谈大模型很多新闻API返回的summary字段已经概括了正文核心足够做成简报。但对一些缺摘要的条目或者摘要本身就是一句话宣传文案的情况就需要生成式摘要。我的实际做法分成两层第一层如果API自带摘要长度大于60字直接使用。好处是零成本、速度快。第二层对摘要过短或明显是营销文案的内容再把原文标题和首段文本送入大模型让模型生成不超过两句话的摘要。这里我不建议每次跑完150条全部请求大模型成本高且速度慢。正确的打开方式是只对进入前十排序的候选做精修摘要。用大模型的时候Prompt要给出非常明确的指令不然模型天然喜欢虚写。一个可复用的Prompt模板请根据提供的新闻标题和正文片段生成一句简洁、客观、信息完整的摘要。 要求 1. 包含核心主体、关键数据、时间。 2. 不使用比喻和夸张修辞。 3. 不超过60个字。 4. 输出结果为纯文本不要添加任何前缀。 【标题】{title} 【正文片段】{snippet}如果厂商支持结构化输出优先用JSON模式拿{summary: ...}会稳定很多不然模型偶尔会加一句该新闻主要报道了...这种废前缀。拿到摘要后我还会在简报里保留原始标题不直接用生成的摘要替代标题因为标题往往是原文来源的表达方式有利于用户点击判断可信度。只有当我发现点击率评估加权后效果更差才换用模型重写的短标题方案。目前保留原标题体验更好。3.5 渲染模板与简报效果渲染器负责把清洗好的新闻列表变成Markdown和HTML。我比较喜欢用一个紧凑的模板每一条包含序号、标题、来源、发布时间、摘要、原文链接。头部信息记录生成时间、关键词、新闻条目总数这样拿到简报能一眼看出这是哪天的覆盖了多少信息。# reporter.py def render_markdown(news_items, keyword, generated_time): lines [ f# {keyword} 每日简报, f生成时间{generated_time} 覆盖新闻{len(news_items)} 条 统计周期24小时内, , ---, , ] for idx, item in enumerate(news_items, 1): lines.append(f**{idx}. {item[title]}**) lines.append(f {item[summary]}) lines.append(f 来源{item[source]} {item[pub_time]} [阅读原文]({item[url]})) lines.append() return \n.join(lines)实际产出的效果像是这样标题和内容均为模拟示例用于展示排版效果# AI创业 每日简报 生成时间2025-06-12 08:00 覆盖新闻23 条 统计周期24小时内 --- **1. 某AI初创企业完成新一轮1.2亿元融资聚焦企业级智能客服** 该公司将把资金用于底层模型优化及销售团队扩张本轮由老股东持续加注。 来源某行业媒体 2025-06-11 21:30 [阅读原文](https://example.com/xxx) **2. 大模型创业公司加速出海东南亚成热门落地场景** 多家AI公司本季度在东南亚推出本地化产品主要涉及语音客服和智能文档处理。 来源某科技网 2025-06-11 19:00 [阅读原文](https://example.com/xxx)可以看到整份简报能在一屏内读完重要信息密度很高。HTML版本会额外加图片和摘要标签样式但内容完全一致。我实测下来Markdown在微信收藏、飞书文档、邮箱里都比较友好HTML则适合放到团队内部知识库。3.6 定时执行三种方式按成本选定时执行是整个每日的关键。我试过三种方式各有使用场景本地环境 系统cron最简单直接在服务器上写一个cron每天早上8点运行一次python reporter.py。适合有常驻Linux机器的人缺点是机器不能关机如果断了电当天简报就没了。GitHub Actions / 类似CI定时任务把代码推到一个私有仓库在workflow里配置schedule: cron表达式每天触发。适合不喜欢买服务器的开发者免费的分钟数对一天一次的任务完全够用。但要注意时区问题cron表达式默认UTC要换算到自己的时区。云函数/定时触发器把抓取和渲染逻辑封装成无服务器函数权限配置好后定时触发没有服务器运维负担但需要额外学习平台配置。我自己目前用的是云函数加定时触发器原因只有一个不想维护一台常驻机器。部署时要用环境变量保存API Key不要把密钥写进代码仓库。云函数的冷启动对这类任务没有影响每天8点触发早高峰可以接受。如果你准备用cron我的建议是执行时间选北京时间早晨8点而不是凌晨0点。0点跑出来的新闻量太少因为很多媒体当天新闻集中在上午发布8点跑刚好收集昨晚和今早的重要动态。4. 常见问题与排查技巧实录4.1 高频问题速查表把这一个多月的使用过程中我遇到的复现率高的故障整理成了一个速查表方便你直接对症下药。问题现象可能原因排查方法请求返回401API Key写了但请求参数里没有传或Key已过期检查请求参数的认证头打印完整URL验证返回乱码接口返回编码不是UTF-8但脚本按UTF-8解析强制resp.encoding utf-8必要时指定其他编码发布时间比当前时间晚时区没有转换接口返回UTC当成本地时间统一转成带时区的时间戳后格式化同样的新闻连续两天出现去重指纹只在任务内有效跨天没有缓存启动时加载data_cache.json结束后写回某天导入的关键词没有任何结果API免费配额耗尽或该词确实没有当日新闻查看接口配额余量换相近关键词定位问题简报里出现明显不相关的条目关键词泛匹配没有排除词扩充排除词库降低关键词命中得分的低分权重定时任务没跑起来cron时区错误或表达式写反先用代码内置一个测试任务跑一次看日志4.2 我踩过的一个经典坑分页中断导致新闻缺失我第一次上线后发现简报里某几个来源的新闻占比异常高检查日志发现是因为在分页请求中有一页返回超时脚本没有重试直接break了导致后面的全部新闻根本没进列表。这个bug最坑的地方是单次运行看不出来因为大多数时候第一页就够用了但遇到热点日新闻量暴涨时第一页和第二页之间就会暴露问题。修复方案给分页循环加上retry机制单页最多重试2次超时时间增加到20秒如果重试仍失败把这一页的查询参数记录下来生成简报时在末尾标注部分新闻源响应超时信息可能不完整。有了这个降级提示简报的可信度反而更高。4.3 排错时的三个好习惯保留原始响应样例。把API返回的JSON写入本地文件保存最近三天的数据。这样当简报出现异常时可以回看是接口数据变了还是自己处理逻辑出错了不用再手动调用一次。给每一条新闻增加来源URL的日志。以前出问题只能看到一个标题想知道它从哪来就得再搜一遍。后来我在调试模式下把URL、发布时间、API原始分数全部打印出来问题定位效率提升非常明显。谨慎修改时间范围。不要在调参的时候把time_range从24h改成7d做测试这会改变任务行为。应该新建一个调试配置对正式环境零影响。4.4 内容和运营层面的避坑技术问题可以靠日志解决但简报的可用性问题往往在运营层面。我用了一周后发现单纯记录行业新闻是不够的用户还是要点开原文才知道这件事和自己有什么关系。于是我在每一条新闻后面增加了一个why字段由代码根据关键词预先写好一句关注点提示——比如今天是融资相关新闻就在标题下面加一行提示关注下轮估值与核心投资方。这个why字段一开始是静态配置后来发现用规则模板生成的效果已经不错不必每篇都上大模型。另外要给简报设置硬上限我通常控制在25条以内。超过这个数阅读负担会直线上升。宁可每天只挑出最重要的几条也不要追求全而多。用户的耐心很有限一份能长期坚持读下去的报告一定是少而精的。5. 进一步扩展多关键词、多推送与个性化如果只想做最简版看到第4章就可以收工了。但如果想让这个工具真正变成团队或个人助理下面几条是我认为性价比最高的扩展方向按开发成本从低到高排序。多关键词合并输出在配置里面增加更多关键词把多份结果聚合到同一份简报里用分节呈现。这对研究AI创业光储充这种跨界观察特别有用可以看到两个趋势在同一时期的交集。推送渠道扩展把Markdown渲染成纯文本后可以接入邮件、IM群机器人、知识库接口。最省事的是先做邮件因为SMTP协议最通用IM机器人接收体验好但需要申请机器人权限和环境变量。注意不要把密钥放在代码里统一走环境变量。新闻源加权如果你明显觉得某些垂直媒体的报道质量高于门户聚合可以给那些来源加一个quality_weight在打分环节放大。这个参数放在配置里方便随时调。周末模式与假期模式周六日的财经科技新闻量会明显下降可以设置成如果没有超过3条高分新闻就不发简报的省电模式。否则周末发一份只有两条新闻的简报反而会让用户对这个工具失去信心。历史归档与趋势统计把每天的简报文件按日期归档积累一个月后就能分析这周和上周比哪些关键词出现频率升高了相当于做一个不带模型训练的舆情指数。我最近正在做的是把data_cache.json升级成SQLite这样不只是去重还可以做简单的关键词热度统计。这是很自然的演进路线文本缓存适合前期SQLite适合长期维护。写在最后的开发体会做这类小而实用的工具技术实现占四成剩下六成是内容策略。我发现真正决定它能不能长期坚持用下来的不是模型能力有多强而是噪音控制得有多好。能坚持每天读一份简报是因为它每一篇都值得点开一旦出现两三天的连续低质量推送用户就会把它关进收藏夹遗忘掉。最后分享一个我每天都在用的小技巧在每个关键词的简报开头自动加一行今日信号强度规则是根据当天新闻总数、高分新闻占比、是否有权重媒体来源生成一个1到5的星级评价。信号强度高的日子我会花更多时间深挖信号弱的日子5分钟扫完即可。这个细节虽然只有一行代码但它让整个工具从信息堆砌变成了主动告诉你今天值不值得关注。如果你正在做类似的东西强烈建议把这个小点子加进去。