首頁>技術>

Appium+mitmdump 爬取京東商品

在前文中,我們曾經用 Charles 分析過京東商品的評論資料,但是可以發現其引數相當複雜,Form 表單有很多加密引數。如果我們只用 Charles 探測到這個介面連結和引數,還是無法直接構造請求的引數,構造的過程涉及一些加密演算法,也就無法直接還原抓取過程。

我們了解了 mitmproxy 的用法,利用它的 mitmdump 元件,可以直接對接 Python 指令碼對抓取的資料包進行處理,用 Python 指令碼對請求和響應直接進行處理。這樣我們可以繞過請求的引數構造過程,直接監聽響應進行處理即可。但是這個過程並不是自動化的,抓取 App 的時候實際是人工模擬了這個拖動過程。如果這個操作可以用程式來實現就更好了。

我們又了解了 Appium 的用法,它可以指定自動化指令碼模擬實現 App 的一系列動作,如點選、拖動等,也可以提取 App 中呈現的資訊。經過上節爬取微信朋友圈的例項,我們知道解析過程比較煩瑣,而且速度要加以限制。如果內容沒有顯示出來解析就會失敗,而且還會導致重複提取的問題。更重要的是,它只可以獲取在 App 中看到的資訊,無法直接提取介面獲取的真實資料,而介面的資料往往是最易提取且資訊量最全的。

綜合以上幾點,我們就可以確定出一個解決方案了。如果我們用 mitmdump 去監聽介面資料,用 Appium 去模擬 App 的操作,就可以繞過複雜的介面引數又可以實現自動化抓取了!這種方式應是抓取 App 資料的最佳方式。某些特殊情況除外,如微信朋友圈資料又經過了一次加密無法解析,而只能用 Appium 提取。但是對於大多數 App 來說,此種方法是奏效的。本節我們用一個例項感受一下這種抓取方式的便捷之處。

1. 本節目標

以抓取京東 App 的商品資訊和評論為例,實現 Appium 和 mitmdump 二者結合的抓取。抓取的資料分為兩部分:一部分是商品資訊,我們需要獲取商品的 ID、名稱和圖片,將它們組成一條商品資料;另一部分是商品的評論資訊,我們將評論人的暱稱、評論正文、評論日期、發表圖片都提取,然後加入商品 ID 欄位,將它們組成一條評論資料。最後資料儲存到 MongoDB 資料庫。

2. 準備工作

請確保 PC 已經安裝好 Charles、mitmdump、Appium、Android 開發環境,以及 Python 版本的 Appium API。Android 手機安裝好京東 App。另外,安裝好 MongoDB 並執行其服務,安裝 PyMongo 庫。具體的配置過程可以參考第 1 章。

3. Charles 抓包分析

獲取商品詳情的介面,這裡提取到的介面是來自 cdnware.m.jd.com 的連結,返回結果是一個 JSON 字串,裡面包含了商品的 ID 和商品名稱,如圖 11-47 和圖 11-48 所示。

圖 11-47 請求概覽

圖 11-48 響應結果

再獲取商品評論的介面,這個過程在前文已提到,在此不再贅述。這個介面來自 api.m.jd.com,返回結果也是 JSON 字串,裡面包含了商品的數條評論資訊。

之後我們可以用 mitmdump 對接一個 Python 指令碼來實現資料的抓取。

4. mitmdump 抓取

新建一個指令碼檔案,然後實現這個指令碼以提取這兩個介面的資料。首先提取商品的資訊,程式碼如下所示:

def response(flow): url = 'cdnware.m.jd.com' if url in flow.request.url: text = flow.response.text data = json.loads(text) if data.get('wareInfo') and data.get('wareInfo').get('basicInfo'): info = data.get('wareInfo').get('basicInfo') id = info.get('wareId') name = info.get('name') images = info.get('wareImage') print(id, name, images)

這裡聲明了介面的部分連結內容,然後與請求的 URL 作比較。如果該連結出現在當前的 URL 中,那就證明當前的響應就是商品詳情的響應,然後提取對應的 JSON 資訊即可。在這裡我們將商品的 ID、名稱和圖片提取出來,這就是一條商品資料。

# 提取評論資料url = 'api.m.jd.com/client.action'if url in flow.request.url: pattern = re.compile('sku".*?"(d+)"') # Request 請求引數中包含商品 ID body = unquote(flow.request.text) # 提取商品 ID id = re.search(pattern, body).group(1) if re.search(pattern, body) else None # 提取 Response Body text = flow.response.text data = json.loads(text) comments = data.get('commentInfoList') or [] # 提取評論資料 for comment in comments: if comment.get('commentInfo') and comment.get('commentInfo').get('commentData'): info = comment.get('commentInfo') text = info.get('commentData') date = info.get('commentDate') nickname = info.get('userNickName') pictures = info.get('pictureInfoList') print(id, nickname, text, date, pictures)

