Python爬蟲進階Scrapy框架精文講解

一、前情提要

為什麼要使用Scrapy 框架?

前兩篇深造篇介紹瞭多線程這個概念和實戰
多線程網頁爬取
多線程爬取網頁項目實戰

經過之前的學習,我們基本掌握瞭分析頁面、分析動態請求、抓取內容,也學會使用多線程來並發爬取網頁提高效率。這些技能點已經足夠我們寫出各式各樣符合我們要求的爬蟲瞭。
但我們還有一個沒解決的問題,那就是工程化。工程化可以讓我們寫代碼的過程從「想一段寫一段」中解脫出來,變得有秩序、風格統一、不寫重復的東西。

而Scrapy 就是爬蟲框架中的佼佼者。它為我們提前想好瞭很多步驟和要處理的邊邊角角的問題,而使用者可以專心於處理解析頁面、分析請求這種最核心的事情。

二、Scrapy框架的概念

Scrapy 是一個純 Python 實現的、流行的網絡爬蟲框架,它使用瞭一些高級功能來簡化網頁的抓取,能讓我們的爬蟲更加的規范、高效。

在這裡插入圖片描述

它可以分為如下幾個部分

組件 功能
Scrapy Engine Scrapy 引擎,負責控制整個系統的數據流和事件的觸發
Scheduler 調度器,接收 Scrapy 引擎發來的請求並將其加入隊列中,等待引擎後續需要時使用
Downloader 下載器,爬取網頁內容,將爬取到的數據返回給 Spiders(爬蟲)
Spiders 爬蟲,這部分是核心代碼,用於解析、提取出需要的數據
Item Pipeline 數據管道,處理提取出的數據,主要是數據清洗、驗證和數據存儲
Downloader middlewares 下載器中間件,處理 Scrapy 引擎和下載器之間的請求和響應
Spider middlewares 爬蟲中間件,處理爬蟲輸入的響應以及輸出結果或新的請求

Scrapy 中數據流的過程如下

步驟 數據流
1 引擎打開一個網站,找到處理該網站對應的爬蟲,並爬取網頁的第一個頁面
2 引擎從爬蟲中獲取第一個頁面地址,並將其作為請求放進調度器中進行調度
3 引擎從調度器中獲取下一個頁面的地址
4 調度器返回下一個頁面的地址給 Scrapy 引擎,Scrapy 引擎通過下載器中間件傳遞給下載器進行爬取
5 爬取到數據後,下載器通過下載器中間件回傳給 Scrapy 引擎
6 Scrapy 引擎將爬取到的數據通過爬蟲中間件傳遞給爬蟲進行數據解析、提取
7 爬蟲處理完數據後,將提取出的數據和新的請求回傳給 Scrapy 引擎
8 Scrapy 將提取出的數據傳給數據管道進行數據清洗等操作,同時將新的請求傳遞給調度器準備進行下一頁的爬取
9 重復 2-8 步,直到調度器中沒有新的請求,數據爬取結束

三、Scrapy安裝

Win + R打開運行,點擊確定

在這裡插入圖片描述

然後在命令行上敲上

pip install scrapy -i https://pypi.doubanio.com/simple/
# 這句話後面 -i https://pypi.doubanio.com/simple/ 表示使用豆瓣的源,這樣安裝會更快

之後點擊回車,等待它自己安裝完成便可!

在這裡插入圖片描述

然後,我們在命令行上敲上scrapy,便會顯示scrapy的信息,這樣就代表安裝成功啦!

在這裡插入圖片描述

註意!!再然後我們在命令行敲上 explorer . (Mac 中是 open .,註意 . 前面有個空格) 命令並回車,可以打開命令行當前所在的目錄。下面,我們就要在這個目錄裡開始編寫代碼。

四、Scrapy實戰運用

這次我們試著用Scrapy爬取的網站是:小眾軟件 https://www.appinn.com/category/windows/

在進行網頁爬取前,我們先需要創建代碼文件,然後利用Scrapy命令進行執行。

在上面我們利用 explorer . 命令打開瞭目錄,在這個目錄下我們創建一個 spider.py 的文件↓
方法:創建文本文件改後綴名即可

在這裡插入圖片描述

然後將爬蟲代碼放進去,現在大傢先復制黏貼代碼,進行嘗試一下,之後我再來講解代碼的含義!

在這裡插入圖片描述

爬蟲代碼

import scrapy
# 定義一個類叫做 TitleSpider 繼承自 scrapy.Spider
class TitleSpider(scrapy.Spider):
    name = 'title-spider'
    # 設定開始爬取的頁面
    start_urls = ['https://www.appinn.com/category/windows/']
    def parse(self, response):
        # 找到所有 article 標簽
        for article in response.css('article'):
            # 解析 article 下面 a 標簽裡的鏈接和標題
            a = article.css('h2.title a')
            if a:
                result = {
                    'title': a.attrib['title'],
                    'url': a.attrib['href'],
                }
                # 得到結果
                yield result
        # 解析下一頁的鏈接
        next_page = response.css('a.next::attr(href)').get()
        if next_page is not None:
            # 開始爬下一頁,使用 parse 方法解析
            yield response.follow(next_page, self.parse)

然後在命令行中執行 scrapy 的 runspider 命令

scrapy runspider spider.py -t csv -o apps.csv
# spider.py 是剛剛寫的爬蟲代碼的文件名
# -t 表示輸出的文件格式,我們用 csv,方便用 Excel 等工具打開
# -o 表示輸出的文件名,所以執行完會出現一個 apps.csv 的文件

敲完上面這句命令,稍等一下,你應該能看見很多的輸出👇

在這裡插入圖片描述

網頁爬取結果

在這裡插入圖片描述

以及目錄裡多出來一個 apps.csv 文件。有 Excel 的同學可以用 Excel 打開 apps.csv,或者直接用記事本或者其他編輯器打開它。

在這裡插入圖片描述

打開後能看見 400 多篇小眾軟件的軟件推薦文章的標題和鏈接👇

在這裡插入圖片描述

可是我們的代碼裡完全沒有用到 requests、beautifulsoup、concurrent 以及文件相關的庫,是怎麼完成瞭一次快速的爬取並寫到文件的呢?別急,讓我為你慢慢講解!

這一串代碼幹瞭什麼?

上面用到的爬蟲代碼

import scrapy
# 定義一個類叫做 TitleSpider 繼承自 scrapy.Spider
class TitleSpider(scrapy.Spider):
    name = 'title-spider'
    # 設定開始爬取的頁面
    start_urls = ['https://www.appinn.com/category/windows/']
    def parse(self, response):
        # 找到所有 article 標簽
        for article in response.css('article'):
            # 解析 article 下面 a 標簽裡的鏈接和標題
            a = article.css('h2.title a')
            if a:
                result = {
                    'title': a.attrib['title'],
                    'url': a.attrib['href'],
                }
                # 得到結果
                yield result
        # 解析下一頁的鏈接
        next_page = response.css('a.next::attr(href)').get()
        if next_page is not None:
            # 開始爬下一頁,使用 parse 方法解析
            yield response.follow(next_page, self.parse)

當運行scrapy runspider spider.py -t csv -o apps.csv時,Scrapy 會執行我們寫在 spider.py裡的爬蟲,也就是上面那段完整的代碼

1、首先,Scrapy 讀到我們設定的啟動頁面 start_urls,開始請求這個頁面,得到一個響應。

// An highlighted block
start_urls = ['https://www.appinn.com/category/windows/']

2、之後,Scrapy 把這個響應交給 默認 的解析方法 parse 來處理。響應 response 就是 parse 的第一個參數

def parse(self, response):

3、在我們自己寫的 parse 方法裡,有兩個部分:一是解析出頁面裡的 article 標簽,得到標題和鏈接作為爬取的結果;二是解析 下一頁 按鈕這個位置,拿到下一頁的鏈接,並同樣繼續請求、然後使用 parse 方法解析

# 把這條結果告訴 Scrapy
yield result
# 通知 Scrapy 開始爬下一頁,使用 parse 方法解析
yield response.follow(next_page, self.parse)

yield 是 Python 中一個較高級的用法,在這裡我們隻需要知道,我們通過 yield 通知 Scrapy 兩件事:我們拿到瞭結果,快去處理吧、我們拿到瞭下一個要爬的鏈接,快去爬取吧。

流程圖

在這裡插入圖片描述

