WSWP(用 python写爬虫) 笔记三:为爬虫添加缓存网页内容功能

来源:互联网 发布:奇游加速器mac 编辑:程序博客网 时间:2024/06/01 07:19

前面已经实现了一个具有数据爬取的爬虫。如果新增需求还要再抓取另一个字段,比如前面的爬虫中要求增加一个国旗图片的url,又该怎么做呢?想要抓取新增的字段,需要重新下载整个需要爬取的网站。对于小型网站来说,可能不算特别大的问题,但是对于那些百万级数据的网站而言,重新下载可能需要耗费很长一段时间。因此,对已爬取的网页进行缓存的方案可以让每个网页只下载一次。

为链接爬虫添加缓存支持

修改上一个爬虫中的downloader.py中的download函数,使其在开始下载网页之前先进性缓存检查,还需要把限速的功能移植到函数内部,这样只有在发生真正下载的时候才会触发限速,而通过加载缓存则不会触发。为了避免每次下载都需要传入多个参数,将download函数重构为一个类,这样参数只需要在构造方法中设置一次,就能在后续下载中多次复用。代码如下:

# downloader.pyimport urllib.requestimport urllib.parseimport urllib.errorfrom .Throttle import Throttleimport socketimport randomDEFAULT_AGENT = 'wswp'DEFAULT_DELAY = 5DEFAULT_RETRIES = 1DEFAULT_TIMEOUT = 60class Downloader:    def __init__(self,delay=DEFAULT_DELAY,userAgent=DEFAULT_AGENT,proxies=None,numRetries=DEFAULT_RETRIES, timeOut=DEFAULT_TIMEOUT, opener=None,cache=None):        socket.setdefaulttimeout(timeOut)        self.throttle = Throttle(delay)        self.userAgent = userAgent        self.proxies = proxies        self.numRetries = numRetries        self.opener = None    def __call__(self, url):        result = None        if self.cache:            try:                result = self.cache[url]            except KeyError:                # url is not available in cache                pass            else:                if self.numRetries >0 and 500<=result['code']<600:                    # server error so ignore result from cache and re-download                    result = None        if result is None:            # result was not loaded from cache so still need to download            self.throttle.wait(url)            proxy = random.choice(self.proxies) if self.proxies else None            headers = {'User-agent': self.userAgent}            result = self.download(url, headers, proxy=proxy,numRetries=self.numRetries)            if self.cache:                # save result to cache                    self.cache[url] = result        return result['html']    def download(self, url, headers, proxy, numRetries, data=None):        print("正在下载:", url)        request = urllib.request.Request(url, data, headers)        opener = self.opener or urllib.request.build_opener()        if proxy:            proxyParams = {urllib.parse.urlparse(url).scheme: proxy}            opener.add_handler(urllib.request.ProxyHandler(proxyParams))        try:            response = opener.open(request)            html = response.read()            code = response.code        except urllib.error.URLError as e:            print('下载错误:', e.reason)            html = ''            if hasattr(e, 'code'):                code = e.code                if numRetries > 0 and 500 <= code < 600:                    return self.download(url, headers, proxy, numRetries - 1, data)            else:                code = None        return {'html':html,'code':code}

上面的代码中Downloader类中有一个比较有意思的部分,那就是__call__特殊方法,在该方法中实现了下载前检查缓存的功能。首先会检查缓存是否已经定义,如果已经定义,则检查之前是否已经缓存了该url,如果已经缓存,则检查之前的下载中是否遇到了服务器端错误。如果检测到上述检查中的任何一项失败,都需要重新下载该网页,然后将缓存结构添加到缓存中。这里的download方法参数和之间的download函数基本一致,只是在返回下载的html的时候多加了一个状态码,该方法也可以直接调用。proxies是一个代理列表,通过随机获取的方式来绕过一定的反爬虫机制。
对于cache类,可以通过调用result=cache[url]从cache中加载数据,并通过cache[url] = resutl向cache中保存结果。这种便捷的接口写法也是Python中字典数据类型的使用方式。为了支持该接口,cache类需要定义__getitem__()和__setitem__()这两个特殊的类方法。
除此之外,为了支持缓存功能,链接爬取模块的代码也要进行一些微调,包括添加cache参数、移除限速以及将download函数替换为新的类等。代码如下所示:

# downloader.pydef linkCrawler(seedUrl, linkRegex=None, delay=5, maxDepth=-1, maxUrls=-1, headers=None, userAgent='wswp', proxies=None, numRetries=1, scrapeCallBack=None,cache=None):    """    Crawl from the given seed URL following links matched by linkRegex    :param seedUrl:  起始url    :param linkRegx: 链接匹配的正则表达式    :param delay: 延迟时间    :param maxDepth: 最深的层次    :param maxUrls:  最多的url数量    :param headers: http请求头    :param userAgent: http头中的userAgent选项    :param proxy: 代理地址    :param numRetries: 重新下载次数    :return:    """    crawlQueue = deque([seedUrl])    seen = { seedUrl:0}    numUrls = 0    rp = getRobots(seedUrl)    Down = Downloader(delay=delay,userAgent=userAgent,proxies=proxies,numRetries=numRetries,cache=cache)    while crawlQueue:        url = crawlQueue.pop()        if rp.can_fetch(userAgent, url):            html = Down(url)            links = []            if scrapeCallBack:                links.extend(scrapeCallBack(url, html) or [])            depth = seen[url]            if depth != maxDepth:                if linkRegex:                    links.extend(link for link in getLinks(html) if re.match(linkRegex, link))                for link in links:                    link = normalize(seedUrl, link)                    if link not in seen:                        seen[link] = depth + 1                        if sameDomain(seedUrl, link):                            crawlQueue.append(link)            numUrls += 1            if numUrls == maxUrls:                break        else:            print('Blocked by robots.txt',url)

到目前为止,这个网络爬虫的基本架构已经搭建好了,接下来就要开始构建实际的缓存了。

原创粉丝点击