Scrapy是一個(gè)為了爬取網(wǎng)站數(shù)據(jù),提取結(jié)構(gòu)性數(shù)據(jù)而編寫(xiě)的應(yīng)用框架。 可以應(yīng)用在包括數(shù)據(jù)挖掘,信息處理或存儲(chǔ)歷史數(shù)據(jù)等一系列的程序中。
其最初是為了頁(yè)面抓取(更確切來(lái)說(shuō),網(wǎng)絡(luò)抓取)所設(shè)計(jì)的, 也可以應(yīng)用在獲取API所返回的數(shù)據(jù)(例如Amazon Associates Web Services) 或者通用的網(wǎng)絡(luò)爬蟲(chóng)。
Scrapy構(gòu)架
下圖顯示Scrapy的結(jié)構(gòu)和組件,箭頭表示框架內(nèi)數(shù)據(jù)流情況。

Scrapy中的數(shù)據(jù)流由執(zhí)行引擎控制,具體流程如下:
- 引擎獲取初始請(qǐng)求從爬蟲(chóng)開(kāi)始抓取數(shù)據(jù)。
- 引擎在調(diào)度器中調(diào)度請(qǐng)求,并要求抓取下一個(gè)請(qǐng)求。
- 調(diào)度器將下一個(gè)請(qǐng)求返回給引擎。
- 引擎將請(qǐng)求發(fā)送到下載器,通過(guò)下載器中間件。
- 一旦頁(yè)面完成下載,下載器會(huì)生成響應(yīng)并將其發(fā)送到引擎,通過(guò)下載中間件。
- 引擎從下載器接收響應(yīng)并將其發(fā)送到爬蟲(chóng)進(jìn)行處理,通過(guò)爬中間件。
- 爬蟲(chóng)處理響應(yīng),并將抓取的項(xiàng)目和新的請(qǐng)求返回到引擎,通過(guò)爬蟲(chóng)中間件。
- 引擎將處理過(guò)的項(xiàng)目發(fā)送到項(xiàng)目管道,然后將處理過(guò)的請(qǐng)求發(fā)送到調(diào)度器,并要求可能的下一個(gè)請(qǐng)求爬取。
- 重復(fù)以上流程直到調(diào)度器沒(méi)有更多的請(qǐng)求。
接下來(lái)我們通過(guò)項(xiàng)目實(shí)例來(lái)看看它是如何運(yùn)行的。
安裝Scrapy
使用pip安裝:
pip install scrapy
windows系統(tǒng)下安裝會(huì)出現(xiàn)異常,需要到https://www.lfd.uci.edu/~gohlke/pythonlibs/下載Twisted庫(kù)的whl文件,切換到文件目錄后,在命令行下輸入:
pip install Twisted-17.9.0-cp35-cp35m-win_amd64.whl
注:當(dāng)前操作系統(tǒng)是64位的,環(huán)境是Python3.5,所以選擇下載Twisted-17.9.0-cp35-cp35m-win_amd64.whl

安裝完后再次運(yùn)行:
pip install scrapy
創(chuàng)建項(xiàng)目

圖靈社區(qū):主要以出版計(jì)算機(jī)、數(shù)學(xué)統(tǒng)計(jì)、科普等圖書(shū),并授權(quán)銷(xiāo)售正版電子書(shū)的在線社區(qū)。是我比較喜歡的出版社之一,有興趣的可以看看上面的書(shū)籍,質(zhì)量都不錯(cuò)。
這里我們以爬取圖靈社區(qū)圖書(shū)信息作為項(xiàng)目先切換到要放置項(xiàng)目的目錄文件夾,在命令行下輸入:
scrapy startproject ituring