沒錯,除瞭解析想要的數據,其他的一切都是 Scrapy 替你完成的。這就是 Scrapy 的最大優勢:
requests 去哪瞭?不需要,隻要把鏈接交給 Scrapy 就會自動幫你完成請求;
concurrent 去哪瞭?不需要,Scrapy 會自動把全部的請求都變成並發的;
怎麼把結果寫到文件?不用實現寫文件的代碼,使用 yield 通知一下 Scrapy 結果即可自動寫入文件;
怎麼繼續爬取下一個頁面?使用 yield 通知 =Scrapy下一個頁面的鏈接和處理方法就好;
BeautifulSoup 去哪瞭?可以不需要,Scrapy 提供瞭好用的 CSS 選擇器。

解析數據這件事情還是值得我們關心的,即使 Scrapy 沒有強制讓我們使用什麼,因此我們非要繼續使用 BeautifulSoup也是可以的,隻需在 parse() 方法裡將 response.text 傳遞給 BeautifulSoup 進行解析、提取即可。

但是 Scrapy 提供瞭很好用的工具,叫做 CSS 選擇器。CSS 選擇器我們在 BeautifulSoup 中簡單介紹過,你還有印象嗎?

BeautifulSoup這個庫忘記瞭的同學,可以看看我之前寫的一篇文章:requests庫和BeautifulSoup庫

Scrapy 中的 CSS 選擇器語法和 BeautifulSoup 中的差不多,Scrapy 中的 CSS 選擇器更加強大一些

# 從響應裡解析出所有 article 標簽
response.css('article')
# 從 article 裡解析出 class 為 title 的 h2 標簽 下面的 a 標簽
article.css('h2.title a')
# 取出 a 裡面的 href 屬性值
a.attrib['href']
# 從響應裡解析出 class 為 next 的 a 標簽的 href 屬性,並取出它的值
response.css('a.next::attr(href)').get()

scrapy 中的 CSS 選擇器可以取代 beautifulsoup 的功能,我們直接用它就解析、提取獲取到的數據。看到這裡,再回頭看上面的完整代碼,試著結合流程圖再理解一下就會有不錯的瞭解瞭。

五、Scrapy的css選擇器教學

我們還是打開之前爬取的網站:小眾軟件 https://www.appinn.com/category/windows/
用網頁開發者工具選中 下一頁↓

在這裡插入圖片描述

註意截圖中被框住的部分,瀏覽器已經展示出來這個按鈕的 CSS 選擇方法是什麼瞭。它告訴我們下一頁按鈕的選擇方式是a.next.page-numbers。

開始css選擇器教學前,我建議你使用 Scrapy 提供的互動工具來體驗一下 CSS 選擇器。方式是在命令行中輸入以下命令並回車

scrapy shell "https://www.appinn.com/category/windows/"

在這裡插入圖片描述

這時 Scrapy 已經訪問瞭這個鏈接,並把獲取到的結果記錄瞭下來,你會進入到一個交互環境,我們可以在這個環境裡寫代碼並一句一句執行。輸入 response 並回車,你能看見類似下面的響應,這就是上面獲取到的網頁結果。

在這裡插入圖片描述

我之前有說過,輸出的200其實就是一個響應狀態碼,意思就是請求成功瞭!
之前的文章:requests庫和BeautifulSoup庫

在這裡插入圖片描述

下面我們就來學習一下css選擇器吧,我們以下圖中“下一頁”按鈕為例子👇

在這裡插入圖片描述

在這裡插入圖片描述

按標簽名選擇

小眾軟件網站下一頁按鈕的選擇方式是 a.next.page-numbers,其中的 a 是標簽名。試著在互動環境中,輸入 response.css(‘a’),可以看到頁面上所有的 a 元素。其他元素也是一樣,例如寫 response.css(‘ul’) 就可以選擇出所有 ul 元素,response.css(‘div’) 可以選擇出 div 元素。

按 class 選擇

a.next.page-numbers 中的.next 和 .page-numbers 表示 class的名字。當我們想要選擇 class 包含 container 的 div 元素,我們可以寫response.css(‘div.container’)。

上面的選擇器前面是標簽名,. 表示 class,後面跟著 class 的名稱。註意,它們是緊緊挨在一起的,中間不能有空格!

當要選擇的元素有多個 class 時,比如下面這樣的一個元素

<a class="next page-numbers" href="/windows/page/2/" rel="external nofollow"   >下一頁</a>

這個 a 元素有 next 和 page-number 兩個 class,可以寫多個 . 來選擇:response.css(‘a.next.page-numbers’)。表示選擇 class 同時包含 next 和 page-numbers 的 a 元素,這裡它們也必須緊挨在一起,之前不能有空格。

按 id 選擇

除瞭 class 選擇器外,同樣也有 id 選擇器,Scrapy 中也是用 # 代表 id。比如網頁上的菜單按鈕,我們看到它的 id 是 pull,class 是 toggle-mobile-menu。所以可以寫 response.css(‘a#pull’),表示我們想選擇一個 id 為 pull 的 a 元素。

當然,你也可以組合使用:response.css(‘a#pull.toggle-mobile-menu’)。表示我們要選擇 id 為 pull,並且 class 包含 toggle-mobile-menu 的 a 元素。

按層級關系選擇

還是小眾軟件的這個頁面,如果我們想用 CSS 選擇器選中標題這個位置的 a 元素,使用 Chrome 選取之後發現這個 a 元素既沒有 id 也沒有 class,瀏覽器也隻給我們提示一個 a👇

在這裡插入圖片描述

這時,我們就需要在該元素的父元素上找找線索,例如我們發現這個 a 元素在一個 h2 元素的下面,而這個 h2 元素是有 class 的,class 為 title 和 post-title。所以我們要做的是選擇 class 為 title 和 post-title 的 h2 元素下面的 a 元素,用 CSS 選擇器寫作

response.css('h2.title.post-title a::text')

可以看到,title 和 post-title 的 h2 元素寫作 h2.title.post-title,是緊緊連在一起的,而下面的 a 元素則在 a 前面加瞭一個空格。想起之前說過的規則瞭嗎?規則便是:並列關系連在一起,層級關系用空格隔開。

.title 和 .post-title 緊跟在 h2 後面,它倆都是 h2 的篩選條件。而空格後面跟著的 a 表示符合 h2.title.post-title 條件元素的子元素中的所有 a 元素。

我們之前還說過,空格後面表示的是所有的子元素,不管是處於多少層的子元素。而如果隻想要第一層的子元素則應該用 > 分隔開。這裡的 a 元素就是第一層的子元素,所以 h2.title.post-title a 和 h2.title.post-title > a 這兩種寫法的效果是一樣的。

取元素中的文本

我們拿到瞭標題位置的 a 元素,想要拿到其中的文本內容就需要在後面加上 ::text,代碼如下

response.css('h2.title.post-title a::text')

在互動環境執行一下,會發現文本內容能獲取到,但不是我們想要的純文本,如果想拿到純文本,還需要使用 get() 或者 getall() 方法,如下

# 取符合條件的第一條數據
response.css('h2.title.post-title a::text').get()

# 取符合條件的所有數據
response.css('h2.title.post-title a::text').getall()

取元素的屬性

還是用這個 a 元素舉例。如果我們想得到這個 a 元素的 href 屬性,需要調用這個元素的 attrib 屬性。在互動環境中執行下面兩句代碼

# 拿到符合選擇器條件的第一個 a 標簽
a = response.css('h2.title.post-title a')
a.attrib['href']

attrib 屬性實際上是一個字典,裡面存儲瞭元素上的所有 HTML 屬性。如果把第二句換成 a.attrib,你就能看到這個 a 元素上的所有屬性。類似的,輸入 a.attrib[‘title’],你可以得到它的 title 屬性。

現在我們試試打印所有符合 h2.title.post-title a 這個條件的標簽的 href 屬性,就像下面這樣

for a in response.css('h2.title.post-title a'):
  print(a.attrib['href'])

或者另一種寫法也可以取到 href 屬性,就是加上 ::attr(href)

for href in response.css('h2.title.post-title a::attr(href)').getall():
  print(href)

本次分享到此結束瞭,非常感謝大傢閱讀!!
有問題歡迎評論區留言!!

以上就是Python爬蟲進階Scrapy框架精文講解的詳細內容,更多關於Python爬蟲Scrapy框架的資料請關註WalkonNet其它相關文章!

推薦閱讀: