三、 使用 Selenium 爬取淘寶商品
在分析 Ajax 抓取相關數據時,不是所有頁面都可以通過分析 Ajax 來完成抓取。比如淘寶的整個頁面數據確實是通過 Ajax 獲取的,但這些 Ajax 接口參數復雜,並且包含有加密密鑰等,如果要構造 Ajax 參數是很困難。像這種頁面最方便的抓取方法是通過 Selenium 。接下就用 Selenium 模擬瀏覽器操作,抓取淘寶的商品信息,並用 pyquery 解析到商品的圖片、名稱、價格、購買人數、店鋪名稱和店鋪所的地信息,並將結果保存到 MongoDB。
接下來需要用到的軟件和庫有:
Chrome瀏覽器軟件
ChromeDriver驅動
Firefox瀏覽器並配置相應的驅動 GeckoDriver。
PhantomJS:一個無界面、可腳本編程的 webkit 瀏覽器引擎,支持多種 web 標准:DOM 操作, CSS 選擇器,JSON、Canvas以及SVG。
Python 的 Selenium 庫。
1、 接口分析
先分析淘寶的接口,看下它比一般的 Ajax 多了什么內容。進入淘寶首頁,搜索商品,搜索 華為P20,進入開發者工具,未發現淘寶頁面的 Ajax 請求。
2、 頁面分析
這次爬取的是商品信息,包括商品圖片、名稱、價格、購買人數、店鋪名稱和店鋪所在地等信息。抓取入口是淘寶的搜索頁面,這個連接可以通過直接構造參數訪問。例如搜索 華為P20,可直接訪問 https://s.taobao.com/search?q=華為P20 ,展示的是第一頁的搜索結果。在頁面下方有分布導航,包括了前5頁的連接,也包括下一頁的連接,同時還有一個輸入任意頁面跳轉的連接。如圖3-1 所示。

