徹底搞懂Scrapy的中介軟體(一)

青南發表於2018-11-19

中介軟體是Scrapy裡面的一個核心概念。使用中介軟體可以在爬蟲的請求發起之前或者請求返回之後對資料進行定製化修改,從而開發出適應不同情況的爬蟲。

“中介軟體”這個中文名字和前面章節講到的“中間人”只有一字之差。它們做的事情確實也非常相似。中介軟體和中間人都能在中途劫持資料,做一些修改再把資料傳遞出去。不同點在於,中介軟體是開發者主動加進去的元件,而中間人是被動的,一般是惡意地加進去的環節。中介軟體主要用來輔助開發,而中間人卻多被用來進行資料的竊取、偽造甚至攻擊。

在Scrapy中有兩種中介軟體:下載器中介軟體(Downloader Middleware)和爬蟲中介軟體(Spider Middleware)。

這一篇主要講解下載器中介軟體的第一部分。

下載器中介軟體

Scrapy的官方文件中,對下載器中介軟體的解釋如下。

下載器中介軟體是介於Scrapy的request/response處理的鉤子框架,是用於全域性修改Scrapy request和response的一個輕量、底層的系統。

這個介紹看起來非常繞口,但其實用容易理解的話表述就是:更換代理IP,更換Cookies,更換User-Agent,自動重試。

如果完全沒有中介軟體,爬蟲的流程如下圖所示。

徹底搞懂Scrapy的中介軟體(一)

使用了中介軟體以後,爬蟲的流程如下圖所示。

徹底搞懂Scrapy的中介軟體(一)

開發代理中介軟體

在爬蟲開發中,更換代理IP是非常常見的情況,有時候每一次訪問都需要隨機選擇一個代理IP來進行。

中介軟體本身是一個Python的類,只要爬蟲每次訪問網站之前都先“經過”這個類,它就能給請求換新的代理IP,這樣就能實現動態改變代理。

在建立一個Scrapy工程以後,工程資料夾下會有一個middlewares.py檔案,開啟以後其內容如下圖所示。

徹底搞懂Scrapy的中介軟體(一)

Scrapy自動生成的這個檔名稱為middlewares.py,名字後面的s表示複數,說明這個檔案裡面可以放很多箇中介軟體。Scrapy自動建立的這個中介軟體是一個爬蟲中介軟體,這種型別在第三篇文章會講解。現在先來建立一個自動更換代理IP的中介軟體。

在middlewares.py中新增下面一段程式碼:

class ProxyMiddleware(object):

    def process_request(self, request, spider):
        proxy = random.choice(settings[`PROXIES`])
        request.meta[`proxy`] = proxy

複製程式碼

要修改請求的代理,就需要在請求的meta裡面新增一個Key為proxy,Value為代理IP的項。

由於用到了random和settings,所以需要在middlewares.py開頭匯入它們:

import random
from scrapy.conf import settings
複製程式碼

在下載器中介軟體裡面有一個名為process_request()的方法,這個方法中的程式碼會在每次爬蟲訪問網頁之前執行。

開啟settings.py,首先新增幾個代理IP:

PROXIES = [`https://114.217.243.25:8118`,
          `https://125.37.175.233:8118`,
          `http://1.85.116.218:8118`]
複製程式碼

需要注意的是,代理IP是有型別的,需要先看清楚是HTTP型的代理IP還是HTTPS型的代理IP。如果用錯了,就會導致無法訪問。

啟用中介軟體

中介軟體寫好以後,需要去settings.py中啟動。在settings.py中找到下面這一段被註釋的語句:

# Enable or disable downloader middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
#    `AdvanceSpider.middlewares.MyCustomDownloaderMiddleware`: 543,
#}
複製程式碼

解除註釋並修改,從而引用ProxyMiddleware。修改為:

DOWNLOADER_MIDDLEWARES = {
  `AdvanceSpider.middlewares.ProxyMiddleware`: 543,
}
複製程式碼

這其實就是一個字典,字典的Key就是用點分隔的中介軟體路徑,後面的數字表示這種中介軟體的順序。由於中介軟體是按順序執行的,因此如果遇到後一箇中介軟體依賴前一箇中介軟體的情況,中介軟體的順序就至關重要。

如何確定後面的數字應該怎麼寫呢?最簡單的辦法就是從543開始,逐漸加一,這樣一般不會出現什麼大問題。如果想把中介軟體做得更專業一點,那就需要知道Scrapy自帶中介軟體的順序,如圖下圖所示。

徹底搞懂Scrapy的中介軟體(一)

數字越小的中介軟體越先執行,例如Scrapy自帶的第1箇中介軟體RobotsTxtMiddleware,它的作用是首先檢視settings.py中ROBOTSTXT_OBEY這一項的配置是True還是False。如果是True,表示要遵守Robots.txt協議,它就會檢查將要訪問的網址能不能被執行訪問,如果不被允許訪問,那麼直接就取消這一次請求,接下來的和這次請求有關的各種操作全部都不需要繼續了。

開發者自定義的中介軟體,會被按順序插入到Scrapy自帶的中介軟體中。爬蟲會按照從100~900的順序依次執行所有的中介軟體。直到所有中介軟體全部執行完成,或者遇到某一箇中介軟體而取消了這次請求。

Scrapy其實自帶了UA中介軟體(UserAgentMiddleware)、代理中介軟體(HttpProxyMiddleware)和重試中介軟體(RetryMiddleware)。所以,從“原則上”說,要自己開發這3箇中介軟體,需要先禁用Scrapy裡面自帶的這3箇中介軟體。要禁用Scrapy的中介軟體,需要在settings.py裡面將這個中介軟體的順序設為None:

DOWNLOADER_MIDDLEWARES = {
  `AdvanceSpider.middlewares.ProxyMiddleware`: 543,
  `scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware`: None,
  `scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware`: None
}
複製程式碼

為什麼說“原則上”應該禁用呢?先檢視Scrapy自帶的代理中介軟體的原始碼,如下圖所示:

徹底搞懂Scrapy的中介軟體(一)

從上圖可以看出,如果Scrapy發現這個請求已經被設定了代理,那麼這個中介軟體就會什麼也不做,直接返回。因此雖然Scrapy自帶的這個代理中介軟體順序為750,比開發者自定義的代理中介軟體的順序543大,但是它並不會覆蓋開發者自己定義的代理資訊,所以即使不禁用系統自帶的這個代理中介軟體也沒有關係。

完整地啟用自定義中介軟體的settings.py的部分內容如下圖所示。

徹底搞懂Scrapy的中介軟體(一)

配置好以後執行爬蟲,爬蟲會在每次請求前都隨機設定一個代理。要測試代理中介軟體的執行效果,可以使用下面這個練習頁面:

http://exercise.kingname.info/exercise_middleware_ip
複製程式碼

這個頁面會返回爬蟲的IP地址,直接在網頁上開啟,如下圖所示。

徹底搞懂Scrapy的中介軟體(一)

這個練習頁支援翻頁功能,在網址後面加上“/頁數”即可翻頁。例如第100頁的網址為:

http://exercise.kingname.info/exercise_middleware_ip/100
複製程式碼

使用了代理中介軟體為每次請求更換代理的執行結果,如下圖所示。

徹底搞懂Scrapy的中介軟體(一)

代理中介軟體的可用代理列表不一定非要寫在settings.py裡面,也可以將它們寫到資料庫或者Redis中。一個可行的自動更換代理的爬蟲系統,應該有如下的3個功能。

  1. 有一個小爬蟲ProxySpider去各大代理網站爬取免費代理並驗證,將可以使用的代理IP儲存到資料庫中。
  2. 在ProxyMiddlerware的process_request中,每次從資料庫裡面隨機選擇一條代理IP地址使用。
  3. 週期性驗證資料庫中的無效代理,及時將其刪除。
    由於免費代理極其容易失效,因此如果有一定開發預算的話,建議購買專業代理機構的代理服務,高速而穩定。

開發UA中介軟體

開發UA中介軟體和開發代理中介軟體幾乎一樣,它也是從settings.py配置好的UA列表中隨機選擇一項,加入到請求頭中。程式碼如下:

class UAMiddleware(object):

    def process_request(self, request, spider):
        ua = random.choice(settings[`USER_AGENT_LIST`])
        request.headers[`User-Agent`] = ua
複製程式碼

比IP更好的是,UA不會存在失效的問題,所以只要收集幾十個UA,就可以一直使用。常見的UA如下:

USER_AGENT_LIST = [
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36",
  "Dalvik/1.6.0 (Linux; U; Android 4.2.1; 2013022 MIUI/JHACNBL30.0)",
  "Mozilla/5.0 (Linux; U; Android 4.4.2; zh-cn; HUAWEI MT7-TL00 Build/HuaweiMT7-TL00) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
  "AndroidDownloadManager",
  "Apache-HttpClient/UNAVAILABLE (java 1.4)",
  "Dalvik/1.6.0 (Linux; U; Android 4.3; SM-N7508V Build/JLS36C)",
  "Android50-AndroidPhone-8000-76-0-Statistics-wifi",
  "Dalvik/1.6.0 (Linux; U; Android 4.4.4; MI 3 MIUI/V7.2.1.0.KXCCNDA)",
  "Dalvik/1.6.0 (Linux; U; Android 4.4.2; Lenovo A3800-d Build/LenovoA3800-d)",
  "Lite 1.0 ( http://litesuits.com )",
  "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727)",
  "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36 SE 2.X MetaSr 1.0",
  "Mozilla/5.0 (Linux; U; Android 4.1.1; zh-cn; HTC T528t Build/JRO03H) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30; 360browser(securitypay,securityinstalled); 360(android,uppayplugin); 360 Aphone Browser (2.0.4)",
]
複製程式碼

配置好UA以後,在settings.py下載器中介軟體裡面啟用它,並使用UA練習頁來驗證UA是否每一次都不一樣。練習頁的地址為:

http://exercise.kingname.info/exercise_middleware_ua。 
複製程式碼

UA練習頁和代理練習頁一樣,也是可以無限制翻頁的。

執行結果如下圖所示。

徹底搞懂Scrapy的中介軟體(一)

開發Cookies中介軟體

對於需要登入的網站,可以使用Cookies來保持登入狀態。那麼如果單獨寫一個小程式,用Selenium持續不斷地用不同的賬號登入網站,就可以得到很多不同的Cookies。由於Cookies本質上就是一段文字,所以可以把這段文字放在Redis裡面。這樣一來,當Scrapy爬蟲請求網頁時,可以從Redis中讀取Cookies並給爬蟲換上。這樣爬蟲就可以一直保持登入狀態。

以下面這個練習頁面為例:

http://exercise.kingname.info/exercise_login_success
複製程式碼

如果直接用Scrapy訪問,得到的是登入介面的原始碼,如下圖所示。

徹底搞懂Scrapy的中介軟體(一)

現在,使用中介軟體,可以實現完全不改動這個loginSpider.py裡面的程式碼,就列印出登入以後才顯示的內容。

首先開發一個小程式,通過Selenium登入這個頁面,並將網站返回的Headers儲存到Redis中。這個小程式的程式碼如下圖所示。

徹底搞懂Scrapy的中介軟體(一)

這段程式碼的作用是使用Selenium和ChromeDriver填寫使用者名稱和密碼,實現登入練習頁面,然後將登入以後的Cookies轉換為JSON格式的字串並儲存到Redis中。

接下來,再寫一箇中介軟體,用來從Redis中讀取Cookies,並把這個Cookies給Scrapy使用:

class LoginMiddleware(object):
    def __init__(self):
        self.client = redis.StrictRedis()
    
    def process_request(self, request, spider):
        if spider.name == `loginSpider`:
            cookies = json.loads(self.client.lpop(`cookies`).decode())
            request.cookies = cookies
複製程式碼

設定了這個中介軟體以後,爬蟲裡面的程式碼不需要做任何修改就可以成功得到登入以後才能看到的HTML,如圖12-12所示。

徹底搞懂Scrapy的中介軟體(一)

如果有某網站的100個賬號,那麼單獨寫一個程式,持續不斷地用Selenium和ChromeDriver或者Selenium 和PhantomJS登入,獲取Cookies,並將Cookies存放到Redis中。爬蟲每次訪問都從Redis中讀取一個新的Cookies來進行爬取,就大大降低了被網站發現或者封鎖的可能性。

這種方式不僅適用於登入,也適用於驗證碼的處理。

這一篇就講到這裡,在下一篇,我們將會介紹如何在下載器中介軟體中整合Selenium,進行請求重試和處理異常。

本文節選自我的新書《Python爬蟲開發 從入門到實戰》完整目錄可以在京東查詢到 item.jd.com/12436581.ht…

買不買書不重要,重要的是請關注我的公眾號:未聞 Code

公眾號已經連續日更三個多月了。在接下來的很長時間裡也會連續日更。

徹底搞懂Scrapy的中介軟體(一)

相關文章