這篇文章同樣的還是轉載崔大的,因為都是一個系列的,所以我就轉載出來了,我覺得很實用。原文鏈接:點我
以下內容為原文。
之前我們介紹了 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 數據,舒服。
當然上面的框架還有很多很多需要優化的地方,大家可以參考思路自己實現。