一. Scrapy框架簡介Scrapy是一個使用Python語言(基于Twisted框架)編寫的開源網(wǎng)絡爬蟲框架,目前由 Scrapinghub Ltd 維護。Scrapy 簡單易用、靈活易拓展、開發(fā)社區(qū)活躍,并且是跨平臺的。在 Linux、MaxOS 以及 Windows 平臺都可以使用。Scrapy 應用程序也使用 Python 進行開發(fā),目前可以支持 Python 2.7 以及 Python 3.4 版本。 Scrapy 功能非常強大,爬取效率高,相關擴展組件多,可配置和可擴展程度非常高,它幾乎可以應對所有反爬網(wǎng)站,是目前 Python 使用最廣泛的爬蟲框架。 1. Scrapy 框架介紹首先我們看看 Scrapy 框架的架構: 它可以分為如下的幾個部分: 模塊 | 作用 |
---|
Engine | 引擎,處理整個系統(tǒng)的數(shù)據(jù)流處理、觸發(fā)事務,是整個框架的核心 | Item | 項目,它定義了爬取結果的數(shù)據(jù)結構,爬取的數(shù)據(jù)會被賦值成該 Item 對象 | Scheduler | 調(diào)度器,接受引擎發(fā)過來的請求并將其加入列中,在引擎再次請求的時候?qū)⒄埱筇峁┙o引擎 | Downloader | 下載器,下載網(wǎng)頁內(nèi)容,并將網(wǎng)頁內(nèi)容返回給蜘蛛 | Spiders | 蜘蛛,其內(nèi)定義了爬取的邏輯和網(wǎng)頁解析規(guī)則,它主要負責解析響應并生成提取結果和新的請求 | Item Pipeline | 項目管道,負責處理由蜘蛛從網(wǎng)頁中抽取的項目,它的主要任務是清洗、驗證和存儲數(shù)據(jù) | Downloader Middlewares | 下載器中間件,位于引擎和下載器之間的鉤子框架,主要處理引擎與下載器之間的請求及響應 | Spide Middlewares | 蜘蛛中間件,位于引擎和蜘蛛之間的鉤子框架,主要處理蜘蛛輸入的響應和輸出的結果及新的請求 |
對于用戶來說,Spider 是最核心的組件,Scrapy 爬蟲開發(fā)是圍繞實現(xiàn) Spider 展開的。 2. 數(shù)據(jù)處理流程Scrapy 中的數(shù)據(jù)流由引擎控制,數(shù)據(jù)流的過程如下: Engine 首先打開一個網(wǎng)站,找到處理該網(wǎng)站的 Spider ,并向該 Spider 請求第一個要爬取的 URL; Engine 從 Spider 中獲取到第一個要爬取的 URL,并通過 Scheduler 以 Request 的形式調(diào)度; Engine 向 Scheduler 請求下一個要爬取的 URL; Scheduler 返回下一個要爬取的 URL 給 Engine,Engine 將 URL 通過 Downloader Middlewares 轉發(fā)給 Downloader Middlewares 下載; 一旦頁面下載完畢,Downloader 生成該頁面的 Response,并將其通過 Downloader Middlewares 發(fā)送給 Engine; Engine 從下載器中接收到 Response,并將其通過 Spider Middleware 發(fā)送給 Spider 處理; Spider 處理 Response,并返回爬取到的 Item 及新的 Request 給 Engine; Engine 將 Spider 返回的 Item給 Item Pipeline,將新 Request 給 Scheduler 重復第 (2) 步到第 (8) 步,直到 Scheduler 中沒有更多的 Request,Engine 關閉該網(wǎng)站,爬取結束。
通過多個組件的相互協(xié)作、不同組件完成工作的不同、組件對異步處理的支持,Scrap 最大限度地利用了網(wǎng)絡帶寬,大大提高了數(shù)據(jù)爬取和處理的效率。 如果把框架中的組件比作人體的各個器官,Request 和 Response 對象便是血液,Item 則是代謝產(chǎn)物。 二. Scrapy 及其依賴庫的安裝在任意操作系統(tǒng)下,都可以使用 pip 安裝 Scrapy,包括在虛擬機中。下面我們在遠程終端進入相應的 env 通過如下命令下載 Scrapy 框架: (pyspider) pyvip@VIP:~$ pip3 install scrapy
Looking in indexes: https://pypi.douban.com/simple
...
Installing collected packages: scrapy
Successfully installed scrapy-2.4.1 為確認 Scrapy 已安裝成功,在 shell 中測試能否執(zhí)行 Scrapy 這條命令: (pyspider) pyvip@VIP:~$ scrapy
Scrapy 2.4.1 - no active project
Usage:
scrapy <command> [options] [args]
Available commands:
bench Run quick benchmark test
commands
fetch Fetch a URL using the Scrapy downloader
genspider Generate new spider using pre-defined templates
runspider Run a self-contained spider (without creating a project)
settings Get settings values
shell Interactive scraping console
startproject Create new project
version Print Scrapy version
view Open URL in browser, as seen by Scrapy
[ more ] More commands available when run from project directory
Use "scrapy <command> -h" to see more info about a command 通過以上檢測,說明 Scrapy 安裝成功了。如上所示,我們安裝的是當前最新版本2.4.1。 除了上述 Scrapy 庫,我們還需要安裝其依賴的其他幾個第三方庫,例如 'lxml'、'Twisted'、'pyOpenSSL' 等。這些第三方庫的安裝方式可以參考 Scrapy 的安裝方式。 除了上述的安裝方式,我們可以直接在 Pycharm 的設置里面下載。 三. Scrapy 項目開發(fā)流程首先我們來看看爬蟲框架 Scrapy 的常用命令。 1. 常用命令命令 | 作用 |
---|
scrapy startproject <project_name> | 在當前目錄下創(chuàng)建一個名為<project_name> 的項目 | scrapy settings [options] | 該命令將會輸出 Scrapy 默認設定;如果你在項目中運行這個命令將會輸出項目的設定值 | scrapy runspider <spider_file.py> | 在未創(chuàng)建項目的情況下,運行一個編寫在 python 文件中的 spider | scrapy shell [url] | 以給定的URL(如果給出)或者空(沒有給出URL)啟動 Scrapy shell | scrapy fetch <url> | 使用 Scrapy 下載器 (downloader) 下載給定的URL,并將獲取到的內(nèi)容送到標準輸出 | scrapy view <url> | 在你的默認瀏覽器中打開給定的 URL,并以Scrapy spider 獲取到的形式展現(xiàn) | scrapy version [-v] | 輸出Scrapy版本 | scrapy –help | 查看幫助信息 | scrapy bench | scrapy 基準測試,用于檢測 scrapy 安裝環(huán)境是否完整 | scrapy crawl <spider_name> | 使用你項目中的 spider 進行爬取,即啟動你的項目 | crapy check [-l] <spider> | 運行 contract 檢查,檢查你項目中的錯誤之處 | scrapy list | 列出當前項目中所有可用的 spider,每行輸出一個 spider | scrapy genspider [-t template] <name> <domain> | 在當前項目中創(chuàng)建 spider |
2. 創(chuàng)建 Scrapy 項目在安裝完成 Scrapy框架之后,我們開始創(chuàng)建第一個 Scrapy 項目,通過這個示例,我們了解一下 Scrapy 框架的開發(fā)流程。 創(chuàng)建 Scrapy 框架的過程與 Django 框架中創(chuàng)建項目文件有些相似。初步流程如下: 然后從Ubuntu 或者 Pycharm 中進入到終端,然后在對應的虛擬環(huán)境下進入到爬蟲項目工程文件夾,然后運行下面的命令: (pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider$ scrapy startproject tutorial
New Scrapy project 'tutorial', using template directory '/home/pyvip/.virtualenvs/pyspider/lib/python3.6/site-packages/scrapy/templates/project', created in:
/home/pyvip/project/Python_Spider/Spider_Project/Quote_Spider/tutorial
You can start your first spider with:
cd tutorial
scrapy genspider example example.com 創(chuàng)建好一個名為 first_scrapy 的項目后,可使用tree命令查看項目目錄下的文件,顯示如下: tutorial
├── tutorial
│ ├── __init__.py
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders
│ └── __init__.py
└── scrapy.cfg 這里各個文件的功能描述如下: scrapy.cfg:它是 Scrapy 項目的配置文件,其內(nèi)定義了項目的配置文件路徑、部署相關信息等內(nèi)容; items.py:它定義 Item 數(shù)據(jù)結構,所有的 Item 的定義都可以放這里; pipelines.py:它定義 Item Pipeline 的實現(xiàn),所有的 Item Pipeline 的實現(xiàn)都可以放這里; settings.py:它定義項目的全局配置; middlewares.py:它定義 Spider Middlewares 和 Downloader Middlewares 的實現(xiàn) spiders:其內(nèi)包含一個個 Spider 的實現(xiàn),每個 Spider 都有一個文件。
3. 創(chuàng)建 SpiderSpider 是需要自己定義的類, Scrapy 用它來從網(wǎng)頁里抓取內(nèi)容,并解析抓取的結果。不過這個類必須繼承 Scrapy 提供的 Spider 類 scrapy.Spider ,還要定義 Spider 的名稱和起始請求,以及怎樣處理爬取的結果的方法。spider 模塊我們可以使用命令行來創(chuàng)建,例如我們要生成 Quotes 這個 Spider,可以執(zhí)行如下命令: (pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider$ cd tutorial/tutorial
(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider/tutorial/tutorial$ ls
__init__.py items.py middlewares.py pipelines.py __pycache__ settings.py spiders
(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider/tutorial/tutorial$ scrapy genspider quotes quotes.
Created spider 'quotes' using template 'basic' in module:
tutorial.spiders.quotes 執(zhí)行完畢之后,spiders 文件夾中多了一個 quotes.py ,它就是剛剛創(chuàng)建的 Spider,內(nèi)容如下所示: import scrapy
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.']
start_urls = ['http://quotes./']
def parse(self, response):
pass 這里有三個屬性——'name'、'allowed_domains'、' start_urls' ,還有一個方法 parse 。 name:它是每個項目唯一的名字,用來區(qū)分不同的 Spider; allowed domains:它是允許爬取的域名,如果初始或后續(xù)的請求鏈接不是這個域名下的,則請求鏈接會被過濾掉; start_urls:它包含了 Spider 在啟動時爬取的 url 列表,初始請求是由它來定義的,即 start_urls 屬性用來設置一個爬蟲的起始爬取點; parse:它是 Spider 的一個方法。默認情況下,被調(diào)用時 start_urls 里面的鏈接構成的請求完成下載執(zhí)行后,返回的響應就會作為唯一的參數(shù)傳遞給這個函數(shù)。該方法負責解析返回的響應、提取數(shù)據(jù)或者進一步生成要處理的請求。
4. 創(chuàng)建 ItemItem 是保存爬取數(shù)據(jù)的容器,它的使用方法和字典類似。不過,相比字典 Item 多了額外的保護機制,可以避免拼寫錯誤或者定義字段錯誤。 創(chuàng)建 Item 需要繼承 scrapy.Item 類,并且定義類型為 scrapy.Field 的字段。觀察目標網(wǎng)站,我們可以獲取到的內(nèi)容有 'text'、'author'、'tags' 。定義 Item ,將 items.py 的內(nèi)容修改如下: import scrapy
class FirstScrapyItem(scrapy.Item):
text = scrapy . Field()
author = scrapy. Field()
tags = scrapy . Field() 5. 解析 Responseparse () 方法的參數(shù) resposne 是 start_urls 里面的鏈接爬取后的結果,所以在 parse() 方法中,我們可以直接對 response 變量包含的內(nèi)容進行解析,比如瀏覽請求結果的網(wǎng)頁源代碼,或者進一步分析源代碼內(nèi)容,或者找出結果中的鏈接而得到下一個請求。
在這里,我們使用 CSS 選擇器進行選擇,parse() 方法的改寫內(nèi)容如下: def parse(self, response):
quotes = response.css('.quote')
for quote in quotes:
text = quote.css('.text::text').extract_first()
author = quote.css('.author::text').extract_first()
tags = quote.css('.tags .tag::text').extract() 6. 使用 ItemItem 可以理解為字典,不過在聲明的時候需要實例化,然后依次用剛才解析的結果賦值 Item 的每一個字段, 最后將 Item 返回即可。QuotesSpider 的改寫如下所示: import scrapy
from ..items import TutorialItem
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.']
start_urls = ['http://quotes./']
def parse(self, response):
quotes = response.css('.quote')
for quote in quotes:
item = TutorialItem()
item['text'] = quote.css('.text::text').extract_first()
item['author'] = quote.css('.author::text').extract_first()
item['tags'] = quote.css('.tags .tag::text').extract()
yield item 如此一來,首頁的所有內(nèi)容被解析出來,并被賦值成了一個個 QuoteItem 。 7. 后續(xù) Request上面的操作實現(xiàn)了從初始頁面抓取內(nèi)容。那么,下一頁的內(nèi)容該如何抓取?這就需要我們從當前頁面中找到信息來生成下一個請求,然后在下一個請求的頁面里找到信息再構造再下一個請求。這樣循環(huán)往復迭代,從而實現(xiàn)整站的爬取。 由于 parse() 就是解析 'text'、'author'、'tags' 的方法,而下一頁的結構和剛才已經(jīng)解析的頁面結構是一樣的,所以我們可以再次使用 parse() 方法來做頁面解析。接下來我們要做的就是利用選擇器得到下一頁鏈接并生成請求,在 parse() 方法后追加如下的代碼: import scrapy
from ..items import TutorialItem
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.']
start_urls = ['http://quotes./']
def parse(self, response):
quotes = response.css('.quote')
for quote in quotes:
item = TutorialItem()
item['text'] = quote.css('.text::text').extract_first()
item['author'] = quote.css('.author::text').extract_first()
item['tags'] = quote.css('.tags .tag::text').extract()
yield item
next = response.css('.pager .next a::attr(href)').extract_first()
url = response.urljoin(next)
yield scrapy.Request(url=url, callback=self.parse) 在上述代碼中,有四個關鍵步驟是爬蟲的關鍵: 01 繼承scrapy.Spider; 02 為Spider取名; 03 設定起始爬取點; 04 實現(xiàn)頁面解析函數(shù)。
(1)繼承 Scrapy.spiderScrapy 框架提供了一個 Spider 基類,我們編寫的 Spider 需要繼承它: import scrapy
class QuotesSpider(scrapy.Spider): 這個 Spider 基類實現(xiàn)了以下內(nèi)容: 供 Scrapy 引擎調(diào)用的接口,例如用來創(chuàng)建 Spider 實例的類方法 from_crawler; 供用戶使用的實用工具函數(shù),例如可以調(diào)用 log 方法將調(diào)試信息輸出到日志; 供用戶訪問的屬性,例如可以通過 settings 屬性訪問配置文件中的配置。
(2)為 Spider 命名在一個 Scrapy 項目中可以實現(xiàn)多個 Spider,每個 Spider 需要有一個能夠區(qū)分彼此的唯一標識,Spider 的類屬性 name 便是這個唯一標識。例如,上述示例項目中的 Spider 名稱是 quotes 。執(zhí)行 scrapy crawl XXX 命令時就用到了這個標識,告訴 Scrapy 使用哪個 Spider 進行爬取。 (3)設定起始爬取點Spider 必然要從某個或某些頁面開始爬取,我們稱這些頁面為起始爬取點,可以通過類屬性 start_urls 來設定起始爬取點。start_urls 通常被實現(xiàn)成一個列表,其中放入所有起始爬取點的 url??吹竭@里,大家可能會產(chǎn)生疑問:我們僅定義了url列表,是誰暗中構造并提交了相應的Request對象呢?通過閱讀 Spider 基類的源碼可以找到答案,相關代碼如下: class Spider(object_ref):
...
def start_requests(self):
for url in self.start_urls:
yield self.make_requests_from_url(url)
def make_requests_from_url(self, url):
return Request(url, dont_filter=True)
def parse(self, response):
raise NotImplementedError
... 從代碼中可以看出,Spider 基類的 start_requests 方法幫助我們構造并提交了 Request 對象,對其中的原理做如下解釋: 實際上,對于起始爬取點的下載請求是由 Scrapy 引擎調(diào)用 Spider 對象的 start_requests 方法提交的,由于 BooksSpider 類沒有實現(xiàn)start_requests 方法,因此引擎會調(diào)用 Spider 基類的 start_requests 方法; 在start_requests 方法中,self.start_urls 便是我們定義的起始爬取點列表(通過實例訪問類屬性),對其進行迭代,用迭代出的每個 url 作為參數(shù)調(diào)用 make_requests_from_url 方法; 在 make_requests_from_url 方法中,我們找到了真正構造 Reqeust 對象的代碼,僅使用 url 和 dont_filter 參數(shù)構造 Request 對象; 由于構造 Request 對象時并沒有傳遞 callback 參數(shù)來指定頁面解析函數(shù),因此默認將 parse 方法作為頁面解析函數(shù)。此時 BooksSpider 必須實現(xiàn) parse 方法,否則就會調(diào)用 Spider 基類的 parse 方法,從而拋出 NotImplementedError 異常(可以看作基類定義了一個抽象接口); 起始爬取點可能有多個,start_requests 方法需要返回一個可迭代對象(列表、生成器等),其中每一個元素是一個 Request 對象。這里,start_requests 方法被實現(xiàn)成一個生成器函數(shù)(生成器對象是可迭代的),每次由 yield 語句返回一個 Request 對象。
由于起始爬取點的下載請求是由引擎調(diào)用 Spider 對象的 start_requests 方法產(chǎn)生的,因此我們也可以在 BooksSpider 中實現(xiàn) start_requests 方法(覆蓋基類 Spider 的 start_requests 方法),直接構造并提交起始爬取點的 Request 對象。在某些場景下使用這種方式更加靈活,例如有時想為 Request 添加特定的 HTTP 請求頭部,或想為 Request 指定特定的頁面解析函數(shù)。 以下是通過實現(xiàn) start_requests 方法定義起始爬取點的示例代碼(改寫QuotesSpider): import scrapy
from ..items import TutorialItem
class QuotesSpider(scrapy.Spider):
name = 'quotes'
allowed_domains = ['quotes.']
def start_requests(self):
yield scrapy.Request('http://quotes./',
callback=self.parse_quote,
headers={'User-Agent': 'Mozilla/5.0'},
dont_filter=True)
def parse_quote(self, response):
quotes = response.css('.quote')
for quote in quotes:
item = TutorialItem()
item['text'] = quote.css('.text::text').extract_first()
item['author'] = quote.css('.author::text').extract_first()
item['tags'] = quote.css('.tags .tag::text').extract()
yield item
next = response.css('.pager .next a::attr(href)').extract_first()
url = response.urljoin(next)
yield scrapy.Request(url=url, callback=self.parse) 到此,我們介紹完了為爬蟲設定起始爬取點的兩種方式: 一、定義 start_urls 屬性;二、實現(xiàn) start_requests 方法。 (4)實現(xiàn)頁面解析函數(shù)頁面解析函數(shù)也就是構造 Request 對象時通過 callback 參數(shù)指定的回調(diào)函數(shù)(或默認的 parse 方法)。頁面解析函數(shù)是實現(xiàn) Spider 中最核心的部分,它需要完成以下兩項工作: 一個頁面中可能包含多項數(shù)據(jù)以及多個鏈接,因此頁面解析函數(shù)被要求返回一個可迭代對象(通常被實現(xiàn)成一個生成器函數(shù)),每次迭代返回一項數(shù)據(jù)(Item或字典)或一個 Request 對象。 8. 運行接下來,進入目錄,運行如下命令: scrapy crawl quotes Scrapy 運行結果如下所示: (pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider$ cd tutorial
(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider/tutorial$ ls
scrapy.cfg tutorial
(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Quote_Spider/tutorial$ scrapy crawl quotes
2021-03-15 11:51:18 [scrapy.utils.log] INFO: Scrapy 2.4.1 started (bot: tutorial)
2021-03-15 11:51:18 [scrapy.utils.log] INFO: Versions: lxml 4.5.2.0, libxml2 2.9.10, cssselect 1.1.0, parsel 1.6.0, w3lib 1.22.0, Twisted 21.2.0, Python 3.6.9 (default, Jul 17 2020, 12:50:27) - [GCC 8.4.0], pyOpenSSL 20.0.1 (OpenSSL 1.1.1j 16 Feb 2021), cryptography 3.4.6, Platform Linux-4.15.0-136-generic-x86_64-with-Ubuntu-18.04-bionic
2021-03-15 11:51:18 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.epollreactor.EPollReactor
2021-03-15 11:51:18 [scrapy.crawler] INFO: Overridden settings:
{'BOT_NAME': 'tutorial',
'NEWSPIDER_MODULE': 'tutorial.spiders',
'ROBOTSTXT_OBEY': True,
'SPIDER_MODULES': ['tutorial.spiders']}
2021-03-15 11:51:18 [scrapy.extensions.telnet] INFO: Telnet Password: fdef5e4b01ff9fce
2021-03-15 11:51:18 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
'scrapy.extensions.telnet.TelnetConsole',
'scrapy.extensions.memusage.MemoryUsage',
'scrapy.extensions.logstats.LogStats']
2021-03-15 11:51:18 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
'scrapy.downloadermiddlewares.retry.RetryMiddleware',
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
'scrapy.downloadermiddlewares.stats.DownloaderStats']
2021-03-15 11:51:29 [scrapy.dupefilters] DEBUG: Filtered duplicate request: <GET http://quotes./page/10/> - no more duplicates will be shown (see DUPEFILTER_DEBUG to show all duplicates)
2021-03-15 11:51:29 [scrapy.core.engine] INFO: Closing spider (finished)
2021-03-15 11:51:29 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 2881,
'downloader/request_count': 11,
'downloader/request_method_count/GET': 11,
'downloader/response_bytes': 23388,
'downloader/response_count': 11,
'downloader/response_status_count/200': 10,
'downloader/response_status_count/404': 1,
'dupefilter/filtered': 1,
'elapsed_time_seconds': 10.21772,
'finish_reason': 'finished',
'finish_time': datetime.datetime(2021, 3, 15, 3, 51, 29, 96046),
'item_scraped_count': 100,
'log_count/DEBUG': 112,
'log_count/INFO': 10,
'memusage/max': 56266752,
'memusage/startup': 56266752,
'request_depth_max': 10,
'response_received_count': 11,
'robotstxt/request_count': 1,
'robotstxt/response_count': 1,
'robotstxt/response_status_count/404': 1,
'scheduler/dequeued': 10,
'scheduler/dequeued/memory': 10,
'scheduler/enqueued': 10,
'scheduler/enqueued/memory': 10,
'start_time': datetime.datetime(2021, 3, 15, 3, 51, 18, 878326)}
2021-03-15 11:51:29 [scrapy.core.engine] INFO: Spider closed (finished) 這里只是部分運行結果 ,中間一些抓取結果已省略。 首先,Scrapy 輸出了當前的版本號以及正在啟動的項目名稱。接著輸出了當前 settings.py 中一些重寫后的配置。然后輸出了當前所應用的 Middlewares 和 Pipelines 。Middlewares 默認是啟用的 ,可以在 settings.py 中修改。Pipelines 默認是空 ,同樣也可以在 settings py 中配置。 接下來就是輸出各個頁面的抓取結果了,可以看到爬蟲一邊解析,一邊翻頁,直至將所有內(nèi)容抓取完畢,然后終止。 最后, Scrapy 輸出了整個抓取過程的統(tǒng)計信息,如請求的字節(jié)數(shù)、請求次數(shù)、響應次數(shù)、完成原因等。 9. 保存到文件運行完 Scrapy 后,我們只在控制臺看到了輸出結果,那如何保存輸出結果呢?要完成這個任務其實不需要任何額外的代碼,Scrapy 提供的 Feed Exports 可以輕松將抓取結果輸出。例如,我們想將上面的結果保存成 JSON 文件,可以執(zhí)行如下命令: scrapy crawl quotes -o quotes.json 命令運行后,項目內(nèi)多了一個 quotes.json 文件,文件包含了剛才抓取的所有內(nèi)容,內(nèi)容是 JSON 格式。如下圖所示: 另外我們還可以每一個 Item 輸出一行 JSON ,輸出后綴為 jl,為 jsonline 的縮寫,命令如下所示: scrapy crawl quotes -o quotes.jl
# 或者
scrapy crawl quotes -o quotes.jsonlines 輸出格式還支持很多種,例如:'csv'、'xml'、'pickle'、'marshal' 等,還支持 'ftp'、's3' 等遠程輸出,另外還可以通過自定義 ItemExporter 來實現(xiàn)其他的輸出。 通過 Scrapy 提供的 Feed Exports ,我們可以輕松地輸出抓取結果到文件。對于一些小型項目來說,這應該足夠了。如果想要更復雜的輸出,如輸出到數(shù)據(jù)庫等,我們可以使用 Item Pileline 來完成。 四. 實例——爬取書籍信息1. 創(chuàng)建項目在本地和遠程服務器的相應文件夾中創(chuàng)建爬蟲項目的文件夾,然后在 Pycharm 中打開本地文件夾,連接遠程文件。 從Ubuntu 或者 Pycharm 中進入終端,然后在對應的虛擬環(huán)境下進入到爬蟲項目工程文件夾,再運行下面的命令: (pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Scrapy_Book$ scrapy startproject toscrapy
New Scrapy project 'toscrapy', using template directory '/home/pyvip/.virtualenvs/pyspider/lib/python3.6/site-packages/scrapy/templates/project', created in:
/home/pyvip/project/Python_Spider/Spider_Project/Scrapy_Book/toscrapy
You can start your first spider with:
cd toscrapy
scrapy genspider example example.com 2. 創(chuàng)建爬蟲從終端進入項目工程文件,運行如下命令: (pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Scrapy_Book/toscrapy$ cd toscrapy
(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Scrapy_Book/toscrapy/toscrapy$ ls
__init__.py items.py middlewares.py pipelines.py settings.py spiders
(pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Scrapy_Book/toscrapy/toscrapy$ scrapy genspider books books.
Created spider 'books' using template 'basic' in module:
toscrapy.spiders.books 3. 解析 Response在 books.py 文件中修改 parse 函數(shù),修改內(nèi)容如下: import scrapy
class BooksSpider(scrapy.Spider):
name = 'books'
allowed_domains = ['books.']
start_urls = ['http://books./']
def parse(self, response):
# 提取數(shù)據(jù)
for book in response.css('article.product_pod'):
name = book.xpath('./h3/a/@title').extract_first()
price = book.css('p.price_color::text').extract_first()
yield {'name': name,
'price': price,
}
# 提取鏈接
next_url = response.css('ul.pager li.next a::attr(href)').extract_first()
if next_url:
next_url = response.urljoin(next_url)
yield scrapy.Request(next_url, callback=self.parse) 4. 運行爬蟲并保存數(shù)據(jù)從終端進入項目工程文件,運行如下命令: (pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Scrapy_Book/toscrapy/toscrapy$ scrapy crawl books
2021-03-15 18:09:09 [scrapy.utils.log] INFO: Scrapy 2.4.1 started (bot: toscrapy)
2021-03-15 18:09:09 [scrapy.utils.log] INFO: Versions: lxml 4.5.2.0, libxml2 2.9.10, cssselect 1.1.0, parsel 1.6.0, w3lib 1.22.0, Twisted 21.2.0, Python 3.6.9 (default, Jul 17 2020, 12:50:27) - [GCC 8.4.0], pyOpenSSL 20.0.1 (OpenSSL 1.1.1j 16 Feb 2021), cryptography 3.4.6, Platform Linux-4.15.0-136-generic-x86_64-with-Ubuntu-18.04-bionic
2021-03-15 18:09:09 [scrapy.utils.log] DEBUG: Using reactor: twisted.internet.epollreactor.EPollReactor
2021-03-15 18:09:09 [scrapy.crawler] INFO: Overridden settings:
{'BOT_NAME': 'toscrapy',
'NEWSPIDER_MODULE': 'toscrapy.spiders',
'ROBOTSTXT_OBEY': True,
'SPIDER_MODULES': ['toscrapy.spiders']}
2021-03-15 18:09:09 [scrapy.extensions.telnet] INFO: Telnet Password: ee928a9f1bd414d4
2021-03-15 18:09:09 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
'scrapy.extensions.telnet.TelnetConsole',
'scrapy.extensions.memusage.MemoryUsage',
'scrapy.extensions.logstats.LogStats']
2021-03-15 18:09:09 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware',
'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
'scrapy.downloadermiddlewares.retry.RetryMiddleware',
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
'scrapy.downloadermiddlewares.stats.DownloaderStats']
......
......
2021-03-15 18:11:54 [scrapy.core.scraper] DEBUG: Scraped from <200 http://books./catalogue/page-50.html>
{'name': "A Spy's Devotion (The Regency Spies of London #1)", 'price': '£16.97'}
2021-03-15 18:11:54 [scrapy.core.scraper] DEBUG: Scraped from <200 http://books./catalogue/page-50.html>
{'name': "1st to Die (Women's Murder Club #1)", 'price': '£53.98'}
2021-03-15 18:11:54 [scrapy.core.scraper] DEBUG: Scraped from <200 http://books./catalogue/page-50.html>
{'name': '1,000 Places to See Before You Die', 'price': '£26.08'}
2021-03-15 18:11:54 [scrapy.core.engine] INFO: Closing spider (finished)
2021-03-15 18:11:54 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/exception_count': 3,
'downloader/exception_type_count/twisted.internet.error.DNSLookupError': 3,
'downloader/request_bytes': 15515,
'downloader/request_count': 53,
'downloader/request_method_count/GET': 53,
'downloader/response_bytes': 291473,
'downloader/response_count': 50,
'downloader/response_status_count/200': 50,
'elapsed_time_seconds': 164.723678,
'finish_reason': 'finished',
'finish_time': datetime.datetime(2021, 3, 15, 10, 11, 54, 716833),
'item_scraped_count': 1000,
'log_count/DEBUG': 1052,
'log_count/ERROR': 2,
'log_count/INFO': 12,
'memusage/max': 62898176,
'memusage/startup': 56143872,
'request_depth_max': 49,
'response_received_count': 50,
'retry/count': 2,
'retry/max_reached': 1,
'retry/reason_count/twisted.internet.error.DNSLookupError': 2,
"robotstxt/exception_count/<class 'twisted.internet.error.DNSLookupError'>": 1,
'robotstxt/request_count': 1,
'scheduler/dequeued': 50,
'scheduler/dequeued/memory': 50,
'scheduler/enqueued': 50,
'scheduler/enqueued/memory': 50,
'start_time': datetime.datetime(2021, 3, 15, 10, 9, 9, 993155)}
2021-03-15 18:11:54 [scrapy.core.engine] INFO: Spider closed (finished) 如果要將上述爬取的數(shù)據(jù)保存到文件,則需要運行如下命令: scrapy crawl books -o books.csv 然后在項目工程文件夾下生成文件 books.csv ,在終端查看該文件內(nèi)容如下: (pyspider) pyvip@VIP:~/project/Python_Spider/Spider_Project/Scrapy_Book/toscrapy/toscrapy$ sed -n '2,$p' books.csv | cat -n
1 A Light in the Attic,£51.77
2 Tipping the Velvet,£53.74
3 Soumission,£50.10
4 Sharp Objects,£47.82
5 Sapiens: A Brief History of Humankind,£54.23
6 The Requiem Red,£22.65
7 The Dirty Little Secrets of Getting Your Dream Job,£33.34
8 "The Coming Woman: A Novel Based on the Life of the Infamous Feminist, Victoria Woodhull",£17.93
9 The Boys in the Boat: Nine Americans and Their Epic Quest for Gold at the 1936 Berlin Olympics,£22.60
10 The Black Maria,£52.15
...... 上述文章內(nèi)容如有錯誤,歡迎各位朋友在評論區(qū)留言!
|