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

笔记 专栏收录该内容
4 篇文章 0 订阅

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

为链接爬虫添加缓存支持

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

# downloader.py
import urllib.request
import urllib.parse
import urllib.error
from .Throttle import Throttle
import socket
import random

DEFAULT_AGENT = 'wswp'
DEFAULT_DELAY = 5
DEFAULT_RETRIES = 1
DEFAULT_TIMEOUT = 60


class 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.py
def 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)

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

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值