爬蟲進階知識>解析網站中的JavaScript

js解析

引言

在瞭解如何調試js之前, 需要簡單瞭解一下http的請求過程

1.DNS域名解析;
2.建立TCP連接;
3.發送HTTP請求;
4.服務器處理請求;
5.返回響應結果;
6.關閉TCP連接;
7.瀏覽器解析HTML;
8.瀏覽器佈局渲染;

上面8個步驟被戲稱爲"天龍八步"

參考鏈接: https://zhuanlan.zhihu.com/p/32370763

簡單說一下我自己的理解:

  1. 瀏覽器解析出域名對應的ip後, 建立與服務器的連接
  2. 向訪問的url地址發起請求, 服務器處理後返回響應結果, 被瀏覽器接收
  3. 在返回的響應內容中, 會包含html,css,js和ajax等代碼, 瀏覽器會依次加載這些代碼
  4. 如果其中包含發送請求的代碼(例如js和ajax請求), 就會再次發起這些請求, 並獲取響應
  5. 加載完畢的響應內容會被展示在瀏覽器中, 直到最終獲取全部響應
  6. 這個過程就是瀏覽器的渲染

但是在爬蟲中, 其發起的請求只能拿到對應的響應. 而我們在瀏覽器中看到的頁面, 很多時候跟爬蟲得到的並不一樣, 這是因爲簡單的爬蟲不具備渲染頁面的能力(可以藉助外部工具來實現, 如selenium), 瀏覽器展示的結果是由多次請求/響應共同渲染而來的, 而爬蟲的一次請求只能得到一個響應

因爲我們爬蟲每次只有一個請求,但是實際中很多請求又js連帶發送,所以我們需要利用爬蟲解析js去實現這些請求的發送

方案

網頁中的參數來源主要由以下四種:

  1. 固定值, 寫死在html中的參數
  2. 用戶給的參數
  3. 服務器(通過ajax)返回的參數, 比如時間戳, token等
  4. js生成的參數

由於網站反爬措施的關係, 通過請求得到的參數很多是在前端js裏生成的, 因此爲了得到參數, 我們由以下方案(順位推薦)

方案1:本地運行js, 把js代碼拿下來,用python代碼去執行它(參見pyexcjs),從而得到所需請求參數
方案2:解析js,破解加密方法或者生成參數值方法,python代碼模擬實現這個方法, 從而得到我們需要的請求參數
方法3:selenium, 如果上面兩種都不行, 這就是我們的絕招, 模擬用戶使用瀏覽器, 獲取渲染後的頁面源碼

本文主要介紹第二種方案

1>篩選參數

以微博登錄爲例, 用戶名和密碼作爲輸入的參數, 在發送給服務器之前會被js做加密處理, 由此也增加了我們使用爬蟲模擬登陸的難度, 因此選擇使用解析js的方法來搞定

每當我們點擊登錄的時候都會發送一個login請求, 如圖
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UefzSUVZ-1573125994896)(E:\Fire\筆記\爬蟲高級.assets\1572767081082.png)]

登錄表單中的參數並不一定都是我們需要的, 可以通過對比多次請求中的參數, 再加上一些經驗和猜想, 過濾掉固定參數 或服務器自帶參數和用戶輸入的參數, 這是剩下的就是js生成的數值或加密數值, 如下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-iUJX7zJA-1573125994899)(E:\Fire\筆記\爬蟲高級.assets\1572768213043.png)]

最終得到的值:

# 圖片 picture id:
pcid: yf-d0efa944bb243bddcf11906cda5a46dee9b8
# 用戶名:
su: cXdlcnRxd3Jl
nonce: 2SSH2A	# 未知
# 密碼:
sp: e121946ac9273faf9c63bc0fdc5d1f84e563a4064af16f635000e49cbb2976d73734b0a8c65a6537e2e728cd123e6a34a7723c940dd2aea902fb9e7c6196e3a15ec52607fd02d5e5a28e18254105358e897996f0b9057afe2d24b491bb12ba29db3265aef533c1b57905bf02c0cee0c546f4294b0cf73a553aa1f7faf9f835e5
prelt: 148	# 未知

請求參數中的用戶名和密碼都是經過加密處理的, 因此如果需要模擬登錄, 我們就需要找到這個加密的方法, 利用它來爲我們的數據進行加密

找到所需的js

  1. 要找到加密方法, 首先我們需要先找到登錄所需的js代碼, 可以使用以下3種方式:

    1. 找事件, 在頁面檢查目標元素,在開發工具的子窗口裏選中Events Listeners, 找到click事件,點擊定位到js代碼, 如圖: [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4igWFpYz-1573125994900)(E:\Fire\筆記\爬蟲高級.assets\1572769323122.png)]

      這種方法找到的不一定是正確的, 所以推薦第二個方法

    2. 找請求, 在Network中點擊列表界面的對應Initiator跳轉至對應js界面
      [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vhMPtIPD-1573125994902)()]

    3. 通過搜索參數名進行定位 在這裏插入圖片描述

  2. 登錄的js代碼:
    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gT2Rb8dq-1573125994902)()]

點擊左下角的{}圖標, 可以更方便查看代碼

  1. 在這個submit的方法上打斷點, 然後輸入用戶名密碼, 先不點登錄, 回到dev tool點擊這個按鈕啓用調試:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NVnckBwQ-1573125994902)()]

  1. 然後再去點登錄按鈕, 這時候就可以開始調試, 調試工具有以下幾種:

    1. 大右箭頭[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-skoTYbsC-1573125994903)()] 跳到下一個斷點(如果沒有斷點就執行完)
    2. 彎箭頭[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bWby0Bb9-1573125994903)()] 逐句執行
    3. 下箭頭[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-TvnnHccl-1573125994904)()] 跳到下一個要執行的函數
    4. 上箭頭[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-MAcE4Gpv-1573125994904)()] 跳出當前執行的函數
    5. 右箭頭[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GNdpA4m7-1573125994905)()] 一步一步執行代碼
    6. 子彈[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-mM2OHbLH-1573125994906)(E:\Fire\筆記\爬蟲高級.assets\1572770809950.png)] 取消當前所有的斷點
    7. 暫停 捕獲異常時暫停
  2. 逐步執行代碼的同時觀察我們輸入的參數, 發生變化的地方即爲加密方法, 如下

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3M3Ulrai-1573125994907)(E:\Fire\筆記\爬蟲高級.assets\1572771322663.png)]

  3. 上圖中的加密方式是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的目的, 就是爲了讓爬蟲能夠使用這些頁面的渲染, 也就是上述的這些功能

參考文章:

一個TCP連接可以發送多少個HTTP請求?

關於堆棧的講解(我見過的最經典的)

python3調用js的庫之execjs

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章