Scrapy框架-模拟登录之JWT验证及post特殊格式数据(json)

一、背景 之前有记录过普通的scrapy模拟登录,这种方法可以满足了日常爬虫的登录需求。
但是技术一直在进步,近几年前后端分离的趋势越来越好,很多web都采用前后端分离的技术。那么登录后的用户权限验证就会出现jwt的形式。(主要是token方式的验证,在模拟登录中要解决的问题)
这里记录一下。
二、登录操作 前后端分离的项目,一般都是react、vue等js语言编写的(没有这方面经验的同志,可以不用往下看了)
有些会采用成型的前端框架,如AntDesign,ElementUI等,它们写出来的web页面,如果用css定位或者xpath定位,是很不准确的。所以最好的办法就是观察数据流,找到api和发送的参数进行构造。
Scrapy框架-模拟登录之JWT验证及post特殊格式数据(json)
文章图片
输入图片说明 以这里的登录为例,通过css定位其实也可以,但是有不稳定的风险。所以还是看api和参数比较稳妥,毕竟css怎么变,api都不会随意改变。
Scrapy框架-模拟登录之JWT验证及post特殊格式数据(json)
文章图片
输入图片说明 选中post那条数据流,看到右侧的请求地址、请求头和参数
Scrapy框架-模拟登录之JWT验证及post特殊格式数据(json)
文章图片
输入图片说明 [图片上传失败...(image-9401fa-1531469273677)]
这样就可以根据请求地址、请求头和参数来构造登录用的代码:

def start_requests(self): """ 重载start_requests方法 通过is_login方法判断是否成功登录 """ login_url = "http://xxx.yyy.ccc.aa/api/v1/oauth/login" login_data = https://www.it610.com/article/{"username": "abcd@easub.com", "password": "faabbccddeeffggd5", "type": "email" }return [scrapy.FormRequest(url=login_url, formdata=https://www.it610.com/article/login_data, callback=self.is_login)]def is_login(self, response):""" 根据返回值中的message值来判断是否登录成功 如果登录成功则对数据传输页发起请求,并将结果回传给parse方法 如果登录失败则提示 由于后面的用户权限验证需要用到token信息,所以这里取到登录后返回的token并传递给下一个方法 """ results = json.loads(response.text) if results['message'] == "succeed": urls = 'http://xxx.yyy.ccc.aa' access_token = results['data']['access_token'] print("登录成功,开始调用方法") yield Request(url=urls, callback=self.parse, meta={"access_token": access_token}) else: print("登录失败,请重新检查")

如果返回信息的json里面message值为succeed及认为登录成功,并调用parse方法。
三、用户权限验证 登录完毕后,我想执行其他的操作,比如上传(post)数据,跟刚才一样,需要观察api的地址和所需参数请求头信息等。
Scrapy框架-模拟登录之JWT验证及post特殊格式数据(json)
文章图片
输入图片说明 Scrapy框架-模拟登录之JWT验证及post特殊格式数据(json)
文章图片
输入图片说明 同样是根据返回的参数和请求头,来构造代码
然而这次却不行,返回的状态码是401,由于scrapy默认只管200和300的状态码,4开头和5开头的都不处理。但是又需要观察401状态返回的东西,可以在settings.py中空白处新增代码:
""" 状态码处理 """ HTTPERROR_ALLOWED_CODES = [400, 401]

然后在下一个方法中观察response回来的数据。
======================================
后来又查询了401的意思,就是未获得授权,也就是用户权限验证不通过,经过多方资料查找,发现请求头中有这么一条:
[图片上传失败...(image-1322be-1531469273677)]
它就是用于用户权限验证的,authorization的值分为两部分,前者是验证采用的类型,后者是具体的参数值。这里的类型可以看到用的是Bearer类型,(传说值默认是用户名+密码的base64字符串,但这个这么长,显然不是64)。
我又去观察登录时候的返回值,发现登录成功后的返回值除了succeed之外,还有其他的一些返回值,里面包括了一个叫access_token的字段,它是用于JWT登录方式用来鉴权的token信息,而且authorization用的也正好就是这个token作为值。
那么代码就应该在第一次登录时候,取出access_token的值,并传递下去,用于后面请求的鉴权:
def is_login(self, response): """ 根据返回值中的message值来判断是否登录成功 如果登录成功则对数据传输页发起请求,并将结果回传给parse方法 如果登录失败则提示 由于后面的用户权限验证需要用到token信息,所以这里取到登录后返回的token并传递给下一个方法 """ results = json.loads(response.text) if results['message'] == "succeed": urls = 'http://xxx.yyy.ccc.aa' access_token = results['data']['access_token'] print("登录成功,开始调用方法") yield Request(url=urls, callback=self.parse, meta={"access_token": access_token}) else: print("登录失败,请重新检查")

