模拟知乎登陆——Python3

常常写爬虫的都知道,有些页面在登陆以前是被禁止抓取的,好比知乎的话题页面就要求用户登陆才能访问,而 “登陆” 离不开 HTTP 中的 Cookie 技术。python

登陆原理

Cookie 的原理很是简单,由于 HTTP 是一种无状态的协议,所以为了在无状态的 HTTP 协议之上维护会话(session)状态,让服务器知道当前是和哪一个客户在打交道,Cookie 技术出现了 ,Cookie 至关因而服务端分配给客户端的一个标识。 git

cookie

  1. 浏览器第一次发起 HTTP 请求时,没有携带任何 Cookie 信息
  2. 服务器把 HTTP 响应,同时还有一个 Cookie 信息,一块儿返回给浏览器
  3. 浏览器第二次请求就把服务器返回的 Cookie 信息一块儿发送给服务器
  4. 服务器收到HTTP请求,发现请求头中有Cookie字段, 便知道以前就和这个用户打过交道了。

分析Post数据

因为知乎进行了改版,网上不少其余的模拟登陆的方式已经不行了,因此这里从原理开始一步步分析要如何进行模拟登陆。github

要把咱们的爬虫假装成浏览器登陆,则首先要理解浏览器登陆时,是怎么发送报文的。首先打开知乎登陆页,打开谷歌浏览器开发者工具,选择Network页,勾选Presev log,点击登录。 咱们很容易看到登陆的请求首等信息:web

模拟登陆最终是要构建请求首和提交参数,即构造 Request Headers和FormData。正则表达式


构建Headers

Request Headers中有几个参数须要注意:算法

  1. Content-Type (后面的boundary指定了表单提交的分割线)
  2. cookie (登录前cookie就不为空,说明以前确定有set-cookie的操做 )
  3. X-Xsrftoken (则是防止Xsrf跨域的Token认证,能够在Response Set-Cookie中找到 )

接下来咱们看看登陆时咱们向服务器请求了什么,由于这是开门的钥匙,咱们必须先知道钥匙由哪些部分组成,才能成功的打开大门:json

能够看到Request Payload中出现最多的是---Webxxx这一字符串,上面已经说过了,这是一个分割线,咱们能够直接忽略,因此第一个参数是:client_id=c3cef7c66a1843f8b3a9e6a1e3160e20 ;第二个参数为grant_type=password....整理了全部的参数以下(知乎的改版可能致使参数改变):api

参数 生成方式
client_id c3cef7c66a1843f8b3a9e6a1e3160e20 固定
grant_type password 固定
timestamp 1530173433263 时间戳
signature 283d218eac893259867422799d6009749b6aff3f Hash
username/password xxxxx/xxxxxx 固定
captcha Null 这是验证码模块,有时会出现

后面还有一些参数是固定参数,这里就不一一列出来了。如今总结咱们须要本身生成的一些参数:跨域

  1. X-Xsrftoken

    利用全局搜索能够发现该参数的值存在cookie中,所以能够利用正则表达式直接从cookie中提取;浏览器

  2. timestamp

    该参数为时间戳,可使用 timestamp = str(int(time.time()*1000))生成

  3. signature

    首先ctrl+shift+F全局搜索signature,发现其是在main.app.xxx.js的一个js文件中生成的,打开该.js文件,而后复制到编辑器格式化代码

    所以咱们能够用python来重写这个hmac加密过程:

    def _get_signature(timestamp):
            """
            经过 Hmac 算法计算返回签名
            实际是几个固定字符串加时间戳
            :param timestamp: 时间戳
            :return: 签名
            """
            ha = hmac.new(b'd1b964811afb40118a12068ff74a12f4', digestmod=hashlib.sha1)
            grant_type = self.login_data['grant_type']
            client_id = self.login_data['client_id']
            source = self.login_data['source']
            ha.update(bytes((grant_type + client_id + source + timestamp), 'utf-8'))
            return ha.hexdigest()

