scrapy爬虫url或者body中遇到随机数或者随机字符串该如何去重(或过滤)

在有些网站的开发中,经常会在链接或者参数中增加可变的量,比如增加随机数、增加随机字符串、增加时间戳或者增加不同的字符串等等来进行请求。有些情况下,我们不在链接或者参数中添加 可变的量也可以请求成功(比如不加时间戳也可以请求成功),随机数不变也可以请求成功,但是有些情况下这两种方式可能都无法请求成功,这就需要我们完全按照他们的要求去请求了。

首先来介绍下主要原理。参考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
r: 0.16512
name:  abc
sex: male

Form Data

id: 1234
r: 0.48913
name:  abc
sex: male

同样和第一种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来去重

你可能感兴趣的:(爬虫,python,爬虫,http,网络协议)