教你輕鬆截獲 Selenium 中的 Ajax 數據

之前我們介紹了 ajax-hook 來實現爬蟲的過程中截獲 Ajax 請求,可以看這篇文章如何用 Hook 實時處理和保存 Ajax 數據,在這裏再另外介紹一個工具 BrowserMob Proxy,利用它我們同樣可以實現 Selenium 爬蟲過程中 Ajax 請求的獲取。

下面我們來簡單介紹一下。

BrowserMob Proxy

BrowserMob Proxy,簡稱 BMP,它是一個 HTTP 代理服務,利用它我們可以截獲 HTTP 請求和響應內容,另外還可以把 Performance data 輸出成一個 HAR 文件。

其 GitHub 鏈接爲:https://github.com/lightbody/browsermob-proxy/。

大家可以點擊進去看看詳情介紹。

實際上其原理就是開了一個代理服務器,然後抓包,同時對接了 Java、Python API,以方便我們可以直接通過代碼來獲取到內容。

案例

官方的一些介紹比較複雜,而且大多數都是 Java 的對接,在這裏我們使用 Python 來實驗一下。

這裏我們就直接通過一個案例來測試下吧,廢話不多說。

還是拿我自己的一個測試網站爲案例,鏈接爲:https://dynamic2.scrape.center/。

頁面如圖所示:

其數據都是通過 Ajax 加載的,同時帶着一些加密參數:

 

這個網站通過 Selenium 爬的話一點問題也沒有,但是由於數據本身就是從 Ajax 加載的,所以如果能直接截獲 Ajax 請求的話,連頁面解析都省了。

所以這裏我們要利用 BrowserMob Proxy 來截獲一下試試。

代碼實現

要用 Python 實現,我們需要先安裝一個 BrowserMob Proxy 的包,命令如下:

pip3 install browsermob-proxy

另外我們還需要下載 browsermob-proxy 的二進制文件,以便於啓動 BrowserMob Proxy。

下載的地址見:https://github.com/lightbody/browsermob-proxy/releases

直接下載 build 過的版本即可:

比如這裏我就下載 browsermob-proxy-2.1.4.zip 文件,直接放到我的項目目錄下。

好,接着呢,我們就可以實現如下代碼:


from browsermobproxy import Server
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

# 啓動代理
server = Server('./browsermob-proxy-2.1.4/bin/browsermob-proxy')
server.start()
proxy = server.create_proxy()
print('proxy', proxy.proxy)

# 啓動瀏覽器
chrome_options = Options()
chrome_options.add_argument('--ignore-certificate-errors')
chrome_options.add_argument('--proxy-server={0}'.format(proxy.proxy))
driver = webdriver.Chrome(options=chrome_options)

# 監聽結果
base_url = 'https://dynamic2.scrape.center/'
proxy.new_har(options={
    'captureContent': True,
    'captureHeaders': True
})
driver.get(base_url)
time.sleep(3)

# 讀取結果
result = proxy.har
for entry in result['log']['entries']:
    print(entry['request']['url'])
    print(entry['response']['content'])

在這裏呢,一共分了四步:

  • 第一步便是啓動 BrowserMob Proxy,它會在本地啓動一個代理服務,這裏注意 Server 的第一個參數需要指定 BrowserMob Proxy 的可執行文件路徑,這裏我就指定了下載下來的 BrowserMob Proxy 的 bin 目錄的 browsermob-proxy 的路徑。
  • 第二步便是啓動 Selenium 了,它可以設置 Proxy Server 爲 BrowserMob Proxy 的地址。
  • 第三步便是訪問頁面同時監聽結果,這裏我們需要調用 new_har 方法,同時指定捕獲 Resopnse Body 和 Headers 信息,緊接着調用 Selenium 的 get 方法訪問一個頁面,這時候瀏覽器便會加載這個頁面,同時所有的請求和響應信息都會被記錄到 HAR 中。
  • 第四步便是讀取 HAR 到內容了,我們調用 log 到 entries 字段,裏面便包含了請求和響應的具體結果,這樣所有的請求和響應信息我們便能獲取到了,Ajax 的內容也不在話下。

運行結果類似如下:

這裏可以看到所有的數據都能獲取到了,包括 Ajax 結果、JavaScript、CSS 文件內容等等。

這裏 har 的內容其實是一個 JSON 對象,裏面記錄了在訪問頁面的過程中發生的所有請求和響應內容,一般內容都會記錄在 logs 的 entries 字段裏面,還有其他的信息如有需要也可以讀取。

所以,這樣我們就能從 Selenium 中獲取 Ajax 請求內容了。

優化

不過像上面這種代碼還是不方便啊,不好複用,不好擴展,我們來稍微改寫下,代碼如下:


from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from browsermobproxy import Server
import time
import json

class BaseFramework(object):

    def __init__(self):
        self.server = Server('./browsermob-proxy-2.1.4/bin/browsermob-proxy')
        self.server.start()
        self.proxy = self.server.create_proxy()
        chrome_options = Options()
        chrome_options.add_argument('--ignore-certificate-errors')
        chrome_options.add_argument('--proxy-server={0}'.format(self.proxy.proxy))
        self.browser = webdriver.Chrome(options=chrome_options)

    def process_request(self, request, response):
        pass

    def process_response(self, response, request):
        pass

    def run(self, func, *args):
        self.proxy.new_har(options={
            'captureContent': True,
            'captureHeaders': True
        })
        func(*args)
        result = self.proxy.har
        for entry in result['log']['entries']:
            request = entry['request']
            response = entry['response']
            self.process_request(request, response)
            self.process_response(response, request)

    def __del__(self):
        self.proxy.close()
        self.browser.close()


class MovieFramework(BaseFramework):

    def process_request(self, request, response):
        print(request)

    def process_response(self, response, request):
        if '/api/movie/' in request['url']:
            print(response['content'])
            text = response['content']['text']
            results = json.loads(text)['results']
            for item in results:
                name = item.get('name')
                with open(f'{name}.json', 'w', encoding='utf-8') as f:
                    json.dump(item, f, ensure_ascii=False, indent=2)

    def load(self, url):
        self.browser.get(url)
        time.sleep(3)

if __name__ == '__main__':
    f = MovieFramework()
    for page in range(1, 5):
        url = f'https://dynamic2.scrape.center/page/{page}'
        f.run(f.load, url)

這裏框架寫的很基礎,還有很多需要完善的地方,就只借着這雛形說說大體思路:

  • 這裏我先定義了一個 BaseFramework,就是基礎框架,然後裏面定義了幾個關鍵方法,__init__ 方法不多說了,就是把一些初始化的工作放進去。然後定義了 run 方法,把 HAR 的聲明、訪問、讀取的操作封裝了一下。然後定義了 process_response 和 process_request 的回調,這裏就沒實現任何操作,可以在子類實現。
  • 如果我們要寫一個爬蟲的話,可以新建一個子類來繼承剛纔定義的 BaseFramework,然後可以自己實現 process_request 和 process_response 來處理請求和響應的結果,比如這裏我就實現了一個 MovieFramework,然後實現了 process_response 處理響應信息,裏面判斷了 Ajax 請求的 URL,然後進行了提取和保存處理。裏面 load 方法就是自行定義的,裏面正常定義邏輯即可。
  • 最後運行的時候使用 run 方法運行自定義的 load 方法即可,傳入 load 方法的參數,即可完成頁面的加載。同時加載的過程中 process_response 方法就會被回調,對結果進行處理。這裏我們就提取了 Ajax 數據,然後保存下來了。

最終運行下,我們就可以看到一條條的電影數據就被保存下來了,如圖所示:


是不是方便多了?有了它我們連頁面解析的那一步都直接省略了,直接拿到了原始 Ajax 數據,舒服。

當然上面的框架還有很多很多需要優化的地方,大家可以參考思路自己實現。

總結

本節我們就講解了利用 BrowserMob Proxy 來截獲和處理 Ajax 數據的方法,實現簡單方便。有了這個我們就不需要非得等頁面加載出來之後再根據頁面渲染結果提取信息了,Ajax 請求直接拿原始數據,爽歪歪!

 

 

點擊這裏,瞭解更多精彩內容

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