說起寫爬蟲整陌,大多數第一時間想到的就是python了恃轩。python語法簡潔明了锁荔,加上及其豐富好用的庫蟀给,用它來寫爬蟲有天然的優(yōu)勢。
之前學python的時候也用requests+lxml
寫過幾個爬蟲玩阳堕,但是都就爬取一些內容就沒繼續(xù)下去了跋理,都沒做成一個項目,中間python也荒廢了好久恬总。最近要學kafka前普,就打算爬點數據來實踐實踐。于是就學起scrapy來壹堰,總的來說拭卿,scrapy還是很容易上手的骡湖,也比較簡單,花了幾天的時間學習加實踐峻厚,也漸漸的掌握了這個爬蟲框架勺鸦。所以打算寫個博客做個總結,以免之后又太久沒用忘記了目木。
scrapy架構原理
scrapy架構圖
[圖片上傳失敗...(image-21611e-1532663400039)]
- scrapy引擎向spider獲取起始Request集合,也就是spider中定義的
start_urls
换途。如果spider重寫了start_requests()
方法,那么這個方法返回的Request集合就是起始Request刽射。 - scrapy引擎將拿到的Request發(fā)給調度中心開始調度军拟。
- scrapy引擎向調度中心請求獲取下一個要爬取的Request。
- scrapy引擎拿到Request后誓禁,然后將Request發(fā)給下載器懈息。這個過程經過一系列在
settings.py
中配置的下載中間件,所有在settings.py
中配置的下載中間件會依次對Request進行處理摹恰”杓蹋——對應DownloaderMiddleware#process_request()
方法 - 下載器根據Request拉取響應的內容,比如Request的url是
http://www.baidu.com
俗慈,下載器就會拉取對應的網頁內容下來并封裝成Response對象姑宽。 - 下載器將Response發(fā)送給scrapy引擎。這個過程也會經過一系列在
settings.py
中配置的下載中間件闺阱,這些下載中間件會依次對Response進行處理炮车。——對應DownloaderMiddleware#process_response()
方法 - scrapy引擎拿到Response后將Response發(fā)給spider,交給對應的spider函數處理酣溃。這里默認方法是
parse()
,這個回調方法構造Request的時候指定瘦穆。引擎發(fā)送Response的過程會經過一系列在settings.py
中配置的spider中間件,這些spider中間件會依次對Response進行一些處理赊豌】富颍——對應SpiderMiddleware#process_spider_input()
- spider處理完Response后會返回一個result,這個result是一個
包含 Request 或 Item 對象的可迭代對象(iterable)
碘饼。然后將result發(fā)給scrapy引擎熙兔,這個過程也會經過一系列在settings.py
中配置的spider中間件,這些spider中間件會依次對這個result進行一些處理派昧∏——對應SpiderMiddleware#process_spider_output()
- scrapy引擎拿到這個result后,會將其中的Item發(fā)送給
Item Pipeline
處理,這些item就會被一系列我們在settings.py
中配置的pipeline
處理蒂萎。同時秆吵,scrapy也會將result中的Request發(fā)給調度中間準備調度。 - 繼續(xù)重復第2步的步驟五慈,直到所有的Request全部處理完后程序退出纳寂。
在scrapy 0.15版本后主穗,spider中間件新增了一個方法,用于處理第一步中spider發(fā)送給引擎的Request,也就是
SpiderMiddleware#process_start_requests(start_requests, spider)
scrapy的組件介紹
一毙芜、Spider
Spider組件主要用來生成要爬取的url忽媒,解析返回的內容,然后生成新的url繼續(xù)交給scrapy去爬取腋粥,或者生成item交給pipeline處理晦雨。
編寫scrapy爬蟲應用時,我們大多數精力應該都是放在spider的編寫上面隘冲。
- 通過定義
start_urls
參數或者重寫start_requests()
方法來給scrapy引擎提供起始的拉取url闹瞧。 - 之后,scrapy引擎拿到url后去獲取對應url網頁中的內容后就會回調spider中的
parse()
方法展辞,我們可以重寫parse()
方法來解析scrapy引擎返回回來的內容奥邮。 - 在
parse()
方法中,我們可以返回一個可迭代的對象罗珍,可以理解為是一個list洽腺,因為list也是一個可迭代對象,這個list里面可以放Request對象或者Item對象覆旱。 - scrapy拿到list后蘸朋,會遍歷整個容器,然后把Request再放進調度等會去獲取內容通殃,對于Item對象度液,scrapy引擎就把這個item對象發(fā)到pipeline去處理厕宗。在pipeline有用戶自己編寫的一套處理邏輯画舌,可以選擇的將item存到文件或者數據庫中。
# -*- coding: utf-8 -*-
import scrapy
class TestSpider(scrapy.Spider):
name = "kongtrio"
# 定義爬取的域名已慢,如果返回的url所屬的域名不在這個列表里面曲聂,scrapy就不會去爬取內容
allowed_domains = ["bbs.hupu.com"]
# 定義起始的url
start_urls = (
'http://bbs.hupu.com/bxj/',
)
# 如果重寫了這個方法,scrapy引擎取到的起始url就是這個方法返回的內容了
# 這樣start_urls就不會生效了
def start_requests(self):
for i in range(1, 10):
yield scrapy.Request('http://bbs.hupu.com/bxj-' + str(i))
def parse(self, response):
# scrapy拉取到url的內容后佑惠,會封裝成Response對象,然后回調這個parse()方法
# 我們可以對這個response進行解析朋腋,然后根據策略返回響應的內容
# scrapy 自帶了xpath的方式解析內容,xpath教程可以看這篇 https://blog.csdn.net/u013332124/article/details/80621638
title_href = response.xpath(".//a[@class='title']/@href").extract_first()
title = response.xpath(".//a[@class='title']/text()").extract_first()
# 返回一個request對象和一個item對象膜楷,request對象放的是標題的url旭咽,后面scrapy會繼續(xù)讀取這個url然后交給parse繼續(xù)解析
return [scrapy.Request(content_url, self.post_content_parse, dont_filter=True),{"title":title}]
二、pipeline
這也是我們需要關心的組件赌厅。前面spider返回的item會經過scrapy引擎的調度發(fā)向pipeline穷绵。
pipeline的組件做的事情很簡單,就是拿到item特愿,然后具體的操作用戶自己實現(xiàn)仲墨。
class HupuSpiderPipeline(object):
def process_item(self, item, spider):
if not item["title"]:
# 如果這個pipeline拋出DropItem異常勾缭,那么這個item就不會傳給后面的pipeline了
raise DropItem("invalid item")
title = item["title"]
print(title)
# return后 會把這個item繼續(xù)傳給后面的pipeline
return item
上面這個pipeline做的事情很簡單,就是從item中獲取title目养,然后打印出來俩由。
我們可以寫多個pipeline,分別做不同業(yè)務的事情癌蚁。但是要注意的是幻梯,在process_item()
方法中,必須將item返回努释,不然后面的pipeline就不會被調起來處理item了礼旅。或者拋出DropItem異常也會中斷item的傳遞洽洁。
編寫好pipeline之后還要記得在settings.py
里面配置痘系,這樣pipeline才會真正被scrapy引擎知道,并開始工作饿自。
# 后面的數字表示pipeline的次序
ITEM_PIPELINES = {
'hupu_spider.pipelines.HupuSpiderPipeline': 300,
# 'hupu_spider.pipelines.HupuImgDownloadPipeline': 400,
}
編寫完pipeline和spider汰翠,我們其實就基本實現(xiàn)了一個簡單的scrapy爬蟲應用了。挺大一部分場景也只要我們編寫pipeline和spider就可以了昭雌。當然复唤,其他的組件也需要了解一下,以面對豐富多樣的需求變動烛卧。
三佛纫、下載中間件
下載中間件主要是用于在scrapy引擎發(fā)送Request到下載器和下載器返回Response給scrapy引擎的過程中。
-
scrapy引擎發(fā)送Request到下載器
scrapy引擎發(fā)送Request到下載器的過程中总放,會經過一個個的下載中間件呈宇,這些中間件會對Request進行處理,可以將處理后的Request再發(fā)送給下一個中間件局雄,也可以中斷Request的處理甥啄,甚至可以不需要經過下載器就直接生成Response然后返回給scrapy引擎,具體的策略由代碼實現(xiàn)來決定炬搭。在所有的中間件都處理過后蜈漓,下載器拿到Request就會開始下載內容然后返回Response了。 -
下載器返回Response給scrapy引擎
下載器通過Request拉取到數據后宫盔,就會封裝成Response返回給scrapy引擎融虽,在這個過程中,也會經過這些下載中間件的處理灼芭。下載中間件可以生成Request重新交給scrapy引擎處理有额,也可以對Response進行一些處理后交給下一個下載中間件,最后抵達scrapy引擎。
下載中間件的定義和使用
我們需要寫一個類來繼承DownloaderMiddleware
谆吴,并在settings.py
中配置編寫好的這個下載中間件倒源。
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.CustomDownloaderMiddleware': 543,
'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': None,
}
- 后面的數字表示該下載中間件的順序,數字越小越接近scrapy引擎
- 如果要禁用內置的一些下載中間件句狼,可以將數字設置為None
編寫下載中間件需要關注的幾個方法:
process_request(request, spider)
: 這個方法接收scrapy傳過來的Request對象笋熬,我們可以在這個方法里面對這個Request進行一些處理,然后根據返回的對象做一些操作:
- 返回None腻菇,通知下一個下載中間件繼續(xù)對這個Request進行處理
- 返回Response對象胳螟,直接生成Response發(fā)給scrapy引擎,這樣Request就不會交給下載器去下載內容了筹吐。注意糖耸,生成的Response還是會經過一個個下載中間件處理
- 返回Request對象,直接把新的Request返回給scrapy引擎重新調度丘薛,但是后面的下載中間件就不會再執(zhí)行了
- 如果其raise一個 IgnoreRequest 異常嘉竟,則安裝的下載中間件的 process_exception() 方法會被調用
process_response(request, response, spider)
:這個方法接收下載器或者其他下載中間件發(fā)送過來的Response,同時還包括對應的Request對象洋侨,并在方法內對response進行一些處理舍扰,然后根據返回的對象類型做一些操作:
- 如果返回一個Request對象,下載中間件的執(zhí)行就會被停止希坚,并且會把這個Request對象交給scrapy引擎重新調度边苹。
- 如果返回一個Response對象,就會通知下一個下載中間件繼續(xù)對這個response進行處理
- 如果其拋出一個 IgnoreRequest 異常裁僧,則調用request的errback(Request.errback)
總結
理解了scrapy的整個結構后个束,下載中間件的功能還是比較好理解的。官方目前也內置了很多實用的下載中間件聊疲,所以在大多數場景下茬底,也不用我們手動去編寫下載中間件。不過復雜的場景還是用的上的售睹,多學一點也沒有壞處桩警。
四、Spider中間件
Spider中間件主要用于scrapy引擎和spider之間的數據處理昌妹。包括spider發(fā)送Request和Item給scrapy引擎以及scrapy引擎發(fā)送Response給spider。
1. spider發(fā)送Request和Item給scrapy引擎
spider返回Request和Item給scrapy引擎過程中握截,會經過一個個spider中間件對result進行處理飞崖。spider中間件可以拿到spider返回的result和請求返回的response,這個result是Request和Item的迭代對象谨胞。spider中間件進行一些處理之后返回一個result固歪。然后下一個spider中間件繼續(xù)拿到result處理。
2.scrapy引擎發(fā)送Response給spider
scrapy引擎發(fā)送Response給spider的過程中,會經過一個個spider中間件對Response進行處理牢裳。處理之后spider中間件可以返回None或者拋出一個異常逢防。返回None的話就會繼續(xù)調用下一個spider中間件繼續(xù)處理response,拋出異常的話就不會往下執(zhí)行了蒲讯。
spider中間件的定義和使用
我們需要寫一個類來繼承SpiderMiddleware
忘朝,并在settings.py
中配置編寫好的這個下載中間件。
SPIDER_MIDDLEWARES = {
'myproject.middlewares.CustomSpiderMiddleware': 543,
'scrapy.contrib.spidermiddleware.offsite.OffsiteMiddleware': None,
}
- 后面的數字表示該下載中間件的順序判帮,數字越小越接近scrapy引擎
- 如果要禁用內置的一些下載中間件局嘁,可以將數字設置為None
編寫spider中間件需要關注的幾個方法:
process_spider_input(response, spider)
:
接收scrapy引擎?zhèn)鬟^來的response,用戶可以在方法內處理該response晦墙。根據返回類型的不同會有不同的表現(xiàn)行為:
- 如果其返回 None 悦昵,Scrapy將會繼續(xù)處理該response,調用所有其他的中間件直到spider處理該response
- 如果其跑出一個異常(exception)晌畅,Scrapy將不會調用任何其他中間件的 process_spider_input() 方法但指,并調用request的errback。errback的輸出將會以另一個方向被重新輸入到中間件鏈中抗楔,使用 process_spider_output() 方法來處理枚赡,當其拋出異常時則帶調用 process_spider_exception() 。
process_spider_output(response, result, spider)
:
當Spider處理response返回result時谓谦,該方法被調用贫橙。
必須返回返回包含 Request 或 Item 對象的可迭代對象。
process_spider_exception(response, exception, spider)
:
當spider或(其他spider中間件的) process_spider_input() 拋出異常時反粥, 該方法被調用 - 如果其返回 None 卢肃,Scrapy將繼續(xù)處理該異常,調用中間件鏈中的其他中間件的 process_spider_exception() 方法才顿,直到所有中間件都被調用莫湘,該異常到達引擎(異常將被記錄并被忽略)
- 如果其返回一個可迭代對象,則中間件鏈的 process_spider_output() 方法被調用郑气, 其他的 process_spider_exception() 將不會被調用
process_start_requests(start_requests, spider)
:
0.15 新版功能
該方法以spider 啟動的request為參數被調用幅垮,執(zhí)行的過程類似于 process_spider_output() ,只不過其沒有相關聯(lián)的response并且必須返回request(不是item)尾组。
其接受一個可迭代的對象(start_requests 參數)且必須返回另一個包含 Request 對象的可迭代對象
總結
目前scrapy也內置了很多spider中間件忙芒,可以滿足大多數場景。雖然平常時候我們可能不會有寫spider中間件的時候讳侨,但是還是有必要了解的呵萨。
五、總結
scrapy的架構原理以及相關組件介紹差不多到這里就結束了跨跨。熟悉Python的話潮峦,scrapy學起來還是很快的。有空寫寫爬蟲也是挺有意思的,大家有空可以學一學忱嘹。
想深入學習還可以多去官方的scrapy中文文檔看一看:
http://docs.pythontab.com/scrapy/scrapy0.24/intro/overview.html
最后嘱腥,有對爬蟲或者java技術感興趣的歡迎聯(lián)系我一起交流~本人郵箱在下方
我的CSDN博客地址:
https://blog.csdn.net/u013332124/article/details/80645690