在 - 簡書-爬蟲數(shù)據(jù)分析學(xué)習(xí)交流 - 微信群里有位朋友Jacky提到爬取中國銀行遇到的問題,一時(shí)興起便做了嘗試。
-
首先還原問題,我們禁用js,在chrome瀏覽器中新建標(biāo)簽頁,F(xiàn)12 > F1 >打開設(shè)置在右下角找到禁用js并勾選
- 打開中國人民銀行條法司網(wǎng)頁發(fā)現(xiàn)如下的頁面顯示
-
然后F12關(guān)閉開發(fā)者控制臺(tái),刷新頁面,顯示正常
- 利用chrome插件,EditThisCookie,在控制臺(tái)中查看cookie如下,同時(shí)禁用js再次打開網(wǎng)頁卻發(fā)現(xiàn)顯示正常,而清空cookie(禁用js)后打開又出現(xiàn)問題顯示。
- 這是因?yàn)樵摼W(wǎng)頁首先傳給你的html文件中包含cookie設(shè)置和動(dòng)態(tài)跳轉(zhuǎn)網(wǎng)址的js代碼,js代碼運(yùn)行后會(huì)自動(dòng)設(shè)置cookie并跳轉(zhuǎn)鏈接,到達(dá)正常頁面。
-
我們查看問題頁面的源碼,Ctrl-U
Paste_Image.png - 源碼很亂,以我的菜雞水平根本看不出來什么東西,這時(shí)需要優(yōu)秀工具的幫助,http://jsbeautifier.org/ 是一款優(yōu)秀js代碼美化和解析工具,我們將代碼放上去解析,終端curl網(wǎng)頁得到的js代碼和解析后的代碼對(duì)比鮮明
-
與一般的代碼美化工具比較(下圖),不僅格式化了代碼,并且可讀化了代碼,這樣以我的水平就可以分析代碼了。
-
首先兩次請(qǐng)求該網(wǎng)址,將兩次美化后的代碼進(jìn)行對(duì)比,我們可以看到不僅在js全局變量上有改變,在其中一個(gè)加密函數(shù)里也有小改動(dòng)。
- 某一個(gè)請(qǐng)求里的js代碼,留存參考,閱讀可先跳過:
var dynamicurl = "/L3RpYW9mYXNpLzE0NDk0MS8xNDQ5NTcvaW5kZXguaHRtbA==";
var wzwschallenge = "RANDOMSTR14925";
var wzwschallengex = "STRRANDOM14925";
var template = 4;
var encoderchars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
function KTKY2RBD9NHPBCIHV9ZMEQQDARSLVFDU(str) {
var out, i, len;
var c1, c2, c3;
len = str.length;
i = 0;
out = "";
while (i < len) {
c1 = str.charCodeAt(i++) & 0xff;
if (i == len) {
out += encoderchars.charAt(c1 >> 2);
out += encoderchars.charAt((c1 & 0x3) << 4);
out += "==";
break;
}
c2 = str.charCodeAt(i++);
if (i == len) {
out += encoderchars.charAt(c1 >> 2);
out += encoderchars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xf0) >> 4));
out += encoderchars.charAt((c2 & 0xf) << 2);
out += "=";
break;
}
c3 = str.charCodeAt(i++);
out += encoderchars.charAt(c1 >> 2);
out += encoderchars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xf0) >> 4));
out += encoderchars.charAt(((c2 & 0xf) << 2) | ((c3 & 0xc0) >> 6));
out += encoderchars.charAt(c3 & 0x3f);
}
return out;
}
function findDimensions() {
var w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
var h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
if (w * h <= 120000) {
return true;
}
var x = window.screenX;
var y = window.screenY;
if (x + w <= 0 || y + h <= 0 || x >= window.screen.width || y >= window.screen.height) {
return true;
}
return false;
}
function QWERTASDFGXYSF() {
var tmp = wzwschallenge + wzwschallengex;
var hash = 0;
var i = 0;
for (i = 0; i < tmp.length; i++) {
hash += tmp.charCodeAt(i);
}
hash *= 11;
hash += 111111;
return "WZWS_CONFIRM_PREFIX_LABEL4" + hash;
}
function HXXTTKKLLPPP5() {
if (findDimensions()) {} else {
var cookieString = "";
cookieString = "wzwstemplate=" + KTKY2RBD9NHPBCIHV9ZMEQQDARSLVFDU(template.toString()) + "; path=/";
document.cookie = cookieString;
var confirm = QWERTASDFGXYSF();
cookieString = "wzwschallenge=" + KTKY2RBD9NHPBCIHV9ZMEQQDARSLVFDU(confirm.toString()) + "; path=/";
document.cookie = cookieString;
window.location = dynamicurl;
}
}
HXXTTKKLLPPP5();
- 這種程度的js代碼(美化后)并不難理解,很明顯可以看出最后的函數(shù)調(diào)用中利用document.cookie設(shè)置了兩個(gè)cookie,并利用window.location設(shè)置了跳轉(zhuǎn)網(wǎng)址,靜下心去分析也能用python寫出相應(yīng)的加密程序,用正則取配到變量,生成我們想要的信息,但是有沒有更快的方法呢。這時(shí)我們又有一個(gè)強(qiáng)大的工具,Js2Py,利用它我們可以解析js代碼變?yōu)閜ython中的可執(zhí)行代碼。如下官方簡單示例:
- 下面的思路就清晰了,先利用js美化工具的python庫jsbeautifier(pip安裝)美化代碼,然后不管js代碼中關(guān)于dom操作的內(nèi)容(如window.xx),取出js全局變量和加密函數(shù),利用js2py生成可執(zhí)行的python函數(shù),以js代碼中的主干邏輯在python里執(zhí)行獲得cookies,在requests.session中更新cookies并訪問js變量中的動(dòng)態(tài)網(wǎng)址,就可以成功到達(dá)內(nèi)容頁,開始爬取解析了。
- 以下為python代碼,可以看到最后成功驗(yàn)證我們爬取到了有效信息。
import requests
import re
import jsbeautifier
import js2py
host_url = 'http://www.pbc.gov.cn/'
dest_url = 'http://www.pbc.gov.cn/tiaofasi/144941/144957/index.html'
# 利用session保存cookie信息,第一次請(qǐng)求會(huì)設(shè)置cookie類似{'wzwsconfirm': 'ab3039756ba3ee041f7e68f634d28882', 'wzwsvtime': '1488938461'},與js解析得到的cookie合起來才能通過驗(yàn)證
r = requests.session()
content = r.get(dest_url).content
# 獲取頁面腳本內(nèi)容
re_script = re.search(r'<script type="text/javascript">(?P<script>.*)</script>', content.decode('utf-8'), flags=re.DOTALL)
# 用點(diǎn)匹配所有字符,用(?P<name>...)獲取:https://docs.python.org/3/howto/regex.html#regex-howto
# cheatsheet:https://github.com/tartley/python-regex-cheatsheet/blob/master/cheatsheet.rst
script = re_script.group('script')
script = script.replace('\r\n', '')
# 在美化之前,去掉\r\n之類的字符才有更好的效果
res = jsbeautifier.beautify(script)
# 美化并一定程度解析js代碼:https://github.com/beautify-web/js-beautify
with open('x.js','w') as f:
f.write(res)
# 寫入文檔進(jìn)行查看分析
jscode_list = res.split('function')
var_ = jscode_list[0]
var_list = var_.split('\n')
template_js = var_list[3] # 依順序獲取,亦可用正則
template_py = js2py.eval_js(template_js)
# 將所有全局變量插入第一個(gè)函數(shù)變?yōu)榫植孔兞坎⒂?jì)算
function1_js = 'function' + jscode_list[1]
position = function1_js.index('{') +1
function1_js = function1_js[:position]+ var_ +function1_js[position:]
function1_py = js2py.eval_js(function1_js)
cookie1 = function1_py(str(template_py)) # 結(jié)果類似'NA=='
# 保存得到的第一個(gè)cookie
cookies = {}
cookies['wzwstemplate'] = cookie1
# 對(duì)第三個(gè)函數(shù)做類似操作
function3_js = 'function' + jscode_list[3]
position = function3_js.index('{') +1
function3_js = function3_js[:position]+ var_ +function3_js[position:]
function3_py = js2py.eval_js(function3_js)
middle_var = function3_py() # 是一個(gè)str變量,結(jié)果類似'WZWS_CONFIRM_PREFIX_LABEL4132209'
cookie2 = function1_py(middle_var)
cookies['wzwschallenge'] = cookie2
# 關(guān)于js代碼中的document.cookie參見 https://developer.mozilla.org/zh-CN/docs/Web/API/Document/cookie
dynamicurl = js2py.eval_js(var_list[0])
# 利用新的cookie對(duì)提供的動(dòng)態(tài)網(wǎng)址進(jìn)行訪問即是我們要達(dá)到的內(nèi)容頁面了
r.cookies.update(cookies)
content = r.get(host_url+dynamicurl).content
# 最后驗(yàn)證是否爬取到有效信息
if u'銀行卡清算機(jī)構(gòu)管理辦法' in content.decode('utf-8'):
print('success')