在爬虫中有一种很重要的操作就是增量爬取,scrapy自带的管道中的去重只能实现该次爬取链接的去重,并不能实现对过往爬取数据的去重,所以选择使用redis来实现增量爬取,这也是大家现在使用较多的方法
参考https://www.jianshu.com/p/f03479b9222d
1.官方去重
上面提起过,官方文档中的在管道里预设了去重模块,但只能在本次爬取内容中去重,无法对比以往爬取的数据。
官方去重DuplicatesPipeline1 2 3 4 5 6 7 8 9 10 11 12
| class DuplicatesPipeline(object): def __init__(self): self.url_seen = set()
def process_item(self, item, spider): if item['art_url'] in self.url_seen: raise DropItem("Duplicate item found: %s" % item) else: self.url_seen.add(item['art_url']) return item
|
可以看出官方自带的DuplicatesPipeline管道中间件没办法完成我们所需要的增量爬取(对比曾经爬过的内容而不只是本次)
2.使用管道中间件
我在网上找到的大部分方法都是在管道中间件中通过使用redis实现了简单的增量爬取,原理就是在redis中存储之前爬虫爬取过的页面url,然后item进入管道的时候判断这个item对应的url在不在redis里,如果在说明爬过,把这个item丢弃;如果不在就继续下一步。
但是这里并没有解决页面url没变但是页面内容发生了变化的情况,如果要解决这个问题那么还应该将item内容编成md5编码,然后对比url的同时对比md5编码,url相同md5不同就覆盖掉原来的。但是我本次并没有实现这个功能,这个功能只适用于内容会发生变化的详情页,一般都用不上,只是提出来解决方法。
pipelines.py1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import mysql.connector import pandas as pd import redis redis_db = redis.Redis(host='127.0.0.1', port=6379, db=4) redis_data_dict = "f_url"
class DuplicatesPipeline(object): conn = mysql.connector.connect(user = 'root', password='yourpassword', database='dbname', charset='utf8')
def __init__(self): redis_db.flushdb() if redis_db.hlen(redis_data_dict) == 0: sql = "SELECT url FROM your_table_name;" df = pd.read_sql(sql, self.conn) for url in df['url'].get_values(): redis_db.hset(redis_data_dict, url, 0)
def process_item(self, item, spider): if redis_db.hexists(redis_data_dict, item['url']): raise DropItem("Duplicate item found: %s" % item)
return item
|
注意要将新写的管道中间件在setting中注册:
setting.py1 2 3 4
| ITEM_PIPELINES = { 'shaoerkepu.pipelines.ShaoerkepuPipeline': 300, + 'shaoerkepu.pipelines.DuplicatesPipeline': 200, }
|
这里主要使用reids中的hash类型,原因是速度,但是为什么速度快俺也不懂,没有深入了解过。个人感觉这里的hash其实有点像是字典,redis_db.hset(redis_data_dict, url, 0),这里面redis_data_dict类似字典名,然后url是key,0是value。一个redis_data_dict里面有很多对key,value。这里由于我们只需要使用url作为key所以所有的value都写了0。详细的python操作redis可以看我的另一篇博客。
3.使用下载中间件
上面介绍了大部分去重工作都在管道中间件中实现,如果涉及到内容的去重也就是md5那么就必须在管道中间件中实现,但是如果只涉及到对url的判断我觉得可以在下载中间件中实现,这样可以省去更多的时间(理论上应该是这样的吧,毕竟下载中间件在request的时候就起作用,管道在最后面才起作用)。
middlewares.py1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| from scrapy import signals from scrapy.exceptions import IgnoreRequest import redis
redis_db = redis.Redis(host='127.0.0.1', port=6379, db=4)
class IngoreRequestMiddleware(object): def process_request(self, request, spider): if request.meta.get('middleware') == 'IngoreRequestMiddleware': if redis_db.hexists('urls', request.url): raise IgnoreRequest("IgnoreRequest : %s" % request.url) else: redis_db.hset('urls', request.url, 0) return None
|
注意要将新写的下载中间件在setting中注册:
settings.py1 2 3 4
| DOWNLOADER_MIDDLEWARES = { # 'shaoerkepu.middlewares.ShaoerkepuDownloaderMiddleware': 543, + 'shaoerkepu.middlewares.IngoreRequestMiddleware': 543, }
|