各种技术贴满满的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