天天看点

【Python爬虫3】在下载的本地缓存做爬虫下载缓存1为链接爬虫添加缓存支持2磁盘缓存3数据库缓存

<a href="#%E4%B8%8B%E8%BD%BD%E7%BC%93%E5%AD%98">下载缓存</a>

<a href="#1%E4%B8%BA%E9%93%BE%E6%8E%A5%E7%88%AC%E8%99%AB%E6%B7%BB%E5%8A%A0%E7%BC%93%E5%AD%98%E6%94%AF%E6%8C%81">1为链接爬虫添加缓存支持</a>

<a href="#2%E7%A3%81%E7%9B%98%E7%BC%93%E5%AD%98">2磁盘缓存</a>

<a href="#21%E7%94%A8%E7%A3%81%E7%9B%98%E7%BC%93%E5%AD%98%E7%9A%84%E5%AE%9E%E7%8E%B0">1用磁盘缓存的实现</a>

<a href="#22%E7%BC%93%E5%AD%98%E6%B5%8B%E8%AF%95">2缓存测试</a>

<a href="#23%E8%8A%82%E7%9C%81%E7%A3%81%E7%9B%98%E7%A9%BA%E9%97%B4">3节省磁盘空间</a>

<a href="#24%E6%B8%85%E7%90%86%E8%BF%87%E6%9C%9F%E6%95%B0%E6%8D%AE">4清理过期数据</a>

<a href="#25%E7%94%A8%E7%A3%81%E7%9B%98%E7%BC%93%E5%AD%98%E7%9A%84%E7%BC%BA%E7%82%B9">5用磁盘缓存的缺点</a>

<a href="#3%E6%95%B0%E6%8D%AE%E5%BA%93%E7%BC%93%E5%AD%98">3数据库缓存</a>

<a href="#31nosql%E6%98%AF%E4%BB%80%E4%B9%88">1NoSQL是什么</a>

<a href="#32%E5%AE%89%E8%A3%85mongodb">2安装MongoDB</a>

<a href="#33mongodb%E6%A6%82%E8%BF%B0">3MongoDB概述</a>

<a href="#34mongodb%E7%BC%93%E5%AD%98%E5%AE%9E%E7%8E%B0">4MongoDB缓存实现</a>

<a href="#35%E5%8E%8B%E7%BC%A9%E5%AD%98%E5%82%A8">5压缩存储</a>

<a href="#36%E7%BC%93%E5%AD%98%E6%B5%8B%E8%AF%95">6缓存测试</a>

<a href="#37mongodb%E7%BC%93%E5%AD%98%E5%AE%8C%E6%95%B4%E4%BB%A3%E7%A0%81">7MongoDB缓存完整代码</a>

上篇文章,我们学习了如何提取网页中的数据,以及将提取结果存到表格中。如果我们还想提取另一字段,则需要重新再下载整个网页,这对我们这个小型的示例网站问题不大,但对于数百万个网页的网站而言来说就要消耗几个星期的时间。所以,我们可以先对网页进行缓存,就使得每个网页只下载一次。

我们将downloader重构一类,这样参数只需在构造方法中设置一次,就能在后续多次复用,在URL下载之前进行缓存检查,并把限速功能移到函数内部。

在Downloader类的call特殊方法实现了下载前先检查缓存,如果已经定义该URL缓存则再检查下载中是否遇到了服务端错误,如果都没问题表明缓存结果可用,否则都需要正常下载该URL存到缓存中。

downloader方法返回添加了HTTP状态码,以便缓存中存储错误机校验。如果不需要限速或缓存的话,你可以直接调用该方法,这样就不会通过call方法调用了。

为了支持缓存功能,链接爬虫代码也需用一些微调,包括添加cache参数、移除限速以及将download函数替换为新的类。

现在,这个支持缓存的网络爬虫的基本架构已经准备好了,下面就要开始构建实际的缓存功能了。

操作系统

文件系统

非法文件名字符

文件名最大长度

Linux

Ext3/Ext4

<code>/</code>和<code>\0</code>

255个字节

OS X

HFS Plus

<code>:</code>和<code>\0</code>

255个UTF-16编码单元

Windows

NTFS

<code>\</code>、<code>/</code>、<code>?</code>、<code>:</code>、<code>*</code>、<code>"</code>、<code>&gt;</code>、<code>&lt;</code>和`

