爬虫如何判断一个页面更新了?

一个网页距离爬虫上次访问是否更新了,这个是重要但是又非常难判断的问题。如果只是一次性抓取来一些数据来用,那这个问题无关紧要。但是通常情况下,我们抓到一个页面之后,还需要知道这个页面的后续变化,如果内容变了,必须再抓一次,让我们数据库保存的数据保持与目标页面的同步。

目前来说,我们使用的方法是定期(例如每天深夜)抓取。将所有的爬虫脚本用 crontab 的方式管理。这样是无状态的,我不需要之后目标网站到底更新了没有,定期抓一次保持同步就可以。

这样的问题就是浪费资源。无论对方更新没有,你都要像个傻瓜一样执行一遍同样的任务。如果能先知道这个网页是否更新了数据,就能停止后续的抓取工作,节省一些带宽和计算资源。如果你要监控1千个网站,那么每天去抓一整遍1000个网站所有页面,和每天抓取1000个网站更新的内容,区别是巨大的。

其实吧,这个是长久以来的痛点。早在远古时代,大家都耿直地在自己的博客上更新内容,订阅其他人的博客(而不必每天打开一下所有的页面)就成了一个硬性的需求,所以就有了RSS。可惜的是,并不是所有网站都提供RSS源的,这个功能指望不上。

在网上查了一下,最后找到两种思路,都不是非常完美。第一种基于 HTTP 协议,第二种基于网页内容。

1.使用HTTP状态码判断

HTTP状态码规定了304字段,在 Chrome 控制台可以看到很多 js css 文件都是 304 状态码的。这种方法优点是节省资源;缺点是极不可靠,因为并不是所有的服务器都会返回 304 状态码,况且现在很多网站是单页应用,动态加载。 index.html 是一直不会变的。知乎上有位同学描述了具体的做法,我直接抄过来了。

  1. 第一次先请求某个网页,抓取到本地,假设文件名为 a.html。这时文件系统有个文件的修改时间。
  2. 第二次访问网页,如果发现本地已经有了 a.html,则向服务器发送一个 If-Modified-Since 的请求。 把 a.html 的修改时间写到请求里。
  3. 如果网页更新了,服务器会返回一个 200 的应答,这时就重新抓取网页,更新本地文件。
  4. 如果网页没有更新,服务器会返回一个304的应答。这时就不需要更新文件了。

这种方法的弊端和 RSS 一样,你不能指望所有的网站都正确实现了 HTTP 。比较靠谱的、但是有些麻烦的是根据网页内容判断。

2.基于网页内容的比较

由于要纵向比较网页内容,所以不免这个思路是“有状态的”,需要数据库或类似的服务保存历史网页。

历史网页的对比也是个难点。有些网页有展示“访问量”,“当前时间”的内容。这些东西的变化不应该算作“网页更新了”。比较靠谱的是监控页面的某个 xpath ,而不是网页全部。不靠谱的是采用各种判断条件花式决定“网页更新没有”。比如:

  • 网页最大面试的 block 是否更新
  • 对网页进行截图,对比截图的相似度
  • diff一下相似率

还发现了一个“网页指纹+海明距离”的算法,大体看了一下这个算法貌似是判断海量网页中两个网页是不是相同的事情。比如同一条新闻被两个网站转载,两个网站 html 完全不一样,但是新闻的内容文字一模一样,采用 simhash 算距离就可以认为这两个网页说的是一件事(我不太确定)。

这方面有一些现成的监控服务,例如:

  1. https://sleepingspider.com/
  2. https://distill.io/
  3. 网页兵 page monitor
  4. 分秒数据

 

我自己也尝试写一个,打算用 webhook 的方式发送通知,用 Celery 固定频率访问网页。然后用自己的想象力写一个判断是否更新的决策。项目进度在这里:https://github.com/laixintao/page-watcher  (最后也可能编程一个坑)

Leave a comment

电子邮件地址不会被公开。 必填项已用*标注