【摘要】 在前一章中,我們已經成功嘗試分析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,就可以直接訪問https://s.taobao.com/search?q=iPad,呈現的就是第一頁的搜索結果,如圖7-22所示。
圖7-22 搜索結果
在頁面下方,有一個分頁導航,其中既包括前5頁的鏈接,也包括下一頁的鏈接,同時還有一個輸入任意頁碼跳轉的鏈接,如圖7-23所示。
圖7-23 分頁導航
這里商品的搜索結果一般最大都為100頁,要獲取每一頁的內容,只需要將頁碼從1到100順序遍歷即可,頁碼數是確定的。所以,直接在頁面跳轉文本框中輸入要跳轉的頁碼,然后點擊“確定”按鈕即可跳轉到頁碼對應的頁面。
這里不直接點擊“下一頁”的原因是:一旦爬取過程中出現異常退出,比如到50頁退出了,此時點擊“下一頁”時,就無法快速切換到對應的后續頁面了。此外,在爬取過程中,也需要記錄當前的頁碼數,而且一旦點擊“下一頁”之后頁面加載失敗,還需要做異常檢測,檢測當前頁面是加載到了第幾頁。整個流程相對比較復雜,所以這里我們直接用跳轉的方式來爬取頁面。
當我們成功加載出某一頁商品列表時,利用Selenium即可獲取頁面源代碼,然后再用相應的解析庫解析即可。這里我們選用pyquery進行解析。下面我們用代碼來實現整個抓取過程。
5. 獲取商品列表
首先,需要構造一個抓取的URL:https://s.taobao.com/search?q=iPad。這個URL非常簡潔,參數q就是要搜索的關鍵字。只要改變這個參數,即可獲取不同商品的列表。這里我們將商品的關鍵字定義成一個變量,然后構造出這樣的一個URL。
然后,就需要用Selenium進行抓取了。我們實現如下抓取列表頁的方法:
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from 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進行解析,實現如下:
from pyquery import PyQuery as pq
def 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,實現代碼如下:
MONGO_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代表頁碼。這里我們實現頁碼遍歷即可,代碼如下:
MAX_PAGE = 100
def 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模式的方式如下:
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瀏覽器,非常簡單,只需要更改一處即可:
browser = webdriver.Firefox()
這里更改了browser對象的創建方式,這樣爬取的時候就會使用Firefox瀏覽器了。
12. 對接PhantomJS
如果不想使用Chrome的Headless模式,還可以使用PhantomJS(它是一個無界面瀏覽器)來抓取。抓取時,同樣不會彈出窗口,還是只需要將WebDriver的聲明修改一下即可:
browser = webdriver.PhantomJS()
另外,它還支持命令行配置。比如,可以設置緩存和禁用圖片加載的功能,進一步提高爬取效率:
SERVICE_ARGS = ['--load-images=false', '--disk-cache=true']
browser = webdriver.PhantomJS(service_args=SERVICE_ARGS)
最后,給出本節的代碼地址:https://github.com/Python3WebSpider/TaobaoProduct。
本節中,我們用Selenium演示了淘寶頁面的抓取。利用它,我們不用去分析Ajax請求,真正做到可見即可爬。
來源:華為云社區 ?作者:崔慶才丨靜覓
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
