老师要求做一个裁判文书网的爬虫,算是我写的第一个爬虫吧,在这里记录一下。
登录接口
这个登录没有验证码,没有机器人验证,这不来一手自动登录?F12 之后抓登录的包:
可以看到用户名没变,密码是加密过的,剩下的 appDomain
是固定的,不用管。
password 加密算法
下一步是找密码的加密算法和密钥。首先翻看 html 里面引入的 js ,在 index.js
中找到了和登录相关的代码:
但仔细看能发现,这里绑定的 name 在 html 文件中根本没有,所以是错的登录代码。
查看 html 文件中登录按钮的代码:
<div class="login-button-container">
<span tabindex="0" data-mainkey="true" class="button button-primary" data-action="login-submit" data-api="/api/login">登录</span>
</div>
可以看到有个 data-action
,必然是绑定登录操作时候用的。全局搜索 login-submit
,在 login.js
中找到了绑定登录操作的代码:
这是个 minify 过后的 js 文件,不是很容易读。搜索 password
关键字可以找到有个 encodePassword
函数,猜想这个就是加密函数:
测试后确定这个是密码的加密函数。用的 JSEncrypt ,用 python 简单实现一下:
def encrypt_password(self):
rsa_key = RSA.importKey(self.passwdPublicKey)
cipher = Cipher_pkcs1_v1_5.new(rsa_key)
cipher_text = base64.b64encode(cipher.encrypt(self.password.encode(encoding="utf-8")))
value = cipher_text.decode('utf-8')
return value
至此,密码的加密搞定。
小问题
搞定密码后一直出现 "帐号没有授权" 之类的错误,卡了挺久的。最后模拟登录时候的情况,把登录前后所有的包都模拟发送一遍后解决。
pre_auth_url = 'https://wenshu.court.gov.cn/tongyiLogin/authorize'
pre_auth_res = requests.post(url=pre_auth_url, data={}, headers=headers)
cookies = {
pre_auth_res.cookies.items()[0][0]: pre_auth_res.cookies.items()[0][1]
}
pre_auth_url_with_params = pre_auth_res.text
with_params_url_res = requests.get(url=pre_auth_url_with_params, headers=headers, allow_redirects=False)
auth_cookies = {
with_params_url_res.cookies.items()[0][0]: with_params_url_res.cookies.items()[0][1]
}
data = {
"username": self.username,
"password": quote(self.encrypt_password()),
"appDomain": "wenshu.court.gov.cn",
}
print("login ...")
login_url = 'https://account.court.gov.cn/api/login'
login_res = requests.post(login_url, headers=headers, data=data, cookies=auth_cookies)
login_res.raise_for_status()
login_text = json.loads(login_res.text)
if login_text['code'] != '000000':
raise Exception(login_text['message'])
login_cookies = {
login_res.cookies.items()[0][0]: login_res.cookies.items()[0][1]
}
with_params_url_res = requests.get(
url=pre_auth_url_with_params,
headers=headers,
cookies=login_cookies,
allow_redirects=False
)
jump_url = with_params_url_res.headers["Location"]
requests.get(url=jump_url, headers=headers, cookies=cookies)
# print(cookies)
self.cookies = cookies
数据接口
搞定登录之后开始找数据接口。按照惯例打开 F12 并搜索一些东西:
有用的数据是后面几个 rest.q4w
的 JSON 数据。体积较大的是文书数据,另外一个是侧边栏的数据。
可以看到文书数据是加密过的。
发送的请求包有些数据也是加密过的。下面一步步解决这些问题。
参数
首先我们来找这些参数都是什么意思,来自哪里。
pageId
pageId
在当前页面的 URL 里有出现:
https://wenshu.court.gov.cn/website/wenshu/181217BMTKHNT2W0/index.html?pageId=26871ff89db9e96d3af2b870c1942b00&s21=%E7%BD%91%E7%BB%9C%E8%AF%88%E9%AA%97
那么 URL 中的 pageId
是从哪里来的呢?
index.js
里面有这么一行:
接着搜索这个 uuid
方法,在 website.js
中找到这个方法:
uuid: function(){
var guid = "";
for (var i = 1; i <= 32; i++) {
var n = Math.floor(Math.random() * 16.0).toString(16);
guid += n;
// if ((i == 8) || (i == 12) || (i == 16) || (i == 20)) guid +=
// "-";
}
return guid;
},
其实就是个 32 位的随机十六进制数。
s21
这其实就是一个分类标识,保持不变就好了。
在
wenshulist1.js
中可以找到这是哪个分类
sortFields
排序标识,保持不变即可。
ciphertext
明显的二进制密文,找找哪里生成的:
接着看这个 cipher
函数,在 strToBinary.js
中找到它的原型:
function cipher() {
var date = new Date();
var timestamp = date.getTime().toString();
var salt = $.WebSite.random(24);
var year = date.getFullYear().toString();
var month = (date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date
.getMonth()).toString();
var day = (date.getDate() < 10 ? "0" + date.getDate() : date.getDate())
.toString();
var iv = year + month + day;
var enc = DES3.encrypt(timestamp, salt, iv).toString();
var str = salt + iv + enc;
var ciphertext = strTobinary(str);
return ciphertext;
}
function strTobinary(str) {
var result = [];
var list = str.split("");
for (var i = 0; i < list.length; i++) {
if (i != 0) {
result.push(" ");
}
var item = list[i];
var binaryStr = item.charCodeAt().toString(2);
result.push(binaryStr);
};
return result.join("");
}
按照这个代码逻辑直接写一个相同的加密函数即可。
@staticmethod
def make_ciphertext():
timestamp = str(int(time.time() * 1000))
salt = ''.join([random.choice(string.digits + string.ascii_lowercase) for _ in range(24)])
iv = datetime.datetime.now().strftime('%Y%m%d')
des = Des()
enc = des.encrypt(timestamp, salt)
strs = salt + iv + enc
result = []
for i in strs:
result.append(bin(ord(i))[2:])
result.append(' ')
return ''.join(result[:-1])
pageNum && pageSize
当前页数和每页数据的数量。
queryCondition
查询条件,其实和上面的 s21
是一样的,只是 JSON 化了。
cfg
固定的字符串,保持不变。
__RequestVerificationToken
在 website.js
中找到:
24位随机数。
解密返回值
在 website.js
中找到请求数据的代码:
可以看到用的是 3DES ,密钥是从返回值中获取的。
class Des(object):
@staticmethod
def encrypt(text, key):
text = pad(text.encode(), DES3.block_size)
iv = datetime.datetime.now().strftime('%Y%m%d').encode()
crypto = DES3.new(key, DES3.MODE_CBC, iv)
x = len(text) % 8
ciphertext = crypto.encrypt(text)
return base64.b64encode(ciphertext).decode("utf-8")
@staticmethod
def decrypt(text, key):
iv = datetime.datetime.now().strftime('%Y%m%d').encode()
crypto = DES3.new(key, DES3.MODE_CBC, iv)
de_text = base64.b64decode(text)
plain_text = crypto.decrypt(de_text)
out = unpad(plain_text, DES3.block_size)
return out.decode()
至此,爬虫已经基本完成了。因为老师不要求爬详情页,所以,交差,饮茶去了 XD
6 comments
您好~我是腾讯云开发者社区运营,关注了您分享的技术文章,觉得内容很棒,我们诚挚邀请您加入腾讯云自媒体分享计划。完整福利和申请地址请见:https://cloud.tencent.com/developer/support-plan
作者申请此计划后将作者的文章进行搬迁同步到社区的专栏下,你只需要简单填写一下表单申请即可,我们会给作者提供包括流量、云服务器等,另外还有些周边礼物。
Appreciate it. Lots of postings!
老公好强!(´இ皿இ`)
?WTF你谁啊
你好 这篇登录模块有问题想请教。我最后生成的SESSION不可用
呃好久没看了,我找时间试一下吧