Scrapy是用python實現的一個為了爬取網站數據,提取結構性數據而編寫的應用框架。使用Twisted高效異步網絡框架來處理網絡通信。
Scrapy架構:

ScrapyEngine:引擎。負責控制數據流在系統中所有組件中流動,並在相應動作發生時觸發事件。 此組件相當於爬蟲的“大腦”,是 整個爬蟲的調度中心。
Schedule:調度器。接收從引擎發過來的requests,並將他們入隊。初始爬取url和后續在頁面里爬到的待爬取url放入調度器中,等待被爬取。調度器會自動去掉重復的url。
Downloader:下載器。負責獲取頁面數據,並提供給引擎,而后提供給spider。
Spider:爬蟲。用戶編些用於分析response並提取item和額外跟進的url。將額外跟進的url提交給ScrapyEngine,加入到Schedule中。將每個spider負責處理一個特定(或 一些)網站。
ItemPipeline:負責處理被spider提取出來的item。當頁面被爬蟲解析所需的數據存入Item后,將被發送到Pipeline,並經過設置好次序
DownloaderMiddlewares:下載中間件。是在引擎和下載器之間的特定鈎子(specific hook),處理它們之間的請求(request)和響應(response)。提供了一個簡單的機制,通過插入自定義代碼來擴展Scrapy功能。通過設置DownloaderMiddlewares來實現爬蟲自動更換user-agent,IP等。
SpiderMiddlewares:Spider中間件。是在引擎和Spider之間的特定鈎子(specific hook),處理spider的輸入(response)和輸出(items或requests)。提供了同樣簡單機制,通過插入自定義代碼來擴展Scrapy功能。
數據流:
1.ScrapyEngine打開一個網站,找到處理該網站的Spider,並向該Spider請求第一個(批)要爬取的url(s);
2.ScrapyEngine向調度器請求第一個要爬取的url,並加入到Schedule作為請求以備調度;
3.ScrapyEngine向調度器請求下一個要爬取的url;
4.Schedule返回下一個要爬取的url給ScrapyEngine,ScrapyEngine通過DownloaderMiddlewares將url轉發給Downloader;
5.頁面下載完畢,Downloader生成一個頁面的Response,通過DownloaderMiddlewares發送給ScrapyEngine;
6.ScrapyEngine從Downloader中接收到Response,通過SpiderMiddlewares發送給Spider處理;
7.Spider處理Response並返回提取到的Item以及新的Request給ScrapyEngine;
8.ScrapyEngine將Spider返回的Item交給ItemPipeline,將Spider返回的Request交給Schedule進行從第二步開始的重復操作,直到調度器中沒有待處理的Request,ScrapyEngine關閉。
安裝scrapy:
1.安裝wheel支持:
$ pip install wheel
2.安裝scrapy框架:
$ pip install scrapy
3.window下,為了避免windows編譯安裝twisted依賴,安裝下面的二進制包
$ pip install Twisted-18.4.0-cp35-cp35m-win_amd64.whl
scrapy項目結構:
在某路徑下創建scrapy項目: $ scrapy startproject my_project
會產生以下目錄和文件:

內部的first目錄:整個項目的全局目錄
item.py:定義Item類,從scrapy.Item繼承,里面定義scrapy.Field類
pipelines.py:處理爬取的數據流向。重要的是process_item()方法
first目錄下的__init__.py:作為包文件必須有的文件
spiders目錄下的__init__.py:也是必須有。在這里可以寫爬蟲類或爬蟲子模塊
settings.py:
BOT_NAME # 爬蟲名
ROBOTSTXT_OBEY = True # 遵守robots協議
USER_AGENT='' # 指定爬取時使用。一定要更改user-agent,否則訪問會報403錯誤
CONCURRENT_REQUEST = 16 # 默認16個並行
DOWNLOAD_DELAY = 3 # 下載延時
COOKIES_ENABLED = False # 缺省是啟用。一般需要登錄時才需要開啟cookie
DEFAULT_REQUEST_HEADERS = {} # 默認請求頭,需要時填寫
SPIDER_MIDDLEWARES # 爬蟲中間件
DOWNLOADER_MIDDLEWARES # 下載中間件
'first.middlewares.FirstDownloaderMiddleware': 543 # 543優先級越小越高
'firstscrapy.pipelines.FirstscrapyPipeline': 300 # item交給哪一個管道處理,300優先級越小越高
豆瓣書評爬取:
創建爬蟲代碼模版
命令:scrapy genspider -t basic book douban.com # book是爬蟲名字;douban.com是要爬取的url的域名
模板如下:
# -*- coding: utf-8 -*- import scrapy class BookSpider(scrapy.Spider): name = 'book' allowed_domains = ['douban.com'] start_urls = ['http://douban.com/'] def parse(self, response): pass
此時就已經成功創建一個名為‘book’的爬蟲,可以通過命令scrapy list查看。
response是服務器端HTTP響應,它是scrapy.http.response.html.HtmlResponse類。由此,修改代碼如下 :
# -*- coding: utf-8 -*- import scrapy from scrapy.http.response.html import HtmlResponse class BookSpider(scrapy.Spider): name = 'book' # 爬蟲名 allowed_domains = ['douban.com'] # 域名。爬蟲爬取范圍 start_urls = ['https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=0&type=T'] # 起始url,從第一頁開始爬取 # 下載器獲取WebServer的response,parse就是解析響應response的內容 def parse(self, response: HtmlResponse): # 如何解析html;返回一個可迭代對象:利用yiled print(type(response)) # scrapy.http.response.html.HtmlResponse print(type(response.text)) # str print(type(response.body)) # bytes print(response.encoding) # utf-8 # 將網頁內容寫入book.html文件內 with open('/Users/dannihong/Documents/leetcode/scrapy_project/file/book.html', 'w', encoding='utf-8') as f: f.write(response.text) f.flush() except Exception as e: print(e)
爬蟲獲得的內容response對象,可以使用解析庫來解析。scrapy包裝了lxml,父類TextResponse類也提供了xpath方法和css方法,可以混合使用這兩套接口解析HTML。解析html頁面內容的示例代碼如下:
# -*- coding: utf-8 -*- from scrapy.http.response.html import HtmlResponse response = HtmlResponse('file:/Users/dannihong/Documents/leetcode/scrapy_project/file/book.html', encoding='utf-8') with open('/Users/dannihong/Documents/leetcode/scrapy_project/file/book.html', encoding='utf8') as f: response._set_body(f.read().encode()) # _set_body方法將其放入response對象里;需要傳入的參數對象是bytes,所以encode() subjects = response.css('li.subject-item') for subject in subjects: # 提取書籍的網頁鏈接 href = subject.xpath('.//h2').css('a::attr(href)').extract() print('href:', href[0]) # 使用正則表達式,選取評分是9分以上的書籍 rate = subject.xpath('.//span[@class="rating_nums"]/text()').re(r'^9.*') # rate = subject.css('span.rating_nums::text').re(r'^9\..*') # 第二種表達方式 if rate: print(rate[0])
item封裝數據:
# first/item.py
import scrapy class Test1ProItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() title = scrapy.Field() # 存放書籍標題的字段 rate = scrapy.Field() # 存放書籍評分的字段
# first/first/spiders/book.py
# -*- coding: utf-8 -*- import scrapy from scrapy.http.response.html import HtmlResponse from ..items import FirstItem # 從上一層的items.py文件里導入 class BookSpider(scrapy.Spider): name = 'book' # 爬蟲名 allowed_domains = ['douban.com'] # 爬蟲爬取范圍 start_urls = ['https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=0&type=T'] # 起始url custom_settings = {'file_name': '/Users/dannihong/Documents/leetcode/scrapy_project/file/books.json'} # 一般設置參數 # 下載器獲取WebServer的response,parse解析響應的內容;輸出items和requests def parse(self, response: HtmlResponse): # 如何解析html;返回一個可迭代對象:利用yiled subjects = response.xpath('//li[@class="subject-item"]') items = [] # 如果用items=[],最后函數要return items for subject in subjects: item = FirstItem() # 聲明一個item,相當於一個字典,存放要爬取的數據 title = subject.xpath('.//h2/a/text()').extract() item['title'] = title[0].strip() rate = subject.css('span.rating_nums::text').extract() item['rate'] = rate[0].strip() items.append(item) with open('book.json', 'w', encoding='utf8') as f: for item in items: f.write('{} {}\n'.format(item['title'], item['rate'])) return items
pipeline處理
將book.py中BookSpider改成生成器,只需要把return items改造成yield item,即由產生一個列表變成yield一個個item。腳手架幫我們創建了一個pipelines.py文件和一個類。
# Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { 'first.pipelines.FirstPipeline': 300, }
整數300表示優先級,越小越高。取值范圍為0-1000。
pipeline.py里常用的方法:
process_item(self, item, spider) # item表示爬取的一個個數據,spider表示item的爬取者,每一個item處理都得調用。返回一個item對象,或者拋出DropItem異常,被丟棄的item對象將不會被pipeline組件處理;
open_spider(self, spider) # spider表示被開啟的spider,調用一次
close_spider(self, spider) # spider表示被關閉的spider,調用一次
__init__(self) # spider創建實例時調用一次
將爬取的數據通過pipeline寫入到json文件中,代碼如下:
# first/spiders/book.py
# -*- coding: utf-8 -*- import scrapy from scrapy.http.response.html import HtmlResponse from ..items import Test1ProItem class BookSpider(scrapy.Spider): name = 'book' allowed_domains = ['douban.com'] start_urls = ['https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=0&type=T'] custom_settings = {'file_name': '/Users/dannihong/Documents/leetcode/scrapy_project/file/books.json'} # spider上自定義配置信息 def parse(self, response: HtmlResponse): # 如何解析html;返回一個可迭代對象:利用yiled subjects = response.xpath('//li[@class="subject-item"]') for subject in subjects: item =FirstProItem() title = subject.xpath('.//h2/a/text()').extract() item['title'] = title[0].strip() rate = subject.css('span.rating_nums::text').extract() item['rate'] = rate[0].strip() yield item # 返回一個可迭代對象生成器
# first/pipelines.py
# -*- coding: utf-8 -*- # Define your item pipelines here # # Don't forget to add your pipeline to the ITEM_PIPELINES setting # See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html import json from scrapy import Spider class Test1ProPipeline(object): def __int__(self): print('~~~~~~~~~~init~~~~~~~~~~') # 每一個item都會執行一次 def process_item(self, item, spider:Spider): print('++++++++++') print(item) self.file.write('{},\n'.format(json.dumps(dict(item)))) return item # 所有過程在起始的時候執行一次 def open_spider(self, spider): print('==========open spider {}=========='.format(spider)) # file_name = '/Users/dannihong/Documents/leetcode/scrapy_project/file/books.json' file_name = spider.settings['file_name'] self.file = open(file_name, 'w', encoding='utf-8') self.file.write('[\n') # 所有過程結束的時候執行一次 def close_spider(self, spider): print('==========close spider {}=========='.format(spider)) self.file.write(']') self.file.close()
