从零搭建每日晨报系统:信息聚合、去重与定时推送实战
1. 一份“每日晨报”到底在解决什么问题每天早上七点半我手机里会准时弹出一条自己给自己发的消息标题格式固定是“每日晨报 · 年-月-日周几”。这个习惯我坚持了快四年中间迭代过至少六个版本从最早的手动复制粘贴到后来半自动抓取再到现在基本全自动生成加人工复核。很多人觉得“晨报”这种东西不就是把新闻标题堆一堆吗有什么好讲的。但真正做过内容聚合的人知道一份能让人每天早上愿意花三分钟看完的晨报背后涉及的信息筛选、去重、排序、摘要压缩、格式统一、定时触发、异常兜底每一个环节都有坑。“每日晨报 · 2026-09-24周四”这个标题本身就是一个非常典型的模板化命名结构。它包含三个核心要素固定前缀每日晨报、日期2026-09-24、星期周四。这三个要素看起来简单但它们决定了整个系统的文件命名规则、检索逻辑、归档策略和推送触发条件。我见过太多人做日报系统最后文件堆在一个文件夹里找起来全靠回忆就是因为命名规则没定好。这份晨报适合谁参考如果你是做运营的、做投资的、做技术选型的、或者单纯想每天花最少时间了解行业动态的人这套方法都能直接用。它不依赖任何特定平台你用什么工具都能搭核心是思路和流程。我下面会把整个系统的设计逻辑、关键细节、实操步骤、踩过的坑全部拆开讲你照着抄作业就行。2. 整体架构设计与核心思路拆解2.1 为什么选择“本地生成定时推送”而不是纯在线服务市面上做信息聚合的工具不少但我最终选择本地脚本生成加定时推送的方案原因有三个。第一是数据可控所有原始数据、中间产物、最终成品都在自己手里不会因为某个服务关停就断档。第二是格式自由在线工具通常只给你固定模板而晨报的排版、字段、摘要长度这些细节只有自己写才能完全掌控。第三是成本极低一台常年开机的低功耗设备或者一台云主机跑一个轻量脚本一个月电费或者主机费用几乎可以忽略。具体架构上我采用的是“采集层-处理层-渲染层-推送层”四层分离的设计。采集层负责从各个信息源拉取原始内容处理层做去重、分类、摘要、排序渲染层把结构化数据填充到Markdown模板里推送层负责在指定时间把成品发到指定渠道。这四层之间通过标准化的JSON结构传递数据任何一层出问题都不会影响其他层排查起来非常清晰。提示不要一上来就追求全自动。我最早的版本是半自动的脚本只负责采集和初步整理摘要和排序由我手动完成。跑了两个月之后我才逐步把摘要和排序也交给脚本。先跑通流程再优化细节这个顺序不能反。2.2 日期与星期字段的处理逻辑标题里的“2026-09-24周四”看起来是小事但在代码里涉及好几个容易出错的点。首先是时区问题如果你的服务器用的是UTC时间而你在东八区那么每天早上八点生成的晨报日期字段可能会变成前一天。我的做法是统一在脚本里显式指定时区不依赖系统默认值。其次是星期计算不同语言和库对星期的起始日定义不同有的把周日当第一天有的把周一当第一天。我实测下来最稳妥的方式是直接用日期对象自带的星期方法然后映射到中文的“周一”到“周日”。还有一个细节是日期格式的零填充。2026年9月24日要写成2026-09-24而不是2026-9-24。这个在文件排序的时候特别重要因为字符串排序时“2026-9-24”会排在“2026-10-01”后面导致归档顺序错乱。我在早期版本就踩过这个坑后来统一用补零格式才解决。2.3 信息源的选取与权重分配晨报的质量取决于信息源的质量。我的信息源分为三类核心源必选每天必看、扩展源可选有则加、备用源核心源失效时顶上。核心源我控制在五到八个太多了会导致信息过载太少了又容易漏掉重要动态。每个源我会打一个权重分权重高的源在排序时会优先展示。权重分配不是拍脑袋定的而是根据过去三个月的实际阅读反馈动态调整的。具体做法是我每周会回顾一下这周晨报里哪些条目我真正点开看了哪些直接划过去了。点开率高的源权重上调长期没人看的源要么降权要么直接移除。这个反馈机制让晨报的内容质量一直保持在一个比较高的水平。3. 核心细节解析与实操要点3.1 去重逻辑为什么简单的标题匹配不够用信息聚合最头疼的问题就是重复。同一个事件不同来源的标题可能完全不一样但说的是一件事。我最早用的是标题完全匹配去重结果发现根本不够用。后来升级到标题相似度加关键词指纹的双重去重策略。具体来说第一步先做标题的归一化处理去掉标点、空格、特殊符号统一转成小写。第二步计算标题之间的编辑距离如果相似度超过某个阈值就认为是重复的。第三步对于相似度处于中间地带的再提取标题里的关键词集合计算杰卡德相似系数两个指标都超过阈值才判定为重复。这套组合拳下来去重准确率比单一方法高了很多。注意去重阈值不要设得太激进。我一开始把相似度阈值设得很低结果把不同公司发布的类似产品公告给合并了导致漏掉了重要信息。后来把阈值调高了一些宁可保留少量重复也不要误杀。3.2 摘要压缩从三百字到五十字的取舍每个信息源给的原始内容长度不一有的只有一句话有的有上千字。晨报的定位是快速浏览所以每条内容最终呈现的摘要控制在五十到八十字之间。这个压缩过程我试过几种方案。纯提取式摘要抽几个关键句速度快但有时候不连贯纯生成式摘要用模型重写流畅但偶尔会偏离原意。我最后采用的是提取加轻量改写的混合方案先用规则提取包含关键信息的句子再对句子做简单的语序调整和连接词补充保证读起来通顺。这里有个实操心得摘要里一定要保留数字、日期、专有名词这三类信息。读者扫一眼晨报最想看到的就是“谁在什么时候做了什么涉及多少钱”。如果摘要把这些丢了那这条信息基本就废了。3.3 排序策略时间优先还是重要性优先排序直接决定了读者第一眼看到什么。我的排序规则是重要性优先时间次之。重要性由三个因素加权计算来源权重、关键词命中情况、内容长度。来源权重前面说过了关键词命中是指这条内容是否包含我预设的关注词列表里的词内容长度作为一个辅助信号通常较长的内容信息量更大。但这里有个例外情况如果某条内容的时间戳非常新比如半小时内发布的我会给它一个额外的时间加成让它排到前面。因为晨报是早上生成的如果半夜有重大消息读者应该第一时间看到。3.4 模板渲染Markdown格式的稳定性保障晨报最终输出为Markdown格式方便在各种笔记软件和文档工具里查看。模板渲染看起来简单但有几个坑。第一是特殊字符转义如果原始内容里包含Markdown的保留字符比如星号、下划线、反引号不转义的话会把排版搞乱。第二是链接处理有些来源的链接特别长直接放进去会让版面很难看我通常会把链接转成短链或者用锚文本代替。第三是空值处理如果某个字段没有数据模板里要显示“暂无”而不是留空否则渲染出来会有奇怪的空白。4. 实操过程与核心环节实现4.1 环境准备与依赖安装这套系统对运行环境要求很低一台能跑Python的机器就行。我目前跑在一台低功耗的小主机上配置是四核处理器加8G内存完全够用。操作系统用的是常见的Linux发行版Windows和macOS也都能跑只是定时任务的配置方式不同。依赖方面核心用到的库不多。网络请求用requests数据处理用标准库的json和datetime文本相似度计算用difflib标准库自带不用额外装模板渲染用string.Template也是标准库。如果你想要更高级的摘要功能可以额外装一些文本处理的库但基础版本不需要。# 创建虚拟环境 python3 -m venv morning_report_env source morning_report_env/bin/activate # 安装基础依赖 pip install requests提示强烈建议用虚拟环境不要直接装在系统Python里。我早期图省事直接装全局后来升级库版本把其他脚本搞崩了排查了半天才发现是依赖冲突。4.2 采集层的实现细节采集层的核心是并发请求加超时控制。如果串行请求十几个源每个源等三秒光采集就要花将近一分钟。我用concurrent.futures里的ThreadPoolExecutor做并发把采集时间压缩到了十秒以内。每个请求设置五秒超时超时了就跳过这个源记录日志不影响其他源。import requests from concurrent.futures import ThreadPoolExecutor, as_completed def fetch_source(source_config): try: resp requests.get( source_config[url], timeout5, headers{User-Agent: Mozilla/5.0} ) resp.raise_for_status() return {source: source_config[name], data: resp.text, ok: True} except Exception as e: return {source: source_config[name], data: None, ok: False, error: str(e)} def fetch_all(sources): results [] with ThreadPoolExecutor(max_workers8) as executor: futures {executor.submit(fetch_source, s): s for s in sources} for future in as_completed(futures): results.append(future.result()) return results这里有个细节User-Agent一定要设置。很多站点对没有UA的请求会直接拒绝或者返回空内容。我一开始没设采集成功率只有一半加上UA之后基本都能拿到数据。4.3 处理层的去重与摘要代码处理层是整套系统里逻辑最复杂的部分。我把它拆成三个独立的函数归一化、去重、摘要。归一化负责把原始文本转成统一格式去重负责找出重复项并保留权重最高的那条摘要负责把长文本压缩到目标长度。import re from difflib import SequenceMatcher def normalize_title(title): # 去掉标点和多余空格转小写 title re.sub(r[^\w\s], , title) title re.sub(r\s, , title).strip().lower() return title def similarity(a, b): return SequenceMatcher(None, a, b).ratio() def deduplicate(items, threshold0.85): kept [] for item in sorted(items, keylambda x: x[weight], reverseTrue): norm normalize_title(item[title]) is_dup False for k in kept: if similarity(norm, k[norm_title]) threshold: is_dup True break if not is_dup: item[norm_title] norm kept.append(item) return kept摘要函数我采用的是关键句提取方案。先把原文按句号、问号、感叹号切分成句子然后给每个句子打分分数由句子位置越靠前分越高、包含关键词数量、句子长度太短太长都降分三个因素决定。最后选取得分最高的两到三个句子拼接起来。4.4 渲染层的模板设计模板我用的是Python标准库的string.Template语法简单不容易出错。模板文件单独存成一个txt方便修改不用动代码。from string import Template TEMPLATE Template(# 每日晨报 · $date$weekday 今日共收录 $count 条动态预计阅读时间 $read_time 分钟。 $sections ) SECTION_TEMPLATE Template(## $section_name $items ) ITEM_TEMPLATE Template(- **$title** $summary 来源$source )渲染的时候要注意如果某个板块没有内容整个板块标题都不要输出而不是输出一个空板块。这个逻辑我在模板外面用条件判断处理。4.5 定时触发与推送配置定时触发我用的是系统自带的定时任务工具。Linux下用crontabWindows下用任务计划程序。时间设定在每天早上七点这样七点半之前肯定能生成完毕。推送渠道我用的是自己给自己发消息的方式具体渠道这里不展开核心思路是调用一个webhook接口把Markdown内容发出去。# crontab配置示例 0 7 * * * /home/user/morning_report_env/bin/python /home/user/generate_report.py /home/user/report.log 21注意crontab里的命令一定要用绝对路径包括Python解释器的路径和脚本的路径。我踩过这个坑手动跑没问题放到crontab里就报“command not found”查了半天才发现是环境变量的问题。5. 常见问题与排查技巧实录5.1 采集失败的五种典型情况采集环节出问题是家常便饭我整理了一个速查表遇到问题按这个顺序排查。现象可能原因排查方法解决方案返回空内容请求头缺失检查UA是否设置补全请求头返回403频率限制看是否请求过快加延时或降低并发返回乱码编码不对检查响应编码手动指定编码连接超时网络问题ping目标地址增加超时或跳过内容结构变了页面改版对比新旧内容更新解析规则这里面最常见的是内容结构变化。信息源的页面改版是不可避免的我的做法是把解析规则写成配置文件页面变了只需要改配置不用改代码。另外我会在采集层加一个内容长度校验如果某个源返回的内容长度突然比历史平均值少了百分之八十以上就判定为异常发告警提醒我去检查。5.2 去重误判与漏判的平衡去重这块我踩的坑最多。早期版本阈值设得太低把不同公司同一天发布的财报公告给合并了因为标题结构太像。后来我把阈值调高又出现了同一事件不同表述没被识别出来的情况。最终的解决方案是双阈值加白名单相似度高于高阈值的直接判定重复低于低阈值的直接判定不重复处于中间的再结合关键词指纹判断。同时维护一个白名单白名单里的关键词组合不参与去重比如“财报”“融资”这类词单独出现时不作为重复依据。5.3 摘要质量不稳定的处理摘要偶尔会出现语句不通顺或者信息缺失的情况。我的处理方式是加一个质量检查环节生成摘要后检查摘要里是否包含原文中的数字和专有名词如果缺失比例超过某个阈值就回退到提取原文前两句话作为摘要。这个兜底机制虽然简单但效果很好基本杜绝了摘要完全跑偏的情况。5.4 定时任务没执行的排查思路定时任务不执行按这个顺序查第一看crontab服务是否在运行第二看日志文件有没有输出如果日志是空的说明任务根本没触发第三手动执行命令看是否报错第四检查环境变量crontab的环境变量和登录shell的环境变量不一样这是最常见的坑。我现在的做法是在脚本开头显式设置所有需要的环境变量不依赖系统继承。5.5 推送内容被截断的问题有些推送渠道对消息长度有限制晨报内容长了会被截断。我的解决方案是分段推送如果内容超过限制就拆成多条消息发送每条消息开头标注“第X部分”。另外Markdown格式在某些渠道里渲染效果不好我会准备一个纯文本版本作为备选根据渠道自动选择格式。6. 我在这套系统上踩过的三个大坑第一个坑是过度依赖单一信息源。早期我的晨报内容有百分之七十来自同一个源结果那个源有一次停更了三天我的晨报直接开了三天天窗。从那以后我强制要求每个板块至少有两个独立来源任何一个挂了都不影响整体。第二个坑是没有做历史归档。最开始生成的晨报看完就删了后来想回顾某个时间点发生了什么完全找不到记录。现在我会把每天的晨报按月份分文件夹归档文件名就是日期检索起来非常方便。归档还有一个好处是可以做月度回顾把一个月的重要动态串起来看比单看每天的晨报有价值得多。第三个坑是摘要写得太长。我一开始觉得信息越多越好每条摘要写一百多字结果一份晨报读下来要十几分钟完全失去了“晨报”快速浏览的意义。后来强制压缩到五十字左右阅读时间控制在三分钟内使用频率反而提高了。7. 后续可以继续扩展的方向这套系统跑稳定之后我陆续加了一些扩展功能。一个是关键词订阅我可以设置某些关键词命中这些关键词的内容会单独标记出来优先展示。另一个是周报自动生成把一周的晨报内容做二次聚合按主题分类生成一份周度总结。还有一个是阅读反馈收集我在每条内容后面加了一个简单的标记看完之后可以标记“有用”或“无用”这些反馈数据用来动态调整来源权重。如果你刚开始搭这套系统我的建议是先把最基础的采集、去重、渲染、推送跑通不要一上来就加各种高级功能。跑通之后用两周你会发现哪些环节最需要优化然后再针对性地改。我见过太多人一开始设计得很复杂结果维护成本太高跑了一周就放弃了。简单、稳定、可持续比功能多更重要。最后分享一个我在实际操作中的小技巧晨报的标题格式一定要固定不要今天用“每日晨报”明天用“今日速览”。固定的标题格式让你在搜索历史记录的时候非常方便输入日期就能定位到当天的内容。这个习惯我坚持了四年现在回头翻看已经积累了一份相当有价值的个人信息档案。