Scrapy一個開源和協做的框架,其最初是爲了頁面抓取 (更確切來講, 網絡抓取 )所設計的,使用它能夠以快速、簡單、可擴展的方式從網站中提取所需的數據。但目前Scrapy的用途十分普遍,可用於如數據挖掘、監測和自動化測試等領域,也能夠應用在獲取API所返回的數據(例如 Amazon Associates Web Services ) 或者通用的網絡爬蟲。Scrapy 是基於twisted框架開發而來,twisted是一個流行的事件驅動的python網絡框架。所以Scrapy使用了一種非阻塞(又名異步)的代碼來實現併發。css
它是爬蟲界最知名的框架。就比如web框架中的djangohtml
Scrapy之因此能實現異步,得益於twisted框架
twisted有事件隊列,哪個事件有活動,就會執行!python
總體架構大體以下:linux
藍條部分,表示中間件!git
能夠將SPIDERS,SCHEDULER,DOWNLOADER,ITEM PIPELINE理解爲4我的。github
它們不直接通信,都是經過ENGINE來完成通信的!web
''' Components: 一、引擎(EGINE) 引擎負責控制系統全部組件之間的數據流,並在某些動做發生時觸發事件。有關詳細信息,請參見上面的數據流部分。 二、調度器(SCHEDULER) 用來接受引擎發過來的請求, 壓入隊列中, 並在引擎再次請求的時候返回. 能夠想像成一個URL的優先級隊列, 由它來決定下一個要抓取的網址是什麼, 同時去除重複的網址 三、下載器(DOWLOADER) 用於下載網頁內容, 並將網頁內容返回給EGINE,下載器是創建在twisted這個高效的異步模型上的 四、爬蟲(SPIDERS) SPIDERS是開發人員自定義的類,用來解析responses,而且提取items,或者發送新的請求 五、項目管道(ITEM PIPLINES) 在items被提取後負責處理它們,主要包括清理、驗證、持久化(好比存到數據庫)等操做 下載器中間件(Downloader Middlewares)位於Scrapy引擎和下載器之間,主要用來處理從EGINE傳到DOWLOADER的請求request,已經從DOWNLOADER傳到EGINE的響應response, 你可用該中間件作如下幾件事: (1) process a request just before it is sent to the Downloader (i.e. right before Scrapy sends the request to the website); (2) change received response before passing it to a spider; (3) send a new Request instead of passing received response to a spider; (4) pass response to a spider without fetching a web page; (5) silently drop some requests. 六、爬蟲中間件(Spider Middlewares) 位於EGINE和SPIDERS之間,主要工做是處理SPIDERS的輸入(即responses)和輸出(即requests) '''
官網連接ajax
在調度器中,能夠設置將重複的網址去重。若是爬取失敗,須要再次爬取,那麼就不能去重。mongodb
因此要不要去重,取決於項目需求而定。shell
若是訪問5個url,當有一個url卡住了。那麼它就會切換到隊列中其餘的url。由於它是異步請求,這裏就能夠高效利用CPU
以前學習的 爬蟲的基本流程
那麼 scrapy是如何幫助咱們抓取數據的呢?
Scrapy中的數據流由執行引擎控制,其過程以下:
1.引擎打開一個網站(open adomain),找處處理該網站的Spider並向該spider請求第一個要爬取的URL(s)。
2.引擎從Spider中獲取到第一個要爬取的URL並在調度器(Scheduler)以Request調度。
3.引擎向調度器請求下一個要爬取的URL。
4.調度器返回下一個要爬取的URL給引擎,引擎將URL經過下載中間件(請求(request)方向)轉發給下載器(Downloader)。
5.一旦頁面下載完畢,下載器生成一個該頁面的Response,並將其經過下載中間件(返回(response)方向)發送給引擎。
6.引擎從下載器中接收到Response並經過Spider中間件(輸入方向)發送給Spider處理。
7.Spider處理Response並返回爬取到的Item及(跟進的)新的Request給引擎。
8.引擎將(Spider返回的)爬取到的Item給ItemPipeline,將(Spider返回的)Request給調度器。
9.(從第二步)重複直到調度器中沒有更多地request,引擎關閉該網站。
上面的內容,參考連接:
http://www.javashuo.com/article/p-vitmtisf-hn.html
每訪問一個url,就會經歷上面列舉的9個步驟,它是一個反覆過程。
若是訪問失敗,能夠再次請求訪問!
spiders包含了爬取和解析這2步。
爲何要有中間件?
請求一個網頁,有幾十個cookie,每一次發送,都得帶上cookie,很麻煩。
好比使用代理,須要換IP訪問。封鎖一個,換一個!
加上代理訪問,須要使用中間件!那麼不管任何請求,均可以帶上cookie。
item就是解析好的數據
windows平臺安裝比較難搞,它不像linux同樣,一條命令就搞定了!不少人學習scrapy,光安裝就卡了半天。
爲了避免讓 學習如癡如醉的你放棄scrapy,這裏列舉出詳細的安裝過程!
先說明一下安裝環境。使用的是windows 10 家庭版,64位操做系統。已經安裝好了Python 3.6
搜索cmd,必須使用管理員身份運行
C:\Windows\system32>pip3 install wheel
C:\Windows\system32>pip3 install lxml
C:\Windows\system32>pip3 install pyopenssl
打開網址:
https://sourceforge.net/projects/pywin32/files/pywin32/
點擊 Build 221
下載pywin32-221.win-amd64-py3.6.exe
直接運行
點擊下一步
點擊下一步
最後就能夠安裝完成了!
打開網頁
https://pypi.org/project/Twisted/#files
這裏不要下載,爲何呢?看文件名,它只支持Python 2.7。但個人是Python 3.6啊!
打開另一個網頁:
https://github.com/zerodhatech/python-wheels
下載文件 Twisted-17.9.0-cp36-cp36m-win_amd64.whl
點擊下載
點擊文件屬性,複製路徑
安裝
C:\Windows\system32>pip3 install C:\Users\vr\Downloads\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
C:\Windows\system32>pip3 install scrapy
到這裏,scrapy就安裝完成了!
Linux平臺安裝比較簡單,一條命令就能夠搞定了
pip3 install scrapy
scrapy -h
scrapy <command> -h
有兩種命令:其中Project-only必須切到項目文件夾下才能執行,而Global的命令則不須要
startproject #建立項目 genspider #建立爬蟲程序 settings #若是是在項目目錄下,則獲得的是該項目的配置 runspider #運行一個獨立的python文件,沒必要建立項目 shell #scrapy shell url地址 在交互式調試,如選擇器規則正確與否 fetch #獨立於程單純地爬取一個頁面,能夠拿到請求頭 view #下載完畢後直接彈出瀏覽器,以此能夠分辨出哪些數據是ajax請求 version #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依賴庫的版本
crawl #運行爬蟲,必須建立項目才行,確保配置文件中ROBOTSTXT_OBEY = False check #檢測項目中有無語法錯誤 list #列出項目中所包含的爬蟲名 edit #編輯器,通常不用 parse #scrapy parse url地址 --callback 回調函數 #以此能夠驗證咱們的回調函數是否正確 bench #scrapy bentch壓力測試
舉例:
建立一個爬蟲項目DianShang
C:\Users\xiao>e: E:\>cd E:\python_script\爬蟲\day3 E:\python_script\爬蟲\day3>scrapy startproject DianShang
生成一個爬蟲程序。注意:左邊的jd表示模塊名,右邊的jd.com表示要訪問的url。
一個項目還能夠訪問多個url
E:\python_script\爬蟲\day3>cd DianShang E:\python_script\爬蟲\day3\DianShang>scrapy genspider jd jd.com
注意:項目名和爬蟲程序名不能重複!
運行scrapy項目。注意:這裏指定的模塊名必須和上面一致!
E:\python_script\爬蟲\day3\DianShang>scrapy crawl jd
一個項目,也能夠啓動多個模塊
主要用到命令,就是上面演示的3個!
1 scrapy startproject DianShang # 建立爬蟲項目 2 scrapy genspider jd jd.com # 生成一個爬蟲程序 3 scrapy crawl jd # 運行scrapy項目
https://docs.scrapy.org/en/latest/topics/commands.html
project_name/ scrapy.cfg # 項目的主配置信息,用來部署scrapy時使用 project_name/ # 爬蟲項目名稱 __init__.py items.py # 數據存儲模板,用於結構化數據,如:Django的Model pipelines.py # 數據持久化 settings.py # 用戶級配置文件 spiders/ # 爬蟲程序的目錄 __init__.py 爬蟲1.py # 爬蟲程序1 爬蟲2.py 爬蟲3.py
文件說明:
注意:
一、通常建立爬蟲文件時,以網站域名命名
二、默認只能在終端執行命令,爲了更便捷操做:
#在項目根目錄下新建:entrypoint.py from scrapy.cmdline import execute execute(['scrapy', 'crawl', 'xiaohua'])
框架基礎:spider類,選擇器
舉例:
上面已經建立好了DianShang項目,修改settings.py,將下面的配置改成False
ROBOTSTXT_OBEY = False
這是配置是否遵循robots協議,通常狀況下,是不遵循的。爲啥呢?你要真的遵循的話,還怎麼爬呀!
涉及到金融,影響國家安全,影響社會和諧穩定...的信息,切莫爬取,不然後果不堪設想!
每次使用命令行運行 scrapy crawl jd 太麻煩了。
在項目根目錄新建一個文件 bin.py。注意,它和scrapy.cfg是同級的!
#在項目根目錄下新建:bin.py from scrapy.cmdline import execute # 最後一個參數是:爬蟲程序名 execute(['scrapy', 'crawl', 'jd'])
直接執行bin.py,就能夠啓動爬蟲程序了!
執行以後,會輸出一段紅色字符串。注意:它不是報錯!
也能夠關閉掉,修改 bin.py
#在項目根目錄下新建:bin.py from scrapy.cmdline import execute # 第三個參數是:爬蟲程序名 execute(['scrapy', 'crawl', 'jd','--nolog'])
再次執行,就不會輸出了!
Spiders是定義如何抓取某個站點(或一組站點)的類,包括如何執行爬行(即跟隨連接)以及如何從其頁面中提取結構化數據(即抓取項目)。換句話說,Spiders是您爲特定站點(或者在某些狀況下,一組站點)爬網和解析頁面定義自定義行爲的地方。
1、 生成初始的Requests來爬取第一個URLS,而且標識一個回調函數 第一個請求定義在start_requests()方法內默認從start_urls列表中得到url地址來生成Request請求, 默認的回調函數是parse方法。回調函數在下載完成返回response時自動觸發 2、 在回調函數中,解析response而且返回值 返回值能夠4種: 包含解析數據的字典 Item對象 新的Request對象(新的Requests也須要指定一個回調函數) 或者是可迭代對象(包含Items或Request) 3、在回調函數中解析頁面內容 一般使用Scrapy自帶的Selectors,但很明顯你也可使用Beutifulsoup,lxml或其餘你愛用啥用啥。 4、最後,針對返回的Items對象將會被持久化到數據庫 經過Item Pipeline組件存到數據庫:https://docs.scrapy.org/en/latest/topics/item-pipeline.html#topics-item-pipeline) 或者導出到不一樣的文件(經過Feed exports:https://docs.scrapy.org/en/latest/topics/feed-exports.html#topics-feed-exports)
舉例:
打開DianShang項目,修改DianShang-->spiders-->jd.py
# -*- coding: utf-8 -*- import scrapy class JdSpider(scrapy.Spider): name = 'jd' allowed_domains = ['jd.com'] start_urls = ['http://jd.com/'] def parse(self, response): print(response,type(response))
執行bin.py,輸出以下:
<200 https://www.jd.com/> <class 'scrapy.http.response.html.HtmlResponse'>
使用Ctrl+鼠標左鍵,點擊這一段代碼中的Spider,查看源代碼
class JdSpider(scrapy.Spider):
查看start_requests方法,看最後2行代碼。
def start_requests(self): cls = self.__class__ if method_is_overridden(cls, Spider, 'make_requests_from_url'): warnings.warn( "Spider.make_requests_from_url method is deprecated; it " "won't be called in future Scrapy releases. Please " "override Spider.start_requests method instead (see %s.%s)." % ( cls.__module__, cls.__name__ ), ) for url in self.start_urls: yield self.make_requests_from_url(url) else: for url in self.start_urls: yield Request(url, dont_filter=True)
它執行了for循環,self.start_urls就是在JdSpider類中定義的start_urls變量,它是一個列表!
若是列表爲空,不會執行yield
最後使用生成器返回了一個Request對象
再查看Request源碼
class Request(object_ref): def __init__(self, url, callback=None, method='GET', headers=None, body=None, cookies=None, meta=None, encoding='utf-8', priority=0, dont_filter=False, errback=None, flags=None): self._encoding = encoding # this one has to be set first self.method = str(method).upper() self._set_url(url) self._set_body(body) assert isinstance(priority, int), "Request priority not an integer: %r" % priority self.priority = priority if callback is not None and not callable(callback): raise TypeError('callback must be a callable, got %s' % type(callback).__name__) if errback is not None and not callable(errback): raise TypeError('errback must be a callable, got %s' % type(errback).__name__) assert callback or not errback, "Cannot use errback without a callback" self.callback = callback self.errback = errback self.cookies = cookies or {} self.headers = Headers(headers or {}, encoding=encoding) self.dont_filter = dont_filter self._meta = dict(meta) if meta else None self.flags = [] if flags is None else list(flags)
參數解釋:
url(string)
- 此請求的網址callback(callable)
- 將使用此請求的響應(一旦下載)做爲其第一個參數調用的函數。有關更多信息,請參閱下面的將附加數據傳遞給回調函數。若是請求沒有指定回調,parse()將使用spider的 方法。請注意,若是在處理期間引起異常,則會調用errback。method(string)
- 此請求的HTTP方法。默認爲'GET'。meta(dict)
- 屬性的初始值Request.meta。若是給定,在此參數中傳遞的dict將被淺複製。body(str或unicode)
- 請求體。若是unicode傳遞了a,那麼它被編碼爲 str使用傳遞的編碼(默認爲utf-8)。若是 body沒有給出,則存儲一個空字符串。無論這個參數的類型,存儲的最終值將是一個str(不會是unicode或None)。headers(dict)
- 這個請求的頭。dict值能夠是字符串(對於單值標頭)或列表(對於多值標頭)。若是 None做爲值傳遞,則不會發送HTTP頭。cookie(dict或list)
- 請求cookie。
encoding(string)
- 此請求的編碼(默認爲'utf-8')。此編碼將用於對URL進行百分比編碼,並將正文轉換爲str(若是給定unicode)。priority(int)
- 此請求的優先級(默認爲0)。調度器使用優先級來定義用於處理請求的順序。具備較高優先級值的請求將較早執行。容許負值以指示相對低優先級。dont_filter(boolean)
- 表示此請求不該由調度程序過濾。當您想要屢次執行相同的請求時忽略重複過濾器時使用。當心使用它,或者你會進入爬行循環。默認爲False。errback(callable)
- 若是在處理請求時引起任何異常,將調用的函數。這包括失敗的404 HTTP錯誤等頁面。它接收一個Twisted Failure實例做爲第一個參數。有關更多信息,請參閱使用errbacks在請求處理中捕獲異常。
.flags
列表。若是給定,列表將被淺複製。
關於參數的更多信息,請參考連接:
https://blog.csdn.net/weixin_37947156/article/details/74974208
通常狀況下,要本身定義start_requests方法。爲何呢?由於它不必定可以訪問目標網站。
好比訪問 https://github.com/ 要獲取個人我的信息。直接用GET請求,不帶任何信息,是獲取不到的。必須登陸才行!
舉例:
進入DianShang項目,修改 DianShang-->spiders-->jd.py,增長start_requests方法
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 class JdSpider(scrapy.Spider): name = 'jd' allowed_domains = ['jd.com'] # start_urls = ['http://jd.com/'] # 佔時沒有用了 def start_requests(self): r1 = Request(url="http://jd.com/") yield r1 # 務必使用yield 返回 def parse(self, response): print(response,type(response))
從新執行bin.py,Pycharm輸出以下:
<200 https://www.jd.com/> <class 'scrapy.http.response.html.HtmlResponse'>
注意:推薦使用yield返回,這樣能夠節省內存。它最後仍是會調用for循環!
start_requests方法,雖然沒有寫callback參數,指定回調函數。它默認的回調函數就是parse
start_requests執行以後,並無發送請求。它只是一個返回一個Request對象,放到一個請求列表中。由twisted進行異步請求!若是獲得了響應信息,則調用parse函數!
上面的parse函數,尚未寫return。因此它不會走架構圖中的7,8步驟
爲了解釋如何使用選擇器,咱們將使用Scrapy shell(提供交互式測試)和Scrapy文檔服務器中的示例頁面,
這是它的HTML代碼:
<html> <head> <base href='http://example.com/' /> <title>Example website</title> </head> <body> <div id='images'> <a href='image1.html'>Name: My image 1 <br /><img src='image1_thumb.jpg' /></a> <a href='image2.html'>Name: My image 2 <br /><img src='image2_thumb.jpg' /></a> <a href='image3.html'>Name: My image 3 <br /><img src='image3_thumb.jpg' /></a> <a href='image4.html'>Name: My image 4 <br /><img src='image4_thumb.jpg' /></a> <a href='image5.html'>Name: My image 5 <br /><img src='image5_thumb.jpg' /></a> </div> </body> </html>
首先,讓咱們打開shell:
scrapy shell https://doc.scrapy.org/en/latest/_static/selectors-sample1.html
而後,在shell加載以後,您將得到響應做爲response shell變量,並在response.selector屬性中附加選擇器。
讓咱們構建一個XPath來選擇title標籤內的文本:
>>> response.selector.xpath('//title/text()') [<Selector xpath='//title/text()' data='Example website'>]
使用XPath和CSS查詢響應很是常見,響應包括兩個便捷快捷方式:response.xpath()和response.css():
>>> response.xpath('//title/text()') [<Selector xpath='//title/text()' data='Example website'>] >>> response.css('title::text') [<Selector xpath='descendant-or-self::title/text()' data='Example website'>]
正如你所看到的,.xpath()而且.css()方法返回一個 SelectorList實例,這是新的選擇列表。此API可用於快速選擇嵌套數據:
>>> response.css('img').xpath('@src').extract() ['image1_thumb.jpg', 'image2_thumb.jpg', 'image3_thumb.jpg', 'image4_thumb.jpg', 'image5_thumb.jpg']
要實際提取文本數據,必須調用selector .extract() 方法,以下所示:
>>> response.xpath('//title/text()').extract() ['Example website']
若是隻想提取第一個匹配的元素,能夠調用選擇器 .extract_first()
>>> response.xpath('//div[@id="images"]/a/text()').extract_first() 'Name: My image 1 '
如今咱們將得到基本URL和一些圖像連接:
>>> response.xpath('//base/@href').extract() ['http://example.com/'] >>> >>> response.css('base::attr(href)').extract() ['http://example.com/'] >>> >>> response.xpath('//a[contains(@href, "image")]/@href').extract() ['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html'] >>> >>> response.css('a[href*=image]::attr(href)').extract() ['image1.html', 'image2.html', 'image3.html', 'image4.html', 'image5.html'] >>> >>> response.xpath('//a[contains(@href, "image")]/img/@src').extract() ['image1_thumb.jpg', 'image2_thumb.jpg', 'image3_thumb.jpg', 'image4_thumb.jpg', 'image5_thumb.jpg'] >>> >>> response.css('a[href*=image] img::attr(src)').extract() ['image1_thumb.jpg', 'image2_thumb.jpg', 'image3_thumb.jpg', 'image4_thumb.jpg', 'image5_thumb.jpg'] >>>
默認使用方式:
DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' Request(...,dont_filter=False) ,若是dont_filter=True則告訴Scrapy這個URL不參與去重。
源碼解析:
from scrapy.core.scheduler import Scheduler 見Scheduler下的enqueue_request方法:self.df.request_seen(request)
自定義去重規則:
from scrapy.dupefilter import RFPDupeFilter,看源碼,仿照BaseDupeFilter #步驟一:在項目目錄下自定義去重文件dup.py class UrlFilter(object): def __init__(self): self.visited = set() #或者放到數據庫 @classmethod def from_settings(cls, settings): return cls() def request_seen(self, request): if request.url in self.visited: return True self.visited.add(request.url) def open(self): # can return deferred pass def close(self, reason): # can return a deferred pass def log(self, request, spider): # log that a request has been filtered pass
抓取的主要目標是從非結構化源(一般是網頁)中提取結構化數據。Scrapy蜘蛛能夠像Python同樣返回提取的數據。雖然方便和熟悉,但P很容易在字段名稱中輸入拼寫錯誤或返回不一致的數據,尤爲是在具備許多蜘蛛的較大項目中。
爲了定義通用輸出數據格式,Scrapy提供了Item類。 Item對象是用於收集抓取數據的簡單容器。它們提供相似字典的 API,並具備用於聲明其可用字段的方便語法。
使用簡單的類定義語法和Field 對象聲明項。這是一個例子:
import scrapy class Product(scrapy.Item): name = scrapy.Field() price = scrapy.Field() stock = scrapy.Field() last_updated = scrapy.Field(serializer=str)
注意那些熟悉Django的人會注意到Scrapy Items被宣告相似於Django Models,除了Scrapy Items更簡單,由於沒有不一樣字段類型的概念。
Field對象用於指定每一個字段的元數據。例如,last_updated上面示例中說明的字段的序列化函數。
您能夠爲每一個字段指定任何類型的元數據。Field對象接受的值沒有限制。出於一樣的緣由,沒有全部可用元數據鍵的參考列表。
Field對象中定義的每一個鍵能夠由不一樣的組件使用,只有那些組件知道它。您也能夠根據Field本身的須要定義和使用項目中的任何其餘鍵。
Field對象的主要目標是提供一種在一個地方定義全部字段元數據的方法。一般,行爲取決於每一個字段的那些組件使用某些字段鍵來配置該行爲。
如下是使用上面聲明的Product項目對項目執行的常見任務的一些示例 。您會注意到API與dict API很是類似。
建立項目 >>> product = Product(name='Desktop PC', price=1000) >>> print product Product(name='Desktop PC', price=1000) 獲取字段值 >>> product['name'] Desktop PC >>> product.get('name') Desktop PC >>> product['price'] 1000 >>> product['last_updated'] Traceback (most recent call last): ... KeyError: 'last_updated' >>> product.get('last_updated', 'not set') not set >>> product['lala'] # getting unknown field Traceback (most recent call last): ... KeyError: 'lala' >>> product.get('lala', 'unknown field') 'unknown field' >>> 'name' in product # is name field populated? True >>> 'last_updated' in product # is last_updated populated? False >>> 'last_updated' in product.fields # is last_updated a declared field? True >>> 'lala' in product.fields # is lala a declared field? False 設定字段值 >>> product['last_updated'] = 'today' >>> product['last_updated'] today >>> product['lala'] = 'test' # setting unknown field Traceback (most recent call last): ... KeyError: 'Product does not support field: lala' 訪問全部填充值 要訪問全部填充值,只需使用典型的dict API: >>> product.keys() ['price', 'name'] >>> product.items() [('price', 1000), ('name', 'Desktop PC')] 其餘常見任務 複製項目: >>> product2 = Product(product) >>> print product2 Product(name='Desktop PC', price=1000) >>> product3 = product2.copy() >>> print product3 Product(name='Desktop PC', price=1000) 從項目建立dicts: >>> dict(product) # create a dict from all populated values {'price': 1000, 'name': 'Desktop PC'} 從dicts建立項目: >>> Product({'name': 'Laptop PC', 'price': 1500}) Product(price=1500, name='Laptop PC') >>> Product({'name': 'Laptop PC', 'lala': 1500}) # warning: unknown field in dict Traceback (most recent call last): ... KeyError: 'Product does not support field: lala'
您能夠經過聲明原始Item的子類來擴展Items(以添加更多字段或更改某些字段的某些元數據)。
例如:
class DiscountedProduct(Product): discount_percent = scrapy.Field(serializer=str) discount_expiration_date = scrapy.Field()
在一個項目被蜘蛛抓取以後,它被髮送到項目管道,該項目管道經過順序執行的幾個組件處理它。
每一個項目管道組件(有時簡稱爲「項目管道」)是一個實現簡單方法的Python類。他們收到一個項目並對其執行操做,同時決定該項目是否應該繼續經過管道或被丟棄而且再也不處理。
項目管道的典型用途是:
每一個項管道組件都是一個必須實現如下方法的Python類:
process_item(self,項目,蜘蛛)
爲每一個項目管道組件調用此方法。process_item()
必需要麼:返回帶數據的dict,返回一個Item (或任何後代類)對象,返回Twisted Deferred或引起 DropItem異常。丟棄的項目再也不由其餘管道組件處理。
此外,他們還能夠實現如下方法:
open_spider(self,蜘蛛)
打開蜘蛛時會調用此方法。
close_spider(self,蜘蛛)
當蜘蛛關閉時調用此方法。
from_crawler(cls,crawler )
若是存在,則調用此類方法以從a建立管道實例Crawler。它必須返回管道的新實例。Crawler對象提供對全部Scrapy核心組件的訪問,
如設置和信號; 它是管道訪問它們並將其功能掛鉤到Scrapy的一種方式。
讓咱們看看下面的假設管道,它調整 price
那些不包含增值稅(price_excludes_vat
屬性)的項目的屬性,並刪除那些不包含價格的項目:
from scrapy.exceptions import DropItem class PricePipeline(object): vat_factor = 1.15 def process_item(self, item, spider): if item['price']: if item['price_excludes_vat']: item['price'] = item['price'] * self.vat_factor return item else: raise DropItem("Missing price in %s" % item)
如下管道將全部已刪除的項目(來自全部蜘蛛)存儲到一個items.jl
文件中,每行包含一個以JSON格式序列化的項目:
import json class JsonWriterPipeline(object): def open_spider(self, spider): self.file = open('items.jl', 'w') def close_spider(self, spider): self.file.close() def process_item(self, item, spider): line = json.dumps(dict(item)) + "\n" self.file.write(line) return item
注意JsonWriterPipeline的目的只是介紹如何編寫項目管道。若是您確實要將全部已刪除的項目存儲到JSON文件中,則應使用Feed導出。
在這個例子中,咱們將使用pymongo將項目寫入MongoDB。MongoDB地址和數據庫名稱在Scrapy設置中指定; MongoDB集合以item類命名。
這個例子的要點是展現如何使用from_crawler()
方法以及如何正確地清理資源:
import pymongo class MongoPipeline(object): collection_name = 'scrapy_items' def __init__(self, mongo_uri, mongo_db): self.mongo_uri = mongo_uri self.mongo_db = mongo_db @classmethod def from_crawler(cls, crawler): return cls( mongo_uri=crawler.settings.get('MONGO_URI'), mongo_db=crawler.settings.get('MONGO_DATABASE', 'items') ) def open_spider(self, spider): self.client = pymongo.MongoClient(self.mongo_uri) self.db = self.client[self.mongo_db] def close_spider(self, spider): self.client.close() def process_item(self, item, spider): self.db[self.collection_name].insert_one(dict(item)) return item
一個過濾器,用於查找重複項目,並刪除已處理的項目。假設咱們的項目具備惟一ID,但咱們的蜘蛛會返回具備相同ID的多個項目:
from scrapy.exceptions import DropItem class DuplicatesPipeline(object): def __init__(self): self.ids_seen = set() def process_item(self, item, spider): if item['id'] in self.ids_seen: raise DropItem("Duplicate item found: %s" % item) else: self.ids_seen.add(item['id']) return item
要激活Item Pipeline組件,必須將其類添加到 ITEM_PIPELINES
設置中,以下例所示:
ITEM_PIPELINES = { 'myproject.pipelines.PricePipeline': 300, 'myproject.pipelines.JsonWriterPipeline': 800, }
您在此設置中爲類分配的整數值決定了它們運行的順序:項目從較低值到較高值類進行。習慣上在0-1000範圍內定義這些數字。
pymongo操做,請參考連接:
https://www.cnblogs.com/yuanchenqi/articles/9602847.html
本文參考連接:
https://www.cnblogs.com/yuanchenqi/articles/9509793.html
新建一個爬蟲程序
scrapy genspider amazon amazon.cn
修改 DianShang-->spiders-->amazon.py,打印響應信息
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] def start_requests(self): r1 = Request(url="http://amazon.cn/") yield r1 def parse(self, response): print(response.text)
修改bin.py
#在項目根目錄下新建:bin.py from scrapy.cmdline import execute # 第三個參數是:爬蟲程序名 execute(['scrapy', 'crawl', 'amazon'])
執行bin.py,發現有一個503錯誤
2018-10-01 14:47:19 [scrapy.core.engine] DEBUG: Crawled (503) <GET https://www.amazon.cn/> (referer: None)
說明,被亞馬遜給攔截了!怎麼辦呢?加1個請求頭
修改 DianShang-->spiders-->amazon.py
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] # 自定義配置,注意:變量名必須是custom_settings custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request(url="http://amazon.cn/",headers=self.settings.get('REQUEST_HEADERS'),) yield r1 def parse(self, response): print(response.text)
再次運行bin.py,發現拿到了網頁html
<!doctype html><html class="a-no-js" data-19ax5a9jf="dingo"> ... </html>
先來搜索ipone x,跳轉頁面以下:
在地址欄中的url太長了,它真正的地址應該是
https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x
修改 DianShang-->spiders-->amazon.py,更改url
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] # 自定義配置,注意:變量名必須是custom_settings custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",headers=self.settings.get('REQUEST_HEADERS'),) yield r1 def parse(self, response): print(response.text)
修改bin.py,關閉日誌
#在項目根目錄下新建:bin.py from scrapy.cmdline import execute # 第三個參數是:爬蟲程序名 execute(['scrapy', 'crawl', 'amazon',"--nolog"])
再次運行bin.py,輸出一段html
先來看搜索到ipone x的頁面。咱們須要爬取 ipone x的名稱,價格,配送方式。
可是這個頁面並無配送方式,點擊一個具體的商品。
刪掉url後面多餘參數,地址以下:
https://www.amazon.cn/dp/B075LGPY95
效果以下:
能夠發現,這個頁面,咱們所須要的3個信息全有。那麼直接訪問這地址獲取就能夠了!
第一步,應該先獲取全部商品的詳情連接。
看搜索到ipone x的頁面,好比第一個商品,它有不少能夠點擊的地方。好比圖片,標題,價格等等。均可以連接到商品詳情頁!
這裏我只選擇 標題部分,來獲取商品的詳情連接!
先copy Xpath規則
修改 DianShang-->spiders-->amazon.py,使用xpath規則解析
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] # 自定義配置,注意:變量名必須是custom_settings custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",headers=self.settings.get('REQUEST_HEADERS'),) yield r1 def parse(self, response): # 商品詳細連接 detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/h2').extract() print(detail_urls)
執行bin.py,輸出以下:
['<h2 data-attribute="Apple 蘋果 手機 iPhone X 銀色 64G" data-max-rows="0" class="a-size-base s-inline s-access-title a-text-normal">Apple 蘋果 手機 iPhone X 銀色 64G</h2>']
商品名都拿到了,獲取href屬性使用@href就能夠了
修改 DianShang-->spiders-->amazon.py
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] # 自定義配置,注意:變量名必須是custom_settings custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",headers=self.settings.get('REQUEST_HEADERS'),) yield r1 def parse(self, response): # 商品詳細連接 detail_urls = response.xpath('//*[@id="result_0"]/div/div[3]/div[1]/a/@href').extract() print(detail_urls)
執行bin.py,輸出以下:
['https://www.amazon.cn/dp/B075LGPY95']
這只是獲取一個商品,獲取整頁的商品呢?
先來找規律,每個商品都是一個li標籤。而且還有一個id屬性爲result_數字。
能夠發現,id屬性的前綴都是result_ 。那麼使用xpath的模擬匹配就能夠了!
修改 DianShang-->spiders-->amazon.py,使用模糊匹配
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] # 自定義配置,注意:變量名必須是custom_settings custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x",headers=self.settings.get('REQUEST_HEADERS'),) yield r1 def parse(self, response): # 商品詳細連接 detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract() print(detail_urls)
再次執行bin.py,輸出了一堆連接,效果以下:
['https://www.amazon.cn/dp/B075LGPY95', 'https://www.amazon.cn/dp/B0763KX27G', ...]
隨便點擊一個,就是商品詳情連接。那麼如何讓scrapy去訪問這些連接呢?
注意:只要parse函數返回一個Request對象,那麼就會放到異步請求列表裏面,由twisted發送異步請求!
修改 DianShang-->spiders-->amazon.py,yield Request對象
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] # 自定義配置,注意:變量名必須是custom_settings custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x", headers=self.settings.get('REQUEST_HEADERS'),) yield r1 def parse(self, response): # 商品詳細連接 detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract() # print(detail_urls) for url in detail_urls: yield Request(url=url, headers=self.settings.get('REQUEST_HEADERS'), # 請求頭 callback=self.parse_detail, # 回調函數 dont_filter=True # 不去重 ) def parse_detail(self, response): # 獲取商品詳細信息 print(response.text)
執行bin.py,輸出一堆字符串。那些都是商品詳情頁的信息!
先來獲取商品的名字
修改 DianShang-->spiders-->amazon.py,修改parse_detail,增長XPath規則
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] # 自定義配置,注意:變量名必須是custom_settings custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x", headers=self.settings.get('REQUEST_HEADERS'),) yield r1 def parse(self, response): # 商品詳細連接 detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract() # print(detail_urls) for url in detail_urls: yield Request(url=url, headers=self.settings.get('REQUEST_HEADERS'), # 請求頭 callback=self.parse_detail, # 回調函數 dont_filter=True # 不去重 ) def parse_detail(self, response): # 獲取商品詳細信息 # 商品名 name = response.xpath('//*[@id="productTitle"]/text()').extract() print(name)
執行輸出:
\n Apple 蘋果 手機 iPhone X 深空灰色 64G\n
發現它有不少換行符,怎麼去除呢?使用strip
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] # 自定義配置,注意:變量名必須是custom_settings custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x", headers=self.settings.get('REQUEST_HEADERS'),) yield r1 def parse(self, response): # 商品詳細連接 detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract() # print(detail_urls) for url in detail_urls: yield Request(url=url, headers=self.settings.get('REQUEST_HEADERS'), # 請求頭 callback=self.parse_detail, # 回調函數 dont_filter=True # 不去重 ) def parse_detail(self, response): # 獲取商品詳細信息 # 商品名 name = response.xpath('//*[@id="productTitle"]/text()').extract() if name: name = name.strip() print(name)
執行bin.py,發現輸出爲空!
修改bin.py,去除關閉日誌
#在項目根目錄下新建:bin.py from scrapy.cmdline import execute # 第三個參數是:爬蟲程序名 execute(['scrapy', 'crawl', 'amazon'])
再次執行,發現有一個錯誤
AttributeError: 'list' object has no attribute 'strip'
哦,原來name是一個list對象。那麼使用extract就不合適了,修改成extract_first。匹配第一個結果!
修改 DianShang-->spiders-->amazon.py,修改parse_detail,使用extract_first
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] # 自定義配置,注意:變量名必須是custom_settings custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x", headers=self.settings.get('REQUEST_HEADERS'),) yield r1 def parse(self, response): # 商品詳細連接 detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract() # print(detail_urls) for url in detail_urls: yield Request(url=url, headers=self.settings.get('REQUEST_HEADERS'), # 請求頭 callback=self.parse_detail, # 回調函數 dont_filter=True # 不去重 ) def parse_detail(self, response): # 獲取商品詳細信息 # 商品名,獲取第一個結果 name = response.xpath('//*[@id="productTitle"]/text()').extract_first() if name: name = name.strip() print(name)
再次執行,輸出:
...
Apple 蘋果 手機 iPhone X 深空灰色 64G
...
這下就正常了!
商品價格,和商品名稱,也是一樣的道理。使用谷歌瀏覽器直接複製XPath規則,就能夠了!
修改 DianShang-->spiders-->amazon.py,修改parse_detail
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] # 自定義配置,注意:變量名必須是custom_settings custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x", headers=self.settings.get('REQUEST_HEADERS'),) yield r1 def parse(self, response): # 商品詳細連接 detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract() # print(detail_urls) for url in detail_urls: yield Request(url=url, headers=self.settings.get('REQUEST_HEADERS'), # 請求頭 callback=self.parse_detail, # 回調函數 dont_filter=True # 不去重 ) def parse_detail(self, response): # 獲取商品詳細信息 # 商品名,獲取第一個結果 name = response.xpath('//*[@id="productTitle"]/text()').extract_first() if name: name = name.strip() # 商品價格 price = response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first() print(name,price)
執行bin.py,輸出以下:
... Apple 蘋果 手機 iPhone X 深空灰色 64G ¥7,288.00 ...
使用谷歌瀏覽器直接複製XPath規則
修改 DianShang-->spiders-->amazon.py,修改parse_detail
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] # 自定義配置,注意:變量名必須是custom_settings custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x", headers=self.settings.get('REQUEST_HEADERS'),) yield r1 def parse(self, response): # 商品詳細連接 detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract() # print(detail_urls) for url in detail_urls: yield Request(url=url, headers=self.settings.get('REQUEST_HEADERS'), # 請求頭 callback=self.parse_detail, # 回調函數 dont_filter=True # 不去重 ) def parse_detail(self, response): # 獲取商品詳細信息 # 商品名,獲取第一個結果 name = response.xpath('//*[@id="productTitle"]/text()').extract_first() if name: name = name.strip() # 商品價格 price = response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first() # 配送方式 delivery = response.xpath('//*[@id="ddmMerchantMessage"]/a/text()').extract_first() print(name,price,delivery)
執行bin.py,輸出以下:
... Apple iPhone X 全網通 移動聯通電信4G 熱銷中 (深空灰, 64G) ¥7,288.00 新思惟官方旗艦店 X-Doria 461238 Apple iPhone X,14.73 釐米(5.8 英寸)灰色 ¥173.70 瞭解更多。 ...
發現了一個問題,若是是手機殼,好比這個連接:
https://www.amazon.cn/dp/B0787HGXQX
使用Xpath獲取時,輸出: 瞭解更多
爲何呢?由於咱們獲取的是a標籤的值!
因此獲取時,就是了解更多了!看上面,有一個亞馬遜中國,這個纔是咱們想要的!
在span標籤id=ddmMerchantMessage,這標籤中,它裏面包含了2個標籤,分別是b標籤和a標籤。
那麼咱們取b標籤的text()值,就能夠了!
修改 DianShang-->spiders-->amazon.py,修改parse_detail
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] # 自定義配置,注意:變量名必須是custom_settings custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x", headers=self.settings.get('REQUEST_HEADERS'),) yield r1 def parse(self, response): # 商品詳細連接 detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract() # print(detail_urls) for url in detail_urls: yield Request(url=url, headers=self.settings.get('REQUEST_HEADERS'), # 請求頭 callback=self.parse_detail, # 回調函數 dont_filter=True # 不去重 ) def parse_detail(self, response): # 獲取商品詳細信息 # 商品名,獲取第一個結果 name = response.xpath('//*[@id="productTitle"]/text()').extract_first() if name: name = name.strip() # 商品價格 price = response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first() # 配送方式,*[1]表示取第一個標籤,也就是b標籤 delivery = response.xpath('//*[@id="ddmMerchantMessage"]/*[1]/text()').extract_first() print(name,price,delivery)
執行bin.py,輸出以下:
... hayder iphone X 保護套透明 TPU 超薄電鍍防禦防撞手機殼適用於蘋果 iPhone X 10 ¥149.09 亞馬遜美國 Apple iPhone X 移動聯通電信4G手機 (256GB, 銀色) ¥8,448.00 新思惟官方旗艦店 ...
發現手機殼的配送方式,也是正確的!
要想將數據保存到MongoDB中,須要使用item
修改 DianShang-->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 AmazonItem(scrapy.Item): name = scrapy.Field() # 商品名 price= scrapy.Field() # 價格 delivery=scrapy.Field() # 配送方式
修改 DianShang-->spiders-->amazon.py,使用item
# -*- coding: utf-8 -*- import scrapy from scrapy import Request # 導入模塊 from DianShang.items import AmazonItem # 導入item class AmazonSpider(scrapy.Spider): name = 'amazon' allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] # 自定義配置,注意:變量名必須是custom_settings custom_settings = { 'REQUEST_HEADERS': { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36', } } def start_requests(self): r1 = Request(url="https://www.amazon.cn/s/ref=nb_sb_ss_i_3_6?field-keywords=iphone+x", headers=self.settings.get('REQUEST_HEADERS'),) yield r1 def parse(self, response): # 商品詳細連接 detail_urls = response.xpath('//li[contains(@id,"result_")]/div/div[3]/div[1]/a/@href').extract() # print(detail_urls) for url in detail_urls: yield Request(url=url, headers=self.settings.get('REQUEST_HEADERS'), # 請求頭 callback=self.parse_detail, # 回調函數 dont_filter=True # 不去重 ) def parse_detail(self, response): # 獲取商品詳細信息 # 商品名,獲取第一個結果 name = response.xpath('//*[@id="productTitle"]/text()').extract_first() if name: name = name.strip() # 商品價格 price = response.xpath('//*[@id="priceblock_ourprice"]/text()').extract_first() # 配送方式,*[1]表示取第一個標籤,也就是b標籤 delivery = response.xpath('//*[@id="ddmMerchantMessage"]/*[1]/text()').extract_first() print(name,price,delivery) # 生成標準化數據 item = AmazonItem() # 執行函數,默認是一個空字典 # 增長鍵值對 item["name"] = name item["price"] = price item["delivery"] = delivery return item # 必需要返回
上面的item只是返回了一個字典,並無真正存儲到MongoDB中。須要PipeLine將數據儲存到MongoDB中
修改 DianShang-->pipelines.py。注意:from_crawler,open_spider,close_spider,process_item這4個方法,必需要定義才行!
# -*- 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 from pymongo import MongoClient class MongodbPipeline(object): def __init__(self, host, port, db, table): self.host = host self.port = port self.db = db self.table = table @classmethod def from_crawler(cls, crawler): """ Scrapy會先經過getattr判斷咱們是否自定義了from_crawler,有則調它來完 成實例化 """ HOST = crawler.settings.get('HOST') PORT = crawler.settings.get('PORT') DB = crawler.settings.get('DB') TABLE = crawler.settings.get('TABLE') return cls(HOST, PORT, DB, TABLE) def open_spider(self, spider): """ 爬蟲剛啓動時執行一次 """ # self.client = MongoClient('mongodb://%s:%s@%s:%s' %(self.user,self.pwd,self.host,self.port)) self.client = MongoClient(host=self.host, port=self.port) def close_spider(self, spider): """ 爬蟲關閉時執行一次 """ self.client.close() def process_item(self, item, spider): # 操做並進行持久化 d = dict(item) if all(d.values()): self.client[self.db][self.table].insert(d) print("添加成功一條")
open_spider,close_spider這2個方法,只會執行一次。
process_item呢?就不必定了。有多少次請求,就執行多少次!每次會插入一條記錄
修改 DianShang\settings.py,增長MongoDB鏈接信息。
最後一行增長如下信息:
# MongoDB鏈接信息 HOST="127.0.0.1" PORT=27017 DB="amazon" # 數據庫名 TABLE="goods" # 表名
再修改 DianShang\settings.py,開啓MongoDB的PipeLine
這裏的300,表示優先級
ITEM_PIPELINES = { 'DianShang.pipelines.MongodbPipeline': 300, }
注意:因爲在pipelines.py中,定義的是MongodbPipeline。因此配置文件這裏,必需要一一匹配,不然報錯:
raise NameError("Module '%s' doesn't define any object named '%s'" % (module, name))
先啓動MongoDB,打開cmd窗口,輸入命令
C:\Users\xiao>mongod
若是輸出:
[thread1] waiting for connections on port 27017
則表示啓動成功了!
Pipeline是不會幫你自動建立數據庫的,因此須要手動建立。
進入數據庫,建立數據庫amazon
C:\Users\xiao>mongo > use amazon switched to db amazon >
從新運行 bin.py,輸出:
... Apple 蘋果 手機 iPhone X 深空灰色 64G ¥7,288.00 新思惟官方旗艦店 添加成功一條 ...
使用 MongoDB客戶端打開表goods,效果以下: