Scrapy爬蟲框架,入門案例
目錄
一、概述
Scrapy,Python開發的一個快速、高層次的螢幕抓取和web抓取框架,用於抓取web站點並從頁面中提取結構化的資料。Scrapy用途廣泛,可以用於資料探勘、監測和自動化測試.
其最初是為了頁面抓取 (更確切來說, 網路抓取 )所設計的, 後臺也應用在獲取API所返回的資料(例如 Amazon Associates Web Services ) 或者通用的網路爬蟲.
Scrapy吸引人的地方在於它是一個框架,任何人都可以根據需求方便的修改。它也提供了多種型別爬蟲的基類,如BaseSpider、sitemap爬蟲等,最新版本又提供了web2.0爬蟲的支援.
二、Scrapy五大基本構成:
Scrapy框架主要由五大元件組成,它們分別是排程器(Scheduler)、下載器(Downloader)、爬蟲(Spider)和實體管道(Item Pipeline)、Scrapy引擎(Scrapy Engine)。下面我們分別介紹各個元件的作用。
(1)、排程器(Scheduler):
排程器,說白了把它假設成為一個URL(抓取網頁的網址或者說是連結)的優先佇列,由它來決定下一個要抓取的網址是 什麼,同時去除重複的網址(不做無用功)。使用者可以自己的需求定製排程器。
(2)、下載器(Downloader):
下載器,是所有元件中負擔最大的,它用於高速地下載網路上的資源。Scrapy的下載器程式碼不會太複雜,但效率高,主要的原因是Scrapy下載器是建立在twisted這個高效的非同步模型上的(其實整個框架都在建立在這個模型上的)。
(3)、 爬蟲(Spider):
爬蟲,是使用者最關心的部份。使用者定製自己的爬蟲(通過定製正則表示式等語法),用於從特定的網頁中提取自己需要的資訊,即所謂的實體(Item)。 使用者也可以從中提取出連結,讓Scrapy繼續抓取下一個頁面。
(4)、 實體管道(Item Pipeline):
實體管道,用於處理爬蟲(spider)提取的實體。主要的功能是持久化實體、驗證實體的有效性、清除不需要的資訊。
(5)、Scrapy引擎(Scrapy Engine):
Scrapy引擎是整個框架的核心.它用來控制偵錯程式、下載器、爬蟲。實際上,引擎相當於計算機的CPU,它控制著整個流程。
三、整體架構圖
本圖按順序說明整個程式執行時候發生的順序。
注意在呼叫下載器時,往往有一個下載器中介軟體,使下載速度提速。
官網架構圖
四、Scrapy安裝以及生成專案
新建一個專案,該專案的結構如下:
執行命令,widows和ubuntu命令格式是一樣的:
下載方式
ubuntu,開啟一個終端,輸入pip install scrapy(或pip3install scrapy)
widows ,開啟一個cmd,輸入pip install scrapy,前提是你裝了pip
scrapy startproject 專案名
scrapy genspider 爬蟲名 域名
scrapy crawl 爬蟲名
我使用的是widows版本,下面演示建立專案的例子
開啟cmd,輸入(預設是在C:\Users\Administrator>這個目錄下,你可以自行切換)
scrapy startproject myfirstPj
cd my firstPj
scrapy genspider baidu www.baidu.com
建立後目錄大致頁如下
|-ProjectName #專案資料夾
|-ProjectName #專案目錄
|-items.py #定義資料結構
|-middlewares.py #中介軟體
|-pipelines.py #資料處理
|-settings.py #全域性配置
|-spiders
|-__init__.py #爬蟲檔案
|-baidu.py
|-scrapy.cfg #專案基本配置檔案
spiders下的baidu.py是scrapy自動為我們生成的
下面再看一下spdier專案的配置檔案,開啟檔案settings.py
BOT_NAME:專案名
USER_AGENT:預設是註釋的,這個東西非常重要,如果不寫很容易被判斷為電腦,簡單點洗一個Mozilla/5.0即可
ROBOTSTXT_OBEY:是否遵循機器人協議,預設是true,需要改為false,否則很多東西爬不了
CONCURRENT_REQUESTS:最大併發數,很好理解,就是同時允許開啟多少個爬蟲執行緒
DOWNLOAD_DELAY:下載延遲時間,單位是秒,控制爬蟲爬取的頻率,根據你的專案調整,不要太快也不要太慢,預設是3秒,即爬一個停3秒,設定為1秒價效比較高,如果要爬取的檔案較多,寫零點幾秒也行
COOKIES_ENABLED:是否儲存COOKIES,預設關閉,開機可以記錄爬取過程中的COKIE,非常好用的一個引數
DEFAULT_REQUEST_HEADERS:預設請求頭,上面寫了一個USER_AGENT,其實這個東西就是放在請求頭裡面的,這個東西可以根據你爬取的內容做相應設定。
ITEM_PIPELINES:專案管道,300為優先順序,越低越爬取的優先度越高
比如我的pipelines.py裡面寫了兩個管道,一個爬取網頁的管道,一個存資料庫的管道,我調整了他們的優先順序,如果有爬蟲資料,優先執行存庫操作。
- ITEM_PIPELINES = {
- ‘scrapyP1.pipelines.BaiduPipeline’: 300,
- ‘scrapyP1.pipelines.BaiduMysqlPipeline’: 200,
- }
到這裡我們嘗試用scrapy做一下爬取,開啟spider.py下的baidu.py(取決於你scrapy genspider 爬蟲名 域名時輸入的爬蟲名)
輸入一下程式碼,我們使用xpath提取百度首頁的標題title
- importscrapy
- classBaiduSpider(scrapy.Spider):
- name =‘baidu’
- allowed_domains = [‘www.baidu.com’]
- start_urls = [‘http://www.baidu.com/’]
- defparse(self, response):
- tile=response.xpath(‘//html/head/title/text()’)
- print(tile)
開啟一個終端cmd,輸入scrapy crawl baidu(爬蟲名),就可以看到一大堆輸出資訊,而其中就包括我們要的內容
使用終端執行太麻煩了,而且不能提取資料,我們一個寫一個run檔案作為程式的入口,splite是必須寫的,目的是把字串轉為列表形式,第一個引數是scrapy,第二個crawl,第三個baidu
- fromscrapyimportcmdline
- cmdline.execute(‘scrapy crawl baidu’.split())
可以在編輯器中輸出了
五、日誌等級與日誌儲存
在setting.py裡面可以設定日誌的等級與日誌存放的路徑
相關變數
LOG_LEVEL= “”
LOG_FILE=”日誌名.log”
日誌等級分為
1.DEBUG 除錯資訊
2.INFO一般資訊
3.WARNING 警告
4.ERROR 普通錯誤
5.CRITICAL 嚴重錯誤
如果設定
LOG_LEVEL=”WARNING”,就只會WARNING等級之下的ERROR和CRITICAL
預設等級是1
六、匯出為json或scv格式
執行爬蟲檔案時新增-o選項即可
scrapy crawl 專案名 -o *.csv
scrapy crawl 專案名 -o *.json
對於json檔案,在setting.js檔案裡新增,設定編碼格式,否則會亂碼:
FEED_EXPORT_ENCODING=’utf-8′
示例:
- fromscrapyimportcmdline
- cmdline.execute(‘scrapy crawl baidu -o baidu.csv’.split())
七、一個完整的案例
這個專案我們的主題是爬騰訊視訊的電影資訊,包括電影名和描述
1.建立專案
開啟一個終端輸入(建議放到合適的路徑下,預設是C盤)
scrapy startproject TXmovies
cdTXmovies
scrapy genspider txms v.qq.com
2.修改setting
修改三項內容,第一個是不遵循機器人協議,第二個是下載間隙,由於下面的程式要下載多個頁面,所以需要給一個間隙(不給也可以,只是很容易被偵測到),第三個是請求頭,新增一個User-Agent,第四個是開啟一個管道
- ROBOTSTXT_OBEY =False
- DOWNLOAD_DELAY =1
- DEFAULT_REQUEST_HEADERS = {
- ‘Accept’:‘text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8’,
- ‘Accept-Language’:‘en’,
- ‘User-Agent’:‘Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36’
- }
- ITEM_PIPELINES = {
- ‘TXmovies.pipelines.TxmoviesPipeline’:300,
- }
3.確認要提取的資料,item項
item定義你要提取的內容(定義資料結構),比如我提取的內容為電影名和電影描述,我就建立兩個變數。Field方法實際上的做法是建立一個字典,給字典新增一個建,暫時不賦值,等待提取資料後再賦值。下面item的結構可以表示為:{‘name’:”,’descripition’:”}。
- # -*- coding: utf-8 -*-
- # Define here the models for your scraped items
- #
- # See documentation in:
- # https://docs.scrapy.org/en/latest/topics/items.html
- importscrapy
- classTxmoviesItem(scrapy.Item):
- # define the fields for your item here like:
- # name = scrapy.Field()
- name = scrapy.Field()
- description = scrapy.Field()
4.寫爬蟲程式
我們要寫的部分是parse方法裡的內容,重點在於如何寫xpath,關於xpath我不多講,有興趣可以看看我另一篇文章,XPATH教程
引入剛剛寫好的item,剛剛說了item裡面建立的變數就是字典的鍵值,可以直接進行賦值。賦值後交給管道處理。
簡單講一下這一段程式碼的思路,首先騰訊視訊的url為https://v.qq.com/x/bu/pagesheet/list?append=1&channel=cartoon&iarea=1&listpage=2&offset=0&pagesize=30
我們注意到offset這一項,第一頁的offset為0,第二頁為30,依次推列。在程式中這一項用於控制抓取第一頁,但是也要給一個範圍,不可能無限大,否則會報錯,可以去看看騰訊一共有多少頁視訊,也可以寫一個異常捕獲機制,捕捉到請求出錯則退出。我這裡僅僅是示範,所以只給了120,也就是4頁。
yield
程式裡一共有兩個yield,我比較喜歡叫它中斷,當然中斷只在CPU中發生,它的作用是移交控制權,在本程式中,我們對item封裝資料後,就呼叫yield把控制權給管道,管道拿到處理後return返回,又回到該程式。這是對第一個yield的解釋。
第二個yield稍微複雜點,這條程式裡利用了一個回撥機制,即callback,回撥的物件是parse,也就是當前方法,通過不斷的回撥,程式將陷入迴圈,如果不給程式加條件,就會陷入死迴圈,如本程式我把if去掉,那就是死迴圈了。
yield scrapy.Request(url=url,callback=self.parse)
xpath
還有一個要注意的是如何提取xpathl裡的資料,我們的寫法有四種,第一種寫法拿到selector選擇器,也就是原資料,裡面有一些我們用不到的東西。第二個extract(),將選擇器序列號為字串。第三個和第四個一樣,拿到字串裡的第一個資料,也就是我們要的資料。
items[‘name’]=i.xpath(‘./a/@title’)[0]
items[‘name’]=i.xpath(‘./a/@title’).extract()
items[‘name’]=i.xpath(‘./a/@title’).extract_first()
items[‘name’]=i.xpath(‘./a/@title’).get()
- # -*- coding: utf-8 -*-
- importscrapy
- from..itemsimportTxmoviesItem
- classTxmsSpider(scrapy.Spider):
- name =‘txms’
- allowed_domains = [‘v.qq.com’]
- start_urls = [‘https://v.qq.com/x/bu/pagesheet/list?append=1&channel=cartoon&iarea=1&listpage=2&offset=0&pagesize=30’]
- offset=0
- defparse(self, response):
- items=TxmoviesItem()
- lists=response.xpath(‘//div[@class=”list_item”]’)
- foriinlists:
- items[‘name’]=i.xpath(‘./a/@title’).get()
- items[‘description’]=i.xpath(‘./div/div/@title’).get()
- yielditems
- ifself.offset <120:
- self.offset +=30
- url =‘https://v.qq.com/x/bu/pagesheet/list?append=1&channel=cartoon&iarea=1&listpage=2&offset={}&pagesize=30’.format(
- str(self.offset))
- yieldscrapy.Request(url=url,callback=self.parse)
5.交給管道輸出
管道可以處理提取的資料,如存資料庫。我們這裡僅輸出。
- # -*- coding: utf-8 -*-
- # Define your item pipelines here
- #
- # Don’t forget to add your pipeline to the ITEM_PIPELINES setting
- # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
- classTxmoviesPipeline(object):
- defprocess_item(self, item, spider):
- print(item)
- returnitem
6.run,執行專案
- fromscrapyimportcmdline
- cmdline.execute(‘scrapy crawl txms’.split())
7.測試結果
白色的管道輸出的結果,紅色的除錯資訊
8.流程梳理
新建專案-》進入專案-》新建爬蟲檔案-》明確抓取的內容,寫item-》寫爬蟲程式,爬取資料-》交給管道處理資料-》調整全域性配置setting-》執行爬蟲程式,可以通過終端或者在程式裡寫一個run程式
9.提速:多執行緒爬取
如果你實現了上面的實驗,不難發現其爬取速度是非常慢,根本的原因就是因為它是順序執行的,你可以從結果中看出,總是前面一頁的內容被輸出,再輸出後面的內容。不適合處理資料量較大的情況,一個好的方式是採用多執行緒的方法,這裡的多執行緒是基於方法的多執行緒,並不是通過建立Thread物件來實現,是在一個方法中,一次性把請求交給排程器。
我們通過重寫start_requests方法來實現我們的想法(這個方法的原始碼在__init__.py下面,有興趣可以看一下)
- # -*- coding: utf-8 -*-
- importscrapy
- from..itemsimportTxmoviesItem
- classTxmsSpider(scrapy.Spider):
- name =‘txms’
- allowed_domains = [‘v.qq.com’]
- url=‘https://v.qq.com/x/bu/pagesheet/list?append=1&channel=cartoon&iarea=1&listpage=2&offset={}&pagesize=30’
- offset=0
- defstart_requests(self):
- foriinrange(0,121,30):
- url=self.url.format(i)
- yieldscrapy.Request(
- url=url,
- callback=self.parse
- )
- defparse(self, response):
- items=TxmoviesItem()
- lists=response.xpath(‘//div[@class=”list_item”]’)
- foriinlists:
- items[‘name’]=i.xpath(‘./a/@title’).get()
- items[‘description’]=i.xpath(‘./div/div/@title’).get()
- yielditems