• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

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

武飞扬头像
shang88888
帮助1

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

首先来介绍下主要原理。参考scrapy-redis调度器源码(即scrapy_redis.scheduler.Scheduler):

  1.  
    # 此段代码就是从scrapy_redis中截取下来的
  2.  
    # 函数名的含义就是请求入队列,即能不能让一个request进入请求队列是这个方法来判断的
  3.  
    def enqueue_request(self, request):
  4.  
    # 如果request的dont_filter不是True并且self.df.request_seen方法返回的是True。就会return False,表示已去重
  5.  
    # 然后这个self.df就是过滤器对象,其实就是settings中设置的DUPEFILTER_CLASS类的对象。
  6.  
    if not request.dont_filter and self.df.request_seen(request):
  7.  
    self.df.log(request, self.spider)
  8.  
    return False
  9.  
    if self.stats:
  10.  
    self.stats.inc_value('scheduler/enqueued/redis', spider=self.spider)
  11.  
    # 所以如果self.df.request_seen方法返回的是Fasle,就会把请求push进队列,就会return True,表示已压进队列
  12.  
    self.queue.push(request)
  13.  
    return True

所以我们可以修改过滤器类,在过滤器类中对原始请求进行复制,复制的请求用来处理可变参数,此时复制的请求就发生了变化,而对此副本请求进行去重,但原始请求不变,仍用来压进请求队列。以下将通过爬虫提交参数的不同方法来分别进行举例讲解。

1、GET方法的url链接参数可变量

比如下面两个网址对应的都是同一html资源:

  1.  
    https://www.百度.com/info?id=1234&r=0.1536745
  2.  
    https://www.百度.com/info?id=1234&r=0.84682

如果我们再scrapy中直接去请求这两个链接时,是无法把另一个链接去重的,此时我们就需要自己构造一个过滤器了。方法如下:

首先我们需要在scrapy项目路径下,和pipelines.py文件同一级新建一个“dupefilter.py”文件。然后在文件中输入以下代码:

  1.  
    from scrapy_redis.dupefilter import RFPDupeFilter
  2.  
     
  3.  
    ## 类名自定义,继承了scrapy_redis的RFPDupeFilter过滤器
  4.  
    class GetRandomDupefilter(RFPDupeFilter):
  5.  
    def request_seen(self, request):
  6.  
    url = request.url
  7.  
    # 此处是将url中的随机参数替换成了空字符串,注意哦,此处不止替换了随机数,还有键名等
  8.  
    url = re.sub('[\?\&]r=[0-9\.] ', "", url)
  9.  
    # 然后用新url来替换之前的url,就出现了复制的请求request_copy。要注意replace方法是返回一个新的请求对象,不对原请求做任何改变
  10.  
    request_copy = request.replace(url=url)
  11.  
    # 再对复制的请求request_copy进行指纹输出用于去重
  12.  
    fp = self.request_fingerprint(request_copy)
  13.  
    # 指纹结果用于add进去重集合,如果可以add进,则集合无该请求,added=1, 否则added=0。added的值表示add进集合的个数。
  14.  
    added = self.server.sadd(self.key, fp)
  15.  
    # 如果added=1,表示无重复,返回False,反之有重复,返回True
  16.  
    return added == 0
学新通

这样的话我们就可以不改变原始请求request来用副本request_copy(修改url)进行去重,从而做到对原始request地去重。

2、POST方法的JSON参数的可变量

比如下面两个json参数(Content-Type: application/json)对应的都是同一html资源,POST的url是“https://www.百度.com/info”:

{
    "id": 1234,
    "r": 0.045612,
    "name": "abc",
    "sex": "male"
}
{
    "id": 1234,
    "r": 0.45816,
    "name": "abc",
    "sex": "male"
}

