python爬蟲實戰(四):selenium爬蟲抓取阿里巴巴採購批發商品
一、前言
二、學習資料(感謝分享)
三、開始爬取
1、先分析目標網址,為什麼選擇selenium
在搜尋中輸入女裝,用F12檢視原始碼,看看網頁顯示的內容是不是Ajax。點選Network,選擇下面的XHR,按F5重新整理頁面,下滑瀏覽器商品頁面
只有兩個Ajax請求,而且Preview裡面並沒有程式碼。瞬間感覺天上掉餡餅了,難道是靜態網頁沒有其他資料請求嗎?如果是這樣直接弄個pyquery解析網頁就是了
然而事實並不是這樣,下滑的時候都能感覺到,靜態網頁是一次性請求完畢,而這個下滑的時候,明顯有新的資料請求,於是再看了一下JS中,果然有資料
如果只是用單純的requests.get()是得不到非同步請求的資料
2、selenium自動化測試,可見即可爬
from selenium import webdriver
browser=webdriver.Chrome()
def crawle():
url='https://www.1688.com/'
browser.get(url=url)
crawle()
執行後就產生了上圖,這個彈出來的框我們要叉掉,可以選擇點選“訪問1688首頁”。為了定位“訪問1688首頁”這個元素,右鍵檢查,可檢視元素
from selenium import webdriver
browser=webdriver.Chrome()
def crawle():
url='https://www.1688.com/'
browser.get(url=url)
button=browser.find_element_by_class_name('identity-cancel')
button.click()
crawle()
可以發現上圖的框沒見了(selenium中元素的定位很重要,有很多,開篇學習資料selenium中有詳細講解)
接下來需要在搜尋框中輸入我們想查詢的資料,如女裝,然後點選搜尋。我們需要做的就是
1、定位搜素框
2、向搜素框中傳入資料
3、定位搜素按鈕,並點選
from selenium import webdriver
browser=webdriver.Chrome()
def crawle():
url='https://www.1688.com/'
browser.get(url=url)
#叉掉首頁彈出大框
button=browser.find_element_by_class_name('identity-cancel')
button.click()
#定位搜尋框
input=browser.find_element_by_id('alisearch-keywords')
input.send_keys('女裝')
#定位搜尋按鈕
sea_button=browser.find_element_by_id('alisearch-submit')
sea_button.click()
crawle()
執行後會跳轉頁面,如下圖,新的頁面中又有框圖,繼續叉掉它
button_1=browser.find_element_by_class_name('s-overlay-close-l')
button_1.click()
接下來我想按照成交量排序,所以定位到成交量,並單擊這個元素
#定位成交量
button_deal=browser.find_elements_by_css_selector('.sm-widget-sort.fd-clr.s-widget-sortfilt li')[1]
button_deal.click()
第一個函式:
from selenium import webdriver
browser=webdriver.Chrome()
def crawle():
url='https://www.1688.com/'
browser.get(url=url)
#叉掉首頁彈出大框
button=browser.find_element_by_class_name('identity-cancel')
button.click()
#定位搜尋框
input=browser.find_element_by_id('alisearch-keywords')
input.send_keys('女裝')
#定位搜尋按鈕
sea_button=browser.find_element_by_id('alisearch-submit')
sea_button.click()
#叉掉框圖
button_1=browser.find_element_by_class_name('s-overlay-close-l')
button_1.click()
#定位成交量
button_deal=browser.find_elements_by_css_selector('.sm-widget-sort.fd-clr.s-widget-sortfilt li')[1]
button_deal.click()
crawle()
3、頁面如願以償地出來了,接下來就該抓取網頁的元素了
在crawl()最後一行呼叫函式get_products()
接下來就在get_products()裡面利用pyquery分析網頁,抓取網頁元素
可以發現每一個商品資訊都在ul下面的li中,我們需要定位到li,然後迴圈訪問每一個li元素。下面主要抓取產品名稱title,產品銷量deal,產品價格price,產品網址url
from selenium import webdriver
browser=webdriver.Chrome()
def crawle():
url='https://www.1688.com/'
browser.get(url=url)
#叉掉首頁彈出大框
button=browser.find_element_by_class_name('identity-cancel')
button.click()
#定位搜尋框
input=browser.find_element_by_id('alisearch-keywords')
input.send_keys('女裝')
#定位搜尋按鈕
sea_button=browser.find_element_by_id('alisearch-submit')
sea_button.click()
#叉掉框圖
button_1=browser.find_element_by_class_name('s-overlay-close-l')
button_1.click()
#定位成交量
button_deal=browser.find_elements_by_css_selector('.sm-widget-sort.fd-clr.s-widget-sortfilt li')[1]
button_deal.click()
get_products()
from pyquery import PyQuery as pq
from bs4 import BeautifulSoup
def get_products():
html=browser.page_source
doc=pq(html)
items=doc('.sm-offer .fd-clr .sm-offer-item').items()
index=0
for item in items:
index+=1
print('*'*50)
title=item.find('.s-widget-offershopwindowtitle').text().split('\n')
title=' '.join(title)
price_a=item.find('.s-widget-offershopwindowprice').text().split('\n')
price=''.join(price_a[:2])
deal=''.join(price_a[2:])
#產品網址
text=item.find('.s-widget-offershopwindowtitle')
soup=BeautifulSoup(str(text),'lxml')
a=soup.select('.s-widget-offershopwindowtitle a')[0]
url=a['href']
print(title)
print(price)
print(deal)
print(url)
print(' (●ˇ∀ˇ●) '*5)
print('一共%d條資料'%index)
crawle()
這個主要就是pyquery的運用
執行後結果如下:
問題來了,明顯這個頁面中不止20條資料。我們可以自己從首頁中搜索女裝,再點選成交量,慢慢向下滑動頁面,會察覺到有些資料是隨著滑動頁面產生新的資料。這說明selenium在載入網頁時,並沒有完全載入完返回。selenium中的顯式等待和隱式等待可以解決這個問題。
顯式等待:讓webdriver等待滿足一定條件以後再進一步執行
隱式等待:讓webdriver等待一定時間後查詢元素
我們選擇顯式等待,並且希望能夠滾動頁面到最底部從而使所有資料加載出來。先手動滑到頁面的底端,可以發現每個li中有一個id,最後的一個id是offer60,可以發現一共就是60個店鋪。
顯示等待,wait會等待id為offer60的元素在15s內返回,否則報錯
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
browser=webdriver.Chrome()
wait=WebDriverWait(browser,15)
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#offer60')))
載入至頁面底端:
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
修改後:
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
browser=webdriver.Chrome()
wait=WebDriverWait(browser,15)
def crawle():
url='https://www.1688.com/'
browser.get(url=url)
#叉掉首頁彈出大框
button=browser.find_element_by_class_name('identity-cancel')
button.click()
#定位搜尋框
input=browser.find_element_by_id('alisearch-keywords')
input.send_keys('女裝')
#定位搜尋按鈕
sea_button=browser.find_element_by_id('alisearch-submit')
sea_button.click()
#叉掉框圖
button_1=browser.find_element_by_class_name('s-overlay-close-l')
button_1.click()
#定位成交量
button_deal=browser.find_elements_by_css_selector('.sm-widget-sort.fd-clr.s-widget-sortfilt li')[1]
button_deal.click()
try:
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#offer60')))
except :
print('*'*30,'超時載入','*'*30,'\n\n\n')
get_products()
from pyquery import PyQuery as pq
from bs4 import BeautifulSoup
def get_products():
html=browser.page_source
doc=pq(html)
items=doc('.sm-offer .fd-clr .sm-offer-item').items()
index=0
for item in items:
index+=1
print('*'*50)
title=item.find('.s-widget-offershopwindowtitle').text().split('\n')
title=' '.join(title)
price_a=item.find('.s-widget-offershopwindowprice').text().split('\n')
price=''.join(price_a[:2])
deal=''.join(price_a[2:])
#產品網址
text=item.find('.s-widget-offershopwindowtitle')
soup=BeautifulSoup(str(text),'lxml')
a=soup.select('.s-widget-offershopwindowtitle a')[0]
url=a['href']
print(title)
print(price)
print(deal)
print(url)
print(' (●ˇ∀ˇ●) '*5)
print('一共%d條資料'%index)
crawle()
執行後:
到處為止,我們已經做到了用selenium自動化開啟網頁,搜尋我們想查詢的資料,然後根據成交量排序抓取資料。
接下來,還有幾點可以改善下:
1、我們想自己在cmd中輸入我想查詢的類別,比如女裝,男裝,內衣等
2、我們不止想抓取第一頁的資料,想自己定義抓取好多頁的資料
3、實現把資料存入到mongodb中
先實現1和2,通過定義main()
def main():
key_words=input('請輸入想查詢的類別:')
page=int(input('你想查詢多少頁的資料:'))
for key in key_words:
crawle(key,page)
在crawle()中就需要新增引數key和page
def crawle(key,page):
url='https://www.1688.com/'
browser.get(url=url)
button=browser.find_element_by_class_name('identity-cancel')
button.click()
input=browser.find_element_by_id('alisearch-keywords')
input.send_keys(key)
sea_button=browser.find_element_by_id('alisearch-submit')
sea_button.click()
button_1=browser.find_element_by_class_name('s-overlay-close-l')
button_1.click()
button_deal=browser.find_elements_by_css_selector('.sm-widget-sort.fd-clr.s-widget-sortfilt li')[1]
button_deal.click()
try:
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#offer60')))
except :
print('*'*30,'超時載入','*'*30,'\n\n\n')
for item in get_products():
save_to_mongo(item,key)
if page>1:
for page in range(2,page+1):
get_more_page(key,page)
當page>1時,我們執行get_more_page(),這個函式會在下圖框中輸入對應頁數,然後跳轉,並呼叫get_products()抓取資料
def get_more_page(key,page):
page_input=browser.find_element_by_class_name('fui-paging-input')
page_input.clear()
page_input.send_keys(page)
button=browser.find_element_by_class_name('fui-paging-btn')
button.click()
try:
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#offer60')))
except :
print('*'*30,'超時載入','*'*30,'\n\n\n')
for item in get_products():
save_to_mongo(item,key)
需要注意的是,我們在最後兩行實現了get_products()和save_to_mongo(), get_products()要想實現這個迴圈需要在get_products()中新增yield 請求
yield{
'title':title,
'deal':deal,
'price':price,
'url':url}
現在,實先儲存到mongodb中,用save_to_mongo()
import pymongo
client=pymongo.MongoClient()
db=client.alibaba
def save_to_mongo(item,key):
#根據關鍵字動態存入相應的表
collection=db[key]
if item:
collection.insert(item)
print('成功儲存到mongo')
這個item也就是get_products()裡面的yield產生的資料
整體程式碼:
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from pyquery import PyQuery as pq
from bs4 import BeautifulSoup
browser=webdriver.Chrome()
wait=WebDriverWait(browser,15)
def crawle(key,page):
url='https://www.1688.com/'
browser.get(url=url)
button=browser.find_element_by_class_name('identity-cancel')
button.click()
input=browser.find_element_by_id('alisearch-keywords')
input.send_keys(key)
sea_button=browser.find_element_by_id('alisearch-submit')
sea_button.click()
button_1=browser.find_element_by_class_name('s-overlay-close-l')
button_1.click()
button_deal=browser.find_elements_by_css_selector('.sm-widget-sort.fd-clr.s-widget-sortfilt li')[1]
button_deal.click()
try:
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#offer60')))
except :
print('*'*30,'超時載入','*'*30,'\n\n\n')
for item in get_products():
save_to_mongo(item,key)
if page>1:
for page in range(2,page+1):
get_more_page(key,page)
def get_more_page(key,page):
page_input=browser.find_element_by_class_name('fui-paging-input')
page_input.clear()
page_input.send_keys(page)
button=browser.find_element_by_class_name('fui-paging-btn')
button.click()
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
try:
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#offer60')))
except :
print('*'*30,'超時載入','*'*30,'\n\n\n')
for item in get_products():
save_to_mongo(item,key)
def get_products():
html=browser.page_source
doc=pq(html)
items=doc('.sm-offer .fd-clr .sm-offer-item').items()
index=0
for item in items:
index+=1
print('*'*50)
title=item.find('.s-widget-offershopwindowtitle').text().split('\n')
title=' '.join(title)
price_a=item.find('.s-widget-offershopwindowprice').text().split('\n')
price=''.join(price_a[:2])
deal=''.join(price_a[2:])
#產品網址
text=item.find('.s-widget-offershopwindowtitle')
soup=BeautifulSoup(str(text),'lxml')
a=soup.select('.s-widget-offershopwindowtitle a')[0]
url=a['href']
print(title)
print(price)
print(deal)
print(url)
yield{
'title':title,
'deal':deal,
'price':price,
'url':url}
print(' (●ˇ∀ˇ●) '*5)
print('一共%d條資料'%index)
import pymongo
client=pymongo.MongoClient()
db=client.alibaba
def save_to_mongo(item,key):
#根據關鍵字動態存入相應的表
collection=db[key]
if item:
collection.insert(item)
print('成功儲存到mongo')
def main():
key_words=input('請輸入想查詢的類別:').split(' ')
page=int(input('你想查詢多少頁的資料:'))
for key in key_words:
crawle(key,page)
main()
最後執行就可以看到資料庫中的資料如圖
咋看之下感覺沒有什麼問題,但是如果我們試著把資料的頁數改為兩頁,仔細觀察控制檯輸出的資訊就會發現兩次打印出的資料都是一樣的,都是第一頁網頁的資訊,這樣肯定不行
解決辦法:要想解決問題,首先得知道問題出在哪裡,各位小夥伴要是自己有興趣,可以嘗試著去解決,下面我寫出我的建議,除錯程式碼,觀察程式碼執行時,chrome瀏覽器確實是跳轉到了第二頁,看我們的get_more_page()函式,裡面有一個execute_script(),它會將網頁滾動到最低
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
然而我們可以發現chrome瀏覽器在跳轉到第二頁的時候並沒有將網頁滑動到最底端,但是又沒有報錯,這說明上面這段程式碼執行了的,但是第二頁確實沒有自動滑動底端,輸出的資料也確實是第一頁的,所以很有可能是瀏覽器響應的速度比程式碼執行的速度快
什麼意思呢?我們的get_more_page()是為了跳轉到其它頁面,在執行
button=browser.find_element_by_class_name('fui-paging-btn')
button.click()
時候,瀏覽器確實跳轉到了指定的頁面,但是browser沒有及時的響應到新的頁面,還停留在上一個頁面,這個時候執行execute_script(),執行get_products()都是在對上一個網頁進行操作,所以我們打印出來的也是上一個頁面的資料,我們只需要讓程式緩幾秒,它便能夠緩過來,加入time.sleep(3)
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from pyquery import PyQuery as pq
from bs4 import BeautifulSoup
import time
browser=webdriver.Chrome()
wait=WebDriverWait(browser,15)
def crawle(key,page):
url='https://www.1688.com/'
browser.get(url=url)
button=browser.find_element_by_class_name('identity-cancel')
button.click()
input=browser.find_element_by_id('alisearch-keywords')
input.send_keys(key)
sea_button=browser.find_element_by_id('alisearch-submit')
sea_button.click()
button_1=browser.find_element_by_class_name('s-overlay-close-l')
button_1.click()
button_deal=browser.find_elements_by_css_selector('.sm-widget-sort.fd-clr.s-widget-sortfilt li')[1]
button_deal.click()
try:
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#offer60')))
except :
print('*'*30,'超時載入','*'*30,'\n\n\n')
for item in get_products():
save_to_mongo(item,key)
if page>1:
for page in range(2,page+1):
get_more_page(key,page)
def get_more_page(key,page):
page_input=browser.find_element_by_class_name('fui-paging-input')
page_input.clear()
page_input.send_keys(page)
button=browser.find_element_by_class_name('fui-paging-btn')
button.click()
time.sleep(3)
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
try:
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#offer60')))
except :
print('*'*30,'超時載入','*'*30,'\n\n\n')
for item in get_products():
save_to_mongo(item,key)
def get_products():
html=browser.page_source
doc=pq(html)
items=doc('.sm-offer .fd-clr .sm-offer-item').items()
index=0
for item in items:
index+=1
print('*'*50)
title=item.find('.s-widget-offershopwindowtitle').text().split('\n')
title=' '.join(title)
price_a=item.find('.s-widget-offershopwindowprice').text().split('\n')
price=''.join(price_a[:2])
deal=''.join(price_a[2:])
#產品網址
text=item.find('.s-widget-offershopwindowtitle')
soup=BeautifulSoup(str(text),'lxml')
a=soup.select('.s-widget-offershopwindowtitle a')[0]
url=a['href']
print(title)
print(price)
print(deal)
print(url)
yield{
'title':title,
'deal':deal,
'price':price,
'url':url}
print(' (●ˇ∀ˇ●) '*5)
print('一共%d條資料'%index)
import pymongo
client=pymongo.MongoClient()
db=client.alibaba
def save_to_mongo(item,key):
#根據關鍵字動態存入相應的表
collection=db[key]
if item:
collection.insert(item)
print('成功儲存到mongo')
def main():
key_words=input('請輸入想查詢的類別:').split(' ')
page=int(input('你想查詢多少頁的資料:'))
for key in key_words:
crawle(key,page)
main()
這個時候再執行發現輸出時不再是重複的內容。
然後,還是沒有結束,我們的main()執行時,key_words是個列表,用空格隔開,但是我們再輸入一種以上的類別時,便報錯了
顯示找不到元素,哪個元素呢?通過錯誤提示可以看出是找不到下圖中的“訪問1688首頁”這個元素,chrome在執行的時候,我們可以觀察到只有第一次搜尋的時候才會出現下圖,以後不會出現,我們只需在執行的時候加入try…except
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from pyquery import PyQuery as pq
from bs4 import BeautifulSoup
import time
browser=webdriver.Chrome()
wait=WebDriverWait(browser,15)
def crawle(key,page):
url='https://www.1688.com/'
browser.get(url=url)
try:
button=browser.find_element_by_class_name('identity-cancel')
button.click()
except:
pass
input=browser.find_element_by_id('alisearch-keywords')
input.send_keys(key)
sea_button=browser.find_element_by_id('alisearch-submit')
sea_button.click()
try:
button_1=browser.find_element_by_class_name('s-overlay-close-l')
button_1.click()
except:
pass
button_deal=browser.find_elements_by_css_selector('.sm-widget-sort.fd-clr.s-widget-sortfilt li')[1]
button_deal.click()
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
try:
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#offer60')))
except :
print('*'*30,'超時載入','*'*30,'\n\n\n')
for item in get_products():
save_to_mongo(item,key)
if page>1:
for page in range(2,page+1):
get_more_page(key,page)
def get_more_page(key,page):
page_input=browser.find_element_by_class_name('fui-paging-input')
page_input.clear()
page_input.send_keys(page)
button=browser.find_element_by_class_name('fui-paging-btn')
button.click()
time.sleep(3)
browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
try:
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#offer60')))
except :
print('*'*30,'超時載入','*'*30,'\n\n\n')
for item in get_products():
save_to_mongo(item,key)
def get_products():
html=browser.page_source
doc=pq(html)
items=doc('.sm-offer .fd-clr .sm-offer-item').items()
index=0
for item in items:
index+=1
print('*'*50)
title=item.find('.s-widget-offershopwindowtitle').text().split('\n')
title=' '.join(title)
price_a=item.find('.s-widget-offershopwindowprice').text().split('\n')
price=''.join(price_a[:2])
deal=''.join(price_a[2:])
#產品網址
text=item.find('.s-widget-offershopwindowtitle')
soup=BeautifulSoup(str(text),'lxml')
a=soup.select('.s-widget-offershopwindowtitle a')[0]
url=a['href']
print(title)
print(price)
print(deal)
print(url)
yield{
'title':title,
'deal':deal,
'price':price,
'url':url}
print(' (●ˇ∀ˇ●) '*5)
print('一共%d條資料'%index)
import pymongo
client=pymongo.MongoClient()
db=client.alibaba
def save_to_mongo(item,key):
#根據關鍵字動態存入相應的表
collection=db[key]
if item:
collection.insert(item)
print('成功儲存到mongo')
def main():
key_words=input('請輸入想查詢的類別(ps:):').split(' ')
page=int(input('你想查詢多少頁的資料:'))
for key in key_words:
time.sleep(3)
crawle(key,page)
main()