該命令會(huì)創(chuàng)建包含下面內(nèi)容的ituring目錄:
- scrapy.cfg:項(xiàng)目的配置文件
- items.py:項(xiàng)目中的item文件
- middlewares.py:項(xiàng)目中的中間件文件
- pipelines.py:項(xiàng)目中的pipelines文件
- settings.py:項(xiàng)目的設(shè)置文件
創(chuàng)建爬蟲(chóng)文件
進(jìn)入項(xiàng)目文件夾ituring/ituring,使用如下命令:
cd ituring
創(chuàng)建爬蟲(chóng)程序并設(shè)定允許爬取的域名地址:
scrapy genspider ituring_spider ituring.com.cn
在spiders目錄下會(huì)新創(chuàng)建ituring_spider.py文件,具體代碼如下:
import scrapy
class IturingSpiderSpider(scrapy.Spider):
name = 'ituring_spider'
allowed_domains = ['ituring.com.cn']
start_urls = ['http://ituring.com.cn/']
def parse(self, response):
pass
定義Item
Item 是保存爬取到的數(shù)據(jù)的容器,本質(zhì)上就是Python字典數(shù)據(jù)類(lèi)型,提供了額外保護(hù)機(jī)制來(lái)避免拼寫(xiě)錯(cuò)誤導(dǎo)致的未定義字段錯(cuò)誤。每個(gè)自定義的Item類(lèi)都繼承scrapy.item類(lèi),字段定義類(lèi)型scrapy.Field類(lèi)屬性。
根據(jù)我們要爬取網(wǎng)站的數(shù)據(jù)進(jìn)行設(shè)定字段,從http://www.ituring.com.cn/book主頁(yè)點(diǎn)擊進(jìn)入書(shū)籍,可以看到書(shū)籍詳情數(shù)據(jù),這里我們需要選取的數(shù)據(jù)有,標(biāo)題、鏈接地址、書(shū)籍圖片、推薦人數(shù)、閱讀人數(shù)及圖書(shū)價(jià)格。
打開(kāi)items.py文件開(kāi)始在Item中定義相應(yīng)字段,對(duì)IturingItem類(lèi)進(jìn)行修改:
import scrapy
class IturingItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field() # 標(biāo)題
link = scrapy.Field() # 鏈接
img = scrapy.Field() # 圖片
up_count = scrapy.Field() # 推薦數(shù)
read_count = scrapy.Field() # 閱讀數(shù)
price = scrapy.Field() # 價(jià)格
編寫(xiě)爬蟲(chóng)
Spider是用戶編寫(xiě)用于從單個(gè)網(wǎng)站(或者一些網(wǎng)站)爬取數(shù)據(jù)的類(lèi)??梢源嬖诙鄠€(gè)Spider類(lèi),但name屬性的值(即爬蟲(chóng)名稱)必須唯一,start_urls作為初始的URL,可以對(duì)其進(jìn)行重寫(xiě),一旦重寫(xiě)則start_urls立即失效。
在編寫(xiě)爬蟲(chóng)前我們先對(duì)網(wǎng)站進(jìn)行分析,開(kāi)始從http://www.ituring.com.cn/book主頁(yè)獲取每本書(shū)籍詳情頁(yè)鏈接,后面將要請(qǐng)求的request發(fā)送給調(diào)度器,接著使用編寫(xiě)的回調(diào)函數(shù)對(duì)詳情頁(yè)進(jìn)行解析。
通過(guò)檢查元素發(fā)現(xiàn)書(shū)籍在a標(biāo)簽下的href屬性值里,并且因?yàn)槭窍鄬?duì)地址,還需要拼接成絕對(duì)地址。

進(jìn)入詳情頁(yè)http://www.ituring.com.cn/book/1927使用谷歌瀏覽器插件 XPath Helper提取需要的字段的XPath進(jìn)行調(diào)試(這里也可以用檢查元素進(jìn)行Copy XPath)。

下面開(kāi)始我們的第一個(gè)爬蟲(chóng),打開(kāi)ituring/ituring/spiders目錄下的ituring_spider.py:
import scrapy
from ituring.items import IturingItem
class IturingSpiderSpider(scrapy.Spider):
name = 'ituring_spider' # 爬蟲(chóng)名稱
allowed_domains = ['ituring.com.cn'] # 允許爬取的域名地址
start_urls = ['http://www.ituring.com.cn/book'] # 初始URL
def parse(self, response):
"""
初始URl默認(rèn)使用該解析方法
"""
for book in response.xpath('//div[@class="book-img"]/a/@href'):
url = response.urljoin(book.extract()) # 從列表頁(yè)獲取書(shū)籍URL鏈接
yield scrapy.Request(url, callback=self.parse_book_info) # 發(fā)送給調(diào)度器,回調(diào)函數(shù)使用parse_book_info
def parse_book_info(self, response):
"""
解析圖書(shū)詳情頁(yè)信息
"""
item = IturingItem() # 定義Item
item['title'] = response.xpath('//div[@class="book-title"]/h2/text()').extract_first()
item['link'] = response.url
item['img'] = response.xpath('//div[@class="book-img"]/a/img/@src').extract_first()
item['up_count'] = response.xpath('//*[@id="toggle-vote"]/span[1]/text()').extract_first()
item['read_count'] = response.xpath('//*[@id="book-fav-vote"]/div/span[1]/text()').extract_first()
item['price'] = response.xpath('//dl/dd/span[@class="price"]/text()').extract_first()
yield item
啟動(dòng)爬蟲(chóng),抓取數(shù)據(jù)
到這里,我們的爬蟲(chóng)程序基本雛形已經(jīng)完成,現(xiàn)在可以運(yùn)行我們的爬蟲(chóng)程序,在命令行下輸入:
scrapy crawl ituring_spider
啟動(dòng)爬蟲(chóng)后,將得到類(lèi)似以下的輸出信息:

可以看到我們的爬蟲(chóng)程序已經(jīng)爬取到詳情頁(yè)信息數(shù)據(jù),但仍然有問(wèn)題數(shù)據(jù)里多了很多無(wú)用的字符例如
\r、\n、¥。
項(xiàng)目管道(Item Pipeline)
當(dāng)Spider最終處理的Item之后,會(huì)被傳遞到項(xiàng)目管道,管道按順序進(jìn)行執(zhí)行處理,在這里我們可以定義處理清除無(wú)用字符串的Pipeline。另外需要判斷進(jìn)入管道的Item是否有需要處理的字段,因?yàn)槲覀円苍S有很多的Item進(jìn)入到管道里。通過(guò)管道將Item里字段多余的無(wú)用字符刪除掉,達(dá)到清理數(shù)據(jù)的效果。
打開(kāi)pipelines.py文件,看到默認(rèn)的管道類(lèi):
class IturingPipeline(object):
def process_item(self, item, spider):
return item
處理后的Item最終需要return,下面開(kāi)始自定義我們的管道吧!
class StripPipeline(object):
"""
清除無(wú)用字符
"""
def process_item(self, item, spider):
if item['price']:
item['price'] = item['price'].replace(' ', '').replace('\r', '').replace('\n', '').replace('¥', '')
if item['title']:
item['title'] = item['title'].replace(' ', '').replace('\r', '').replace('\n', '')
return item
翻頁(yè)爬取
在這之前我們還只是爬取單頁(yè)的URL鏈接,而爬取多頁(yè)則需要分析網(wǎng)頁(yè)的翻頁(yè)。通過(guò)點(diǎn)擊下一頁(yè),發(fā)現(xiàn)原來(lái)的URL跳轉(zhuǎn)到http://www.ituring.com.cn/book?tab=book&sort=hot&page=1,看出page參數(shù)是翻頁(yè)頁(yè)碼,起始頁(yè)是0。使用構(gòu)造頁(yè)碼的方式可以遍歷所有的頁(yè)碼頁(yè)面,當(dāng)沒(méi)有獲取到對(duì)應(yīng)數(shù)據(jù)則停止。進(jìn)一步分析發(fā)現(xiàn)最有一頁(yè)沒(méi)有下一頁(yè),而下一頁(yè)則正是我們接下來(lái)要爬取的頁(yè)面。所以可以通過(guò)判斷當(dāng)前頁(yè)面是否有下一頁(yè),如果有則從“下一頁(yè)”標(biāo)簽中的鏈接開(kāi)始爬取,如果沒(méi)有下一頁(yè)則爬取完后停止程序。


打開(kāi)ituring_spider.py文件,添加翻頁(yè)代碼:
import scrapy
from ituring.items import IturingItem
class IturingSpiderSpider(scrapy.Spider):
name = 'ituring_spider' # 爬蟲(chóng)名稱
allowed_domains = ['ituring.com.cn'] # 允許爬取的域名地址
start_urls = ['http://www.ituring.com.cn/book'] # 初始URL
def parse(self, response):
"""
初始URl默認(rèn)使用該解析方法
"""
for book in response.xpath('//div[@class="book-img"]/a/@href'):
url = response.urljoin(book.extract()) # 從列表頁(yè)獲取書(shū)籍URL鏈接
yield scrapy.Request(url, callback=self.parse_book_info) # 發(fā)送給調(diào)度器,回調(diào)函數(shù)使用parse_book_info
next_page = response.xpath('//div/ul/li[@class="PagedList-skipToNext"]/a/@href')
if next_page:
url = response.urljoin(next_page.extract_first()) # 下一頁(yè)的鏈接
yield scrapy.Request(url, callback=self.parse)
def parse_book_info(self, response):
"""
解析圖書(shū)詳情頁(yè)信息
"""
item = IturingItem() # 定義Item
item['title'] = response.xpath('//div[@class="book-title"]/h2/text()').extract_first()
item['link'] = response.url
item['img'] = response.xpath('//div[@class="book-img"]/a/img/@src').extract_first()
item['up_count'] = response.xpath('//*[@id="toggle-vote"]/span[1]/text()').extract_first()
item['read_count'] = response.xpath('//*[@id="book-fav-vote"]/div/span[1]/text()').extract_first()
item['price'] = response.xpath('//dl/dd/span[@class="price"]/text()').extract_first()
yield item
配置settings
不管是管道還是中間件,都需要到setting文件里面進(jìn)行設(shè)置啟用,這步可以可以在編寫(xiě)完管道或中間件后進(jìn)行。
設(shè)置存放在settings.py文件,打開(kāi)后編輯添加配置信息:
- 激活管道
管道對(duì)應(yīng)的值越大則越后通過(guò),這里可以看到兩個(gè)管道,默認(rèn)管道和處理無(wú)用字符的管道。
ITEM_PIPELINES = {
'ituring.pipelines.IturingPipeline': 300,
'ituring.pipelines.StripPipeline': 400,
}
- 設(shè)置延時(shí)
因?yàn)榕老x(chóng)程序設(shè)計(jì)爬取到多頁(yè)面,為防止對(duì)服務(wù)器造成影響和可能被kill掉,需要添加每次請(qǐng)求延時(shí)時(shí)間。
DOWNLOAD_DELAY = 3
保存數(shù)據(jù)
最簡(jiǎn)單存儲(chǔ)爬取的數(shù)據(jù)的方式是使用Feed exports,其自帶的類(lèi)型有:
- JSON
- JSON lines
- CSV
- XML
你也可以通過(guò)FEED_EXPORTERS設(shè)置擴(kuò)展支持的屬性,也可以存儲(chǔ)到數(shù)據(jù)庫(kù)里,數(shù)據(jù)庫(kù)推薦使用MongoDB。
在啟動(dòng)爬蟲(chóng)的命令后面加上-o file_name.json將對(duì)爬取的數(shù)據(jù)進(jìn)行序列化并采用JSON格式存儲(chǔ),生成文件file_name.json文件。
scrapy crawl ituring_spider -o items.json

最后打開(kāi)items.json文件查看數(shù)據(jù)發(fā)現(xiàn)title標(biāo)題數(shù)據(jù)中文亂碼,打開(kāi)settings.py文件配置FEED_EXPORTERS導(dǎo)出的編碼方式:
# 設(shè)置輸出格式
# FEED_EXPORT_ENCODING = 'utf-8'