第六章 Scrapy框架
回顧一下寫一個爬蟲需要做的一些步驟,使用requests庫發送網絡請求、使用lxml等解析技術對數據進行解析、使用數據庫等方法進行存儲數據,另外還可以在請求網絡的時候進行更換IP、設置請求頭等。
每次爬蟲都要幹這麼多活,如果每次都從零開始寫則比較浪費時間,所以我們需要一個框架,這個框架幫我們把一些基本的爬蟲前奏都準備好了,我們只需要“站在巨人的肩膀上”即可。而Scrapy
框架就是這個“巨人的肩膀”。
它的工作原理如下:
各模塊功能如下:
Engine(引擎)
: scrap框架的核心部分。負責每個模塊之間的通信、傳遞數據等。spiders(爬蟲)
:將需要爬取的鏈接發送引擎,最後引擎把其他模塊請求回來的數據再發送回爬蟲,然後爬蟲就可以對數據進行解析。(這部分是開發者自己寫的,因爲要爬取哪些連接,解析哪些數據是我們自己決定的)Scheduler(調度器)
:負責接收引擎發送過來的請求,並按照一定的方式進行排列和整理,決定了鏈接爬取的順序。Downloader(下載器)
:負責接收引擎傳過來的下載請求,然後去網絡上下載對應的數據再交還給引擎。Item Pipelines(管道)
:負責將spider(爬蟲)
傳遞過來的數據進行保存。具體保存的方式和位置,也是由開發者自行決定。Downloader Middlewares(下載中間件)
:處於引擎跟下載器中間,處理下載請求部分。如在此可以設置請求頭、IP地址等。Spider Middlewares(爬蟲中間件)
:處於爬蟲跟引擎中間,處理解析部分。
各模塊執行過程如下:
- 引擎打開一個網站,找到處理該網站的Spider並向該spider請求第一個要爬取的URL(s)。
- 引擎從Spider中獲取到第一個要爬取的URL並在調度器(Scheduler)以Request調度。
- 引擎向調度器請求下一個要爬取的URL。
- 調度器返回下一個要爬取的URL給引擎,引擎將URL通過下載中間件(請求(request)方向)轉發給下載器(Downloader)。
- 一旦頁面下載完畢,下載器生成一個該頁面的Response,並將其通過下載中間件(返回(response)方向)發送給引擎。
- 引擎從下載器中接收到Response並通過Spider中間件(輸入方向)發送給Spider處理。
- Spider處理Response並返回爬取到的Item及(跟進的)新的Request給引擎。
- 引擎將(Spider返回的)爬取到的Item給Item Pipeline,將(Spider返回的)Request給調度器。
- (從第二步)重複直到調度器中沒有更多地request,引擎關閉該網站。
原理圖及執行過程的描述參考於這篇文章:Scrapy架構圖(工作原理)
如果覺得很不理解沒關係,可以先往下學,之後再回過頭來看這張圖就會豁然開朗了。
6.1 快速入門
使用Scrapy之前需要先安裝:pip install scrapy
。
如果在window
系統下,還需安裝一個東西:pip install pypiwin32
創建項目:
進入你想把此項目存放的目錄,使用命令 scrapy startproject 項目名稱
來創建項目。如 scrapy startproject scrapy_demo
用pycharm或其他編輯器打開該項目,項目結構如下:
主要文件的作用:
items.py
:用來存放爬蟲爬取下來數據的模型,即要存儲的各字段。middlewares.py
:用來存放下載/爬蟲中間件的文件。pipelines.py
:用來將items.py
中的模型進行持久化存儲。settings.py
:爬蟲的一些配置信息(比如請求頭、多久發送一次請求、IP代理池等許多設置)。scrap.cfg
:項目的配置文件。spiders包
:存放所有的爬蟲文件。
以上步驟只是創建了項目,下一步還需要創建爬蟲(我們在爬蟲文件夾下進行寫代碼)
創建爬蟲前需要先進入到剛纔創建的項目中,然後通過命令 scrapy genspider 爬蟲名字 要爬取網站的域名
。如scrapy genspider demo1 baidu.com
注意:爬蟲名字不能跟項目名字重複。
此時,你會發現spiders文件夾下多了個demo1
文件,內容如下:
allowed_domains
是指以後所有的爬蟲的鏈接都是在該域名下的,並不會說你要爬取百度的網址,卻給你爬了個谷歌的網址。
stats_urls
是指爬蟲剛啓動時是向該鏈接發送網絡請求。
這些都創建好之後,就可以運行項目了,需要注意的是,在編輯器中是無法直接運行的,需要進入到爬蟲文件夾下運行cmd命令 scrapy crwal 爬蟲名字
運行項目。放心,以後在編輯器中有便捷方式來運行,現在先不介紹。
至此,一個scrapy就成功創建並運行起來了。來回顧一下剛纔的操作:
- 創建項目:
scrapy startproject 項目名字
- 創建爬蟲:
scrapy genspider 爬蟲名字 爬取的域名
,注意爬蟲的名字不能與項目名字相同。 - 運行爬蟲:
scrapy crwal 爬蟲名字
6.2 漸漸深入
pipelines.py文件函數:
__init__(self)
構造函數,創建pipelines時執行open_spider(self,spider)
spider(爬蟲) 被打開時自動執行process_item(self, item, spider)
當爬蟲有item傳入時被調用close_spider(self,spider)
當spider(爬蟲)被關閉的時候執行
tips: 一般需要存儲文件時在__init__(self)
或者open_spider(self,spider)
中編寫打開文件的操作或者鏈接數據庫操作,在process_item(self, item, spider)
中編寫寫入數據的操作,最後在close_spider(self,spider)
中關閉文件或數據庫
settings.py文件常用設置:
-
ROBOTSTXT_OBEY = True
是否遵循機器人協議,我們需要把它改成False
-
DEFAULT_REQUEST_HEADERS
設置請求頭DEFAULT_REQUEST_HEADERS = { 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en', # 設置User-Agent 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362' }
-
DOWNLOADER_MIDDLEWARES
開啓下載中間件,例如我這裏寫了三個下載中間件,後面的數字越小表示優先級越高DOWNLOADER_MIDDLEWARES = { 'scrapy_demo.middlewares.ScrapyDemoDownloaderMiddleware': 543, 'scrapy_demo.middlewares.UserAgentDownloadMiddleware': 500, 'scrapy_demo.middlewares.IPProxyDownloadMiddlleware': 400, }
-
SPIDER_MIDDLEWARES
爬蟲中間件,用法同下載中間件。 -
ITEM_PIPELINES
開啓下載項。
tips:以上的配置在settings文件中都是註釋了的,我們再使用到相應功能的時候,要記得解除註釋。
如何在編輯器中運行項目(怎麼樣纔不用每次都去cmd那裏運行項目):
-
在項目根目錄中創建一個
py
文件,一般我喜歡命名爲start.py
。 -
內容爲:
# 導入命令行 from scrapy import cmdline # 使用命令行運行項目 cmdline.execute("scrapy crawl 項目名".split())
-
每次要運行項目時運行此文件即可。
結合案例介紹文件:
以爬取糗事百科網站爲例,假如本次案例我們需要爬取到糗事百科的段子跟作者,並把它保存爲json文件。
首先創建好項目、爬蟲,在根目錄下創建start.py
,接着在settings
文件夾下把協議變成False,設置請求頭。
qsbk_spider.py 內容:
# -*- coding: utf-8 -*-
import scrapy
# 從本項目的items中導入模型
from qsbk.items import QsbkItem
class QsbkSpiderSpider(scrapy.Spider):
name = 'qsbk_spider'
allowed_domains = ['qiushibaike.com']
# 將默認的start_urls改成自己想要爬取的網址,這裏選擇第一頁的段子
start_urls = ['https://www.qiushibaike.com/text/page/1/']
def parse(self, response):
# 在scrapy中response可以直接使用xpath、css選擇器
# 返回的是SelectorList(繼承自List)
divs = response.xpath("//div[@class='col1 old-style-col1']/div")
# SelectorList裏面的每個元素都是Selector
for div in divs:
# 通過get 獲取SelectorList的第一個Selector並將其變成字符串
author = div.xpath(".//h2/text()").get()
# text的類型爲SelectorList,因爲他返回很多的Selector
# 通過getall 將所有的Selector變成字符串再將這些字符串組成列表
text = div.xpath(".//div[@class='content']//span//text()").getall()
# 將列表變成字符串同時去掉頭尾的空白符
text = ''.join(text).strip()
# 將參數傳給item模型,然後自動把item傳到pipelines
yield QsbkItem(author=author, text=text)
itmes.py 內容:
import scrapy
class QsbkItem(scrapy.Item):
# 在item模型中定義好要保存的數據
# 接收qsbk_spider傳過來的參數
author = scrapy.Field()
text = scrapy.Field()
pipelines.py 內容:
import json
class QsbkPipeline(object):
def open_spider(self, spider):
self.fp = open('qsbk.json', 'a', encoding='utf-8')
def process_item(self, item, spider):
# 此時的item爲QsbkItem類型,需要轉成字典纔可以變成json
text = json.dumps(dict(item), ensure_ascii=False)
self.fp.write(text + '\n')
return item
def close_spider(self, spider):
self.fp.close()
- 記得要在settings中打開pipelines。
小結:
- 爬蟲第一步,對協議說“不”
response
對象可以執行xpath
、css
語法來提取數據。- 提取出來的數據類型可能是
Selector
或SelectorList
,如果想要獲取其中的字符串或字符串列表,應該執行get
或getall
方法 - 如果要將數據傳給pipelines處理,可以使用
yield
yield
與return
是有區別的,讀者可自行百度查閱資料。
到了這一步,就對一個網頁解析完了,現在來看下如何多其他頁面發起“進攻”吧。
首先要爬取其他頁面,就必須知道其他頁面的url或者找到這些url的規律,分析糗事百科段子的url可以發現,
text/page/1/
page後面的數字就代表了頁數,這時你可能想到用個變量來代表頁數,實際操作後你會發現,這種操作並不那麼理想。而實際我們一般也不會這麼幹,像這種網站一般都可以用域名+相對路徑來訪問到不同的頁面,糗事百科中可以找到 下一頁按鈕 的 a標籤中 的href
就是下一頁的url
的相對路徑
只需要改動qsbk_spider.py 的內容:
import scrapy
from qsbk.items import QsbkItem
class QsbkSpiderSpider(scrapy.Spider):
name = 'qsbk_spider'
allowed_domains = ['qiushibaike.com']
start_urls = ['https://www.qiushibaike.com/text/page/1/']
# 設置基本域名 方便連接成完整url
domains_url = 'https://www.qiushibaike.com'
def parse(self, response):
divs = response.xpath("//div[@class='col1 old-style-col1']/div")
for div in divs:
author = div.xpath(".//h2/text()").get()
text = div.xpath(".//div[@class='content']//span//text()").getall()
text = ''.join(text).strip()
yield QsbkItem(author=author, text=text)
# 對一個頁面解析完了之後繼續解析其他頁面
# 最後一個li標籤的href是下一頁的相對路徑
hrefs = response.xpath('//div[@class="col1 old-style-col1"]/ul/li[last()]/a/@href').get()
# 在最後一頁的時候無此相對路徑,此時結束爬蟲
if not hrefs:
return
else:
# 將要爬取下一頁的url傳給調度器並告訴他,要用parse函數解析該url
# 注意parse不要加括號
yield scrapy.Request(self.domains_url+hrefs, callback=self.parse)
還記得工作原理圖的第8步嗎,引擎會根據 yield
返回的實例類型來執行不同的操作。
如果是 scrapy.Request
對象,引擎把該對象指向的鏈接發送給調度器並在請求完成後調用該對象的回調函數。該鏈接就是 self.domains_url+hrefs
回調函數就是self.parse
如果是 scrapy.Item
對象,引擎會將這個對象傳遞給 pipelines.py
做進一步處理
tips:在爬取整個網站的時候,由於爬取的速度很快爬取的數量較大,容易把別人服務器弄垮,所以身爲良好市民的我們可以在settings中把DOWNLOAD_DELAY
(下載延遲) 打開,可以設置爲1,表示下載完成後等待1秒鐘再繼續下載。
6.3 POST請求
前面的例子是發送get請求的,如果要發送post請求則需要使用FormRquest
,而發送post請求情況又分爲:爬蟲一開始就要發送post請求、爬蟲過程中發送post請求。這兩種使用post請求的方式有點不同:
爬蟲一開始就要發送post請求
- 需要重寫
start_requests
方法並且請求的url不要使用start_urls
裏面的,要自己寫過請求的url
。因爲該方法默認使用的是用get方法來請求start_urls
中的鏈接。 - 在此方法中使用
FromRequest
來發送post請求。
爬蟲過程中發送post請求:使用FormRequest
發送post請求
看例子:
import scrapy
class RenrenSpider(scrapy.Spider):
name = 'renren'
allowed_domains = ['renren.com']
start_urls = ['http://renren.com/']
# 重寫start_requests
def start_requests(self):
# 重寫要請求的url
url = 'http://www.renren.com/Login.do'
data = {
"email": "你的賬號",
"password": "你的密碼"
}
# 攜帶數據,發送post請求
yield scrapy.FormRequest(url, formdata=data, callback=self.htmlParser)
def htmlParser(self, response):
# 解析頁面或其他對頁面的操作
pass
爬蟲過程中發送post請求:
# -*- coding: utf-8 -*-
import scrapy
class QsbkSpiderSpider(scrapy.Spider):
name = 'qsbk_spider'
allowed_domains = ['qiushibaike.com']
start_urls = ['https://www.qiushibaike.com/text/page/1/']
def parse(self, response):
url = ''
# 發送post請求
yield scrapy.FormRequest(url, callback=self.parse)
小結:
- 發送post請求使用
FormRequest
函數
6.4 Request跟Response對象
request常用屬性:
url
:請求的urlcallback
:解析url執行的回調函數methods
:請求的方法,默認爲get。headers
:請求頭,隨機請求頭信息可以在此設置,否則在settings中設置。meta
:在不同請求或請求與響應中傳遞數據,如後面設置代理IP的時候會用到dont_filter
:是否使用調度器過濾功能,默認爲True
response常用屬性:
meta
:接收請求傳遞的meta屬性。text
:返回unicode字符串xpath
:xpath選擇器css
:css選擇器
6.5 CrawelSpider
上面的案例中,爬取多個網頁時,明明我們已經發現了url的規律,卻還需要自己獲取相對路徑,比較麻煩。而CrawelSpider
就可以幫我們解決這個不必要的麻煩,直接告訴它url的規律,他就會屁顛屁顛的幫我們去爬取。
CrawelSpider
繼承自Scrapy
,在scrapy基礎上加了新的功能。
創建CrawelSpider
的步驟跟scrapy差不多:
- 創建工程:
scrapy startproject 項目名
- 創建爬蟲:
scrapy genspider -t crawl 爬蟲名 域名
(就這一步不同) - 運行爬蟲:
scrapy crawl 爬蟲名
瞭解CrawelSpider
主要了解下面兩個東西:
LinkExtractors
url提取器:自動爬取滿足正則表達式規則的url
主要參數:
- allow:允許爬取的url,即只有滿足該條件下的url纔會被爬取。
- deny:禁止爬取的url,即滿足該條件下的url不會被爬取。(較少用)
- allow_domains:允許爬取的域名。
- deny_domains:禁止爬取的域名。
- restrict_xpaths:嚴格的xapth,和allow共同過濾鏈接。
Rule
規則類:對爬取網站的動作定義了特定的操作。
主要參數:
- Link_extractors:
LinkExtractors
對象,制定爬取規則。 - callback:指定滿足規則的url執行的回調函數。
- follow:指定根據該規則從response中提取的鏈接是否跟進。
- process_links:指定該Spider中哪個的函數將會被調用,從link_extractor中獲取到鏈接列表將會調用該函數。
tips:callback
跟follow
是“死對頭”,callback爲None,則follow默認設置爲True。
class CrawlDemoSpider(CrawlSpider):
name = 'crawl_demo'
allowed_domains = ['qiushibaike.com']
start_urls = ['https://www.qiushibaike.com/text/page/1/']
# 規則類的元祖,包含多個規則
rules = (
# 規則1:一直爬取滿足.+/text/page/\d/的url
Rule(LinkExtractor(allow=r'.+/text/page/\d/'), follow=True),
# 規則2:對滿足.+article-.+\.html的url使用detail_parse函數進行解析,解析完就結束
Rule(LinkExtractor(allow=r'.+article/\d'), callback="detail_parse", follow=False)
)
def parse_item(self, response):
# 解析操作
pass
規則1是爬取每一頁的段子詳情的url,而規則二則是對段子詳情的url進行爬取。所以前者需要使用follow一直跟進到最後一頁,後者這不需要follow,反而需要callback來對段子詳情進行解析。
可以看到,crawlSpider爲我們省去了爬取每頁的url這一個步驟,我們只需要指定規則讓spider去爬,就不用了手動寫yield scrapy.Request函數了。
6.6 下載圖片或文件
使用scrapy框架也可以很快速便捷的下載大量的圖片或文件,只需要根據它的規定來做就可以了。
如下載圖片的ImagesPipeline
(下載文件就把Images改爲Files)
當使用ImagesPipeline
下載文件的時候,按以下步驟:
-
在items中定義兩個屬性,
image_urls
以及images
# 存儲下載圖片的鏈接,爲一個列表 image_urls = scrapy.Field() # 當下載完成後,會把下載的相關信息存儲到images中。比如下載路徑等 images = scrapy.Field()
-
在settings中配置
IMAGE_STROE = '存放路徑'
,設置圖片下載存放的路徑 -
在pipelines中將原有的設置改爲
scrapy.pipelines.images.ImagesPipeline:100
-
如果覺得scrapy內置的ImagesPipeline類滿足不了自己,可以自己寫一個方法,然後此方法繼承ImagesPipeline。(具體可以根據源碼來改)
6.7 下載中間件(設置隨機請求頭和代理IP)
下載中間件可以在引擎將請求發給下載器之前進行一些操作,比如設置隨機請求頭和代理IP等。要實現下載中間件必須在middlewares.py
中自己編寫下載中間件的類,該類至少實現兩個函數的其中一個:process_request
、process_response
這兩個函數都是下載中間件自動執行的函數:
process_request(self,request,spider) :在下載器發送請求之前執行,一般在這裏設置隨機請求頭跟IP
參數:
- request:發送請求的request對象
- spider:發送請求的spider對象
返回值:
- 爲None時:爬蟲會繼續處理request,執行其他中間件中的相應方法,直到合適的下載器處理函數被調用(返回response)
- 爲response對象時:爬蟲不會調用其他的process_request方法。
- 爲request對象時:不在使用之前的request對象,而是用此request對象返回數據。
process_response(self,request,response,spider) :下載器將下載的數據傳送到引擎中執行。
參數:
- request:發送請求的request對象
- spider:發送請求的spider對象
- response:被處理的response對象
返回值:
-
爲response對象時:會將這個新的response對象傳給其他中間件,最終傳給引擎。
-
爲request對象時:下載器被切斷,返回的request會重新被下載器調度。
設置隨機請求頭和IP代理:
middlewares.py
代碼如下:
import random
class UserAgentDownloadMiddleware(object):
user_agent = [] # 寫你的請求頭列表
def process_request(self,request,spider):
# 隨機選擇一個請求頭
ug = random.choice(self.user_agent)
# 將該請求頭作爲本次請求的請求頭
request.headers['User-Agent'] = ug
class IPProxyDownloadMiddlleware(object):
PROXIES = [] # 寫你的代理IP列表
def process_request(self,request,spider):
# 隨機選擇一個代理IP
proxy = random.choice(self.PROXIES)
# 將該IP作爲本次請求的IP
request.meta['proxy'] = proxy
小結:
- 設置隨機請求頭:
request.headers['User-Agent'] = ug
- 設置隨機IP代理:
request.meta['proxy'] = proxy
整個爬蟲教程就到此結束啦~~,希望對下夥伴們有所幫助!
加油,學習永不停歇.