scrapy redis 增量爬虫

各种技术贴满满的copy, paste味道,真遇到实际问题却很少提及,比如最常见的增量爬虫。scrapy redis在第一次爬取时可以用自带的驱虫中间件,但是增量爬虫不行,这里我写了个中间件可以实现增量爬取,前提是目标url会变化。如果是url不变但是网页内容变化的增量爬虫,就需要新的解决方案了。

虽然配合redis,scrapy理论上可以暂停重启,但是有时候程序可能有问题一次ctrl c无法停止,强制两次ctrl C又会破坏scrapy redis的重启机制。很多时候dupefilter里的url指纹和mysql数据库里的url可能不一致,甚至有些情况下你必须清空dupefilter重新运行爬虫(强制停止爬虫时很多requests虽然没有爬取成功但是指纹却放到了dupefilter,下一次爬取时就会忽略这些url),这个中间件就能解决你的问题。

方案很简单,就是从mysql读取所有的url放到redis,每次request之前都会检查下所要请求的url是否已经爬取了,redis比mysql效率高很多,非常适合这种秒级应用。另外,如果url很多,可以用md5来节省redis内存,这里没有用,很容易实现。

class DedupeMiddleware(object): # 定制去重中间件

    logger = logging.getLogger(__name__) # 利用这个可以方便进行scrapy 的log输出
    client = redis.Redis(host=settings.REDIS_HOST, port=6379, db=0, password=settings.REDIS_PWD)
    if client:
        logger.info('redis connected for dedupe')
    else:
        logger.info('redis connect failed for dedupe') 

    def process_request(self, request, spider):
        # Called for each request that goes through the downloader
        # middleware.

        # Must either:
        # - return None: continue processing this request
        # - or return a Response object
        # - or return a Request object
        # - or raise IgnoreRequest: process_exception() methods of
        #   installed downloader middleware will be called
        if self.client.hexists('existing_url', request.url): #取item里的url和key里的字段对比,看是否存在,存在就丢掉这个item。不存在返回item给后面的函数处理
            self.logger.info(f'{request.url} already in mysql')
            raise IgnoreRequest(f'{request.url} already in mysql')
        return None