爬虫(3)-- 下载缓存

内存缓存

将下载的网页缓存到内存,以避免碰到相同的网页重新下载,同时提供时间限速功能。定义一个下载类

class Downloader:
    def __init__(self,decay = 5,user_agent = 'wswp',proxies = None,
                    num_retries = 1,cache = None):
        self.throttle = Throttle(decay)
        self.user_agent = user_agent
        self.proxies = proxies
        self.num_retries = num_retries
        self.cache = cache
    def __call__(self,url):
        result = None
        if self.cache:
            try:
                result = self.cache[url]  #直接尝试从缓存中获取
            except KeyError:
                pass
            else:
                if self.num_retries > 0 and 500 <= result['code'] < 600:
                    result = None         #设置为 None 重新下载
        
        if result is None:
            self.throttle.wait(url)
            proxy = random.choice(self.proxies) if self.proxies else None
            headers = {"User-agent":self.user_agent}
            result = self.download(url,headers,proxy,self.num_retries)
            if self.cache:
                # save result to cache
                self.cache[url] = result
        return result["html"]
    
    def download(self,url,headers,proxy,num_retries,data = None):
        ...
        return {"html":html,"code":code}

上面的代码使用 __call__ 的特殊方法实现了下载前检查缓存的功能。该方法首先检查缓存是否定义,如果已经定义,则检查之前是否缓存了改 URL。如果该 URL 已经被缓存,则检查之前的下载中是否遇到了服务端错误。最后,如果也没有发生过服务端错误,则表明该缓存结果可用。相应的链接爬虫调整如下:

def link_crawler(...,cache = None):
    crawl_queue = [seed_url]
    seen = {seed_url:0}
    num_urls = 0
    rp = get_robots(seed_url)
    D = Downloader(decay = decay,user_agent = user_agent,proxies = proxies,num_retries = num_retries,cache = cache)
    while crawl_queue:
        url = crawl_queue.pop()
        depth = seen[url]
        if rp.can_fetch(user_agent,url):
            html = D(url)   # 调用的是特殊方法 __call__
            links = []
            ...

磁盘缓存

实现磁盘缓存,我们得为每一个 url 准备一个目录,可以用 url 作为目录,例如:

import re
url = "http://example.webscraping.com/default/view/Australia-1"
dir = re.sub('[^/0-9a-zA-Z\-.,;_]','_',url)
print dir # http_//example.webscraping.com/default/view/Australia-1

但有一种边界情况需要考虑,那就是 URL 路径可能会以 / 结尾。比如
http://www.baidu.com/
http://www.baidu.com/1

这样的话 http://www.baidu.com/ 就是一个目录名而不是文件名,可行的解决方案是添加 index.html 作为文件名

import urlparse
url = "http://www.baidu.com/ss/"
url = urlparse.urlsplit(url) # SplitResult(scheme = 'http',netloc = 'www.baidu.com',path = '/ss/',query = '',fragment = '')
if url.path.endswith("/"):
    path = url.path + "index.html"
filename = url.netloc + path + url.query
print filename # www.baidu.com/ss/index.html

讨论了 url 和文件名的映射关系后,这里开始实现将 url 缓存至硬盘的类

import os
import re
import urlparse

class DiskCache(object):
    def __init__(self,cache_dir = 'cache'):
        self.cache_dir = cache_dir
        self.max_length = max_length
    def url_to_path(self,url):
        components = urlparse.urlsplit(url)
        path = components.path
        if not path:
            path = '/index.html'
        elif path.endswith('/'):
            path += 'index.html'
        filename = components.netloc + path + components.query
        filename = re.sub('[^/0-9a-zA-Z\-.,;_ ]','_',filename)
        return os.path.join(self.cache_dir,filename)
    def __getitem__(self,url):
        path = self.url_to_path(url)
        if os.path.exists(path):
            with open(path) as fp:
                return pickle.load(fp)
        else:
            raise KeyError(url + " does not exists")
    def __setitem__(self,url,result):
        path = self.url_to_path(url)
        folder = os.path.dirname(path)
        if not os.path.exists(folder):
            os.makedirs(folder)
        with open(path,"wb") as fp:
            fp.write(pickle.dumps(result))

在 __setitem__ 中,使用 url_to_path( ) 方法将 URL 映射为安全文件名,在必要的情况下还需要创建父目录。pickle 模块会把输入转化成字符串,然后保存到磁盘中。而在 __getitem__ 中,首先将 URL 映射为文件名,如果文件存在,则加载其内容并反序列化,不存在则跑出 KeyError 异常。

数据库缓存

在磁盘缓存的基础上,只不过是将数据写入数据库而已(比如 MongoDB),不需要将 url 映射成路径,只需要将 url 设成 id 保存到数据库即可,不再过多介绍。因为就一个写入的方式差别。python 完全可以实现 MongoDB 的插入操作。

数据压缩

数据压缩就跟爬虫关系不大了,毕竟数据压缩什么时候都可以压缩,只不过应用在爬虫上减少存储空间。实现方法很简单,只需要在保存之前使用 zlib 压缩即可。

import cPickle as pickle
import zlib

data_compress = zlib.compress(pickle.dumps(data))   # 压缩 data
zlib.decompress(data_compress) = pickle.dumps(data) # 解压缩

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值