验证码

登陆提交的表单中有个captcha参数,这是登陆的验证码参数,有时候登陆时会出现须要验证码的状况。captcha 验证码,是经过 GET 请求单独的 API 接口返回是否须要验证码(不管是否须要,都要请求一次),若是是 True 则须要再次 PUT 请求获取图片的 base64 编码。

因此登陆验证的过程总共分为三步,首先GET请求看是否须要验证码;其次根据GET请求的结果,若是为True,则须要发送PUT请求来获取验证的图片;最后将验证的结果经过POST请求发送给服务器。

这是lang=cn的API须要提交的数据形式,实际上有两个 API,一个是识别倒立汉字,一个是常见的英文验证码,任选其一便可,汉字是经过 plt 点击坐标,而后转为 JSON 格式。

最后还有一点要注意,若是有验证码,须要将验证码的参数先 POST 到验证码 API,再随其余参数一块儿 POST 到登陆 API。该部分完整的代码以下:

def _get_captcha(lang, headers):
    if lang == 'cn':
        api = 'https://www.zhihu.com/api/v3/oauth/captcha?lang=cn'
    else:
        api = 'https://www.zhihu.com/api/v3/oauth/captcha?lang=en'
    resp = self.session.get(api, headers=headers)
    show_captcha = re.search(r'true', resp.text)

    if show_captcha:
        put_resp = self.session.put(api, headers=headers)
        json_data = json.loads(put_resp.text)
        img_base64 = json_data['img_base64'].replace(r'\n', '')
        with open('./captcha.jpg', 'wb') as f:
            f.write(base64.b64decode(img_base64))
        img = Image.open('./captcha.jpg')
        if lang == 'cn':
            plt.imshow(img)
            print('点击全部倒立的汉字,按回车提交')
            points = plt.ginput(7)
            capt = json.dumps({'img_size': [200, 44],
                                'input_points': [[i[0]/2, i[1]/2] for i in points]})
        else:
            img.show()
            capt = input('请输入图片里的验证码:')
        # 这里必须先把参数 POST 验证码接口
        self.session.post(api, data={'input_text': capt}, headers=headers)
        return capt
    return ''

保存Cookie

最后实现一个检查登陆状态的方法,若是访问登陆页面出现跳转,说明已经登陆成功,这时将 Cookies 保存起来(这里 session.cookies 初始化为 LWPCookieJar 对象,因此有 save 方法),这样下次登陆能够直接读取 Cookies 文件。

self.session.cookies = cookiejar.LWPCookieJar(filename='./cookies.txt')
def check_login(self):
    resp = self.session.get(self.login_url, allow_redirects=False)
    if resp.status_code == 302:
        self.session.cookies.save()
        return True
    return False

总结

理解了咱们须要哪些信息,以及信息的提交方式,如今来整理完整的登陆过程:

  1. 构建HEADERS请求头和FORM_DATA表单的基本信息,通常为固定不变的信息;
  2. 从cookies中获取x-xsrftoken,更新到headers中;
  3. 检查用户名和密码是否在data表单中,若是没有,则须要更新用户名和密码到表单中;
  4. 获取时间戳,并利用时间戳来计算signature参数,模拟js中的hmac过程;
  5. 检查验证码,若是须要验证码,则先将验证码的结果POST到验证API端口,手动输入验证码的结果;
  6. 将时间戳、验证码以及signature这三个参数更新到Request Payload中,即程序中的login_data表单;
  7. headersdata这两个表单信息POST到Login_API这个接口,能够查看咱们登陆时的信息,是把提交的信息发送到https://www.zhihu.com/api/v3/...
  8. 检查返回的response结果,若是有error,则输出错误的结果;不然表示登陆成功,保存cookie文件。

参考代码:

模拟知乎登陆

相关文章
相关标签/搜索