`

为了保证在不同文件系统中,我们的文件路径都是安全的,就需要把除数字、字母和基本符号的其他字符替换为下划线。

此外,文件名及其目录长度需要限制在255个字符以内。

还有一种边界情况,就是URL以斜杠结尾。这样分割URL后就会造成一个非法的文件名。例如:

对于第一个URL可以在后面添加index.html作为文件名,所以可以把index作为目录名,1为子目录名,index.html为文件名。

现在可以把URL到目录和文件名完整映射逻辑结合起来,就形成了磁盘缓存的主要部分。该构造方法传入了用于设定缓存位置的参数,然后在url_to_path方法中应用了前面讨论的文件名限制。

现在我们还缺少根据文件名存取数据的方法,就是Downloader类<code>result=cache[url]</code>和<code>cache[url]=result</code>的接口方法:<code>__getitem__()</code>和<code>__setitem__()</code>两个特殊方法。

在<code>__setitem__()</code>中,我们使用url_to_path()方法将URL映射为安全文件名,在必要情况下还需要创建目录。这里使用的pickle模块会把输入转化为字符串(序列化),然后保存到磁盘中。

在<code>__getitem__()</code>中,还是先用url_to_path()方法将URL映射为安全文件名。然后检查文件是否存在,如果存在则加载内容,并执行反序列化,恢复其原始数据类型;如果不存在,则说明缓存中还没有该URL的数据,此时会抛出KeyError异常。

可以在python命令前加<code>time</code>计时。我们可以发现,如果是在本地服务器的网站,当缓存为空时爬虫实际耗时<code>0m58.710s</code>,第二次运行全部从缓存读取花了<code>0m0.221s</code>,快了<code>265</code>多倍。如果是爬取远程服务器的网站的数据时,将会耗更多时间。

为节省缓存占用空间,我们可以对下载的HTML文件进行压缩处理,使用zlib压缩序列化字符串即可。

从磁盘加载后解压的代码如下:

压缩所有网页之后,缓存占用大小<code>2.8 MB</code>下降到<code>821.2 KB</code>,耗时略有增加。

本节中,我们将为缓存数据添加过期时间,以便爬虫知道何时需要重新下载网页。在构造方法中,我们使用timedelta对象将默认过期时间设置为30天,在<code>__set__</code>方法中把当前时间戳保存在序列化数据中,在<code>__get__</code>方法中对比当前时间和缓存时间,检查是否过期。

为了测试时间功能,我们可以将其缩短为5秒,如下操作:

由于受制于文件系统的限制,之前我们将URL映射为安全文件名,然而这样又会引发一些问题:

- 有些URL会被映射为相同的文件名。比如URL:<code>.../count.asp?a+b</code>,<code>.../count.asp?a*b</code>。

- URL截断255个字符的文件名也可能相同。因为URL可以超过2000下字符。

使用URL哈希值为文件名可以带来一定的改善。这样也有一些问题:

- 每个卷和每个目录下的文件数量是有限制的。FAT32文件系统每个目录的最大文件数65535,但可以分割到不同目录下。

- 文件系统可存储的文件总数也是有限的。ext4分区目前支持略多于1500万个文件,而一个大型网站往往拥有超过1亿个网页。

要想避免这些问题,我们需要把多个缓存网页合并到一个文件中,并使用类似B+树的算法进行索引。但我们不会自己实现这种算法,而是在下一节中介绍已实现这类算法的数据库。

爬取时,我们可能需要缓存大量数据,但又无须任何复杂的连接操作,因此我们将选用NoSQL数据库,这种数据库比传统的关系型数据库更容易扩展。在本节中,我们将选用目前非常流行的MongoDB作为缓存数据库。

- 列数据存储(如HBase);

- 键值对存储(如Redis);

- 图形数据库(如Neo4j);

- 面向文档的数据库(如MongoDB)。

检测安装是否成功,在本地启动MongoDB服务器:

然后,在Python中,使用MongoDB的默认端口尝试连接MongoDB:

下面是MongoDB示例代码:

当插入同一条记录时,MongoDB会欣然接受并执行这次操作,但通过查找发现记录没更新。

为了存储最新的记录,并避免重复记录,我们将ID设置为URL,并执行<code>upsert</code>操作。该操作表示当记录存在时则更新记录,否则插入新记录。

现在我们已经准备好创建基于MongoDB的缓存了,这里使用了和之前的DiskCache类相同的接口。我们在下面构造方法中创建了<code>timestamp</code>索引,在达到给定的时间戳之后,MongoDB的这一便捷功能可以自动过期删除记录。

下面我们来测试一下这个MongoCache类,我们用默认0时间间隔<code>timedelta()</code>对象进行测试,此时记录创建后应该会马上会被删除,但实际却没有。这是因为MongoDB运行机制造成的,MongoDB后台运行了一个每分钟检查一次过期记录的任务。所以我们可以再等一分钟,就会发现缓存过期机制已经运行成功了。

可以看出,用数据库缓存的读取时间是磁盘缓存的两倍,但成功地避免了磁盘缓存的缺点。

Wu_Being 博客声明:本人博客欢迎转载,请标明博客原文和原链接!谢谢!

【Python爬虫3】在下载的本地缓存做爬虫下载缓存1为链接爬虫添加缓存支持2磁盘缓存3数据库缓存

如果你看完这篇博文,觉得对你有帮助,并且愿意付赞助费,那么我会更有动力写下去。