圖3-1 分頁導航
可以看出搜索結果最大為 100 頁,任意頁面輸入框的值默認是第2頁。要獲取每一頁的內容,將頁碼從1 到 100 順序遍歷即可,頁碼數是確定的。可直接在頁面跳轉文本框中輸入要跳轉的頁碼,點擊“確定”可跳轉到頁碼對應的頁面。
這里沒有點擊下一頁來獲取,是因為爬取過程中出現異常退出時,此時點擊“下一頁”就不能切換到對應的后續頁面。因此在爬取過程中還要記錄當前頁碼數,如果點擊下一頁后加載失敗,還要做異常檢測,檢測當前頁面是加載到第幾頁。整個流程相對復雜,所以直接用跳轉方式爬取頁面。
成功加載一頁商品列表時,利用 Selenium 來獲取頁面源代碼,再利用相應的解析庫解析,這里使用 pyqeury 解析。
3、 獲取商品列表
首先構造要抓取的URL:https://s.taobao.com/search?q=華為P20。參數 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() # 初始化 Chrome瀏覽器對象
wait = WebDriverWait(browser, 10) # 設置等待時間10秒
KEYWORD = '華為P20' # 設置搜索關鍵字
def index_page(page):
"""
抓取索引面
:param page: 頁碼
"""
print('正在爬取第', page, '頁')
try:
url = 'https://s.taobao.com/search?q=' + quote(KEYWORD) # 構造 URL
browser.get(url) # 連接 URL,首先訪問搜索商品的連接,判斷頁碼是否大於 1,
if page > 1:
input = wait.until(
# 使用 CSS 選擇器獲取下面的 input標簽,也就是頁碼輸入框
EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input'))
)
submit = wait.until(
# 使用 CSS 選擇器獲取到 span標簽的“確定”按鈕
EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit'))
)
input.clear() # 清除輸入框的默認值
input.send_keys(page) # 給輸入框輸入要獲取的頁碼
submit.click() # 輸入頁碼后點擊提交
# page等於1或者大於1都執行下面的代碼
#EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#mainsrp-pager li.item.active > span"), str(page))
obj = EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#mainsrp-pager li.item.active > span"), str(page))
print(obj)
wait.until(
# 使用 CSS 選擇器獲取當前 span 標簽中的頁碼,並讓頁面與 page 的頁碼比較
EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#mainsrp-page li.item.active > span"), str(page))
)
# 等待商品信息加載出來,並使用選擇器選擇商品信息,調用 get_products() 方法提取商品信息,
# 如果不能正常加載就報 TimeoutException 異常
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item')))
get_products()
except TimeoutException:
#index_page(page)
print("請求超時")
上面這段代碼首先使用 Chrome 瀏覽器構造一個 WebDriver 對象,接着指定一個關鍵詞 華為P20,接着定義 index_page() 方法,用於抓取商品列表頁。在 index_page() 方法中先訪問搜索商品的鏈接,然后進行判斷當前的頁碼,如果大於1就進行跳轉頁操作,否則等待頁面加載完成。
等待加載時,使用了 WebDriverWait 對象,並指定等待條件,同時指定一個最長等待時間,這里是 10 秒。在這個時間內成功匹配等待條件,就說明頁面元素成功加載出來,接着返回相應結果並繼續向下執行,否則到了最大等待時間還沒有加載出來,就直接拋出超時異常。
要等待商品信息加載出來,可指定 presence_of_element_located 這個條件,然后傳入 .m-itemlist .items .item 這個CSS選擇器,選擇器對應的內容就是每個商品的信息塊,可以網頁源代碼上查看。加載成功就執行后面的 get_products() 方法,提取商品信息。
對於翻頁操作,先獲取頁碼輸入框,賦值為 input,接着獲取“確定”按鈕,賦值為 submit。接着調用 clear() 方法清空輸入框,再調用 send_keys() 方法將頁碼填充到輸入框中點擊“確定”按鈕即可。成功跳轉后頁碼會高亮顯示,只要對當前高亮的頁碼判斷是當前的頁碼數即可,所以使用了另一外等待條件 text_to_be_present_in_element,它會等待指定的文本出現在某一個節點里面時即返回成功。將高亮的頁碼節點對應的 CSS 選擇器和當前要跳轉的頁碼通過參數傳遞給這個等待條件,這樣它就會檢測當前高亮的頁面節點是不是本次傳過來的,如果是就證明頁面跳轉到了這一頁,頁面就跳轉成功。
這樣在 index_page() 方法中可傳入對應的頁碼,待加載出對應的頁碼商品列表后,再調用 get_products() 方法進行頁面解析。
4、 解析商品列表
下面就使用 get_products() 方法對商品列表進行解析。直接獲取頁面源代碼用 pyquery 進行解析。代碼如下:
from pyquery import PyQuery as pq
def get_products():
"""
提取商品數據
"""
html = browser.page_source # 獲取網頁源代碼
doc = pq(html) # 構造 pyquery 對象
# 使用 CSS 選擇器獲取商品信息列表,匹配的是整個頁面的每個商品,匹配結果有多個
items = doc("#mainsrp-itemlist .items .item").items()
for item in items:
# 遍歷每個商品,每一個 item 變量都是 pyquery 對象,調用它的 find() 方法,使用 CSS 選擇器獲取單個商品特定內容
product = {
'image': item.find('.pic .img').attr('data-src'),
'price': item.find('.price').text().replace("\n",""),
'deal': item.find('.deal-cnt').text(), # 購買人數
'title': item.find('.title').text().strip(), # 商品描述信息
'shop': item.find('.shop').text(), # 商家信息
'location': item.find('.location').text() # 商家所在地區
}
print(product)
#save_to_mongo(product) # 將獲取到的商品信息保存到 Mongodb
先來看一下商品信息的源碼,如圖3-2所示。

