常常写爬虫的都知道,有些页面在登陆以前是被禁止抓取的,好比知乎的话题页面就要求用户登陆才能访问,而 “登陆” 离不开 HTTP 中的 Cookie 技术。python
Cookie 的原理很是简单,由于 HTTP 是一种无状态的协议,所以为了在无状态的 HTTP 协议之上维护会话(session)状态,让服务器知道当前是和哪一个客户在打交道,Cookie 技术出现了 ,Cookie 至关因而服务端分配给客户端的一个标识。 git
因为知乎进行了改版,网上不少其余的模拟登陆的方式已经不行了,因此这里从原理开始一步步分析要如何进行模拟登陆。github
要把咱们的爬虫假装成浏览器登陆,则首先要理解浏览器登陆时,是怎么发送报文的。首先打开知乎登陆页,打开谷歌浏览器开发者工具,选择Network页,勾选Presev log,点击登录。 咱们很容易看到登陆的请求首等信息:web
模拟登陆最终是要构建请求首和提交参数,即构造 Request Headers和FormData。正则表达式
Request Headers中有几个参数须要注意:算法
接下来咱们看看登陆时咱们向服务器请求了什么,由于这是开门的钥匙,咱们必须先知道钥匙由哪些部分组成,才能成功的打开大门: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 | 这是验证码模块,有时会出现 |
后面还有一些参数是固定参数,这里就不一一列出来了。如今总结咱们须要本身生成的一些参数:跨域
利用全局搜索能够发现该参数的值存在cookie中,所以能够利用正则表达式直接从cookie中提取;浏览器
该参数为时间戳,可使用 timestamp = str(int(time.time()*1000))生成
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 ''
最后实现一个检查登陆状态的方法,若是访问登陆页面出现跳转,说明已经登陆成功,这时将 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
理解了咱们须要哪些信息,以及信息的提交方式,如今来整理完整的登陆过程:
x-xsrftoken
,更新到headers中;signature
参数,模拟js中的hmac过程;signature
这三个参数更新到Request Payload
中,即程序中的login_data表单;headers
和data
这两个表单信息POST到Login_API这个接口,能够查看咱们登陆时的信息,是把提交的信息发送到https://www.zhihu.com/api/v3/... ;error
,则输出错误的结果;不然表示登陆成功,保存cookie文件。参考代码: