前段時間我寫了一篇《scrapy快速入門》,簡單介紹了一點scrapy的知識。最近我的搬瓦工讓牆了,而且我又學了一點mongodb的知識,是以這次就來介紹一些scrapy的進階知識,做一些真正有用的爬蟲來。
scrapy元件介紹
首先先來介紹一下scrapy的體系結構群組件。
- scrapy引擎。顧名思義,它負責調用爬蟲爬取頁面并将資料交給管道來處理。
- 排程程式。排程程式從引擎擷取請求,然後将這些請求交給相應的處理者來處理。
- 下載下傳器。下載下傳器負責擷取web頁面,然後将它們交給引擎來處理。
- 爬蟲。這是我們要編寫的部分,爬蟲負責從頁面抽取要處理的資料,然後交由管道來處理。
- 項目管道。爬蟲抽取出來的資料,就要交給管道來處理,我們可以編寫自己的管道來清洗、驗證和持久化資料。
- 下載下傳器中間件。下載下傳器中間件在下載下傳器和scrapy引擎之間工作,我們可以利用下載下傳器中間件在将頁面交給爬蟲之前做一些事情。scrapy内置了一些下載下傳器中間件,這些中間件将在後面介紹。
- 爬蟲中間件。爬蟲中間件可以讓我們控制爬蟲的回調等等。在後面會介紹一些内置的爬蟲中間件。
下面是scrapy官網的結構圖,可以幫助我們了解scrapy的體系。

scrapy結構圖
項目(Item)
在以前的爬蟲中我們都是直接傳回一個字典,其實這并不是最佳實踐。scrapy提供了一個Item基類,我們可以通過繼承這個類定義自己的結構化資料,比到處傳遞字典更好。下面是官方文檔的例子。
import scrapy
class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
last_updated = scrapy.Field(serializer=str)
這些項目類一般都定義在scrapy項目的
items.py
檔案中。定義好之後,在爬蟲中我們就不應該在反掌字典了,而是初始化并傳回我們自定義的Item對象。
請求和響應對象
scrapy通過請求和響應對象來處理網頁請求,這部分的文檔可以參考
https://doc.scrapy.org/en/latest/topics/request-response.html。請求和響應還有一些子類,可以幫助我們完成更具體的工作。例如
Request
的子類
FormRequest
就可以幫助我們模拟使用者登入。
有時候需要模拟使用者登入,這時候可以使用
FormRequest.from_response
方法。這時候爬蟲功能稍有變化,
parse
函數用來發送使用者名和密碼,抽取資料的操作放在回調函數中進行。
import scrapy
class LoginSpider(scrapy.Spider):
name = 'example.com'
start_urls = ['http://www.example.com/users/login.php']
def parse(self, response):
return scrapy.FormRequest.from_response(
response,
formdata={'username': 'john', 'password': 'secret'},
callback=self.after_login
)
def after_login(self, response):
# 檢查是否登入成功
if "authentication failed" in response.body:
self.logger.error("Login failed")
return
# 在這裡繼續爬取資料
管道(pipeline)
管道用來處理爬蟲抽取到的資料,我們可以通過管道對資料進行驗證和持久化等操作。管道其實就是帶有
process_item(self, item, spider)
函數的一個普通類。下面是scrapy官方文檔的例子,這個例子驗證擷取到的資料是否存在價格字段,并丢棄沒有價格字段的無效資料。這裡還引用了scrapy預定義的
DropItem
異常,這個異常必須在管道中抛出,表示管道應該丢棄這個資料。如果想了解scrapy異常,可以檢視
官方文檔。
from scrapy.exceptions import DropItem
class PricePipeline(object):
vat_factor = 1.15
def process_item(self, item, spider):
if item['price']:
if item['price_excludes_vat']:
item['price'] = item['price'] * self.vat_factor
return item
else:
raise DropItem("Missing price in %s" % item)
管道不是一定義好就能用的,還需要在配置檔案
settings.py
中激活。
ITEM_PIPELINES = {
'myproject.pipelines.PricePipeline': 300,
'myproject.pipelines.JsonWriterPipeline': 800,
}
将資料儲存到MongoDB的管道
管道除了驗證資料,還可以将資料儲存到資料庫中。這時候僅僅一個
process_item(self, item, spider)
函數就不夠了。是以操作資料庫的管道還應該包含幾個函數用于建立和關閉資料庫連接配接。
下面的例子也是scrapy官方文檔的例子,示範了持久化資料管道的用法。這個管道是從類方法
from_crawler(cls, crawler)
中初始化出來的,該方法實際上讀取了scrapy的配置檔案。這和直接将資料庫連接配接寫在代碼中相比,是更加通用的方式。初始化和關閉資料庫連接配接的操作都在對應的方法中執行。
import pymongo
class MongoPipeline(object):
collection_name = 'scrapy_items'
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def close_spider(self, spider):
self.client.close()
def process_item(self, item, spider):
self.db[self.collection_name].insert_one(dict(item))
return item
使用檔案和圖檔管道
檔案和圖檔管道處理過程
除了自己編寫管道之外,scrapy還預定義了幾個管道,可以幫助我們友善的儲存檔案和圖檔。這些管道有以下特點:
- 可以避免重複下載下傳最近的檔案。
- 指定檔案儲存位置(檔案系統或者亞馬遜S3)
對于圖檔管道來說還有額外功能:
- 将圖檔轉換成常見格式(JPG)和模式(RGB)
- 生成圖檔縮略圖
- 隻下載下傳大于某長寬的圖檔
使用檔案管道的過程如下:
- 首先需要Item類中有
和file_urls
兩個屬性,然後在爬蟲中将想爬取的檔案位址放到files
屬性中,然後傳回file_urls
- 在Item傳遞到檔案管道的時候,排程程式會用下載下傳器将位址對應的檔案下載下傳下來,将檔案屬性(包括儲存路徑等)放到
屬性中,files
file_urls
中是一一對應的files
使用圖檔管道的過程是相似的,不過要操作的屬性是
image_urls
images
如果你不想使用這幾個屬性,其實屬性名也是可以修改的,需要修改下面四個屬性。
FILES_URLS_FIELD = 'field_name_for_your_files_urls'
FILES_RESULT_FIELD = 'field_name_for_your_processed_files'
IMAGES_URLS_FIELD = 'field_name_for_your_images_urls'
IMAGES_RESULT_FIELD = 'field_name_for_your_processed_images'
管道配置
要啟用檔案管道和圖檔管道,同樣需要激活,當然如果同時激活這兩個管道也是可行的。
ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1}
# 或者
ITEM_PIPELINES = {'scrapy.pipelines.files.FilesPipeline': 1}
檔案和圖檔儲存位置需要分别指定。
FILES_STORE = '/path/to/valid/dir'
IMAGES_STORE = '/path/to/valid/dir'
檔案和圖檔管道可以避免下載下傳最近的檔案,對應的檔案過期時間也可以配置,機關是天。
# 120 days of delay for files expiration
FILES_EXPIRES = 120
# 30 days of delay for images expiration
IMAGES_EXPIRES = 30
圖檔管道可以在儲存圖檔的時候同時生成縮略圖,縮略圖配置是一個字典,鍵是縮略圖的名字,值是縮略圖長和寬。
IMAGES_THUMBS = {
'small': (50, 50),
'big': (270, 270),
}
最後圖檔會儲存成下面這樣,圖檔的檔案名是圖檔路徑的SHA1哈希值。
/圖檔儲存路徑/full/完整圖檔.jpg
/圖檔儲存路徑/thumbs/small/小圖檔.jpg
/圖檔儲存路徑/thumbs/big/中圖檔.jpg
如果不想使用SHA1哈希值作為檔案名,可以繼承
ImagesPipeline
基類并重寫
file_path
函數,
這裡是另外一位簡書作者的爬蟲項目,他重寫了這個函數。我們可以作為參考。
如果要過濾小圖檔,啟用下面的配置。預設情況下對圖檔尺寸沒有限制,是以所有圖檔都會下載下傳。
IMAGES_MIN_HEIGHT = 110
IMAGES_MIN_WIDTH = 110
預設情況下檔案和圖檔管道不支援重定向,遇到需要重定向的連結意味着下載下傳失敗,不過我們也可以啟用重定向。
MEDIA_ALLOW_REDIRECTS = True
下載下傳器中間件
下載下傳器中間件可以在scrapy引擎和爬蟲之間操縱請求和響應對象。要啟用下載下傳器中間件,啟用下面的配置。這是一個字典,字典的鍵是要啟用的中間件,值會用來比較中間件之間的順序。
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.CustomDownloaderMiddleware': 543,
}
如果希望禁用某些内置的中間件,可以将值設定為
None
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.CustomDownloaderMiddleware': 543,
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
}
編寫自己的下載下傳器中間件
自定義下載下傳器中間件應該繼承
scrapy.downloadermiddlewares.DownloaderMiddleware
類,該類有如下幾個方法,用于操縱請求和響應,我們隻要重寫這幾個方法即可。這幾個方法的作用請參考
,它們比較複雜,是以我就不說了。
- process_request(request, spider)
- process_response(request, response, spider)
- process_exception(request, exception, spider)
内置的下載下傳器中間件
scrapy内置了14個下載下傳器中間件,我簡單介紹一下其中的幾個。詳情參考文檔。
CookiesMiddleware
用于在爬蟲發起請求和擷取響應的時候保持Cookie。
DefaultHeadersMiddleware
用于設定請求的預設請求頭。
該配置位于
DEFAULT_REQUEST_HEADERS
,預設值如下。
{
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
HttpProxyMiddleware
設定使用的網絡代理。
UserAgentMiddleware
設定使用的使用者代理。
爬蟲中間件
與下載下傳器中間件類似,啟用爬蟲中間件需要一個字典來配置。
SPIDER_MIDDLEWARES = {
'myproject.middlewares.CustomSpiderMiddleware': 543,
}
想要關閉某個中間件的時候傳遞
None
值。
SPIDER_MIDDLEWARES = {
'myproject.middlewares.CustomSpiderMiddleware': 543,
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware': None,
}
自定義爬蟲中間件
編寫自己的爬蟲中間件需要繼承
scrapy.spidermiddlewares.SpiderMiddleware
基類,并重寫以下幾個方法。
- process_spider_input(response, spider)
- process_spider_output(response, result, spider)
- process_spider_exception(response, exception, spider)
- process_start_requests(start_requests, spider)
内置的爬蟲中間件
scrapy内置了5個爬蟲中間件,這裡我僅介紹一兩個。
DepthMiddleware
該中間件記錄了爬蟲爬取請求位址的深度。
我們可以使用
DEPTH_LIMIT
來指定爬蟲爬取的深度。
UrlLengthMiddleware
該中間件會過濾掉超出最大允許長度的URL,爬蟲不會通路這些超長URL。
最大長度通過
URLLENGTH_LIMIT
配置來指定,預設值是2083。
URLLENGTH_LIMIT = 2083
内建服務
scrapy内置了幾個服務,可以讓我們使用scrapy更加友善。
日志
爬蟲類定義了
log
函數,我們可以友善的在爬蟲類中記錄日志。
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
start_urls = ['https://scrapinghub.com']
def parse(self, response):
self.logger.info('Parse function called on %s', response.url)
日志相關的配置,點選可以跳轉到官方文檔檢視詳細資訊。
發送電子郵件
有時候我們可能希望爬到一定數量的資料就發送電子郵件進行提醒。scrapy也内置了這個功能。我們可以通過構造函數參數來建立郵件發送器。
from scrapy.mail import MailSender
mailer = MailSender(這裡是構造函數參數)
也可以從配置檔案執行個體化。
mailer = MailSender.from_settings(settings)
然後調用
send
方法就可以發送郵件了。
mailer.send(to=["[email protected]"], subject="Some subject", body="Some body", cc=["[email protected]"])
電子郵件相關配置參考
web服務
這個功能本來是寫在官方文檔内建服務條目下的,但是實際上這個功能已經變成了一個單獨的項目,需要額外安裝。
pip install scrapy-jsonrpc
然後在擴充中包含這個功能。
EXTENSIONS = {
'scrapy_jsonrpc.webservice.WebService': 500,
}
還需要在配置中啟用該功能。
JSONRPC_ENABLED = True
然後在爬蟲運作的時候通路
http://localhost:6080/crawler即可檢視爬蟲運作情況了。
該項目的其他配置檢視其
優化爬蟲
爬蟲項目可以通過修改一些配置進行優化。
增大并發數
并發數可以通過下面的配置進行設定。具體的并發數需要根據伺服器的CPU等設定來進行更改。一般來說伺服器CPU使用在80%-90%之間使用率比較高。我們可以從并發數100開始反複進行測試。
CONCURRENT_REQUESTS = 100
增大線程池
scrapy通過一個線程池來進行DNS查詢,增大這個線程池一般也可以提高scrapy性能。
REACTOR_THREADPOOL_MAXSIZE = 20
降低日志級别
預設情況下scrapy使用
debug
級别來列印日志,通過降低日志級别,我們可以減少日志列印,進而提高程式運作速度。
LOG_LEVEL = 'INFO'
禁用Cookie
如果不是必須的,我們可以通過禁用Cookie來提高性能。如果需要登入使用者才能爬取資料,不要禁用Cookie。
COOKIES_ENABLED = False
關閉重試
頻繁重試可能導緻目标伺服器響應緩慢,我們自己通路不了别人也通路不了。是以可以考慮關閉重試。
RETRY_ENABLED = False
減少下載下傳逾時
如果網絡連接配接比較快的話,我們可以減少下載下傳逾時,讓爬蟲卡住的請求中跳出來,一般可以提高爬蟲效率。
DOWNLOAD_TIMEOUT = 15
關閉重定向
如果不是必要的話,我們可以關閉重定向來提高爬蟲性能。
REDIRECT_ENABLED = False
自動調整爬蟲負載
scrapy有一個擴充可以自動調節伺服器負載,它通過一個算法來确定最佳的爬蟲延時等設定。它的文檔在
這裡相關配置如下,點選連結可以跳轉到對應文檔。
-
AUTOTHROTTLE_ENABLED
-
AUTOTHROTTLE_START_DELAY
-
AUTOTHROTTLE_MAX_DELAY
-
AUTOTHROTTLE_TARGET_CONCURRENCY
-
AUTOTHROTTLE_DEBUG
-
CONCURRENT_REQUESTS_PER_DOMAIN
-
CONCURRENT_REQUESTS_PER_IP
-
DOWNLOAD_DELAY
部署爬蟲
官方文檔介紹了兩種部署爬蟲的方式,可以将爬蟲部署到伺服器上遠端執行。第一種是通過
Scrapyd開源項目來部署,也是這裡要介紹的方式。第二種是通過scrapy公司提供的商業收費版服務
Scrapy Cloud部署,推薦有财力的公司考慮。
伺服器端
首先伺服器需要安裝scrapyd包,如果是Linux系統還可以考慮使用對應的包管理器來安裝。
pip install scrapyd
apt-get install scrapyd
然後運作scrapyd服務,如果使用系統包管理器安裝,那麼可能已經配置好了systemd檔案。
scrapyd
# 或者
systemctl enable scrapyd
scrapyd附帶了一個簡單的web界面可以幫助我們檢視爬蟲運作情況,預設情況下通路
http://localhost:6800/來檢視這個界面。
scrapyd的配置檔案可以是
~/.scrapyd.conf
或者
/etc/scrapyd/scrapyd.conf
。下面是一個簡單配置,綁定所有端口,這樣一來從任意位置都可以通路web界面。
[scrapyd]
bind_address = 0.0.0.0
scrapyd的功能可以檢視其
API文檔用戶端
用戶端如果要上傳爬蟲,可以通過伺服器API的端點addversion.json來實作,或者安裝一個簡便工具
scrapyd-client首先安裝用戶端工具。
pip install scrapyd-client
這個用戶端目前好像有bug,在windows下運作
scrapy-deploy
指令不會直接執行,而是彈出一個檔案關聯對話框。如果你遇到這種情況,可以找到Python安裝路徑下的腳本路徑(例如
C:\Program Files\Python36\Scripts
),然後編寫一個
scrapyd-deploy.bat
批處理檔案,内容如下。這樣就可以正常運作了。
@"c:\program files\python36\python.exe" "c:\program files\python36\Scripts\scrapyd-deploy" %*
然後切換到項目路徑,編輯項目全局配置檔案
scrapy.cfg
,添加部署路徑。
[deploy]
url = http://192.168.64.136:6800/
project = quotesbot
然後直接運作
scrapy-deploy
指令,就可以看到項目已經成功部署到伺服器上了。
部署成功截圖
運作爬蟲需要使用scrapyd的API,例如使用curl,可以用下面的指令。
curl http://192.168.64.136:6800/schedule.json -d project=quotesbot -d spider=toscrape-css
或者使用Jetbrains 系列IDE 2017.3的基于編輯器的HTTP用戶端。
基于編輯器的HTTP用戶端
然後點選Jobs就可以看到爬蟲已經開始運作了。如果要檢視狀态,點選右邊的log即可。
運作的爬蟲
以上就是scrapy的進階介紹了,利用這些功能,我們可以編寫更加實用的爬蟲,并将它們部署到伺服器上。