目录

用Python写网络爬虫(3)-下载缓存和并发下载

由于并发下载实在没有啥写的东西,不如就合并到这一节吧。

下载缓存

当需要抓取的数据发生变化时,我们需要重新下载整个网站。对于大型网站而言,重新抓取可能需要耗费几个星期的时间。一种可行的方式是对已爬取网页进行缓存的方案,可以让每个网页之下载一次。

添加缓存支持

使用回调添加缓存支持后,下载的流程是这样的:

  1. 接收一个新的URL;
  2. 该URL是否已被缓存;
  3. 如果已经缓存,检查:
    • 之前的下载时是否发生5xx错误;
    • 缓存是否已经过期失效;
  4. 如果缓存可用返回缓存内容;
  5. 否则,进行限速检测,然后下载;
  6. 新下载内容和状态码写入缓存,或更新缓存。

磁盘缓存

将下载到的网页按照URL的路径存储到文件系统中。

  • 需要对URL进行安全映射,屏蔽掉非法的文件名字符;
  • 不能超过文件系统对文件名最大长度的限制;
  • URL路径可能会以/结尾,此时/后面的空字符串就会成为一个非法的文件名。但是如果移除这个/,使用父字符串作为文件名,又会造成无法保存其他URL的问题。

DiskCache类需要实现以下几个方法:

  • __init__(self, cache_dir='cache'):初始化,设定缓存目录,设定过期时间;
  • url_to_path(self, url):URL到文件路径的转换方法;
  • __getitem__(self, url):根据URL获取缓存;
  • __setitem__(self, url, result):存储缓存。

为了节约磁盘空间,可以使用zlib模块对序列化字符串逆行压缩。

**缺点:**受制于本地文件系统的限制。包括:文件名使用的字符、文件名长度、每个目录的最大文件数和文件系统可存储的文件总数等。

数据库缓存

爬取时,我们可能需要缓存大量的数据,而又无须任何复杂的连接操作,因为我们将选用NoSQL数据库,这种数据库比传统的SQL数据库更容易扩展。

MongoDB是一个面向文档的数据库,非常适合存储网页文件。MongoDB中的键值可以重复,当使用insert插入时,若使用了同一个键值,将会产生多条记录。避免重复的方式是使用upsert,若键值已经存在则会更新记录,而不是插入新的纪录。

MongoCache类同样要实现__getitem____setitem__方法,同时在构造方法中,我们创建了timestamp索引。在达到给定时间戳一定秒数之后,MongoDB便可以自动删除记录。

不过,MongoDB缓存无法按照给定时间精确清理过期记录,会存在至多一分钟的延迟,这是由MongoDB的运行机制造成的——MongoDB运行了一个后台任务每分钟会检查一次过期记录。一般来说,由于缓存过期时间通常设定为几周或几个月,所以这个相对较小的延时不会存在太大问题。

并发下载

多线程+多进程,可以显著提升效率。但提升不是无限的,因为线程越多,线程间切换的时间也越长。

爬虫网络请求是IO阻塞的,因此使用多线程能提高效率。若是CPU计算阻塞的,则只有多进程+多核CPU才能提升效率。