Scrapy是Python開發的一個非常流行的網絡爬蟲框架,可以用來抓取Web站點並從頁面中提取結構化的數據,被廣泛的用於數據挖掘、數據監測和自動化測試等領域。下圖展示了Scrapy的基本架構,其中包含了主要組件和係統的數據處理流程(圖中帶數字的紅色箭頭)。
- Scrapy引擎(Engine):Scrapy引擎是用來控制整個係統的數據處理流程。
- 調度器(Scheduler):調度器從Scrapy引擎接受請求並排序列入隊列,並在Scrapy引擎發出請求後返還給它們。
- 下載器(Downloader):下載器的主要職責是抓取網頁並將網頁內容返還給蜘蛛(Spiders)。
- 蜘蛛(Spiders):蜘蛛是有Scrapy用戶自定義的用來解析網頁並抓取特定URL返回的內容的類,每個蜘蛛都能處理一個域名或一組域名,簡單的說就是用來定義特定網站的抓取和解析規則。
- 條目管道(Item Pipeline):條目管道的主要責任是負責處理有蜘蛛從網頁中抽取的數據條目,它的主要任務是清理、驗證和存儲數據。當頁面被蜘蛛解析後,將被發送到條目管道,並經過幾個特定的次序處理數據。每個條目管道組件都是一個Python類,它們獲取了數據條目並執行對數據條目進行處理的方法,同時還需要確定是否需要在條目管道中繼續執行下一步或是直接丟棄掉不處理。條目管道通常執行的任務有:清理HTML數據、驗證解析到的數據(檢查條目是否包含必要的字段)、檢查是不是重複數據(如果重複就丟棄)、將解析到的數據存儲到數據庫(關係型數據庫或NoSQL數據庫)中。
- 中間件(Middlewares):中間件是介於Scrapy引擎和其他組件之間的一個鈎子框架,主要是爲了提供自定義的代碼來拓展Scrapy的功能,包括下載器中間件和蜘蛛中間件。
Scrapy的整個數據處理流程由Scrapy引擎進行控制,通常的運轉流程包括以下的步驟:
-
引擎詢問蜘蛛需要處理哪個網站,並讓蜘蛛將第一個需要處理的URL交給它。
-
引擎讓調度器將需要處理的URL放在隊列中。
-
引擎從調度那獲取接下來進行爬取的頁面。
-
調度將下一個爬取的URL返回給引擎,引擎將它通過下載中間件發送到下載器。
-
當網頁被下載器下載完成以後,響應內容通過下載中間件被發送到引擎;如果下載失敗了,引擎會通知調度器記錄這個URL,待會再重新下載。
-
引擎收到下載器的響應並將它通過蜘蛛中間件發送到蜘蛛進行處理。
-
蜘蛛處理響應並返回爬取到的數據條目,此外還要將需要跟進的新的URL發送給引擎。
-
引擎將抓取到的數據條目送入條目管道,把新的URL發送給調度器放入隊列中。
上述操作中的2-8步會一直重複直到調度器中沒有需要請求的URL,爬蟲停止工作。
可以先創建虛擬環境並在虛擬環境下使用pip安裝scrapy。
$
項目的目錄結構如下圖所示。
(venv) $ tree
.
|____ scrapy.cfg
|____ douban
| |____ spiders
| | |____ __init__.py
| | |____ __pycache__
| |____ __init__.py
| |____ __pycache__
| |____ middlewares.py
| |____ settings.py
| |____ items.py
| |____ pipelines.py
說明:Windows係統的命令行提示符下有tree命令,但是Linux和MacOS的終端是沒有tree命令的,可以用下面給出的命令來定義tree命令,其實是對find命令進行了定制並別名爲tree。
alias tree="find . -print | sed -e 's;[^/]*/;|____;g;s;____|; |;g'"
Linux係統也可以通過yum或其他的包管理工具來安裝tree。
yum install tree
根據剛才描述的數據處理流程,基本上需要我們做的有以下幾件事情:
-
在items.py文件中定義字段,這些字段用來保存數據,方便後續的操作。
# -*- coding: utf-8 -*- # Define here the models for your scraped items # # See documentation in: # https://doc.scrapy.org/en/latest/topics/items.html import scrapy class DoubanItem(scrapy.Item): name = scrapy.Field() year = scrapy.Field() score = scrapy.Field() director = scrapy.Field() classification = scrapy.Field() actor = scrapy.Field()
-
在spiders文件夾中編寫自己的爬蟲。
(venv) $ scrapy genspider movie movie.douban.com --template=crawl
# -*- coding: utf-8 -*- import scrapy from scrapy.selector import Selector from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from douban.items import DoubanItem class MovieSpider(CrawlSpider): name = 'movie' allowed_domains = ['movie.douban.com'] start_urls = ['https://movie.douban.com/top250'] rules = ( Rule(LinkExtractor(allow=(r'https://movie.douban.com/top250\?start=\d+.*'))), Rule(LinkExtractor(allow=(r'https://movie.douban.com/subject/\d+')), callback='parse_item'), ) def parse_item(self, response): sel = Selector(response) item = DoubanItem() item['name']=sel.xpath('//*[@id="content"]/h1/span[1]/text()').extract() item['year']=sel.xpath('//*[@id="content"]/h1/span[2]/text()').re(r'\((\d+)\)') item['score']=sel.xpath('//*[@id="interest_sectl"]/div/p[1]/strong/text()').extract() item['director']=sel.xpath('//*[@id="info"]/span[1]/a/text()').extract() item['classification']= sel.xpath('//span[@property="v:genre"]/text()').extract() item['actor']= sel.xpath('//*[@id="info"]/span[3]/a[1]/text()').extract() return item
說明:上面我們通過Scrapy提供的爬蟲模板創建了Spider,其中的rules中的LinkExtractor對象會自動完成對新的鏈接的解析,該對象中有一個名爲extract_link的回調方法。Scrapy支持用XPath語法和CSS選擇器進行數據解析,對應的方法分別是xpath和css,上面我們使用了XPath語法對頁面進行解析,如果不熟悉XPath語法可以看看後面的補充說明。
到這裏,我們已經可以通過下面的命令讓爬蟲運轉起來。
(venv)$ scrapy crawl movie
可以在控制台看到爬取到的數據,如果想將這些數據保存到文件中,可以通過
-o
參數來指定文件名,Scrapy支持我們將爬取到的數據導出成JSON、CSV、XML、pickle、marshal等格式。(venv)$ scrapy crawl moive -o result.json
-
在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 pymongo from scrapy.exceptions import DropItem from scrapy.conf import settings from scrapy import log class DoubanPipeline(object): def __init__(self): connection = pymongo.MongoClient(settings['MONGODB_SERVER'], settings['MONGODB_PORT']) db = connection[settings['MONGODB_DB']] self.collection = db[settings['MONGODB_COLLECTION']] def process_item(self, item, spider): #Remove invalid data valid = True for data in item: if not data: valid = False raise DropItem("Missing %s of blogpost from %s" %(data, item['url'])) if valid: #Insert data into database new_moive=[{ "name":item['name'][0], "year":item['year'][0], "score":item['score'], "director":item['director'], "classification":item['classification'], "actor":item['actor'] }] self.collection.insert(new_moive) log.msg("Item wrote to MongoDB database %s/%s" % (settings['MONGODB_DB'], settings['MONGODB_COLLECTION']), level=log.DEBUG, spider=spider) return item
利用Pipeline我們可以完成以下操作:
- 清理HTML數據,驗證爬取的數據。
- 丟棄重複的不必要的內容。
- 將爬取的結果進行持久化操作。
-
修改settings.py文件對項目進行配置。
# -*- coding: utf-8 -*- # Scrapy settings for douban project # # For simplicity, this file contains only settings considered important or # commonly used. You can find more settings consulting the documentation: # # https://doc.scrapy.org/en/latest/topics/settings.html # https://doc.scrapy.org/en/latest/topics/downloader-middleware.html # https://doc.scrapy.org/en/latest/topics/spider-middleware.html BOT_NAME = 'douban' SPIDER_MODULES = ['douban.spiders'] NEWSPIDER_MODULE = 'douban.spiders' # Crawl responsibly by identifying yourself (and your website) on the user-agent USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.54 Safari/536.5' # Obey robots.txt rules ROBOTSTXT_OBEY = True # Configure maximum concurrent requests performed by Scrapy (default: 16) # CONCURRENT_REQUESTS = 32 # Configure a delay for requests for the same website (default: 0) # See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay # See also autothrottle settings and docs DOWNLOAD_DELAY = 3 RANDOMIZE_DOWNLOAD_DELAY = True # The download delay setting will honor only one of: # CONCURRENT_REQUESTS_PER_DOMAIN = 16 # CONCURRENT_REQUESTS_PER_IP = 16 # Disable cookies (enabled by default) COOKIES_ENABLED = True MONGODB_SERVER = '120.77.222.217' MONGODB_PORT = 27017 MONGODB_DB = 'douban' MONGODB_COLLECTION = 'movie' # Disable Telnet Console (enabled by default) # TELNETCONSOLE_ENABLED = False # Override the default request headers: # DEFAULT_REQUEST_HEADERS = { # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', # 'Accept-Language': 'en', # } # Enable or disable spider middlewares # See https://doc.scrapy.org/en/latest/topics/spider-middleware.html # SPIDER_MIDDLEWARES = { # 'douban.middlewares.DoubanSpiderMiddleware': 543, # } # Enable or disable downloader middlewares # See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html # DOWNLOADER_MIDDLEWARES = { # 'douban.middlewares.DoubanDownloaderMiddleware': 543, # } # Enable or disable extensions # See https://doc.scrapy.org/en/latest/topics/extensions.html # EXTENSIONS = { # 'scrapy.extensions.telnet.TelnetConsole': None, # } # Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { 'douban.pipelines.DoubanPipeline': 400, } LOG_LEVEL = 'DEBUG' # Enable and configure the AutoThrottle extension (disabled by default) # See https://doc.scrapy.org/en/latest/topics/autothrottle.html #AUTOTHROTTLE_ENABLED = True # The initial download delay #AUTOTHROTTLE_START_DELAY = 5 # The maximum download delay to be set in case of high latencies #AUTOTHROTTLE_MAX_DELAY = 60 # The average number of requests Scrapy should be sending in parallel to # each remote server #AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 # Enable showing throttling stats for every response received: #AUTOTHROTTLE_DEBUG = False # Enable and configure HTTP caching (disabled by default) # See https://doc.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings HTTPCACHE_ENABLED = True HTTPCACHE_EXPIRATION_SECS = 0 HTTPCACHE_DIR = 'httpcache' HTTPCACHE_IGNORE_HTTP_CODES = [] HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
-
XPath路徑表達式:XPath使用路徑表達式來選取XML文檔中的節點或者節點集。
-
XPath節點:元素、屬性、文本、命名空間、處理指令、注釋、根節點。
-
XPath語法。(注:下面的例子來自於菜鳥教程網站的XPath教程。)
XML文件。
<?xml version="1.0" encoding="UTF-8"?> <bookstore> <book> <title lang="eng">Harry Potter</title> <price>29.99</price> </book> <book> <title lang="eng">Learning XML</title> <price>39.95</price> </book> </bookstore>
XPath語法。
路徑表達式 結果 bookstore 選取 bookstore 元素的所有子節點。 /bookstore 選取根元素 bookstore。注釋:假如路徑起始於正斜杠( / ),則此路徑始終代表到某元素的絕對路徑! bookstore/book 選取屬於 bookstore 的子元素的所有 book 元素。 //book 選取所有 book 子元素,而不管它們在文檔中的位置。 bookstore//book 選擇屬於 bookstore 元素的後代的所有 book 元素,而不管它們位於 bookstore 之下的什麽位置。 //@lang 選取名爲 lang 的所有屬性。 XPath謂詞。
路徑表達式 結果 /bookstore/book[1] 選取屬於 bookstore 子元素的第一個 book 元素。 /bookstore/book[last()] 選取屬於 bookstore 子元素的最後一個 book 元素。 /bookstore/book[last()-1] 選取屬於 bookstore 子元素的倒數第二個 book 元素。 /bookstore/book[position()<3] 選取最前面的兩個屬於 bookstore 元素的子元素的 book 元素。 //title[@lang] 選取所有擁有名爲 lang 的屬性的 title 元素。 //title[@lang='eng'] 選取所有 title 元素,且這些元素擁有值爲 eng 的 lang 屬性。 /bookstore/book[price>35.00] 選取 bookstore 元素的所有 book 元素,且其中的 price 元素的值須大於 35.00。 /bookstore/book[price>35.00]/title 選取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值須大於 35.00。 通配符用法。
路徑表達式 結果 /bookstore/* 選取 bookstore 元素的所有子元素。 //* 選取文檔中的所有元素。 //title[@*] 選取所有帶有屬性的 title 元素。 選取多個路徑。
路徑表達式 結果 //book/title | //book/price 選取 book 元素的所有 title 和 price 元素。 //title | //price 選取文檔中的所有 title 和 price 元素。 /bookstore/book/title | //price 選取屬於 bookstore 元素的 book 元素的所有 title 元素,以及文檔中所有的 price 元素。