当前位置:脚本大全 > > 正文

redis的scan命令的使用(scrapy-redis源码分析之发送POST请求详解)

时间:2021-09-29 03:02:24类别:脚本大全

redis的scan命令的使用

scrapy-redis源码分析之发送POST请求详解

1 引言

这段时间在研究美团爬虫,用的是scrapy-redis分布式爬虫框架,奈何scrapy-redis与scrapy框架不同,默认只发送get请求,换句话说,不能直接发送post请求,而美团的数据请求方式是post,网上找了一圈,发现关于scrapy-redis发送post的资料寥寥无几,只能自己刚源码了。

2 美团post需求说明

先来说一说需求,也就是说美团post请求形式。我们以获取某个地理坐标下,所有店铺类别列表请求为例。获取所有店铺类别列表时,我们需要构造一个包含位置坐标经纬度等信息的表单数据,以及为了向下一层parse方法传递的一些必要数据,即meta,然后发起一个post请求。

url:

请求地址,即url是固定的,如下所示:

  • ?
  • 1
  • url = 'http://i.waimai.meituan.com/openh5/poi/filterconditions?_=1557367197922'
  • url最后面的13位数字是时间戳,实际应用时用time模块生成一下就好了。

    表单数据:

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • form_data = {
  •  'initiallat': '25.618626',
  •  'initiallng': '105.644569',
  •  'actuallat': '25.618626',
  •  'actuallng': '105.644569',
  •  'geotype': '2',
  •  'wm_latitude': '25618626',
  •  'wm_longitude': '105644569',
  •  'wm_actual_latitude': '25618626',
  •  'wm_actual_longitude': '105644569'
  • }
  • meta数据:

    meta数据不是必须的,但是,如果你在发送请求时,有一些数据需要向下一层parse方法(解析爬虫返回的response的方法)中传递的话,就可以构造这一数据,然后作为参数传递进request中。

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • meta = {
  •  'lat': form_data.get('initiallat'),
  •  'lng': form_data.get('initiallng'),
  •  'lat2': form_data.get('wm_latitude'),
  •  'lng2': form_data.get('wm_longitude'),
  •  'province': '**省',
  •  'city': '**市',
  •  'area': '**区'
  • }
  • 3 源码分析

    采集店铺类别列表时需要发送怎样一个post请求在上面已经说明了,那么,在scrapy-redis框架中,这个post该如何来发送呢?我相信,打开我这篇博文的读者都是用过scrapy的,用scrapy发送post肯定没问题(重写start_requests方法即可),但scrapy-redis不同,scrapy-redis框架只会从配置好的redis数据库中读取起始url,所以,在scrapy-redis中,就算重写start_requests方法也没用。怎么办呢?我们看看源码。

    我们知道,scrapy-redis与scrapy的一个很大区别就是,scrapy-redis不再继承spider类,而是继承redisspider类的,所以,redisspider类源码将是我们分析的重点,我们打开redisspider类,看看有没有类似于scrapy框架中的start_requests、make_requests_from_url这样的方法。redisspider源码如下:

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • class redisspider(redismixin, spider):
  •  @classmethod
  •  def from_crawler(self, crawler, *args, **kwargs):
  •  obj = super(redisspider, self).from_crawler(crawler, *args, **kwargs)
  •  obj.setup_redis(crawler)
  •  return obj
  • 很遗憾,在redisspider类中没有找到类似start_requests、make_requests_from_url这样的方法,而且,redisspider的源码也太少了吧,不过,从第一行我们可以发现redisspider继承了redisminxin这个类,所以我猜redisspider的很多功能是从父类继承而来的(拼爹的redisspider)。继续查看redisminxin类源码。redisminxin类源码太多,这里就不将所有源码贴出来了,不过,惊喜的是,在redisminxin中,真找到了类似于start_requests、make_requests_from_url这样的方法,如:start_requests、next_requests、make_request_from_data等。有过scrapy使用经验的童鞋应该都知道,start_requests方法可以说是构造一切请求的起源,没分析scrapy-redis源码之前,谁也不知道scrapy-redis是不是和scrapy一样(后面打断点的方式验证过,确实一样,话说这个验证有点多余,因为源码注释就是这么说的),不过,还是从start_requests开始分析吧。start_requests源码如下:

  • ?
  • 1
  • 2
  • def start_requests(self):
  •  return self.next_requests()
  • 呵,真简洁,直接把所有任务丢给next_requests方法,继续:

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • def next_requests(self):
  •  """returns a request to be scheduled or none."""
  •  use_set = self.settings.getbool('redis_start_urls_as_set',    defaults.start_urls_as_set)
  •  fetch_one = self.server.spop if use_set else self.server.lpop
  •  # xxx: do we need to use a timeout here?
  •  found = 0
  •  # todo: use redis pipeline execution.
  •  while found < self.redis_batch_size: # 每次读取的量
  •   data = fetch_one(self.redis_key) # 从redis中读取一条记录
  •   if not data:
  •    # queue empty.
  •    break
  •   req = self.make_request_from_data(data) # 根据从redis中读取的记录,实例化一个request
  •   if req:
  •    yield req
  •   found += 1
  •   else:
  •    self.logger.debug("request not made from data: %r", data)
  •  
  •  if found:
  •   self.logger.debug("read %s requests from '%s'", found, self.redis_key)
  • 上面next_requests方法中,关键的就是那个while循环,每一次循环都调用了一个make_request_from_data方法,从函数名可以函数,这个方法就是根据从redis中读取从来的数据,实例化一个request,那不就是我们要找的方法吗?进入make_request_from_data方法一探究竟:

  • ?
  • 1
  • 2
  • 3
  • def make_request_from_data(self, data):
  •  url = bytes_to_str(data, self.redis_encoding)
  •  return self.make_requests_from_url(url) # 这是重点,圈起来,要考
  • 因为scrapy-redis默认值发送get请求,所以,在这个make_request_from_data方法中认为data只包含一个url,但如果我们要发送post请求,这个data包含的东西可就多了,我们上面美团post请求说明中就说到,至少要包含url、form_data。所以,如果我们要发送post请求,这里必须改,make_request_from_data方法最后调用的make_requests_from_url是scrapy中的spider中的方法,不过,我们也不需要继续往下看下去了,我想诸位都也清楚了,要发送post请求,重写这个make_request_from_data方法,根据传入的data,实例化一个request返回就好了。

     4 代码实例

    明白上面这些东西后,就可以开始写代码了。修改源码吗?不,不存在的,改源码可不是好习惯。我们直接在我们自己的spider类中重写make_request_from_data方法就好了:

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • from scrapy import formrequest
  • from scrapy_redis.spiders import redisspider
  •  
  •  
  • class meituanspider(redisspider):
  •  """
  •  此处省略若干行
  •  """
  •  
  •  def make_request_from_data(self, data):
  •   """
  •   重写make_request_from_data方法,data是scrapy-redis读取redis中的[url,form_data,meta],然后发送post请求
  •   :param data: redis中都去的请求数据,是一个list
  •   :return: 一个formrequest对象
  •    """
  •   data = json.loads(data)
  •   url = data.get('url')
  •   form_data = data.get('form_data')
  •   meta = data.get('meta')
  •   return formrequest(url=url, formdata=form_data, meta=meta, callback=self.parse)
  •  
  •  def parse(self, response):
  •   pass
  • 搞清楚原理之后,就是这么简单。万事俱备,只欠东风——将url,form_data,meta存储到redis中。另外新建一个模块实现这一部分功能:

  • ?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • def push_start_url_data(request_data):
  •  """
  •  将一个完整的request_data推送到redis的start_url列表中
  •  :param request_data: {'url':url, 'form_data':form_data, 'meta':meta}
  •  :return:
  •  """
  •  r.lpush('meituan:start_urls', request_data)
  •  
  •  
  • if __name__ == '__main__':
  •  url = 'http://i.waimai.meituan.com/openh5/poi/filterconditions?_=1557367197922'
  •  form_data = {
  •   'initiallat': '25.618626',
  •   'initiallng': '105.644569',
  •   'actuallat': '25.618626',
  •   'actuallng': '105.644569',
  •   'geotype': '2',
  •   'wm_latitude': '25618626',
  •   'wm_longitude': '105644569',
  •   'wm_actual_latitude': '25618626',
  •   'wm_actual_longitude': '105644569'
  •  }
  •  meta = {
  •   'lat': form_data.get('initiallat'),
  •   'lng': form_data.get('initiallng'),
  •   'lat2': form_data.get('wm_latitude'),
  •   'lng2': form_data.get('wm_longitude'),
  •   'province': '**省',
  •   'city': '*市',
  •   'area': '**区'
  •  }
  •  request_data = {
  •   'url': url,
  •   'form_data': form_data,
  •   'meta': meta
  •  }
  •  push_start_url_data(json.dumps(request_data))
  • 在启动scrapy-redis之前,运行一下这一模块即可。如果有很多poi(地理位置兴趣点),循环遍历每一个poi,生成request_data,push到redis中。这一循环功能就你自己写吧。

    5 总结

    没有什么是撸一遍源码解决不了的,如果有,就再撸一遍!

    好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对开心学习网的支持。

    原文链接:http://www.cnblogs.com/chenhuabin/p/10867285.html

    上一篇下一篇

    猜您喜欢

    热门推荐