该方法和上面类似,是将json中的可变量删除进行去重:

  1.  
    from scrapy_redis.dupefilter import RFPDupeFilter
  2.  
    import json
  3.  
     
  4.  
    ## 类名自定义,继承了scrapy_redis的RFPDupeFilter过滤器
  5.  
    class PostJsonRandomDupefilter(RFPDupeFilter):
  6.  
    def request_seen(self, request):
  7.  
    # 此时body是b'{"id": 1234, "r": 0.045612, "name": "abc", "sex": "male"}'
  8.  
    body = request.body
  9.  
    # 因为是采用的json.loads,所以body最好是json.dumps得到的。
  10.  
    # 此时body_data是{"id": 1234, "r": 0.045612, "name": "abc", "sex": "male"}
  11.  
    body_data = json.loads(body)
  12.  
    # body_data删除"r"参数,此时body_data是{"id": 1234, "name": "abc", "sex": "male"}
  13.  
    body_data.pop("r", None)
  14.  
    # 然后用新body_data字符串来替换之前的body,就出现了复制的请求request_copy。
  15.  
    request_copy = request.replace(body=json.dumps(body_data))
  16.  
    # 再对复制的请求request_copy进行指纹输出用于去重
  17.  
    fp = self.request_fingerprint(request_copy)
  18.  
    # 指纹结果用于add进去重集合,如果可以add进,则集合无该请求,added=1, 否则added=0。added的值表示add进集合的个数。
  19.  
    added = self.server.sadd(self.key, fp)
  20.  
    # 如果added=1,表示无重复,返回False,反之有重复,返回True
  21.  
    return added == 0
学新通

此处是将body先json化然后再删除可变键值对,当然也可以直接用正则表达式替换成空字符串也可以。(字符串替换要注意body是字节类型

3、POST方法的form参数的可变量

比如下面两个form参数(Content-Type: application/x-www-form-urlencoded)对应的都是同一html资源,POST的url是“https://www.百度.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是字节类型

  1.  
    from scrapy_redis.dupefilter import RFPDupeFilter
  2.  
     
  3.  
    ## 类名自定义,继承了scrapy_redis的RFPDupeFilter过滤器
  4.  
    class PostFormRandomDupefilter(RFPDupeFilter):
  5.  
    def request_seen(self, request):
  6.  
    # 此时body的值是b'id=1234&r=0.16512&name=abc&sex=male'
  7.  
    body = request.body
  8.  
    # 此处是将body中的随机参数替换成了空字符串,注意哦,此处不止替换了随机数,还有键名等。
  9.  
    # 此时body的值是b'id=1234&name=abc&sex=male'
  10.  
    body = re.sub(b'\&*r=[0-9\.] ', b"", body)
  11.  
    # 然后用新body来替换之前的body,就出现了复制的请求request_copy。
  12.  
    request_copy = request.replace(body=body)
  13.  
    # 再对复制的请求request_copy进行指纹输出用于去重
  14.  
    fp = self.request_fingerprint(request_copy)
  15.  
    # 指纹结果用于add进去重集合,如果可以add进,则集合无该请求,added=1, 否则added=0。added的值表示add进集合的个数。
  16.  
    added = self.server.sadd(self.key, fp)
  17.  
    # 如果added=1,表示无重复,返回False,反之有重复,返回True
  18.  
    return added == 0
学新通

最后我们只需要在settings.py文件中对过滤器进行指定,即根据情况选择输入以下代码:

  1.  
    DUPEFILTER_CLASS = "myproject.dupefilter.GetRandomDupefilter" # GET方法的url链接参数可变量的过滤器
  2.  
    DUPEFILTER_CLASS = "myproject.dupefilter.PostJsonRandomDupefilter" # POST方法的JSON参数的可变量的过滤器
  3.  
    DUPEFILTER_CLASS = "myproject.dupefilter.PostFormRandomDupefilter" # POST方法的form参数的可变量的过滤器
  4.  
    # 根据具体情况决定使用哪种过滤器

以上方法是将可变量替换成空字符串(或者删除可变量键值对)的操作,如果可变量太多的话,我们可以用re.findall()提取出不变的量用作新的url或者body来去重

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgfibac
系列文章
更多 icon
同类精品
更多 icon
继续加载