让我大吃一堑的前后分离 web 站模拟登录

栏目: Python · 发布时间: 5年前

内容简介:scrapy 模拟登录相信大家都会,而且非常的熟练。但是技术一直在进步(尤其是前端领域),近几年前后端分离的趋势越来越明显,很多 web 站都采用前后端分离的技术。以前保存用户身份信息靠 Cookie,那前后分离这种技术组合靠什么校验用户身份呢?前后端分离的项目,一般都是 react、vue 等 js 库编写的,进而涌现出了一批优秀的前端框架或组件,如阿里巴巴前端团队的 AntDesign,饿了么前端团队的 ElementUI 等。由于前后端分离的原因,后端必定有 API,所以最好的爬取策略不是在页面使用

scrapy 模拟登录相信大家都会,而且非常的熟练。但是技术一直在进步(尤其是前端领域),近几年前后端分离的趋势越来越明显,很多 web 站都采用前后端分离的技术。以前保存用户身份信息靠 Cookie,那前后分离这种技术组合靠什么校验用户身份呢?

二、登录操作

前后端分离的项目,一般都是 react、vue 等 js 库编写的,进而涌现出了一批优秀的前端框架或组件,如阿里巴巴前端团队的 AntDesign,饿了么前端团队的 ElementUI 等。由于前后端分离的原因,后端必定有 API,所以最好的爬取策略不是在页面使用 CSS 定位或者 Xpath 定位,而是观察网络请求记录,找到 api 以及请求时发送的参数并用 Python 进行构造、模拟请求。

让我大吃一堑的前后分离 web 站模拟登录
在这里输入图片标题

以这里的登录为例,通过css定位其实也可以,但是有不稳定的风险。所以还是看api和参数比较稳妥,前端变化的几率比后端高出太多。在页面中打开调试工具,然后定位到『网络』选项卡,接着打开登录页并输入用户名密码并登录。

让我大吃一堑的前后分离 web 站模拟登录
在这里输入图片标题

在请求记录中找到并选中方法为 post 的那条记录就可以查看此请求的详细信息,比如请求地址、请求头和参数。请求详情如下图所示:

让我大吃一堑的前后分离 web 站模拟登录
在这里输入图片标题

请求参数如下图所示:

让我大吃一堑的前后分离 web 站模拟登录
在这里输入图片标题

可以看到请求参数中有用户名、密码以及用户名类型(比如手机号或邮箱)。得到完整的请求信息后就可以根据请求地址、请求头和参数来构造登录用的代码,Scrapy 常用登录代码如下:

def start_requests(self):
        """ 重载start_requests方法 通过is_login方法判断是否成功登录 """
        login_url = "http://xxx.yyy.ccc.aa/api/v1/oauth/login"
        login_data = {
            "username": "abcd@easub.com",
            "password": "faabbccddeeffggd5",
            "type": "email"
        }

        return [scrapy.FormRequest(url=login_url, formdata=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 的地址和所需参数请求头等信息用代码进行构造,模拟真实的网络请求发送场景。下图为提交表单的请求详情信息:

让我大吃一堑的前后分离 web 站模拟登录
请求详情
让我大吃一堑的前后分离 web 站模拟登录
请求参数

跟上面类似,根据返回的参数和请求头构造代码,结果会如何?

结果返回的状态码是 401,由于 scrapy 默认只处理 2xx 和 3xx 状态的请求、4开头和5开头的都不处理,但是我们又需要观察401状态返回的内容,这怎么办呢?

我们可以在settings.py中空白处新增代码:

""" 状态码处理 """
HTTPERROR_ALLOWED_CODES = [400, 401]
复制代码

然后在下一个方法中观察response回来的数据(这个地方当时作为萌新的我是懵逼的,所以委屈各位读者大佬跟我一起懵逼)。

后来查询了401的意思:未获得授权,也就是用户权限验证不通过。经过多方资料查找,发现请求头中有这么一条:

让我大吃一堑的前后分离 web 站模拟登录
在这里输入图片标题

它就是用于用户权限验证的,authorization 的值分为两部分:type 和 credentials,前者是验证采用的类型,后者是具体的参数值。这里的类型可以看到用的是 Bearer 类型。

我又去观察登录时候的返回值,发现登录成功后的返回值除了 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 必须是数组。

再次观察请求头信息,发现请求头信息中还有:

让我大吃一堑的前后分离 web 站模拟登录
在这里输入图片标题

我将这个叫做 content-type 的字段和参数加入到 header 中:

header = {
            "authorization": "Bearer " + access_token,
            "content-type": "application/json",
        }
复制代码

这样关于 categories 必须是数组的提示就没有了。

但是返回的状态码依然是400,而且提示变成了 "url不能为空"。

这到底又是怎么一回事?

多方探查都没有结果。

真是伤心

后来我又想起了,既然这里的文本类型 是application/json,那么提交出去的文本应该是 json 类型数据,而不是 python 的 dict 字典。

于是打开 json 在线解析,对传递的参数进行观察,发现这样的数据并不满足json格式:

让我大吃一堑的前后分离 web 站模拟登录
在这里输入图片标题

后来尝试对它进行更改:

让我大吃一堑的前后分离 web 站模拟登录
在这里输入图片标题

在外层增加了一对{},然后又将 categories 的值加上了双引号,才是正确的 json 格式(我是真的又菜又蠢)。

将这样的数据拿到 postman 中进行测试,发现是不行的。又经过我不断的测试,最终确定了 postman 的请求格式为:

让我大吃一堑的前后分离 web 站模拟登录
在这里输入图片标题
让我大吃一堑的前后分离 web 站模拟登录
在这里输入图片标题
让我大吃一堑的前后分离 web 站模拟登录
在这里输入图片标题

我是对 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)
复制代码

这样发送请求,终于成功了!!!

为什么成功了?

首先看一看 json.dumps 函数的用途是什么: json.dumps() 用于将 dict 类型的数据转成 str。

虽然没有摸清楚消息发送失败的根本原因(有可能是目标网站后端对数据格式进行校验,也有可能是 Scrapy 框架会在发送请求前对参数进行处理所以导致的问题),但是已经可以猜出个大概。同时也在本次爬虫任务中学习到了一些知识。

从本文中我们学会了三个知识:

第 1 是萌新要多问、多测试,没有解决不了的计算机问题;

第 2 是爬取使用前后端分离技术的 Web 站时应该优先选择从 API 下手;

第 3 是网络请求详情中看到的参数格式并非是你认为的参数格式,它有可能是经过编码的字符串;


以上所述就是小编给大家介绍的《让我大吃一堑的前后分离 web 站模拟登录》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Agile Web Development with Rails, Third Edition

Agile Web Development with Rails, Third Edition

Sam Ruby、Dave Thomas、David Heinemeier Hansson / Pragmatic Bookshelf / 2009-03-17 / USD 43.95

Rails just keeps on changing. Rails 2, released in 2008, brings hundreds of improvements, including new support for RESTful applications, new generator options, and so on. And, as importantly, we’ve a......一起来看看 《Agile Web Development with Rails, Third Edition》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具