js解析
引言
在瞭解如何調試js之前, 需要簡單瞭解一下http的請求過程
1.DNS域名解析;
2.建立TCP連接;
3.發送HTTP請求;
4.服務器處理請求;
5.返回響應結果;
6.關閉TCP連接;
7.瀏覽器解析HTML;
8.瀏覽器佈局渲染;
上面8個步驟被戲稱爲"天龍八步"
參考鏈接: https://zhuanlan.zhihu.com/p/32370763
簡單說一下我自己的理解:
- 瀏覽器解析出域名對應的ip後, 建立與服務器的連接
- 向訪問的url地址發起請求, 服務器處理後返回響應結果, 被瀏覽器接收
- 在返回的響應內容中, 會包含html,css,js和ajax等代碼, 瀏覽器會依次加載這些代碼
- 如果其中包含發送請求的代碼(例如js和ajax請求), 就會再次發起這些請求, 並獲取響應
- 加載完畢的響應內容會被展示在瀏覽器中, 直到最終獲取全部響應
- 這個過程就是瀏覽器的渲染
但是在爬蟲中, 其發起的請求只能拿到對應的響應. 而我們在瀏覽器中看到的頁面, 很多時候跟爬蟲得到的並不一樣, 這是因爲簡單的爬蟲不具備渲染頁面的能力(可以藉助外部工具來實現, 如selenium), 瀏覽器展示的結果是由多次請求/響應共同渲染而來的, 而爬蟲的一次請求只能得到一個響應
因爲我們爬蟲每次只有一個請求,但是實際中很多請求又js連帶發送,所以我們需要利用爬蟲解析js去實現這些請求的發送
方案
網頁中的參數來源主要由以下四種:
- 固定值, 寫死在html中的參數
- 用戶給的參數
- 服務器(通過ajax)返回的參數, 比如時間戳, token等
- js生成的參數
由於網站反爬措施的關係, 通過請求得到的參數很多是在前端js裏生成的, 因此爲了得到參數, 我們由以下方案(順位推薦)
方案1:本地運行js, 把js代碼拿下來,用python代碼去執行它(參見pyexcjs),從而得到所需請求參數
方案2:解析js,破解加密方法或者生成參數值方法,python代碼模擬實現這個方法, 從而得到我們需要的請求參數
方法3:selenium, 如果上面兩種都不行, 這就是我們的絕招, 模擬用戶使用瀏覽器, 獲取渲染後的頁面源碼
本文主要介紹第二種方案
1>篩選參數
以微博登錄爲例, 用戶名和密碼作爲輸入的參數, 在發送給服務器之前會被js做加密處理, 由此也增加了我們使用爬蟲模擬登陸的難度, 因此選擇使用解析js的方法來搞定
每當我們點擊登錄的時候都會發送一個login請求, 如圖
登錄表單中的參數並不一定都是我們需要的, 可以通過對比多次請求中的參數, 再加上一些經驗和猜想, 過濾掉固定參數 或服務器自帶參數和用戶輸入的參數, 這是剩下的就是js生成的數值或加密數值, 如下:
最終得到的值:
# 圖片 picture id:
pcid: yf-d0efa944bb243bddcf11906cda5a46dee9b8
# 用戶名:
su: cXdlcnRxd3Jl
nonce: 2SSH2A # 未知
# 密碼:
sp: e121946ac9273faf9c63bc0fdc5d1f84e563a4064af16f635000e49cbb2976d73734b0a8c65a6537e2e728cd123e6a34a7723c940dd2aea902fb9e7c6196e3a15ec52607fd02d5e5a28e18254105358e897996f0b9057afe2d24b491bb12ba29db3265aef533c1b57905bf02c0cee0c546f4294b0cf73a553aa1f7faf9f835e5
prelt: 148 # 未知
請求參數中的用戶名和密碼都是經過加密處理的, 因此如果需要模擬登錄, 我們就需要找到這個加密的方法, 利用它來爲我們的數據進行加密
找到所需的js
-
要找到加密方法, 首先我們需要先找到登錄所需的js代碼, 可以使用以下3種方式:
-
找事件, 在頁面檢查目標元素,在開發工具的子窗口裏選中
Events Listeners
, 找到click事件,點擊定位到js代碼, 如圖:這種方法找到的不一定是正確的, 所以推薦第二個方法
-
找請求, 在
Network
中點擊列表界面的對應Initiator
跳轉至對應js界面
-
通過搜索參數名進行定位
-
-
登錄的js代碼:
點擊左下角的
{}
圖標, 可以更方便查看代碼
- 在這個
submit
的方法上打斷點, 然後輸入用戶名密碼, 先不點登錄, 回到dev tool點擊這個按鈕啓用調試:
-
然後再去點登錄按鈕, 這時候就可以開始調試, 調試工具有以下幾種:
- 大右箭頭 跳到下一個斷點(如果沒有斷點就執行完)
- 彎箭頭 逐句執行
- 下箭頭 跳到下一個要執行的函數
- 上箭頭 跳出當前執行的函數
- 右箭頭 一步一步執行代碼
- 子彈 取消當前所有的斷點
- 暫停 捕獲異常時暫停
-
逐步執行代碼的同時觀察我們輸入的參數, 發生變化的地方即爲加密方法, 如下
-
上圖中的加密方式是base64, 因此我們可以使用代碼來試一下:
import base64 a = "aaaaaaaaaaaa" # 輸入的用戶名 print(base64.b64encode(a.encode())) # 得到的加密結果:b'YWFhYWFhYWFhYWFh' # 如果用戶名包含@等特殊符號, 需要先用parse.quote()進行轉義
得到的加密結果與網頁上js的執行結果一致
實踐
爬取百度翻譯的api
import execjs
import requests
import re
import json
def get_sign(word, gtk):
with open('baidu.js', 'r') as f:
js_code = f.read()
sign = execjs.compile(js_code) # 執行定義代碼, 可通過call調用
return sign.call('e', word, gtk)
def run_translate(word):
# 創建session對象
session = requests.session()
# 定義UA
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36'
}
# 獲取百度翻譯的頁面源碼
session.get('https://fanyi.baidu.com/', headers=headers)
# 百度翻譯比較特殊, 需要發送兩次請求, 至於原因我也不清楚
response = session.get('https://fanyi.baidu.com/', headers=headers)
# 獲取參數:
token = re.findall(r"token: '(.*?)'", response.text)[0]
gtk = re.findall(r"window.gtk = '(.*?)'", response.text)[0]
# 更新請求頭的參數, 爲後續post請求做準備
session.headers.update({'referer': 'https://fanyi.baidu.com/'})
# 發送第一次請求, 檢測輸入的語言
session.post('https://fanyi.baidu.com/langdetect/', data={'query': word})
sign = get_sign(word, gtk)
# 定義第二次請求的參數
data = {
'from': 'en',
'to': 'zh',
'query': word,
'transtype': 'translang', # translang:點擊按鈕翻譯; realtime:自動翻譯
'simple_means_flag': '3',
'sign': sign,
'token': token,
}
resp = session.post('https://fanyi.baidu.com/v2transapi/', data=data).text
print(json.loads(resp)['dict_result']['simple_means']['word_means'])
if __name__ == '__main__':
run_translate(input('英譯漢>>:'))
提問:
簡單說一說,js在頁面中的作用,我們爲什麼要解析它
在瀏覽器訪問一個網站時, 網站會加載三種類型的代碼, 靜態代碼(html/css), 動態代碼(js/jq), 外部代碼(媒體文件), js的作用就是爲網站提供更多功能(例如動態加載數據,觸發各種事件,完成前後端交互,數據加密等工作)
衆所周知, 爬蟲是不能執行頁面渲染的, 因此我們解析js的目的, 就是爲了讓爬蟲能夠使用這些頁面的渲染, 也就是上述的這些功能
參考文章: