在前一章中,我們已經成功嘗試分析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介面包含幾個引數,其中_ksTS
、rn
引數不能直接發現其規律,如果要去探尋它的生成規律,也不是做不到,但這樣相對會比較煩瑣,所以如果直接用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
節點,包含id
、class
、data-src
、alt
和src
等屬性。這裡之所以可以看到這張圖片,是因為它的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()
方法需要接收引數page
,page
代表頁碼。這裡我們實現頁碼遍歷即可,程式碼如下:
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請求,真正做到可見即可爬。
視訊學習資源:
本書之後的部分內容屬進階內容,暫不開放。
如需檢視更多可以購買電子版或實體書籍檢視。
本書由圖靈教育-人民郵電出版社出版發行。
全書預覽圖:
預覽連結為:
germey.gitbooks.io/python3webs…
書籍購買地址:
本資源首發於崔慶才的個人部落格靜覓: Python3網路爬蟲開發實戰教程 | 靜覓
如想了解更多爬蟲資訊,請關注我的個人微信公眾號:進擊的Coder
weixin.qq.com/r/5zsjOyvEZ… (二維碼自動識別)