這裡指定了介面的部分連結內容,以判斷當前請求的 URL 是不是獲取評論的 URL。如果滿足條件,那麼就提取商品的 ID 和評論資訊。

商品的 ID 實際上隱藏在請求中,我們需要提取請求的表單內容來提取商品的 ID,這裡直接用了正則表示式。

商品的評論資訊在響應中,我們像剛才一樣提取了響應的內容,然後對 JSON 進行解析,最後提取出商品評論人的暱稱、評論正文、評論日期和圖片資訊。這些資訊和商品的 ID 組合起來,形成一條評論資料。

最後用 MongoDB 將兩部分資料分開儲存到兩個 Collection,在此不再贅述。

執行此指令碼,命令如下所示:

mitmdump -s script.py

手機的代理設定到 mitmdump 上。我們在京東 App 中開啟某個商品,下拉商品評論部分,即可看到控制檯輸出兩部分的抓取結果,結果成功儲存到 MongoDB 資料庫,如圖 11-49 所示。

圖 11-49 儲存結果

5. Appium 自動化

將 Appium 對接到手機上,用 Appium 驅動 App 完成一系列動作。進入 App 後,我們需要做的操作有點選搜尋框、輸入搜尋的商品名稱、點選進入商品詳情、進入評論頁面、自動滾動重新整理,基本的操作邏輯和爬取微信朋友圈的相同。

京東 App 的 Desired Capabilities 配置如下所示:

{ 'platformName': 'Android', 'deviceName': 'MI_NOTE_Pro', 'appPackage': 'com.jingdong.app.mall', 'appActivity': 'main.MainActivity'}

首先用 Appium 內建的驅動開啟京東 App,如圖 11-50 所示。

圖 11-50 除錯介面

這裡進行一系動作操作並錄製下來,找到各個頁面的元件的 ID 並做好記錄,最後再改寫成完整的程式碼。參考程式碼實現如下所示:

from appium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom time import sleep class Action(): def __init__(self): # 驅動配置 self.desired_caps = { 'platformName': PLATFORM, 'deviceName': DEVICE_NAME, 'appPackage': 'com.jingdong.app.mall', 'appActivity': 'main.MainActivity' } self.driver = webdriver.Remote(DRIVER_SERVER, self.desired_caps) self.wait = WebDriverWait(self.driver, TIMEOUT)  def comments(self): # 點選進入搜尋頁面 search = self.wait.until(EC.presence_of_element_located((By.ID, 'com.jingdong.app.mall:id/mp'))) search.click() # 輸入搜尋文字 box = self.wait.until(EC.presence_of_element_located((By.ID, 'com.jd.lib.search:id/search_box_layout'))) box.set_text(KEYWORD) # 點選搜尋按鈕 button = self.wait.until(EC.presence_of_element_located((By.ID, 'com.jd.lib.search:id/search_btn'))) button.click() # 點選進入商品詳情 view = self.wait.until(EC.presence_of_element_located((By.ID, 'com.jd.lib.search:id/product_list_item'))) view.click() # 進入評論詳情 tab = self.wait.until(EC.presence_of_element_located((By.ID, 'com.jd.lib.productdetail:id/pd_tab3'))) tab.click()  def scroll(self): while True: # 模擬拖動 self.driver.swipe(FLICK_START_X, FLICK_START_Y + FLICK_DISTANCE, FLICK_START_X, FLICK_START_Y) sleep(SCROLL_SLEEP_TIME)  def main(self): self.comments() self.scroll() if __name__ == '__main__': action = Action() action.main()

程式碼實現比較簡單,邏輯與上一節微信朋友圈的抓取類似。注意,由於 App 版本更新的原因,互動流程和元素 ID 可能有更改,這裡的程式碼僅做參考。

下拉過程已經省去了用 Appium 提取資料的過程,因為這個過程我們已經用 mitmdump 幫助實現了。

程式碼執行之後便會啟動京東 App,進入商品的詳情頁,然後進入評論頁再無限滾動,這樣就代替了人工操作。Appium 實現模擬滾動,mitmdump 進行抓取,這樣 App 的資料就會儲存到資料庫中。

  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 自動化運維-centos 8 kickstart系統批量部署