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': '',
'wm_latitude': '',
'wm_longitude': '',
'wm_actual_latitude': '',
'wm_actual_longitude': ''
}
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源码如下:
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源码如下:
def start_requests(self):
return self.next_requests()
呵,真简洁,直接把所有任务丢给next_requests方法,继续:
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方法一探究竟:
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方法就好了:
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中。另外新建一个模块实现这一部分功能:
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': '',
'wm_latitude': '',
'wm_longitude': '',
'wm_actual_latitude': '',
'wm_actual_longitude': ''
}
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 总结
没有什么是撸一遍源码解决不了的,如果有,就再撸一遍!
scrapy-redis源码解读之发送POST请求的更多相关文章
- redis源码解读--内存分配zmalloc
目录 主要函数 void *zmalloc(size_t size) void *zcalloc(size_t size) void zrealloc(void ptr, size_t size) v ...
- CesiumJS 2022^ 源码解读[7] - 3DTiles 的请求、加载处理流程解析
目录 1. 3DTiles 数据集的类型 2. 创建瓦片树 2.1. 请求入口文件 2.2. 创建树结构 2.3. 瓦片缓存机制带来的能力 3. 瓦片树的遍历更新 3.1. 三个大步骤 3.2. 遍历 ...
- (十)redis源码解读
一.redis工作机制 redis是 单线程,所有命令(set,get等)都会加入到队列中,然后一个个执行. 二.为什么redis速度快? 1.基于内存 2.redis协议resp 简单.可读.效率高 ...
- SDWebImage源码解读之SDWebImageDownloaderOperation
第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...
- AFNetworking 3.0 源码解读 总结(干货)(下)
承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...
- AFNetworking 3.0 源码解读 总结(干货)(上)
养成记笔记的习惯,对于一个软件工程师来说,我觉得很重要.记得在知乎上看到过一个问题,说是人类最大的缺点是什么?我个人觉得记忆算是一个缺点.它就像时间一样,会自己消散. 前言 终于写完了 AFNetwo ...
- AFNetworking 3.0 源码解读(八)之 AFImageDownloader
AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义.在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋 ...
- AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager
做ios开发,AFNetworking 这个网络框架肯定都非常熟悉,也许我们平时只使用了它的部分功能,而且我们对它的实现原理并不是很清楚,就好像总是有一团迷雾在眼前一样. 接下来我们就非常详细的来读一 ...
- AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization
这篇就讲到了跟请求相关的类了 关于AFNetworking 3.0 源码解读 的文章篇幅都会很长,因为不仅仅要把代码进行详细的的解释,还会大概讲解和代码相关的知识点. 上半篇: URI编码的知识 关于 ...
随机推荐
- Provider org.apache.xerces.jaxp.DocumentBuilderFactoryImpl not found 问题排查
自定义的classLoader启动spring容器,遇到 Provider org.apache.xerces.jaxp.DocumentBuilderFactoryImpl not subtype ...
- HIVE- 大数据运维之hive管理
我现在在一家公司负责大数据平台(CDH平台)的运维管理,最常遇见的问题我总结出来,并且继续在下面更新.希望方便自己以后trouble shooting以及方便各位同行解决问题与学习. 关于做运维有几个 ...
- HIVE- 数据倾斜
数据倾斜就是由于数据分布不均匀,数据大量集中到一点上,造成数据热点.大多数情况下,分为一下三种情况: 1.map端执行比较快,reduce执行很慢,因为partition造成的数据倾斜. 2.某些re ...
- Unity3D之Mesh(六)绘制扇形、扇面、环形
前言: 绘制了圆,就想到绘制与之相关的几何图形,以便更灵活的掌握Mesh动态创建模型的机制与方法. 一.分析: 首先,结合绘制圆的过程绘制环形: 圆形是由segments个等腰三角形组成的(上一篇中, ...
- php中socket的使用
php中使用socket在服务器端主要使用这么几个函数: 1/$socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP)创建一个socket 2/sock ...
- 输入框input内容变化与onpropertychange事件的兼容
一.输入框常用的几个事件 onblur 元素失去焦点. onchange 域的内容被改变. onclick 当用户点击某个对象时调用的事件句柄. ondblclick 当用户双击某个对象时调用的事件句 ...
- Java集合操作类Collections的一些常用方法
public static void main(String[] args) { List<Integer> list = new ArrayList<Integer>(); ...
- BZOJ5461: [PKUWC2018]Minimax
BZOJ5461: [PKUWC2018]Minimax https://lydsy.com/JudgeOnline/problem.php?id=5461 分析: 写出\(dp\)式子:$ f[x] ...
- nodejs 静态文件服务器
https://cnodejs.org/topic/4f16442ccae1f4aa27001071 http://blog.csdn.net/zhangxin09/article/details/8 ...
- Java中String和byte[]间的 转换
数据库的字段中使用了blob类型时,在entity中此字段可以对应为byte[] 类型,保存到数据库中时需要把传入的参数转为byte[]类型,读取的时候再通过将byte[]类型转换为String类型. ...