前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >让我大吃一堑的前后分离 web 站模拟登录

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

作者头像
崔庆才
发布2018-12-26 15:12:51
1.2K0
发布2018-12-26 15:12:51
举报
文章被收录于专栏:进击的Coder进击的Coder

阅读本文大概需要 11 分钟。

一、背景

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

二、登录操作

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

输入图片说明

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

需要打码的纯洁登录框

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

需要打码的纯洁登录请求详情

请求参数如下图所示:

需要打码的纯洁登录请求参数

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

代码语言:javascript
复制
    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 的地址和所需参数请求头等信息用代码进行构造,模拟真实的网络请求发送场景。下图为提交表单的请求详情信息:

纯洁的网络请求详情

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

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

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

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

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

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

输入图片说明

它就是用于用户权限验证的,authorization 的值分为两部分 type 和 credentials 。前者是验证采用的类型,后者是具体的参数值。这里的类型可以看到用的是 Bearer 类型。我又去观察登录时候的返回值,发现登录成功后的返回值除了 succeed 之外,还有其他的一些返回值,里面包括了一个叫 access_token 的字段,看样子它是 JWT 登录方式用来鉴权的 token 信息,经过比对确认 authorization 用的也正好就是这个 token 作为值。

那么代码就应该在第一次登录时候,取出access_token的值,并传递下去,用于后面请求的鉴权,所以代码改为:

代码语言:javascript
复制
    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 中以对数据进行请求:

代码语言:javascript
复制
header = {
            "authorization": "Bearer " + access_token
        }

这样就解决了用户权限的问题,不再出现401

四、postman发送请求特殊格式数据(json)

在 parse 方法中根据浏览器观察到的参数进行构造:

代码语言:javascript
复制
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 必须是数组。

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

依然纯洁到要打码

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

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

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

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

这到底又是怎么一回事?

多方探查都没有结果。

真是伤心

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

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

输入图片说明

后来尝试对它进行更改:

输入图片说明

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

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

输入图片说明

输入图片说明

输入图片说明

我是对 Auth、Headers 和 Raw 进行设置(请跟我一起懵逼),才终于成功发送 post,返回正确的信息!!!

五、Scrapy 发送 Json 格式数据

在 postman 测试通过后,说明这样的做法是可行的,但是代码上怎么编写呢?

用之前的 scrapy.Formdata 是不行的,它的 formdat= 默认使用 dict 格式,如果强行转成 json 格式也是会报错的。

经过群里咨询和搜索,发现要用 scrapy.http 的 Requst 方法(平时经常用的这个):

代码语言:javascript
复制
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 是网络请求详情中看到的参数格式并非是你认为的参数格式,它有可能是经过编码的字符串;

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-12-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 进击的Coder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景
  • 二、登录操作
  • 三、用户权限验证
  • 四、postman发送请求特殊格式数据(json)
  • 五、Scrapy 发送 Json 格式数据
    • 为什么成功了?
    相关产品与服务
    数据库
    云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档