一、请求进入登录页面获取cookies
用的是requests_html模块中的HTMLSession,实例化后的对象发送请求都会自动保存cookie发送。所以后续就不需要进行获取、保存、校验等操作
def get_first_cookie(self):
self.session.get(url=self.head_page_url)
二、校验验证码
在校验验证码之前肯定要发请求获取校验码保存,再输入校验码,最后再发请求校验验证码。。
因为在知乎登录中有校验码有三种情况
没有校验码
- 英文校验码:输入4位数字或字母组合,url链接中带有lang=en。这种校验码比较好处理,这里就采用这个api
正文校验码:点击下面倒着的中文,url链接中带有lang=en
有三个请求,api为同一个,就是请求方式和携带的参数不同
(1)get请求:判断是否有验证码,不携带参数,如果有校验码会响应{"show_captcha":true},否则为{"show_captcha":false}
(2)put请求:获取验证码,不携带参数,返回的响应是{"img_base64":"xxxxxx"},base64编码后的一串字符
(3)post请求:校验验证码,携带参数,校验成功返回的响应是{"success":true},否则为{"success":false}
def handle_captcha(self):
r = self.session.get(url=self.captcha_api) # 发送get判断是否有验证码
res = r.json()
if res.get('show_captcha'):
r = self.session.put(url=self.captcha_api) # 有验证码,发送请求获取验证码
img_base64 = r.json().get('img_base64')
# 将得到的验证码解码后存为capcha.png图片,这里就需要导入base64这个模块,用b64decode解码
with open('captcha.png', 'wb') as f:
f.write(base64.b64decode(img_base64))
# open打开图片,show显示出来。需要导入从PIL中导入Image模块
img = Image.open('captcha.png')
img.show()
# 输入验证码,携带验证码发生post请求,参数是从浏览器中查看的
self.captcha = input('请输入校验码:')
r = self.session.post(url=self.captcha_api,data={"input_text":self.captcha})
res = r.json()
if res.get('success'):
print('验证码正确')
else:
self.handle_captcha()
三、登录校验
从浏览器的抓包工具中看到,前端对携带的参数进行了加密处理,所以我们需要知道前端代码中是在哪里进行了加密处理,参数都有哪些?
- 搜索 encrypt(加密) 找到有该字的js文件(static.zhihu.com/heifetz/main.app.8b5cc380b705bb3ed141.js文件),找到这行加密函数
var b = function(e) {
return __g._encrypt(encodeURIComponent(e)) // encodeURIComponent() 函数可把字符串作为 URI 组件进行编码。
};
- 是不是不知道e是什么鬼,在这行代码上打上断点运行,然后在Console输入e回车就知道e是什么了
client_id=c3cef7c66a1843f8b3a9e6a1e3160e20&grant_type=password×tamp=1571675789070&source=com.zhihu.web&signature=652fa1c9bd1831abce73a5ee87d2dd9f748ce308&username=%2B86182000000001&password=xxxx&captcha=%7B%22img_size%22%3A%5B200%2C44%5D%2C%22input_points%22%3A%5B%5B93.33331298828125%2C21.052078247070312%5D%2C%5B158.33331298828125%2C21.052078247070312%5D%5D%7D&lang=cn&utm_source=&ref_source=other_https%3A%2F%2Fwww.zhihu.com%2Fsignin%3Fnext%3D%252F"
一长串的字符,仔细观察下,他们其实就是携带的参数,其中signature是加密的字符串,现在找到signature的加密函数
同样搜索signature,找到signature的加密函数,现在就是要把js代码转成python。python中有个模块execjs可以在python中运行js代码。那么先打加密相关的函数拷贝到js文件中,然后在代码中用execjs来操作这个文件
携带加密后的数据,发送登录请求
def login_in(self):
# 请求携带的参数
formdata = {
"client_id": "xxxxxxxx", # 这是客户端id,打印e直接粘贴复制过来
"grant_type": "password",
"timestamp": str(int(time.time() * 1000)),
"source": "com.zhihu.web",
"signature": self.signature, # 看js的加密函数,其实就是用了hmac加盐加密,详情参见完整代码中的get_signature(self)方法
"username": "+86182000000", # 用户名,请输入自己的
"password": "xxxxx", # 密码
"captcha": self.captcha,
"lang": "en",
"utm_source": '',
"ref_source": "other_https://Fwww.zhihu.com%2Fsignin%3Fnext%3D%252F"
}
with open('知乎加密.js','rt',encoding='utf-8') as f:
# 读取js代码
js = f.read()
# 编译。cwd是依赖的环境。js执行代码通常是在node中,所以需要npm install jsdom安装依赖
execjs_obj = execjs.compile(js,cwd='node_modules')
# execjs_obj.call(函数名,参数)
res = execjs_obj.call('b',urlencode(formdata))
# 请求头,必须要有下面的参数,不然会报错
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36",
"content-type": "application/x-www-form-urlencoded", # 数据类型
"x-zse-83": "3_2.0" # 版本
}
r = self.session.post(url=self.sign_in_api, data=res,headers=headers)
# 登录成功的状态码,在这里是201
if r.status_code == 201:
print('登录成功')
r = self.session.get(url='https://www.zhihu.com/') # 登录成功后调到知乎首页
print(r.text)
else:
print('用户名或密码错误')
四、完整代码
import time,hmac
from hashlib import sha1
from requests_html import HTMLSession
import base64,execjs
from PIL import Image
from urllib.parse import urlencode
class Spider():
def __init__(self):
self.session = HTMLSession()
self.captcha_api = "https://www.zhihu.com/api/v3/oauth/captcha?lang=en"
self.sign_in_api = "https://www.zhihu.com/api/v3/oauth/sign_in"
self.head_page_url = "https://www.zhihu.com/signin?next=%2F"
self.headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36"
}
self.captcha = ''
self.signature = ''
def get_first_cookie(self):
self.session.get(url=self.head_page_url)
def handle_captcha(self):
r = self.session.get(url=self.captcha_api)
res = r.json()
if res.get('show_captcha'):
r = self.session.put(url=self.captcha_api)
img_base64 = r.json().get('img_base64')
with open('captcha.png', 'wb') as f:
f.write(base64.b64decode(img_base64))
img = Image.open('captcha.png')
img.show()
self.captcha = input('请输入校验码:')
r = self.session.post(url=self.captcha_api,data={"input_text":self.captcha})
res = r.json()
if res.get('success'):
print('验证码正确')
else:
self.handle_captcha()
def get_signature(self):
r = hmac.new(b'd1b964811afb40118a12068ff74a12f4',digestmod=sha1) # 第一个是加盐,digestmod参数是加密算法
r.update(b"password")
r.update(b"c3cef7c66a1843f8b3a9e6a1e3160e20")
r.update(b"com.zhihu.web")
r.update(str(int(time.time()*1000)).encode('utf-8'))
self.signature = r.hexdigest()
def login_in(self):
formdata = {
"client_id": "xxxxxxxx",
"grant_type": "password",
"timestamp": str(int(time.time() * 1000)),
"source": "com.zhihu.web",
"signature": self.signature,
"username": "+86182000000", # 用户名,请输入自己的
"password": "xxxxx", # 密码
"captcha": self.captcha,
"lang": "en",
"utm_source": '',
"ref_source": "other_https://Fwww.zhihu.com%2Fsignin%3Fnext%3D%252F"
}
with open('知乎加密.js','rt',encoding='utf-8') as f:
js = f.read()
execjs_obj = execjs.compile(js,cwd='node_modules')
# execjs_obj.call(函数名,参数)
res = execjs_obj.call('b',urlencode(formdata))
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36",
"content-type": "application/x-www-form-urlencoded",
"x-zse-83": "3_2.0"
}
r = self.session.post(url=self.sign_in_api, data=res,headers=headers)
if r.status_code == 201:
print('登录成功')
r = self.session.get(url='https://www.zhihu.com/')
print(r.text)
else:
print('用户名或密码错误')
def run(self):
self.get_first_cookie()
self.handle_captcha()
self.get_signature()
self.login_in()
if __name__ == '__main__':
zhihu = Spider()
zhihu.run()