下面的pase方法中,将authorization设定到header中以对数据进行请求:
header = { "authorization": "Bearer " + access_token }

这样就解决了用户权限的问题,不再出现401
四、postman发送请求特殊格式数据(json) 在parse方法中,根据浏览器观察到的参数,进行构造:
datas = { "url": "https://www.youtube.com/watch?v=eWeACm7v01Y", "title": "看上去可爱其实很笨的狗#动物萌宠#", "share_text": "看上去可爱其实很笨的狗#动物萌宠#[doge]", "categories": {'0': '00e2e120-37fd-47a8-a96b-c6fec7eb563d'} }

由于categories里面是个数组,所以在构造的时候也可以直接写数据,然后用scrapy.Formdata来进行post。发现返回的状态是这次是400,并且提示:categories必须是数组
再次观察请求头信息,发现请求头信息中还有:
[图片上传失败...(image-e2574e-1531469273677)]
叫做content-type的参数,我将它加入到header中:
header = { "authorization": "Bearer " + access_token, "content-type": "application/json", }

这样关于categories的提示就没有了。但是返回的状态码依然是400,而且提示变成了url不能为空,这到底又是怎么一回事?
多方探查都没有结果。
真是伤心
后来我又想起了,既然这里的文本类型是application/json,那么提交出去的文本应该是json类型数据,而不是python的dict字典类型数据。
于是打开json在线解析,对传递的参数进行观察,发现这样的数据并不满足json格式:
Scrapy框架-模拟登录之JWT验证及post特殊格式数据(json)
文章图片
输入图片说明 后来尝试对它进行更改:
Scrapy框架-模拟登录之JWT验证及post特殊格式数据(json)
文章图片
输入图片说明 在外层增加了一对{},然后又将categories的值加上了双引号,才是正确的json格式。
但是如果这样,拿到postman中进行测试,是不行的,后来经过反复测试,最终确定了postman的请求格式为:
Scrapy框架-模拟登录之JWT验证及post特殊格式数据(json)
文章图片
输入图片说明 Scrapy框架-模拟登录之JWT验证及post特殊格式数据(json)
文章图片
输入图片说明 Scrapy框架-模拟登录之JWT验证及post特殊格式数据(json)
文章图片
输入图片说明 对Auth、Headers和Raw进行设置,才终于成功发送post,返回正确的信息!!!
五、scrapy发送Json格式数据 在postman测试通过后,说明这样的做法是可行的,但是代码上怎么编写呢?
用之前的scrapy.Formdata是不行的,它的formdat=默认使用dict格式,如果强行转成json格式也是会报错的。经过群里咨询和搜索,发现要用scrapy.http的Requst方法(平时常用的这个):
access_token = response.meta['access_token'] urls = "http://aaa.bbb.xxx.yy/api/v1/material/extract" datas = { "url": "https://www.youtube.com/watch?v=eWeACm7v01Y", "title": "看上去可爱其实很笨的狗#动物萌宠#", "share_text": "看上去可爱其实很笨的狗#动物萌宠#[doge]", "categories": {'0': '00e2e120-37fd-47a8-a96b-c6fec7eb563d'} } header = { "authorization": "Bearer " + access_token, "content-type": "application/json", } yield Request(url=urls, method='POST', body=json.dumps(datas), headers=header, callback=self.parse_details)

【Scrapy框架-模拟登录之JWT验证及post特殊格式数据(json)】这样才发送请求,终于成功了!!!

    推荐阅读