在我們的爬蟲世界中,scrapy和selenium都是必不可少的神器,而且它們經(jīng)常會在一起使用。但是,如何在scrapy中使用selenium呢?我想有很多小伙伴都遇到過這個問題(因為我也遇到過),那么,接下來就讓我們一起來學(xué)一學(xué)這個操作。
首先我們從豆瓣閱讀這個案例說起 網(wǎng)址:https://read.douban.com/charts?dcs=original-featured&dcm=normal-nav
我們需要做的需求為:獲取每一個排行榜中的內(nèi)容,每個排行里面只有10條數(shù)據(jù)

通過分析發(fā)現(xiàn),我們需要提取的數(shù)據(jù)是通過Ajax加載的,對比其url如下(在此只列舉以下三個,分析其他的url也可以):
長篇連載榜:https://read.douban.com/j/index//charts?type=unfinished_column&index=featured&verbose=1
中篇榜:https://read.douban.com/j/index//charts?type=intermediate_finalized&index=featured&verbose=1
長篇推薦票月榜:https://read.douban.com/j/index//charts?type=most_voted_column&index=featured&verbose=1
對比發(fā)現(xiàn):在每個url中只有type和index的值不同,其余全部相同。所以只要我們找到了type和index值的生成規(guī)律,就可以動態(tài)的改變其值,從而達到我們的目的。
這時我們冷靜的一想,要想發(fā)起Ajax請求,就必須要先點擊我們需要的那個榜單(即長篇連載榜、中篇榜等等),在我們點擊的時候發(fā)起了Ajax請求,那么這個值肯定就與這個有關(guān)了,通過f12我們發(fā)現(xiàn),該a標(biāo)簽里面的href屬性中有type和index的值,而且和我們發(fā)起Ajax請求的url里面的值相同,這正好驗證了我們的想法

此時的我們興高采烈的發(fā)起請求,接收響應(yīng),然后提取數(shù)據(jù)。但是,我們發(fā)現(xiàn)我們提取不到數(shù)據(jù),他返回的是一個空列表,而且我們的xpath語法是正確的(正則也可以)。
出現(xiàn)這種問題,肯定是因為我們的xpath語法出現(xiàn)了問題,那前面不是說了我們的xpath語法是正確,為什么現(xiàn)在又說是錯誤的呢?原因有一下兩種:
一:我們提取數(shù)據(jù)一切以瀏覽器給我們返回的數(shù)據(jù)為準(zhǔn),瀏覽器返回數(shù)據(jù)時頁面結(jié)構(gòu)發(fā)生了變化,與我們在f12里面看到的結(jié)構(gòu)不同。
二:瀏覽器在給我們返回的數(shù)據(jù)中,根本就沒有我們想要的數(shù)據(jù)。
為了尋找是以上那種原因,我們在網(wǎng)頁源代碼中查找我們要尋找的內(nèi)容(這里查找的是長篇連載榜),發(fā)現(xiàn)出現(xiàn)的所有數(shù)據(jù)都不在網(wǎng)頁中,而是在一個script標(biāo)簽中,所以這是第二種原因:瀏覽器給我們返回的數(shù)據(jù)中根本就沒有我們想要的數(shù)據(jù)

對于這種問題,我們只有通過selenium驅(qū)動真實的瀏覽器來請求頁面從而獲得我們想要的數(shù)據(jù)。那么問題來了,在scrapy框架中如何使用selenium呢?這時,我們就又不得不說一下scrapy的運行流程了(流程圖畫的不好,不喜勿噴)

我們知道scrapy中首先會自動執(zhí)行start_urls里面的url,因此我們把selenium的方法寫在爬蟲文件中肯定是不可能的。那么,往哪寫呢?這時我們想一想,scrapy每次前往下載器的時候首先要必須經(jīng)過下載器中間件,所以我們要在那里面添加selenium的方法才能達到我們想要的效果。
接下來,我們就一起在下載器中間件里面使用一下selenium
1):新創(chuàng)建一個python文件,寫我們的中間件(寫在middlewares.py里面沒有問題,新建文件只是我的個人愛好)
2):新建一個類,添加process_request(一般將middlewares.py文件里面的process_requests)復(fù)制過來
class DbydDownloaderMiddleware:
? ??def process_request(self,request,spider):
? ? return None
3):這時我們就可以寫入selenium了
首先我們需要創(chuàng)建一個初始化函數(shù),即(__init__),因為我們要調(diào)用瀏覽器
def __init__(self):
????# 調(diào)用瀏覽器
? ? self.driver = webdriver.Chrome(executable_path=r'D:/chrome/chromedriver.exe')
????pass
4):使用selenium
我們在使用selenium時需要傳遞一個url,那么這個url從哪里來呢?
其實,這個我們之前就分析過,對每個url進行請求時都要經(jīng)過下載器中間件,而在process_requests里面有一個request參數(shù)那里就有我們需要的信息,此時進行操作的url就是request.url
因此代碼如下:
self.driver.get(url=request.url)
# 為了保證頁面數(shù)據(jù)能夠全部加載,在這里暫停1秒
time.sleep(1)
# 返回數(shù)據(jù)
content =self.driver.page_source
# 關(guān)閉瀏覽器
self.driver.close()
這里又有一個問題:scrapy在執(zhí)行完下載器中間件之后,如果返回None就又會執(zhí)行原來的操作,即自動對start_urls里面的url進行請求,然后傳遞給parse函數(shù),此時我們寫的中間件就不起作用了。因此,我們需要在這里中斷其自動執(zhí)行的操作,那就是返回一個響應(yīng)對象, 將content(selenium返回的頁面內(nèi)容)傳遞給parse函數(shù),然后到parse函數(shù)里面進行我們想要的操作。那么,如何返回呢?這時就需要導(dǎo)入如下方法
from scrapy.httpimport HtmlResponse
然后我們不再返回None,返回如下信息
return HtmlResponse(url=request.url,body=content,request=request,encoding='utf-8')
這里的url是告訴這是哪個url的響應(yīng),body是響應(yīng)體,即頁面返回的內(nèi)容。這樣我們就成功的在scrapy中使用到了selenium
這個問題我們解決了,那么繼續(xù)回到我們的案例中,我們是要爬取每個排行榜中的內(nèi)容,因此當(dāng)我們將所需要的url拼接成功之后又要發(fā)起請求,這時又出現(xiàn)了問題,scrapy每次發(fā)起請求都會執(zhí)行下載器中間件里面的代碼,這也就意味著我們那么多請求都要用selenium來發(fā)起。我們知道,selenium的運行速度是非常慢的,這大大降低了我們的效率。那么,也就是說我們只需要在第一次發(fā)起請求的時候使用一下selenium,其余都不需要。那么,該如何操作呢?
其實這個很簡單,那就是在下載器中間件中進行一次判斷,如果請求的url是我們首次請求的url,那么就使用selenium,如果不是,那就返回None,讓scrapy自動發(fā)起請求,這樣就ok了。
當(dāng)然最后一件事不能忘,那就是在settings文件中使用我們的下載器中間件,這樣scrapy才會執(zhí)行我們寫的中間件功能。
好了,分析就分析到這,接下來就讓我們一起看看代碼吧。




好了,保存數(shù)據(jù)的代碼大家就自己寫吧。