在有些网站的开发中,经常会在链接或者参数中增加可变的量,比如增加随机数、增加随机字符串、增加时间戳或者增加不同的字符串等等来进行请求。有些情况下,我们不在链接或者参数中添加 可变的量也可以请求成功(比如不加时间戳也可以请求成功),随机数不变也可以请求成功,但是有些情况下这两种方式可能都无法请求成功,这就需要我们完全按照他们的要求去请求了。
首先来介绍下主要原理。参考scrapy-redis调度器源码(即scrapy_redis.scheduler.Scheduler):
# 此段代码就是从scrapy_redis中截取下来的
# 函数名的含义就是请求入队列,即能不能让一个request进入请求队列是这个方法来判断的
def enqueue_request(self, request):
# 如果request的dont_filter不是True并且self.df.request_seen方法返回的是True。就会return False,表示已去重
# 然后这个self.df就是过滤器对象,其实就是settings中设置的DUPEFILTER_CLASS类的对象。
if not request.dont_filter and self.df.request_seen(request):
self.df.log(request, self.spider)
return False
if self.stats:
self.stats.inc_value('scheduler/enqueued/redis', spider=self.spider)
# 所以如果self.df.request_seen方法返回的是Fasle,就会把请求push进队列,就会return True,表示已压进队列
self.queue.push(request)
return True
所以我们可以修改过滤器类,在过滤器类中对原始请求进行复制,复制的请求用来处理可变参数,此时复制的请求就发生了变化,而对此副本请求进行去重,但原始请求不变,仍用来压进请求队列。以下将通过爬虫提交参数的不同方法来分别进行举例讲解。
1、GET方法的url链接参数可变量
比如下面两个网址对应的都是同一html资源:
https://www.baidu.com/info?id=1234&r=0.1536745
https://www.baidu.com/info?id=1234&r=0.84682
如果我们再scrapy中直接去请求这两个链接时,是无法把另一个链接去重的,此时我们就需要自己构造一个过滤器了。方法如下:
首先我们需要在scrapy项目路径下,和pipelines.py文件同一级新建一个“dupefilter.py”文件。然后在文件中输入以下代码:
from scrapy_redis.dupefilter import RFPDupeFilter
## 类名自定义,继承了scrapy_redis的RFPDupeFilter过滤器
class GetRandomDupefilter(RFPDupeFilter):
def request_seen(self, request):
url = request.url
# 此处是将url中的随机参数替换成了空字符串,注意哦,此处不止替换了随机数,还有键名等
url = re.sub('[\?\&]r=[0-9\.]+', "", url)
# 然后用新url来替换之前的url,就出现了复制的请求request_copy。要注意replace方法是返回一个新的请求对象,不对原请求做任何改变
request_copy = request.replace(url=url)
# 再对复制的请求request_copy进行指纹输出用于去重
fp = self.request_fingerprint(request_copy)
# 指纹结果用于add进去重集合,如果可以add进,则集合无该请求,added=1, 否则added=0。added的值表示add进集合的个数。
added = self.server.sadd(self.key, fp)
# 如果added=1,表示无重复,返回False,反之有重复,返回True
return added == 0
这样的话我们就可以不改变原始请求request来用副本request_copy(修改url)进行去重,从而做到对原始request地去重。
2、POST方法的JSON参数的可变量
比如下面两个json参数(Content-Type: application/json)对应的都是同一html资源,POST的url是“https://www.baidu.com/info”:
{ "id": 1234, "r": 0.045612, "name": "abc", "sex": "male" } | { "id": 1234, "r": 0.45816, "name": "abc", "sex": "male" } |
该方法和上面类似,是将json中的可变量删除进行去重:
from scrapy_redis.dupefilter import RFPDupeFilter
import json
## 类名自定义,继承了scrapy_redis的RFPDupeFilter过滤器
class PostJsonRandomDupefilter(RFPDupeFilter):
def request_seen(self, request):
# 此时body是b'{"id": 1234, "r": 0.045612, "name": "abc", "sex": "male"}'
body = request.body
# 因为是采用的json.loads,所以body最好是json.dumps得到的。
# 此时body_data是{"id": 1234, "r": 0.045612, "name": "abc", "sex": "male"}
body_data = json.loads(body)
# body_data删除"r"参数,此时body_data是{"id": 1234, "name": "abc", "sex": "male"}
body_data.pop("r", None)
# 然后用新body_data字符串来替换之前的body,就出现了复制的请求request_copy。
request_copy = request.replace(body=json.dumps(body_data))
# 再对复制的请求request_copy进行指纹输出用于去重
fp = self.request_fingerprint(request_copy)
# 指纹结果用于add进去重集合,如果可以add进,则集合无该请求,added=1, 否则added=0。added的值表示add进集合的个数。
added = self.server.sadd(self.key, fp)
# 如果added=1,表示无重复,返回False,反之有重复,返回True
return added == 0
此处是将body先json化然后再删除可变键值对,当然也可以直接用正则表达式替换成空字符串也可以。(字符串替换要注意body是字节类型)
3、POST方法的form参数的可变量
比如下面两个form参数(Content-Type: application/x-www-form-urlencoded)对应的都是同一html资源,POST的url是“https://www.baidu.com/info”:
Form Data id: 1234 | Form Data id: 1234 |
同样和第一种GET方法类似,是将body中的可变量替换成空,替换时要注意body是字节类型:
from scrapy_redis.dupefilter import RFPDupeFilter
## 类名自定义,继承了scrapy_redis的RFPDupeFilter过滤器
class PostFormRandomDupefilter(RFPDupeFilter):
def request_seen(self, request):
# 此时body的值是b'id=1234&r=0.16512&name=abc&sex=male'
body = request.body
# 此处是将body中的随机参数替换成了空字符串,注意哦,此处不止替换了随机数,还有键名等。
# 此时body的值是b'id=1234&name=abc&sex=male'
body = re.sub(b'\&*r=[0-9\.]+', b"", body)
# 然后用新body来替换之前的body,就出现了复制的请求request_copy。
request_copy = request.replace(body=body)
# 再对复制的请求request_copy进行指纹输出用于去重
fp = self.request_fingerprint(request_copy)
# 指纹结果用于add进去重集合,如果可以add进,则集合无该请求,added=1, 否则added=0。added的值表示add进集合的个数。
added = self.server.sadd(self.key, fp)
# 如果added=1,表示无重复,返回False,反之有重复,返回True
return added == 0
最后我们只需要在settings.py文件中对过滤器进行指定,即根据情况选择输入以下代码:
DUPEFILTER_CLASS = "myproject.dupefilter.GetRandomDupefilter" # GET方法的url链接参数可变量的过滤器
DUPEFILTER_CLASS = "myproject.dupefilter.PostJsonRandomDupefilter" # POST方法的JSON参数的可变量的过滤器
DUPEFILTER_CLASS = "myproject.dupefilter.PostFormRandomDupefilter" # POST方法的form参数的可变量的过滤器
# 根据具体情况决定使用哪种过滤器
以上方法是将可变量替换成空字符串(或者删除可变量键值对)的操作,如果可变量太多的话,我们可以用re.findall()提取出不变的量用作新的url或者body来去重。