(1)安裝Scrapy環境
步驟請參考:https://blog.csdn.net/c406495762/article/details/60156205
需要注意的是,安裝的時候需要根據自己的python的版本進行安裝。
(2)創建Scrapy項目
通過命令創建:
scrapy startproject tutorial
在任意文件夾運行都可以,如果提示權限問題,可以加sudo運行。這個命令將會創建一個名字為tutorial的文件夾,文件夾結構如下:
|____scrapy.cfg # Scrapy部署時的配置文件 |____tutorial # 項目的模塊,引入的時候需要從這里引入 | |______init__.py | |______pycache__ | |____items.py # Items的定義,定義爬取的數據結構 | |____middlewares.py # Middlewares的定義,定義爬取時的中間件 | |____pipelines.py # Pipelines的定義,定義數據管道 | |____settings.py # 配置文件 | |____spiders # 放置Spiders的文件夾 | | |______init__.py | | |______pycache__
Spider是由你來定義的Class,Scrapy用它來從網頁里抓取內容,並將抓取的結果解析。不過這個Class必須要繼承Scrapy提供的Spider類scrapy.Spider,並且你還要定義Spider的名稱和起始請求以及怎樣處理爬取后的結果的方法。
創建一個Spider也可以用命令生成,比如要生成Quotes這個Spider,可以執行命令。
scrapy genspider 爬蟲名稱 "作用域(eg:baidu.com)"
首先進入到剛才創建的tutorial文件夾,然后執行genspider這個命令,第一個參數是Spider的名稱,第二個參數是網站域名。執行完畢之后,你會發現在spiders文件夾中多了一個quotes.py,這就是你剛剛創建的Spider,內容如下:
# -*- coding: utf-8 -*- import scrapy class QuotesSpider(scrapy.Spider): name = "quotes" allowed_domains = ["quotes.toscrape.com"] start_urls = ['http://quotes.toscrape.com/'] def parse(self, response): pass
可以看到有三個屬性,name,allowed_domains,start_urls,另外還有一個方法parse
name,每個項目里名字是唯一的,用來區分不同的Spider。
allowed_domains允許爬取的域名,如果初始或后續的請求鏈接不是這個域名下的,就會被過濾掉。
start_urls,包含了Spider在啟動時爬取的url列表,初始請求是由它來定義的。
parse,是Spider的一個方法,默認情況下,被調用時start_urls里面的鏈接構成的請求完成下載后,返回的response就會作為唯一的參數傳遞給這個函數,該方法負責解析返回的response,提取數據或者進一步生成要處理的請求。
創建Item
Item是保存爬取數據的容器,它的使用方法和字典類似,雖然你可以用字典來表示,不過Item相比字典多了額外的保護機制,可以避免拼寫錯誤或者為定義字段錯誤。
創建Item需要繼承scrapy.Item類,並且定義類型為scrapy.Field的類屬性來定義一個Item。觀察目標網站,我們可以獲取到到內容有text, author, tags
所以可以定義如下的Item,修改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 QuoteItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() ''' item 時報錯爬取數據的容器,它的使用方法和字典類似,雖然你可以用字典來表示, 不過Item相比字典多了額外的保護機制,可以避免拼寫錯誤或者未定義字段錯誤。 創建item需要繼承Scrapy.Item類,並且定義類型為scrapy.Field的類屬性來定義一 個Item。觀察目標網站,我們可以獲取到內容有text、author、tags ''' text = scrapy.Field() author = scrapy.Field() tags = scrapy.Field()
定義了三個Field,接下來爬取時我們會使用它。
解析Response
在上文中說明了parse方法的參數resposne是start_urls里面的鏈接爬取后的結果。所以在parse方法中,我們可以直接對response包含的內容進行解析,比如看看請求結果的網頁源代碼,或者進一步分析源代碼里面包含什么,或者找出結果中的鏈接進一步得到下一個請求。
觀察網站,我們可以看到網頁中既有我們想要的結果,又有下一頁的鏈接,所以兩部分我們都要進行處理。
首先看一下網頁結構,每一頁都有多個class為quote的區塊,每個區塊內都包含text,author,tags,所以第一部需要找出所有的quote,然后對每一個quote進一步提取其中的內容。
提取的方式可以選用CSS選擇器或XPath選擇器,在這里我們使用CSS選擇器進行選擇,parse方法改寫如下:
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()
在這里使用了CSS選擇器的語法,首先利用選擇器選取所有的quote賦值為quotes變量。
然后利用for循環對每個quote遍歷,解析每個quote的內容。
對text來說,觀察到它的class為text,所以可以用.text來選取,這個結果實際上是整個帶有標簽的元素,要獲取它的內容,可以加::text來得到。這時的結果是大小為1的數組,所以還需要用extract_first方法來獲取第一個元素,而對於tags來說,由於我們要獲取所有的標簽,所以用extract方法獲取即可。
<div class="quote" itemscope="" itemtype="http://schema.org/CreativeWork"> <span class="text" itemprop="text">“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”</span> <span>by <small class="author" itemprop="author">Albert Einstein</small> <a href="/author/Albert-Einstein">(about)</a> </span> <div class="tags"> Tags: <meta class="keywords" itemprop="keywords" content="change,deep-thoughts,thinking,world"> <a class="tag" href="/tag/change/page/1/">change</a> <a class="tag" href="/tag/deep-thoughts/page/1/">deep-thoughts</a> <a class="tag" href="/tag/thinking/page/1/">thinking</a> <a class="tag" href="/tag/world/page/1/">world</a> </div> </div>
使用Item
剛才定義了Item,接下來就要輪到使用它了,你可以把它理解為一個字典,不過在聲明的時候需要實例化。然后依次對剛才解析的結果賦值,返回即可。
接下來QuotesSpider改寫如下:
# -*- coding: utf-8 -*- import scrapy from tutorial.items import QuoteItem class QuotesSpider(scrapy.Spider): # 爬蟲名稱 name = 'quotes' # 允許爬蟲爬取的域名,如果初始或者后續請求鏈接不是這個域名下,就會被過濾 allowed_domains = ['quotes.toscrape.com'] # 包含了Spider在啟動時爬取的url列表,初始請求是由它來定義的 start_urls = ['http://quotes.toscrape.com/'] # 時Spider的一個方法,默認情況下,被調用時start_urls里面的 # 鏈接構成的請求完成下載后,返回的response就會被作為唯一的 # 參數傳遞給這個函數,該方法負責解析返回的response,提取數 # 據或者進一步生成要處理的請求 def parse(self, response): quotes = response.css(".quote") for quote in quotes: item = QuoteItem() 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('.page .next a::attr("href")').extract_first() url = response.urljoin(next) yield scrapy.Request(url = url,callback = self.parse)
如此一來,首頁的所有內容就解析出來了,並賦值成了一個個QuoteItem。
后續Request
如上的操作實現了從初始頁面抓取內容,不過下一頁的內容怎樣繼續抓取?這就需要我們從該頁面中找到信息來生成下一個請求,然后下一個請求的頁面里找到信息再構造下一個請求,這樣循環往復迭代,從而實現整站的爬取。
觀察到剛才的頁面拉到最下方,有一個Next按鈕,查看一下源代碼,可以發現它的鏈接是/page/2/,實際上全鏈接就是http://quotes.toscrape.com/page/2,通過這個鏈接我們就可以構造下一個請求。
構造請求時需要用到scrapy.Request,在這里我們傳遞兩個參數,url和callback。
url,請求鏈接
callback,回調函數,當這個請求完成之后,獲取到response,會將response作為參數傳遞給這個回調函數,回調函數進行解析或生成下一個請求,如上文的parse方法。
在這里,由於parse就是用來解析text,author,tags的方法,而下一頁的結構和剛才已經解析的頁面結構是一樣的,所以我們還可以再次使用parse方法來做頁面解析。
好,接下來我們要做的就是利用選擇器得到下一頁鏈接並生成請求,在parse方法后追加下面的代碼。
next = response.css('.pager .next a::attr(href)').extract_first() url = response.urljoin(next) yield scrapy.Request(url=url, callback=self.parse)
第一句代碼是通過CSS選擇器獲取下一個頁面的鏈接,需要獲取<a>超鏈接中的href屬性,在這里用到了::attr(href)操作,通過::attr加屬性名稱我們可以獲取屬性的值。然后再調用extract_first方法獲取內容。
第二句是調用了urljoin方法,它可以將相對url構造成一個絕對的url,例如獲取到的下一頁的地址是/page/2,通過urljoin方法處理后得到的結果就是http://quotes.toscrape.com/page/2/
第三句是通過url和callback構造了一個新的請求,回調函數callback依然使用的parse方法。這樣在完成這個請求后,response會重新經過parse方法處理,處理之后,得到第二頁的解析結果,然后生成第二頁的下一頁,也就是第三頁的請求。這樣就進入了一個循環,直到最后一頁。
通過幾行代碼,我們就輕松地實現了一個抓取循環,將每個頁面的結果抓取下來了。
現在改寫之后整個Spider類是這樣的:
# -*- coding: utf-8 -*- import scrapy from tutorial.items import QuoteItem class QuotesSpider(scrapy.Spider): # 爬蟲名稱 name = 'quotes' # 允許爬蟲爬取的域名,如果初始或者后續請求鏈接不是這個域名下,就會被過濾 allowed_domains = ['quotes.toscrape.com'] # 包含了Spider在啟動時爬取的url列表,初始請求是由它來定義的 start_urls = ['http://quotes.toscrape.com/'] # 時Spider的一個方法,默認情況下,被調用時start_urls里面的 # 鏈接構成的請求完成下載后,返回的response就會被作為唯一的 # 參數傳遞給這個函數,該方法負責解析返回的response,提取數 # 據或者進一步生成要處理的請求 def parse(self, response): quotes = response.css(".quote") for quote in quotes: item = QuoteItem() 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('.page .next a::attr("href")').extract_first() url = response.urljoin(next) yield scrapy.Request(url = url,callback = self.parse)
接下來讓我們試着運行一下看看結果,進入目錄,運行如下命令:
scrapy crawl quotes
正常情況下這里就會得到如圖效果(部分):
但是也有可能會遇到這種情況:
再回過頭看看我們上邊輸出的正確結果,這個時候的解決辦法是通過提示報錯的地址找到manhole.py,然后將該文件中的所有async改為其它的名字比如async1,即可解決相應的問題。
首先Scrapy輸出了當前的版本號,啟動的項目。其次輸出了當前在settings.py中的一些重寫后的配置。然后輸出了當前所應用的middlewares和pipelines,middlewares是默認啟用的,如果要修改,我們可以在settings.py中修改,pipelines默認是空,同樣也可以在settings.py中配置,后面會進行講解。
再接下來就是輸出各個頁面的抓取結果了,可以看到它一邊解析,一邊翻頁,直至將所有內容抓取完畢,然后終止。
在最后Scrapy輸出了整個抓取過程的統計信息,如請求的字節數,請求次數,響應次數,完成原因等等。
這樣整個Scrapy程序就成功運行完畢了。
可以發現我們通過非常簡單的一些代碼就完成了一個網站內容的爬取,相比之前自己一點點寫程序是不是簡潔太多了?
保存到文件
剛才運行完Scrapy后,我們只在控制台看到了輸出結果,如果想將結果保存該怎么辦呢?
比如最簡單的形式,將結果保存成Json文件。
要完成這個其實不需要你寫任何額外的代碼,Scrapy提供了Feed Exports可以輕松地將抓取結果輸出,例如我們想將上面的結果保存成Json文件,可以執行如下命令:
scrapy crawl quotes -o quotes.json
運行后發現項目內就會多了一個quotes.json文件,里面包含的就是剛才抓取的所有內容,是一個Json格式,多個項目由中括號包圍,是一個合法的Json格式。
另外你還可以每一個Item一個Json,最后的結果沒有中括號包圍,一行對應一個Item,命令如下:
scrapy crawl quotes -o quotes.jl
或者
scrapy crawl quotes -o quotes.jsonlines
另外還支持很多格式輸出,例如csv,xml,pickle,marshal等等,還支持ftp,s3等遠程輸出,另外還可以通過自定義ItemExporter來實現其他的輸出。
例如如下命令分別對應輸出為csv,xml,pickle,marshal,格式以及ftp遠程輸出:
scrapy crawl quotes -o quotes.csv scrapy crawl quotes -o quotes.xml scrapy crawl quotes -o quotes.pickle scrapy crawl quotes -o quotes.marshal scrapy crawl quotes -o ftp://user:pass@ftp.example.com/path/to/quotes.csv
其中ftp輸出需要你正確配置好你的用戶名,密碼,地址,輸出路徑,否則會報錯。
通過Scrapy提供的Feed Exports我們可以輕松地輸出抓取結果到文件,對於一些小型項目這應該是足夠了,不過如果想要更復雜的輸出,如輸出到數據庫等等,你可以使用Item Pileline更方便地實現。