圖3-2 商品信息源碼
由圖3-2 可知,它是一個 img 節點,包含id、class、data-src、alt和 src 等屬性。src屬性是圖片的URL,此外還有 data-src 屬性,也是圖片的URL,不過它是完整的大圖,src 是壓縮后的小圖,這里獲取 data-src 屬性來作為商品的圖片。因此,先用 find()方法找到圖片節點,接着調用 attr() 方法獲取商品的 data-src 屬性,這樣就提取到商品圖片連接。用同樣的方法提取商品中的價格、成交量、名稱、店鋪所在地等信息。將提取結果賦值為 product 字典,接着調用 save_to_mongo() 方法將其保存到 MongoDB。
5、 保存到 MongoDB
將商品信息保存到 MongoDB的源代碼如下:
import pymongo
MONGO_URL = 'localhost'
MONGO_DB = 'taobao'
MONGO_COLLECTION = 'products_hwP20'
client = pymongo.MongoClient(MONGO_URL) # 連接 MongoDB
db = client[MONGO_DB] # 指定數據庫
def save_to_mongo(result):
"""
保存到 MongoDB
:param result: 結果
"""
try:
# 指定 Collection 名稱,並將數據插入到 MongoDB
if db[MONGO_COLLECTION].insert(result):
print('存儲到 MongoDB 成功')
except Exception:
print('存儲到 MongoDB 失敗')
6、 遍歷每頁和運行
前面定義的 get_index() 方法需要接收參數 page,使用 for 循環對頁碼遍歷,傳遞 page 參數即可。代碼如下所示:
MAX_PAGE = 100 # 定義最大訪問的頁面數
def main():
"""
遍歷每一頁
"""
for i in range(1, MAX_PAGE + 1):
index_page(i)
browser.close()
if __name__ == '__main__':
main()
成功運行代碼的話,所有的信息都會保存到 MongoDB里面。
7、 Chrome Headless 模式
Chrome 最新的版本(從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 模式。
要使用 Firefox 瀏覽器,只需要改這一處即可:
browser = webdriver.Firefox()
還可以使用 PhantomJS 的無界面瀏覽器抓取數據,同樣不彈出窗口,只需要將 WebDriver 的聲明修改如下即可:
browser = webdriver.PhantomJS()
PhantomJS 還支持命令行配置,例如,可以設置緩存和禁用圖片加載功能來提高爬取效率:
SERVICE_ARGS = ['--load-images=false', '--disk-cache=true']
browser = webdriver.PhantomJS(service_args=SERVICE_ARGS)
最后,完整的代碼如下所示:
import pymongo
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
from pyquery import PyQuery as pq
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
browser = webdriver.Chrome(chrome_options=chrome_options) # 初始化 Chrome瀏覽器對象
#browser = webdriver.Chrome() # 初始化 Chrome瀏覽器對象
#browser = webdriver.Firefox() # 初始化 Firefox瀏覽器對象
# SERVICE_ARGS = ['--load-images=false', '--disk-cache=true']
# browser = webdriver.PhantomJS(service_args=SERVICE_ARGS)
wait = WebDriverWait(browser, 10) # 設置等待時間10秒
KEYWORD = '華為P20' # 設置搜索關鍵字
def index_page(page):
"""
抓取索引面
:param page: 頁碼
"""
print('正在爬取第', page, '頁')
try:
url = 'https://s.taobao.com/search?q=' + quote(KEYWORD) + '&imgfile=&js=1&stats_click=search_radio_all%3A1&initiative_id=staobaoz_20190329&ie=utf8' # 構造 URL
#url = 'https://s.taobao.com/search?q=' + KEYWORD # 構造 URL
browser.get(url) # 連接 URL,首先訪問搜索商品的連接,判斷頁碼是否大於 1,
if page > 1:
input = wait.until(
# 使用 CSS 選擇器獲取下面的 input標簽,也就是頁碼輸入框
EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input'))
)
submit = wait.until(
# 使用 CSS 選擇器獲取到 span標簽的“確定”按鈕
EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit'))
)
input.clear() # 清除輸入框的默認值
input.send_keys(page) # 給輸入框輸入要獲取的頁碼
submit.click() # 輸入頁碼后點擊提交
# page等於1或者大於1都執行下面的代碼
#EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#mainsrp-pager li.item.active > span"), str(page))
#obj = EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#mainsrp-pager li.item.active > span"), str(page))
#print(obj)
wait.until(
# 使用 CSS 選擇器獲取當前 span 標簽中的頁碼,並讓頁面與 page 的頁碼比較
EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#mainsrp-pager li.item.active > span"), str(page))
)
# 等待商品信息加載出來,並使用選擇器選擇商品信息,調用 get_products() 方法提取商品信息,如果不能正常加載就報 TimeoutException 異常
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item')))
get_products()
except TimeoutException:
#index_page(page)
print("請求超時")
def get_products():
"""
提取商品數據
"""
html = browser.page_source # 獲取網頁源代碼
doc = pq(html) # 轉為 pyquery 對象
# 使用 CSS 選擇器獲取商品信息列表,匹配的是整個頁面的每個商品,匹配結果有多個
items = doc("#mainsrp-itemlist .items .item").items()
for item in items:
# 遍歷每個商品,每一個 item 變量都是 pyquery 對象,調用它的 find() 方法,使用 CSS 選擇器獲取單個商品特定內容
product = {
'image': item.find('.pic .img').attr('data-src'),
'price': item.find('.price').text().replace("\n",""),
'deal': item.find('.deal-cnt').text(), # 購買人數
'title': item.find('.title').text().strip(), # 商品描述信息
'shop': item.find('.shop').text(), # 商家信息
'location': item.find('.location').text() # 商家所在地區
}
print(product)
save_to_mongo(product) # 將獲取到的商品信息保存到 Mongodb
# 將商品信息保存到 MongoDB
MONGO_URL = 'localhost'
MONGO_DB = 'taobao'
MONGO_COLLECTION = 'products_hwP20'
client = pymongo.MongoClient(MONGO_URL) # 連接 MongoDB
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 失敗')
MAX_PAGE = 100 # 定義最大訪問的頁面數
def main():
"""
遍歷每一頁
"""
for i in range(1, MAX_PAGE + 1):
index_page(i)
browser.close()
if __name__ == '__main__':
main()