ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

webresearcer:搭建Web自动化调研流水线的设计与实践

webresearcer:搭建Web自动化调研流水线的设计与实践 webresearcer 算是我的一个长期维护项目1 月 25 号这天的更新是一个比较关键的节点整条 Web 自动化调研流水线第一次完整跑通了。这里简单记录一下这个项目的设计思路、核心模块、实际部署过程以及我踩过的一些坑。如果你也在做类似的信息采集、内容聚合或者智能简报工具这篇应该能帮你省下不少试错时间。1. 项目定位与整体设计思路1.1 这个工具解决的是“研究过程”的问题做内容研究、竞品分析、行业跟踪的人都有一个共同痛点信息源太多手动一个个打开网页看太费时间。webresearcer 本质上是一个个人化的 Web 调研工具它的目标不是做一个搜索引擎而是把“订阅来源、抓取正文、去重归档、生成摘要报告”这整条链路自动化。一旦跑起来每天早上只需要看它生成的摘要文件就能快速知道各个信息源发生了什么变化。项目名里的 webresearcer 就是 web researcher 的意思既然是内部工具名字就没太讲究能用就行。2026-1-25 这个日期是当天的版本记录我习惯把每次迭代的版本号写成实际日期方便回溯。1.2 为什么不用现成的采集框架很多人第一反应是这功能用现成的 RSS 阅读器、或者是现成的爬虫框架不就行了吗其实不行。RSS 阅读器只能解决“订阅”的问题解决不了“去重”“全文提取”“关键词跟踪”的需求通用爬虫框架比如 Scrapy适合大规模站点采集但对个人研究场景来说太重了配置反爬、中间件、调度器一圈下来可能比写业务逻辑花的时间还多。webresearcer 的核心设计原则是“够用就好”。它不追求覆盖全网只追求把我自己关心的一批来源稳定地读进来然后把重复内容去掉最后生成一个可读性强的摘要。这个定位决定了技术选型可以非常简单Python 3 feedparser trafilatura SQLite FTS5整个项目依赖很少部署在一台普通服务器上就能跑。1.3 整条流水线的数据流转流水线从来源配置开始到摘要报告结束中间一共有五步读取订阅源配置包括 RSS/Atom 地址和处理规则拉取新条目对每个链接抓取正文对正文做清洗去掉广告、导航、页脚等干扰用相似度算法去重过滤掉已经入库的内容写入 SQLite并基于新增内容生成摘要报告。每一步的输出都是下一步的输入所以排错的时候思路非常清晰先看数据到没到再看数据处理没处理对。实际调试中90% 的问题都出在“数据没到”这一步也就是网络抓取异常这在我后面第四节会单独讲。2. 核心模块拆解从抓取到报告2.1 采集层RSS 优先网页兜底采集层是整个流水线的入口。我采用“RSS 优先网页兜底”的策略原因很简单RSS 是结构化数据解析稳定、效率高、对目标站点友好。现在很多资讯站虽然不主动宣传 RSS但只要你把/feed、/rss、/atom.xml这些路径试一遍大部分都能找到种子地址。对于没有 RSS 的页面我写了一个兜底逻辑直接抓取列表页用 CSS 选择器提取文章链接。这个逻辑不稳定因为页面改版就可能失效所以我对这种来源会额外加一个“健康度”标记连续三次抓取失败就自动停用并告警。采集频率也是需要考虑的参数。我默认设置为每 30 分钟轮询一次 RSS每小时抓取一次非 RSS 来源。这里有一个经验值不要对同一域名请求太频繁尤其是有反爬策略的站点间隔拉长到 5 分钟以上是最低要求。2.2 清洗层正文提取与去重正文提取我用的 trafilatura这是一个专门做网页正文提取的 Python 库比传统的 readability 准确率高不少。它的用法很简单传入 HTML 字符串就能返回正文文本和元数据。实际测试下来对新闻、博客、技术文档这三类页面的提取效果都很稳基本不需要正则表达式去修补。去重是整个项目里最让我头疼的模块。最早我用的是标题完全匹配结果发现很多网站喜欢在标题后面加站点名比如“XXX功能上线 - 某某资讯”同一篇文章在不同来源会有不同标题完全匹配根本挡不住。后来我改成了 SimHash 相似度算法把每篇文章转成一个 64 位的指纹然后比较汉明距离。距离在 3 以内的判定为重复这个阈值是我跑了三周历史数据后调出来的误杀率大概在 2% 左右可以接受。2.3 索引层本地全文检索去重之后的数据会落到 SQLite 里。SQLite 自带 FTS5 全文检索扩展对小规模个人项目来说完全够用省掉了部署 Elasticsearch 的麻烦。我建了一个 articles 表和一个对应的 FTS5 虚拟表每次写入数据时同步更新索引。全文检索在摘要报告之外的价值是“按人找文章”。比如我想查一下“某个产品”最近一个月被哪些信息源报道过直接跑一条 SQLSELECT ... FROM articles_fts WHERE articles_fts MATCH 产品名就能拿到结果。这种临时查询能力让我在写调研报告时省了很多时间。2.4 输出层摘要生成与报告报告生成我坚持“模板优先AI 辅助”。为什么不用 AI 全自动写摘要因为调研场景对准确性要求很高自动生成的内容可能出现事实性偏差不核对就发出去容易出事。我的做法是这样的先用模板把新增文章按分类整理成列表包括标题、来源、链接、发布时间和正文的前 200 字然后对需要深度分析的文章单独调用本地部署的小模型做摘要。这个模型不追求复杂推理只做抽取式摘要也就是从原文里挑最重要的几句话而不是重新组织语言这样能最大限度避免“编造内容”的问题。最终产物是一个 Markdown 文件放在 reports 目录下按日期命名。3. 实操过程从零搭一个能跑的版本3.1 环境准备我是在一台 Ubuntu 22.04 的服务器上部署的配置很低2 核 4G 就够用。代码用 Python 3.10 编写依赖就几个pip install feedparser trafilatura beautifulsoup4SQLite 用系统自带的版本即可Python 内置了 sqlite3 模块不需要额外安装数据库服务。定时任务我用 cron 实现没有引入 Celery 或者 APScheduler因为调度需求太简单了没必要为两行配置引入一个重依赖。3.2 数据模型与关键代码数据库设计非常朴素核心就是两张表。第一张是 sources记录订阅源信息CREATE TABLE sources ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, url TEXT NOT NULL, kind TEXT DEFAULT rss, enabled INTEGER DEFAULT 1, last_fetch_at TEXT, fail_count INTEGER DEFAULT 0 );第二张是 articles记录抓取到的文章CREATE TABLE articles ( id INTEGER PRIMARY KEY AUTOINCREMENT, source_id INTEGER NOT NULL, title TEXT NOT NULL, url TEXT NOT NULL, content_text TEXT, content_hash TEXT, simhash TEXT, published_at TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE VIRTUAL TABLE articles_fts USING fts5( title, content_text, contentarticles, content_rowidid );FTS5 的content参数是关键它让虚拟表和原表建立关联更新原表数据时可以用触发器自动同步到虚拟表。我写了一个简单的处理函数负责从抓取到入库的整个流程def process_entry(source, entry): # entry 来自 feedparser里面包含 link、title、published 等字段 if is_duplicate(entry.title, entry.link): return {status: duplicate, title: entry.title} html fetch_url(entry.link) text trafilatura.extract(html) if not text: return {status: empty, title: entry.title} sim simhash(text) if is_similar(sim): return {status: similar, title: entry.title} save_article(source.id, entry, text, sim) return {status: ok, title: entry.title}每一步都返回一个明确的状态这样我在跑批量历史数据的时候可以清楚看到每条记录被拦在哪一步。3.3 调度与参数调优cron 配置是这样写的每 30 分钟执行一次采集每天凌晨 1 点生成报告*/30 * * * * cd /opt/webresearcer python3 collect.py logs/collect.log 21 0 1 * * * cd /opt/webresearcer python3 report.py logs/report.log 21采集时间窗口是一个值得调优的参数。一开始我设置为 5 分钟一次发现大量请求被目标站限流日志里全是 429 状态码改成 30 分钟之后稳定了很多信息及时性也没有明显下降。对个人调研来说30 分钟已经足够真有什么重大新闻你肯定先于 RSS 从别处知道了。去重阈值我也做了几次调整。SimHash 的汉明距离阈值初始设成 5结果发现不同文章也会被误判为相似因为技术资讯和新闻稿的用词本来就接近把阈值降到 3 之后误判率明显下降。这个参数每个项目都可能不同建议从 3 起步跑一周数据后再看准确率。4. 常见问题与排查实录4.1 抓取超时与反爬最频繁的问题是抓取超时。有些 RSS 源很久没更新服务器响应变得极慢默认的 10 秒超时根本不够。我遇到过一个源请求发出后 40 秒才返回数据占住了采集线程导致整个队列被堵住。解决办法是给所有网络请求设置两个超时层连接超时 5 秒读取超时 30 秒。trafilatura 底层用的是类似 requests 的会话可以这样设置import trafilatura downloaded trafilatura.fetch_url(url)但 trafilatura.fetch_url 的超时参数不好控制所以我改用了 requests 自己做抓取、再做正文提取import requests resp requests.get(url, timeout(5, 30), headers{ User-Agent: Mozilla/5.0 (compatible; webresearcer/1.0) }) text trafilatura.extract(resp.text)关于 User-Agent我的建议是尽量伪装成一个普通的浏览器标识而不是暴露爬虫身份。有些站点对 UA 很敏感直接返回 403。当然这也要遵守目标站的 robots 协议和平台规则做一个有礼貌的采集者这是底线。4.2 正文提取不干净trafilatura 对大多数页面表现良好但有几个例外视频网站的文章页、动态渲染的 SPA 页面、以及带登录墙的付费内容。SPA 页面返回的 HTML 里经常只有一段 JS 代码trafilatura 提取出来的正文是空字符串。我补了一个兜底方案如果提取结果为空或者长度少于 300 字就尝试用 BeautifulSoup 分析article、main、.content这类常见容器标签。如果还是空就标记为“需人工查看”在报告里单独列出来。这套兜底逻辑不算完美但能把成功率从 80% 提到 92% 左右。4.3 相似文章误杀SimHash 在短文本上的表现不如长文本。对于 500 字以下的内容指纹的区分度变差两篇完全不相关的短文也可能算成相似。我观察到这个问题后对短文本走了另一条路直接用规范化后的标题做精确匹配只有标题完全相同才判重。这个策略听起来比 SimHash 简单但实际操作里更稳。短文本场景本来就是“同一篇稿子被多个源转载”标题基本一致精确匹配足够了等到长文本再上 SimHash 处理“标题不同但内容相似”的深度转载场景分工明确。4.4 数据膨胀与磁盘占用跑了一个月后我发现数据库文件涨到了 1.5GB 左右。原因有两方面一是很多文章存储了完整正文一篇几千字很正常二是 FTS5 索引本身就占用额外空间。对于个人项目来说1.5GB 不算大问题但如果是长期积累就需要加一个清理策略。我加了一个简单的归档机制180 天前的文章只保留标题、URL、摘要和发布日期正文清空。这样在查询历史时仍然能看到“什么时候、哪个源、发了什么”只是无法阅读全文既控制了体积又保留了检索价值。清理任务同样挂在 cron 里每月跑一次。提示清理前一定要备份数据库。我在上线清理脚本的第三天就吃了亏一条 delete 语句把昨天的数据误删了一部分幸好有备份才恢复。备份一条命令就够了sqlite3 data.db .backup backup-$(date %Y%m%d).db。根据我个人的使用体会webresearcer 这类工具最大的价值不是“抓得多”而是“沉淀得久”。数据积累到三个月之后你再去做行业盘点、竞品回顾会发现以前手动搜索几个小时的内容现在一条 SQL 就出来了。后续我还计划给它加上分类标签自动生成和跨来源时间轴的功能让每一篇文章在时间维度上找到自己的位置。如果你也在做类似的东西我建议先把采集和去重这两块打磨扎实地基稳了上层功能都只是时间问题。
返回列表