[Python3網路爬蟲開發實戰] 7-動態渲染頁面爬取-4-使用Selenium爬取淘寶商品

崔慶才丨靜覓發表於2018-03-30

在前一章中,我們已經成功嘗試分析Ajax來抓取相關資料,但是並不是所有頁面都可以通過分析Ajax來完成抓取。比如,淘寶,它的整個頁面資料確實也是通過Ajax獲取的,但是這些Ajax介面引數比較複雜,可能會包含加密金鑰等,所以如果想自己構造Ajax引數,還是比較困難的。對於這種頁面,最方便快捷的抓取方法就是通過Selenium。本節中,我們就用Selenium來模擬瀏覽器操作,抓取淘寶的商品資訊,並將結果儲存到MongoDB。

1. 本節目標

本節中,我們要利用Selenium抓取淘寶商品並用pyquery解析得到商品的圖片、名稱、價格、購買人數、店鋪名稱和店鋪所在地資訊,並將其儲存到MongoDB。

2. 準備工作

本節中,我們首先以Chrome為例來講解Selenium的用法。在開始之前,請確保已經正確安裝好Chrome瀏覽器並配置好了ChromeDriver;另外,還需要正確安裝Python的Selenium庫;最後,還對接了PhantomJS和Firefox,請確保安裝好PhantomJS和Firefox並配置好了GeckoDriver。如果環境沒有配置好,可參考第1章。

3. 介面分析

首先,我們來看下淘寶的介面,看看它比一般Ajax多了怎樣的內容。

開啟淘寶頁面,搜尋商品,比如iPad,此時開啟開發者工具,截獲Ajax請求,我們可以發現獲取商品列表的介面,如圖7-19所示。

圖7-19 列表介面

它的連結包含了幾個GET引數,如果要想構造Ajax連結,直接請求再好不過了,它的返回內容是JSON格式,如圖7-20所示。

圖7-20 JSON資料

但是這個Ajax介面包含幾個引數,其中_ksTSrn引數不能直接發現其規律,如果要去探尋它的生成規律,也不是做不到,但這樣相對會比較煩瑣,所以如果直接用Selenium來模擬瀏覽器的話,就不需要再關注這些介面引數了,只要在瀏覽器裡面可以看到的,都可以爬取。這也是我們選用Selenium爬取淘寶的原因。

4. 頁面分析

本節的目標是爬取商品資訊。圖7-21是一個商品條目,其中包含商品的基本資訊,包括商品圖片、名稱、價格、購買人數、店鋪名稱和店鋪所在地,我們要做的就是將這些資訊都抓取下來。

圖7-21 商品條目

抓取入口就是淘寶的搜尋頁面,這個連結可以通過直接構造引數訪問。例如,如果搜尋iPad,就可以直接訪問s.taobao.com/search?q=iP…,呈現的就是第一頁的搜尋結果,如圖7-22所示。

圖7-22 搜尋結果

在頁面下方,有一個分頁導航,其中既包括前5頁的連結,也包括下一頁的連結,同時還有一個輸入任意頁碼跳轉的連結,如圖7-23所示。

圖7-23 分頁導航

這裡商品的搜尋結果一般最大都為100頁,要獲取每一頁的內容,只需要將頁碼從1到100順序遍歷即可,頁碼數是確定的。所以,直接在頁面跳轉文字框中輸入要跳轉的頁碼,然後點選“確定”按鈕即可跳轉到頁碼對應的頁面。

這裡不直接點選“下一頁”的原因是:一旦爬取過程中出現異常退出,比如到50頁退出了,此時點選“下一頁”時,就無法快速切換到對應的後續頁面了。此外,在爬取過程中,也需要記錄當前的頁碼數,而且一旦點選“下一頁”之後頁面載入失敗,還需要做異常檢測,檢測當前頁面是載入到了第幾頁。整個流程相對比較複雜,所以這裡我們直接用跳轉的方式來爬取頁面。

當我們成功載入出某一頁商品列表時,利用Selenium即可獲取頁面原始碼,然後再用相應的解析庫解析即可。這裡我們選用pyquery進行解析。下面我們用程式碼來實現整個抓取過程。

5. 獲取商品列表

首先,需要構造一個抓取的URL:s.taobao.com/search?q=iP…。這個URL非常簡潔,引數q就是要搜尋的關鍵字。只要改變這個引數,即可獲取不同商品的列表。這裡我們將商品的關鍵字定義成一個變數,然後構造出這樣的一個URL。

然後,就需要用Selenium進行抓取了。我們實現如下抓取列表頁的方法:

12345678910111213141516171819202122232425262728293031323334from selenium import webdriverfrom selenium.common.exceptions import TimeoutExceptionfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.support.wait import WebDriverWaitfrom urllib.parse import quote browser = webdriver.Chrome()wait = WebDriverWait(browser, 10)KEYWORD = 'iPad' def index_page(page):    """    抓取索引頁    :param page: 頁碼    """    print('正在爬取第', page, '頁')    try:        url = 'https://s.taobao.com/search?q=' + quote(KEYWORD)        browser.get(url)        if page > 1:            input = wait.until(                EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input')))            submit = wait.until(                EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit')))            input.clear()            input.send_keys(page)            submit.click()        wait.until(            EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#mainsrp-pager li.item.active > span'), str(page)))        wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item')))        get_products()    except TimeoutException:        index_page(page)複製程式碼

這裡首先構造了一個WebDriver物件,使用的瀏覽器是Chrome,然後指定一個關鍵詞,如iPad,接著定義了index_page()方法,用於抓取商品列表頁。

在該方法裡,我們首先訪問了搜尋商品的連結,然後判斷了當前的頁碼,如果大於1,就進行跳頁操作,否則等待頁面載入完成。

等待載入時,我們使用了WebDriverWait物件,它可以指定等待條件,同時指定一個最長等待時間,這裡指定為最長10秒。如果在這個時間內成功匹配了等待條件,也就是說頁面元素成功載入出來了,就立即返回相應結果並繼續向下執行,否則到了最大等待時間還沒有載入出來時,就直接丟擲超時異常。

比如,我們最終要等待商品資訊載入出來,就指定了presence_of_element_located這個條件,然後傳入了.m-itemlist .items .item這個選擇器,而這個選擇器對應的頁面內容就是每個商品的資訊塊,可以到網頁裡面檢視一下。如果載入成功,就會執行後續的get_products()方法,提取商品資訊。

關於翻頁操作,這裡首先獲取頁碼輸入框,賦值為input,然後獲取“確定”按鈕,賦值為submit,分別是圖7-24中的兩個元素。

圖7-24 跳轉選項

首先,我們清空了輸入框,此時呼叫clear()方法即可。隨後,呼叫send_keys()方法將頁碼填充到輸入框中,然後點選“確定”按鈕即可。

那麼,怎樣知道有沒有跳轉到對應的頁碼呢?我們可以注意到,成功跳轉某一頁後,頁碼都會高亮顯示,如圖7-25所示。

圖7-25 頁碼高亮顯示

我們只需要判斷當前高亮的頁碼數是當前的頁碼數即可,所以這裡使用了另一個等待條件text_to_be_present_in_element,它會等待指定的文字出現在某一個節點裡面時即返回成功。這裡我們將高亮的頁碼節點對應的CSS選擇器和當前要跳轉的頁碼通過引數傳遞給這個等待條件,這樣它就會檢測當前高亮的頁碼節點是不是我們傳過來的頁碼數,如果是,就證明頁面成功跳轉到了這一頁,頁面跳轉成功。

這樣剛才實現的index_page()方法就可以傳入對應的頁碼,待載入出對應頁碼的商品列表後,再去呼叫get_products()方法進行頁面解析。

6. 解析商品列表

接下來,我們就可以實現get_products()方法來解析商品列表了。這裡我們直接獲取頁面原始碼,然後用pyquery進行解析,實現如下:

12345678910111213141516171819from pyquery import PyQuery as pqdef get_products():    """    提取商品資料    """    html = browser.page_source    doc = pq(html)    items = doc('#mainsrp-itemlist .items .item').items()    for item in items:        product = {            'image': item.find('.pic .img').attr('data-src'),            'price': item.find('.price').text(),            'deal': item.find('.deal-cnt').text(),            'title': item.find('.title').text(),            'shop': item.find('.shop').text(),            'location': item.find('.location').text()        }        print(product)        save_to_mongo(product)複製程式碼

首先,呼叫page_source屬性獲取頁碼的原始碼,然後構造了PyQuery解析物件,接著提取了商品列表,此時使用的CSS選擇器是#mainsrp-itemlist .items .item,它會匹配整個頁面的每個商品。它的匹配結果是多個,所以這裡我們又對它進行了一次遍歷,用for迴圈將每個結果分別進行解析,每次迴圈把它賦值為item變數,每個item變數都是一個PyQuery物件,然後再呼叫它的find()方法,傳入CSS選擇器,就可以獲取單個商品的特定內容了。

比如,檢視一下商品資訊的原始碼,如圖7-26所示。

圖7-26 商品資訊原始碼

可以發現,它是一個img節點,包含idclassdata-srcaltsrc等屬性。這裡之所以可以看到這張圖片,是因為它的src屬性被賦值為圖片的URL。把它的src屬性提取出來,就可以獲取商品的圖片了。不過我們還注意data-src屬性,它的內容也是圖片的URL,觀察後發現此URL是圖片的完整大圖,而src是壓縮後的小圖,所以這裡抓取data-src屬性來作為商品的圖片。

因此,我們需要先利用find()方法找到圖片的這個節點,然後再呼叫attr()方法獲取商品的data-src屬性,這樣就成功提取了商品圖片連結。然後用同樣的方法提取商品的價格、成交量、名稱、店鋪和店鋪所在地等資訊,接著將所有提取結果賦值為一個字典product,隨後呼叫save_to_mongo()將其儲存到MongoDB即可。

7. 儲存到MongoDB

接下來,我們將商品資訊儲存到MongoDB,實現程式碼如下:

123456789101112131415MONGO_URL = 'localhost'MONGO_DB = 'taobao'MONGO_COLLECTION = 'products'client = pymongo.MongoClient(MONGO_URL)db = client[MONGO_DB]def save_to_mongo(result):    """    儲存至MongoDB    :param result: 結果    """    try:        if db[MONGO_COLLECTION].insert(result):            print('儲存到MongoDB成功')    except Exception:        print('儲存到MongoDB失敗')複製程式碼

這裡首先建立了一個MongoDB的連線物件,然後指定了資料庫,隨後指定了Collection的名稱,接著直接呼叫insert()方法將資料插入到MongoDB。此處的result變數就是在get_products()方法裡傳來的product,包含單個商品的資訊。

8. 遍歷每頁

剛才我們所定義的get_index()方法需要接收引數pagepage代表頁碼。這裡我們實現頁碼遍歷即可,程式碼如下:

1234567MAX_PAGE = 100def main():    """    遍歷每一頁    """    for i in range(1, MAX_PAGE + 1):        index_page(i)複製程式碼

其實現非常簡單,只需要呼叫一個for迴圈即可。這裡定義最大的頁碼數為100,range()方法的返回結果就是1到100的列表,順序遍歷,呼叫index_page()方法即可。

這樣我們的淘寶商品爬蟲就完成了,最後呼叫main()方法即可執行。

9. 執行

執行程式碼,可以發現首先會彈出一個Chrome瀏覽器,然後會訪問淘寶頁面,接著控制檯便會輸出相應的提取結果,如圖7-27所示。

圖7-27 執行結果

可以發現,這些商品資訊的結果都是字典形式,它們被儲存到MongoDB裡面。

再看一下MongoDB中的結果,如圖7-28所示。

圖7-28 儲存結果

可以看到,所有的資訊都儲存到MongoDB裡了,這說明爬取成功。

10. Chrome Headless模式

從Chrome 59版本開始,已經開始支援Headless模式,也就是無介面模式,這樣爬取的時候就不會彈出瀏覽器了。如果要使用此模式,請把Chrome升級到59版本及以上。啟用Headless模式的方式如下:

1
2
3
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
browser = webdriver.Chrome(chrome_options=chrome_options)

首先,建立ChromeOptions物件,接著新增headless引數,然後在初始化Chrome物件的時候通過chrome_options傳遞這個ChromeOptions物件,這樣我們就可以成功啟用Chrome的Headless模式了。

11. 對接Firefox

要對接Firefox瀏覽器,非常簡單,只需要更改一處即可:

1
browser = webdriver.Firefox()

這裡更改了browser物件的建立方式,這樣爬取的時候就會使用Firefox瀏覽器了。

12. 對接PhantomJS

如果不想使用Chrome的Headless模式,還可以使用PhantomJS(它是一個無介面瀏覽器)來抓取。抓取時,同樣不會彈出視窗,還是隻需要將WebDriver的宣告修改一下即可:

1
browser = webdriver.PhantomJS()

另外,它還支援命令列配置。比如,可以設定快取和禁用圖片載入的功能,進一步提高爬取效率:

1
2
SERVICE_ARGS = ['--load-images=false', '--disk-cache=true']
browser = webdriver.PhantomJS(service_args=SERVICE_ARGS)

最後,給出本節的程式碼地址:github.com/Python3WebS…

本節中,我們用Selenium演示了淘寶頁面的抓取。利用它,我們不用去分析Ajax請求,真正做到可見即可爬。


視訊學習資源:


本書之後的部分內容屬進階內容,暫不開放。

如需檢視更多可以購買電子版或實體書籍檢視。

本書由圖靈教育-人民郵電出版社出版發行。

全書預覽圖:

[Python3網路爬蟲開發實戰] 7-動態渲染頁面爬取-4-使用Selenium爬取淘寶商品

預覽連結為:

germey.gitbooks.io/python3webs…

書籍購買地址:

item.jd.com/26114674847…

item.jd.com/26124473455…


本資源首發於崔慶才的個人部落格靜覓: Python3網路爬蟲開發實戰教程 | 靜覓

如想了解更多爬蟲資訊,請關注我的個人微信公眾號:進擊的Coder

weixin.qq.com/r/5zsjOyvEZ… (二維碼自動識別)


相關文章