scrapy-redis源码分析之发送POST请求详解
1引言
这段时间在研究美团爬虫,用的是scrapy-redis分布式爬虫框架,奈何scrapy-redis与scrapy框架不同,默认只发送GET请求,换句话说,不能直接发送POST请求,而美团的数据请求方式是POST,网上找了一圈,发现关于scrapy-redis发送POST的资料寥寥无几,只能自己刚源码了。
2美团POST需求说明
先来说一说需求,也就是说美团POST请求形式。我们以获取某个地理坐标下,所有店铺类别列表请求为例。获取所有店铺类别列表时,我们需要构造一个包含位置坐标经纬度等信息的表单数据,以及为了向下一层parse方法传递的一些必要数据,即meta,然后发起一个POST请求。
url:
请求地址,即url是固定的,如下所示:
url='http://i.waimai.meituan.com/openh5/poi/filterconditions?_=1557367197922'
url最后面的13位数字是时间戳,实际应用时用time模块生成一下就好了。
表单数据:
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中。
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源码如下:
classRedisSpider(RedisMixin,Spider): @classmethod deffrom_crawler(self,crawler,*args,**kwargs): obj=super(RedisSpider,self).from_crawler(crawler,*args,**kwargs) obj.setup_redis(crawler) returnobj
很遗憾,在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源码如下:
defstart_requests(self): returnself.next_requests()
呵,真简洁,直接把所有任务丢给next_requests方法,继续:
defnext_requests(self):
"""Returnsarequesttobescheduledornone."""
use_set=self.settings.getbool('REDIS_START_URLS_AS_SET',defaults.START_URLS_AS_SET)
fetch_one=self.server.spopifuse_setelseself.server.lpop
#XXX:Doweneedtouseatimeouthere?
found=0
#TODO:Useredispipelineexecution.
whilefound
上面next_requests方法中,关键的就是那个while循环,每一次循环都调用了一个make_request_from_data方法,从函数名可以函数,这个方法就是根据从redis中读取从来的数据,实例化一个request,那不就是我们要找的方法吗?进入make_request_from_data方法一探究竟:
defmake_request_from_data(self,data):
url=bytes_to_str(data,self.redis_encoding)
returnself.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方法就好了:
fromscrapyimportFormRequest
fromscrapy_redis.spidersimportRedisSpider
classMeituanSpider(RedisSpider):
"""
此处省略若干行
"""
defmake_request_from_data(self,data):
"""
重写make_request_from_data方法,data是scrapy-redis读取redis中的[url,form_data,meta],然后发送post请求
:paramdata:redis中都去的请求数据,是一个list
:return:一个FormRequest对象
"""
data=json.loads(data)
url=data.get('url')
form_data=data.get('form_data')
meta=data.get('meta')
returnFormRequest(url=url,formdata=form_data,meta=meta,callback=self.parse)
defparse(self,response):
pass
搞清楚原理之后,就是这么简单。万事俱备,只欠东风——将url,form_data,meta存储到redis中。另外新建一个模块实现这一部分功能:
defpush_start_url_data(request_data):
"""
将一个完整的request_data推送到redis的start_url列表中
:paramrequest_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总结
没有什么是撸一遍源码解决不了的,如果有,就再撸一遍!
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。