ARTICLE DETAIL

资讯详情

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

抓取订阅源的3道防线:超时、XML魔数校验与guid去重,RSS项目如何拒绝脏数据

抓取订阅源的3道防线:超时、XML魔数校验与guid去重,RSS项目如何拒绝脏数据 抓取订阅源的3道防线超时、XML魔数校验与guid去重RSS项目如何拒绝脏数据【免费下载链接】rssPROJECT MIGRATED TO CODEBERG -- A simple twitter-feed-style RSS aggregator written in PHP, Laravel, Inertia.js, Tailwind and Vue.js项目地址: https://gitcode.com/gh_mirrors/rs/rssRSSrss是一个用 PHP、Laravel、Inertia.js、Vue.js 和 Tailwind 构建的RSS聚合器RSS aggregator以推特信息流的形式展示各订阅源文章。它的定时抓取任务每天要访问几十上百个外部订阅源而外部数据从来不可信——有的源站响应极慢、有的返回 HTML 错误页、有的 guid 忽有忽无。这个项目用三道轻量防线超时、XML 魔数校验、guid 去重把脏数据挡在数据库之外代码量极少但思路非常值得 RSS 阅读器新手借鉴。为什么 RSS 聚合器必须拒绝脏数据聚合器的数据全部来自外部 XML 流一旦脏数据入库后果是链式的慢速源站拖垮抓取队列其他订阅源无法按时刷新HTML 错误页404、502、反爬页面被当成 feed 解析产生乱码帖子缺少 guid 的源导致同一篇文章被反复插入信息流出现大量重复。rss 项目把防线全部做在抓取链路里三个关键文件各司其职防线负责文件拦截对象① 超时app/Rss/FeedPostFetcher.php慢速 / 不可达源站② XML 魔数校验app/Rss/FeedPostFetcher.php非 XML 响应体③ guid 去重app/Rss/RssParser.php app/Jobs/RefreshFeedJob.php重复 / 残缺条目防线一10秒超时让慢源站无法拖垮队列抓取入口在FeedPostFetcher::fetchForFeed()第一行就是防线$feedResponse Http::timeout(10)-withUserAgent(rss/4.5.6)-get($feed-url); if (!$feedResponse-successful()) { return []; }设计上有两个细节值得注意硬超时 10 秒——不管源站多慢请求最多等 10 秒就放弃返回空数组而不是抛异常保证定时任务能继续处理下一个订阅源静默失败——非 200 响应直接return []不记录、不重试、不告警。这是一个低维护项目的取舍宁可这一轮丢数据也不让异常打断整批抓取。 对新手来说RSS 抓取超时控制是第一课外部 HTTP 请求必须设置超时否则一个卡死的源站就能让你的队列停摆数小时。防线二XML 魔数校验用前 20 个字符识别真假 feed很多源站在出错时会返回 HTML 页面登录墙、404 页、反爬提示这些内容如果直接丢给 XML 解析器轻则解析失败重则把script里的字符串当成文章标题入库。rss 的做法极其便宜——魔数校验$rssData ltrim($feedResponse-body()); $tagStart explode( , substr($rssData, 0, 20))[0]; $validStarts [?xml, feed, rss]; if (!in_array($tagStart, $validStarts)) { return []; }逻辑拆开看ltrim先剥掉前导空白有些源站会在 XML 声明前输出空格或 BOM取前 20 个字符里的第一个单词按空格切分判断它是不是?xml、feedAtom或rssRSS三者都不是说明这不是 feed整包丢弃。只读 20 个字符就完成一次内容类型识别比信任Content-Type响应头可靠得多——很多源站的 MIME 头是错的这个校验相当于 RSS 数据清洗里的文件头检查。随后数据才交给 RssParser 解析且解析器外层还包了一层try/catch任何解析异常同样静默返回空数组。防线三guid 去重同一篇文章永远只有一条记录guid 去重是三道防线中最讲究的一道它横跨解析、落库、表结构三层。字段级清洗残缺条目直接丢弃RssParser::isValidPostData() 对每个条目做四重检查title、url、guid三者任一为空即丢弃url必须以http://或https://开头拦截相对路径、mailto:等怪值;published_at必须大于 1000过滤掉 1970 年的脏时间戳。同时 RssParser::getPostDataForRssItem() 处理了最常见的脏数据形态——guid 缺失RSS 条目没有guid时回退用link充当 guidAtom 条目则用id字段。这保证进入下一层的条目都有可用的去重键。数据库层唯一索引 updateOrCreate表结构上posts 迁移文件 建了联合唯一索引$table-unique([feed_id, guid]);写入时 RefreshFeedJob::handle() 使用updateOrCreate([guid ...])同一个 feed 下的同一个 guid存在则更新、不存在才插入。唯一索引 upsert 双保险即使并发抓取也不会产生重复行。作业层同一 feed 两小时内的任务互斥还有最后一层隐藏防线RefreshFeedJob 实现了ShouldBeUnique接口uniqueFor 7200意味着同一个订阅源 2 小时内的重复刷新任务会被队列直接丢弃。对应测试 RefreshFeedJobTest 验证了连发 3 次同一 feed 的任务只会有 1 次入队。这把应用层去重延伸到了任务调度去重。眼见为实干净的聚合流经过三道防线过滤后前端信息流里只有结构完整的帖子。项目提供卡片、列表、紧凑三种视图深色模式下同样清爽动手验证test-feed 命令与测试用例项目内置了一个命令行工具 TestFeedCommand可以直接验证某个订阅源能否通过全部防线php artisan rss:test-feed https://danb.me/blog/index.xml它会同步跑一遍完整抓取链路成功则打印抓到的文章列表失败则提示数据无法抓取或未识别为有效数据——正好对应前两道防线。单元测试层面RefreshFeedJobTest 用Http::fake()伪造了标准 RSS 响应验证了 guid 落库、时间戳换算等行为RssParserTest 则覆盖了 XML 解析与字段清洗的各种边界情况。想要本地跑起来看效果git clone https://gitcode.com/gh_mirrors/rs/rss cd rss composer install npm install npm run build小结三条防线的设计哲学防线成本拦截时机一句话概括超时1 行配置网络层慢就是失败别等XML 魔数读 20 个字符解码前不像 XML 的就不是 feedguid 去重唯一索引 upsert入库层同文只留一条这套 RSS 数据校验方案没有引入任何第三方清洗库核心逻辑加起来不到 50 行却覆盖了聚合器最常见的三类脏数据问题。对想要自建 RSS 阅读器的新手这是用最小代码量换最大数据质量的教科书式示范——超时管住网络、魔数管住格式、guid 管住重复层层拦截干净入库。【免费下载链接】rssPROJECT MIGRATED TO CODEBERG -- A simple twitter-feed-style RSS aggregator written in PHP, Laravel, Inertia.js, Tailwind and Vue.js项目地址: https://gitcode.com/gh_mirrors/rs/rss创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表