1. 从手动刷信息到自动推送:这个任务到底在解决什么问题
每天早上打开手机,十几个信息源挨个刷一遍,看看有没有新的 AI 产品冒出来——这件事我干了大概小半年,后来实在扛不住了。不是说信息不重要,而是重复劳动太消耗人。你想想,同一个动作,每天花二三十分钟,一个月下来就是十几个小时,这些时间拿来做点深度研究不香吗?
所以我就琢磨着,能不能让 Hermes 帮我把这个活儿接了。Hermes 是我本地跑的一个自动化调度工具,你可以把它理解成一个"定时任务管家",它能按照你设定的时间、频率、触发条件去执行指定的脚本或工作流。我给它安排的任务很明确:每天固定时间,自动去几个渠道搜索最近冒出来的 AI 产品,把结果整理好,推送到我指定的地方。
这个项目的核心价值就三个字:自动化。把"搜索—筛选—整理—推送"这条链路完整地串起来,中间不需要人工干预。适合谁来参考?我觉得两类人最合适:一类是像我这样每天需要跟踪特定领域动态的从业者,另一类是手上有 Hermes 或者其他调度工具、想找个实际场景练手的开发者。哪怕你之前没怎么接触过自动化工作流,跟着思路走一遍也能搭起来。
整个方案的技术栈不复杂,核心就是调度层 + 采集层 + 处理层 + 推送层四块。调度层用 Hermes 负责定时触发,采集层负责从搜索渠道拿数据,处理层做去重和格式化,推送层把结果送到你手上。每一层我都会拆开讲清楚为什么这么设计、有哪些坑要避开。
2. 整体架构设计与方案选型思路
2.1 为什么选 Hermes 做调度而不是系统自带的定时任务
很多人第一反应是用系统自带的 cron 或者计划任务,这没问题,简单直接。但我选 Hermes 有几个实际考量。
第一,可视化管理。cron 表达式写起来快,但过两周你自己都忘了那行0 8 * * *到底跑的是什么。Hermes 提供了任务列表和执行日志的界面,哪个任务成功了、哪个失败了、上次跑了多久,一目了然。
第二,失败重试机制。网络请求这种事,偶尔超时太正常了。cron 跑失败就失败了,你得自己写重试逻辑。Hermes 内置了重试策略,可以配置重试次数和间隔,省了不少事。
第三,任务依赖编排。我这个流程里,搜索和推送是有先后依赖的——搜索没完成,推送就没东西可推。Hermes 支持任务链,前一个任务成功后才触发下一个,逻辑上更清晰。
当然,如果你已经在用 cron 而且跑得挺稳,没必要为了这个项目专门换。工具是为人服务的,不是反过来。
2.2 采集层的渠道选择与取舍
搜索 AI 产品的渠道大致分几类:产品聚合平台、行业资讯站、社区讨论帖、社交媒体。我最终选了两个主要渠道加一个补充渠道。
主要渠道选的是产品聚合平台,因为这类平台的信息结构化程度高,产品名称、简介、发布时间都有固定字段,解析起来省事。补充渠道用的是行业社区的讨论区,这里的信息更及时,很多产品还没上聚合平台就先在社区里被讨论了,但缺点是格式不统一,需要多做一层清洗。
我没有把社交媒体纳入自动采集范围,原因有两个:一是接口限制多,稳定性差;二是噪音太大,筛选成本高。人工偶尔刷刷可以,放进自动化流程里性价比不高。
提示:渠道选择的核心原则是"稳定性优先于覆盖面"。宁可少覆盖一个渠道,也不要因为某个渠道频繁失败导致整个任务链断掉。
2.3 推送方式的选择逻辑
推送这块我试过三种方式:邮件、即时通讯工具、本地文件。
邮件的好处是格式丰富,可以发 HTML 表格,但打开率低,容易被淹没在收件箱里。即时通讯工具打开率高、提醒及时,但消息长度有限制,内容多了要分多条发。本地文件适合做归档,但不适合做即时提醒。
最终的方案是即时通讯工具推摘要 + 本地文件存全量。摘要里只放当天新增产品的名称和一句话简介,点进去能看详情。全量数据按日期存成结构化文件,方便后续检索和回溯。
3. 核心环节拆解与实操要点
3.1 搜索关键词的设计与动态调整
搜索关键词直接决定了你能搜到什么。一开始我用的是"AI 产品""AI 工具"这种大词,结果返回的内容又杂又多,大量重复和无关信息。后来我调整了策略,把关键词分成三组:
- 核心词:AI、人工智能、大模型、机器学习
- 产品词:产品、工具、应用、平台、上线
- 时间词:今日、本周、最新、新发布
三组词做组合,比如"AI 工具 最新""大模型 产品 上线",这样搜出来的结果精准度明显提升。但关键词不是设一次就完事了,我大概每两周会回顾一下搜索结果的质量,如果发现某个词带来的无效结果太多,就把它换掉。
这里有个经验:关键词要跟着领域热点走。比如某个阶段大家都在讨论某个技术方向,那这个方向的词就应该加进去。我一般会留两三个"机动位",专门放这种临时性的热词。
3.2 数据去重的三种策略
去重是这个项目里最容易被低估的环节。你想想,同一个产品可能今天在渠道 A 出现,明天在渠道 B 出现,如果不做去重,你的推送里全是重复内容,用不了几天你就不想看了。
我用了三层去重策略:
第一层:URL 去重。每个产品详情页的链接是唯一的,直接拿 URL 做比对,重复的直接丢掉。这是最快最准的一层。
第二层:名称模糊匹配。有些产品在不同渠道的名称略有差异,比如带不带版本号、有没有副标题。我用的是编辑距离算法,相似度超过阈值的判定为同一产品。阈值设多少合适?我实测下来 0.85 比较稳,太低会误杀,太高会漏掉。
第三层:历史库比对。维护一个已推送产品的历史库,每次新采集的数据都跟历史库比对一次。这层能解决跨天重复的问题——同一个产品昨天推过了,今天就不该再推。
import difflib def is_duplicate(name1, name2, threshold=0.85): ratio = difflib.SequenceMatcher(None, name1, name2).ratio() return ratio >= threshold注意:去重阈值不要设得太激进。我一开始设了 0.95,结果同一个产品的不同表述被当成两个产品推了两次,后来降到 0.85 才正常。
3.3 结果格式化的字段设计
推送出去的内容要让人一眼看懂,字段设计很关键。我最终保留了五个字段:
| 字段名 | 说明 | 是否必填 |
|---|---|---|
| 产品名称 | 产品的正式名称 | 是 |
| 一句话简介 | 不超过 50 字的描述 | 是 |
| 来源渠道 | 从哪个渠道采集到的 | 是 |
| 采集时间 | 精确到分钟 | 是 |
| 详情链接 | 原始页面地址 | 否 |
简介字段我做了长度限制,超过 50 字就截断加省略号。为什么是 50 字?因为即时通讯工具的单条消息预览大概就显示这么多,太长了反而看不全。
3.4 推送时机的选择
推送时间我定在每天早上 8 点。这个时间点是我反复调整后确定的。太早了没人看,太晚了上午的节奏已经起来了没空看。8 点刚好是大多数人刚到工位、还没进入深度工作状态的窗口期。
如果当天没有新增产品怎么办?我的处理是推一条"今日无新增"的简短消息。有人觉得没内容就不推了,但我觉得推一条空消息反而让人安心——知道任务跑了,只是确实没新东西。不然你会怀疑是不是任务挂了。
4. 完整实操流程与关键配置
4.1 环境准备与依赖安装
先把基础环境搭好。我用的 Python 3.10,主要依赖三个库:requests负责网络请求,beautifulsoup4负责页面解析,schedule做本地测试时的简易调度。
pip install requests beautifulsoup4 scheduleHermes 的安装按照官方文档走就行,装完之后确认服务能正常启动。我建议先在本地把整个流程跑通,再挂到 Hermes 上做定时调度。这样出问题的时候排查起来方便,不用在调度环境里来回折腾。
4.2 采集脚本的编写要点
采集脚本的核心逻辑是:发请求、拿响应、解析内容、提取字段。听起来简单,但有几个细节要注意。
请求头要带全。很多渠道会检查 User-Agent,不带的话直接返回 403。我一般会带上 User-Agent、Accept、Accept-Language 这几个常用头。
超时时间要设。不设超时的话,遇到慢响应能把整个任务卡死。我设的是连接超时 10 秒、读取超时 30 秒。
异常要捕获。网络请求失败太常见了,不能让一个请求失败导致整个脚本崩溃。每个渠道的采集逻辑都包在 try-except 里,失败了记录日志继续跑下一个。
import requests from bs4 import BeautifulSoup headers = { "User-Agent": "Mozilla/5.0 (compatible; ProductTracker/1.0)", "Accept": "text/html,application/xhtml+xml", "Accept-Language": "zh-CN,zh;q=0.9" } def fetch_page(url): try: resp = requests.get(url, headers=headers, timeout=(10, 30)) resp.raise_for_status() return resp.text except requests.RequestException as e: print(f"采集失败: {url}, 原因: {e}") return None4.3 数据处理的管道设计
采集回来的原始数据不能直接推,中间要经过一条处理管道。我的管道分四步:
- 清洗:去掉 HTML 标签、多余空白、特殊字符
- 提取:从清洗后的文本里抽出产品名称和简介
- 去重:走前面说的三层去重逻辑
- 排序:按采集时间倒序排列,最新的排前面
这四步我写成了一个独立的处理函数,输入是原始数据列表,输出是处理好的结构化列表。这样做的好处是,如果以后要换采集渠道,处理逻辑不用动,只要保证输入的数据格式一致就行。
4.4 Hermes 任务的配置与挂载
在 Hermes 里新建一个任务,配置项大概是这样:
- 任务名称:AI 产品每日追踪
- 执行频率:每天一次
- 执行时间:08:00
- 超时时间:300 秒
- 重试策略:失败重试 2 次,间隔 60 秒
- 执行命令:
python /path/to/main.py
超时时间设 300 秒是因为整个流程走下来大概需要 1 到 2 分钟,留点余量防止偶发的网络慢。重试间隔设 60 秒是为了给渠道服务端一点缓冲时间,太短了重试也是白重试。
配置完之后先手动触发一次,确认能正常跑通,再开启定时调度。我第一次配的时候忘了改执行路径,任务跑了但一直报"文件不存在",排查了半天才发现是路径写错了。
4.5 推送模块的实现
推送模块我封装成了一个独立函数,接收处理好的数据列表,格式化成消息文本,然后调用推送接口发出去。
消息格式我用了简单的 Markdown 风格:
今日 AI 产品新增 3 条 1. 产品A - 一句话简介 2. 产品B - 一句话简介 3. 产品C - 一句话简介 详情见本地归档文件如果当天没有新增,就发"今日无新增产品"。推送接口的地址和凭证我放在环境变量里,不硬编码在代码中,这样换环境的时候不用改代码。
5. 常见问题与排查技巧实录
5.1 采集失败的高频原因与对策
采集失败是最常见的问题,我整理了一个速查表:
| 现象 | 可能原因 | 排查方法 | 解决方式 |
|---|---|---|---|
| 返回 403 | 请求头不完整 | 检查 User-Agent | 补全常用请求头 |
| 返回 429 | 请求频率过高 | 看日志里的请求间隔 | 加延时或降低频率 |
| 返回空内容 | 页面结构变了 | 手动访问对比 | 更新解析规则 |
| 连接超时 | 网络波动或服务端慢 | 重试一次看是否恢复 | 加大超时时间 |
| 解析报错 | HTML 结构不匹配 | 打印原始 HTML 检查 | 调整选择器 |
提示:页面结构变更是最隐蔽的问题。渠道改版后,你的解析规则可能还能跑,但提取出来的字段是空的或者错的。我建议每次推送前做一个简单的字段校验,发现异常字段就告警。
5.2 去重逻辑的边界情况处理
去重逻辑有几个边界情况容易出问题。
同名不同产品。有些产品名称很通用,比如"智能助手",不同公司出的都叫这个名。这种情况光靠名称匹配会误判。我的处理是,名称匹配的同时要比对来源渠道,如果渠道不同且名称完全相同,标记为"待确认"而不是直接去重。
名称包含关系。比如"产品A"和"产品A Pro",编辑距离算出来相似度很高,但其实是两个产品。我的处理是,如果一个是另一个的子串,且长度差超过 3 个字符,就不判定为重复。
历史库膨胀。历史库越跑越大,比对速度会变慢。我的做法是只保留最近 90 天的记录,更早的归档到冷存储里。90 天足够覆盖绝大多数重复场景了。
5.3 推送消息被截断的处理
即时通讯工具对消息长度有限制,产品多了容易被截断。我的处理是分页推送,每页最多 10 条,超过就分多条发。每条消息开头标注"第 X 页,共 Y 页",方便阅读。
另一个方案是只推摘要,详情放本地文件。但实测下来,大多数人不会主动去翻本地文件,所以还是分页推送更实用。
5.4 任务执行时间过长的优化
一开始整个流程跑下来要 5 分钟,主要时间花在串行请求上。后来我改成了并发请求,用线程池同时请求多个渠道,时间降到了 1 分半左右。
from concurrent.futures import ThreadPoolExecutor def fetch_all(urls): with ThreadPoolExecutor(max_workers=5) as executor: results = list(executor.map(fetch_page, urls)) return results并发数不要设太大,5 个左右比较合适。太大了容易触发渠道的频率限制,反而得不偿失。
5.5 日志记录与问题回溯
日志是这个项目的"黑匣子"。我记录了三个级别的日志:INFO 记录正常流程节点,WARNING 记录可恢复的异常,ERROR 记录导致任务失败的问题。
日志文件按天切割,保留最近 30 天。出问题的时候,先看 ERROR 日志定位失败环节,再看 WARNING 日志了解上下文。我踩过的坑是日志打得太少,出了问题完全不知道卡在哪一步,后来加了详细的流程日志才好转。
6. 几个让我少走弯路的实操心得
6.1 先跑通最小闭环再优化
我一开始想的是把功能做全,采集、去重、推送、归档一次性都写好。结果写了三天,跑起来一堆问题,排查都不知道从哪下手。后来我换了个思路,先只做"采集一个渠道 + 推送纯文本",跑通了再逐步加渠道、加去重、加格式化。这样每一步都是可验证的,出问题也好定位。
这个经验我觉得适用于所有自动化项目:最小闭环优先。先让流程能跑起来,哪怕粗糙一点,然后再迭代优化。
6.2 给任务留一个"手动触发"入口
自动化任务最怕的是"你不知道它到底跑没跑"。我除了定时调度,还留了一个手动触发的入口,随时可以跑一次看看效果。调试的时候特别有用,不用等到第二天早上才能验证改动。
6.3 推送内容宁少勿多
我一开始恨不得把所有采集到的信息都推出去,结果推送消息又长又杂,自己都不想看。后来砍到只推"新增产品",每条不超过 50 字,阅读体验好了很多。推送的目的是提醒,不是替代阅读。真正感兴趣的内容,用户会自己点进去看详情。
6.4 定期回顾任务价值
自动化任务跑久了容易变成"僵尸任务"——还在跑,但你已经不看推送了。我大概每个月会回顾一次:这个任务推送的内容我还有在看吗?如果连续一周都没点开过,那要么是推送内容需要调整,要么是这个任务本身价值不大了。工具是为人服务的,别让任务变成负担。
6.5 归档数据的二次利用
本地归档的全量数据其实是个宝藏。我后来基于这些数据做了一个简单的趋势统计——每周新增产品数量、各渠道的贡献占比、高频关键词变化。这些统计反过来又能帮我优化搜索关键词和渠道选择,形成了一个正向循环。归档不只是备份,更是后续分析的原料。
这个任务从搭起来到现在跑了大概四个月,中间调整过关键词、换过推送格式、优化过并发策略,现在基本稳定在每天早上 8 点准时推送。偶尔有渠道改版导致采集失败,看眼日志改一下解析规则就能恢复。整体来说,投入产出比很高,每天省下来的那二三十分钟,攒起来能干不少正事。