知乎是爬虫的一个经典案例,因为他经常改版,越来越难爬,可能我这个教程写完他就又改版了。
1. 登录,且url跳转
2. 参数加密
3. 验证码
本文将介绍模拟登录知乎的详细过程。
使用 fiddler 抓包
1. 获取登录url
输入账号、密码等,登录网站
post 方式访问url,页面跳转,箭头所指是真实的 登录url
2. 获取登录参数
可以看到 form data 加密了
处理方法
需要解决两个问题:提交了哪些参数;如何加密
1. 首先需要进入 source 面板,找寻相关 js 文件与加密函数;
2. 搜索与加密相关的英文,搜索方法见我的博客《浏览器抓包》,只要相关的函数名没有加密,就能搜到,这里搜索 encrypt;【encrypt:加密】
3. 在浏览器中格式化 js 代码,定位到加密函数,获取行号;【往往可以搜到很多个encrypt,浏览器中只匹配到第一个,所以要拷到编辑器中,搜索定位】
4. 在对应行号设置断点;【注意行号可能不完全相同,在上下几行中找找对应函数】
5. 重新登录,进行调试,抓取登录参数;
加密函数
var b = function(e) {
return __g._encrypt(encodeURIComponent(e))
};
"client_id=c3cef7c66a1843f8b3a9e6a1e3160e20&grant_type=password×tamp=1559629752508&source=com.zhihu.web&signature=15317e3484b64449697b285a69a09af8ff23a1af&username=yanshuangwu258%40sina.com&password=6712007&captcha=&lang=en&ref_source=homepage&utm_source="
加密函数 b 传入参数e,先进行 encodeURIComponent,根据经验应该是 编码成 key-value 形式,然后进行加密
先把参数意义搞清楚
client_id=c3cef7c66a1843f8b3a9e6a1e3160e2 客户端id
grant_type=password 授权类型
timestamp=1559629752508 时间戳
source=com.zhihu.web 源地址
signature=15317e3484b64449697b285a69a09af8ff23a1af 签名
username=yanshuangwu258%40sina.com 用户名
password=6712007 密码
captcha= 验证码
lang=en 验证码类型
ref_source=homepage
utm_source=
多试几次,观察参数值是否固定;
经对比,不固定的是 时间戳、签名、验证码;
时间戳就是时间,算是已知的,剩下就要得到签名和验证码了
3. 获取签名
从上得知,签名也是经过加密的;
破解方法类似第2步;在 source 中搜索 signature;
定位行数,设置断点,调试;
signature 在这个函数中生成
function(e, t, n) {
"use strict";
var r = n(745)
, o = n.n(r)
, i = n(183)
, a = n.n(i);
Object.assign;
a()("zhihu-redux-middlewares:oauth");
var c = "c3cef7c66a1843f8b3a9e6a1e3160e20";
var u = Object.assign || function(e) {
for (var t = 1; t < arguments.length; t++) {
var n = arguments[t];
for (var r in n)
Object.prototype.hasOwnProperty.call(n, r) && (e[r] = n[r])
}
return e
}
;
t.a = function(e, t) {
var n = Date.now()
, r = new o.a("SHA-1","TEXT");
return r.setHMACKey("d1b964811afb40118a12068ff74a12f4", "TEXT"),
r.update(e),
r.update(c),
r.update("com.zhihu.web"),
r.update(String(n)),
u({
clientId: c,
grantType: e,
timestamp: n,
source: "com.zhihu.web",
signature: r.getHMAC("HEX") #######
}, t)
}
该函数传入 e、t和一些全局变量,e是字符串‘password’,t 见截图,n是时间戳,c 见代码,
这个函数显示了 signature 的加密过程;【此处需要学习常规加密方法】
此处通过 秘钥d1b964811afb40118a12068ff74a12f4 和 SHA-1密码散列函数,进行加密,r.update 又添加了 e、c、‘com.zhihu.web’、string(n),
由截图和代码可知,e代表‘password’, c为"c3cef7c66a1843f8b3a9e6a1e3160e20",n为时间戳,由此可算出 signature
4. 获取登录验证码
知乎验证码的特点
1. 登录知乎不是每次都需要验证码
2. 知乎有两种验证码,一种是 “点击倒立的文字”,一种是 “英文字母”
验证码分析 - 操作过程
1. 访问知乎登录页面,F12,然后刷新
可以看到,验证码url返回 false,即无需验证码
此时 的 request url 如下图
2. 多次刷新登录页面,观察 验证码 url 的 response,直至为 true
返回 true ,代表需要验证码
此时的 request url 如下图
可以看到 和不需验证码的url 相同,method 都是 get
我们发现紧接着又有一个 验证码url,是什么呢?
这应该就是验证码图片, base64 编码的图片。
base64 编码的图片。可先存入本地,而后手动输入
看下headers
我们发现 method 变成了 put,request url 还是一样
也就是说,如果访问验证码url返回true, 会自动再次请求这个url,请求方式为 put, 返回 base64编码的图片
3. 输入账号、密码,弹出验证码,输入验证码,点击登录
首先是 post 了验证码数据,同样的url
post 参数如下图
key 是 input_text, value 为 图片大小和倒立文字的位置,这是倒立文字验证码
英文字母如下图
key 也是 input_text,value 为英文字母
倒立文字 和 英文字母 的url 不同, 文字 cn,字母 en
也就是说,得到 base64 编码的图片后,要给该 url post 验证码,然后才能登录
至此,我们得到验证码,并 post,获取登录的所有参数。
也可以尝试通过 搜索 登录url 的 js 关键字,获取登录参数。
import json
import requests
import time
from hashlib import sha1
from time import sleep
import hmac
import base64
from PIL import Image
class Zhihu(object):
def __init__(self):
self.session=requests.session()
self.headers={
# 'authority':'www.zhihu.com',
'user-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0',
}
self.session.headers.update(self.headers)
self.picture=None
self.signature=None
self.picture_url=None
def getcapture(self):
# 获取验证码方法,有时候不用获取验证码就可以直接登录
# lang=en是英文字母 验证码
message=self.session.get(url='https://www.zhihu.com/api/v3/oauth/captcha?lang=en').json() # get 检测是否需要验证码
print(message)
if message['show_captcha'] == False:
self.picture=''
else:
self.picture_url = self.session.put(url='https://www.zhihu.com/api/v3/oauth/captcha?lang=en').json() # put 获取验证码
# 采用base64格式将验证码通过图片格式显示出来
with open('captcha.jpg','wb') as f:
f.write(base64.b64decode(self.picture_url['img_base64']))
image=Image.open('captcha.jpg')
image.show()
self.picture=input('请输入验证码')
sleep(2)
message1=self.session.post(url='https://www.zhihu.com/api/v3/oauth/captcha?lang=en',data={'input_text':self.picture}).json() # post 验证码
print(message1)
def get_signature(self):
# 知乎登陆的主要问题在于找到signature了这是重点。
a=hmac.new('d1b964811afb40118a12068ff74a12f4'.encode('utf-8'),digestmod=sha1)
a.update('password'.encode('utf-8'))
a.update(b'c3cef7c66a1843f8b3a9e6a1e3160e20')
a.update(b'com.zhihu.web')
a.update(str(int(time.time()*1000)).encode())
self.signature=a.hexdigest()
def Login_phone(self):
# 登录
data={
'client_id':'c3cef7c66a1843f8b3a9e6a1e3160e20',#'c3cef7c66a1843f8b3a9e6a1e3160e20',
'grant_type':'password',
'timestamp':str(int(time.time()*1000)),
'source':'com.zhihu.web',
'signature':self.signature,
'username':'xxxxxx@sina.com',
'password':'xxxxxxx',
'captcha':self.picture,
'lang':'en',
# 'ref_source':'homepage',
# 'utm_source':''
}
headers = {
# 'scheme':'https',
# 'accept':'*/*',
# 'accept-encoding':'gzip, deflate, br',
# 'accept-language':'zh-CN,zh;q=0.8',
# 'cache-control':'no-cache',
# 'content-length':'412',
# 'origin':'https://www.zhihu.com',
'content-type':'application/x-www-form-urlencoded',
# 'referer':'https://www.zhihu.com/signin?next=%2F',
'x-zse-83':'3_2.0',
}
message=self.session.post(url='https://www.zhihu.com/api/v3/oauth/sign_in', headers=headers, data=data)
message.encoding='utf-8'
print(message.text)
print(json.loads(message.text)['error']['message'])
def target_url(self,url):
text=self.session.get(url)
return text.text
if __name__ == "__main__":
zhihu=Zhihu()
zhihu.getcapture() # 验证码
zhihu.get_signature() # signature
zhihu.Login_phone() # 登录
# print(zhihu.target_url('https://www.zhihu.com/'))
知乎模拟登陆还是很复杂的