上一篇咱們講到了七夜音樂台的需求和所需要的技術。咱們今天就講一下爬蟲,為什么要講爬蟲,因為音樂台的數據源需要通過爬蟲來獲取,不可能手動來下載。(我的新書《Python爬蟲開發與項目實戰》出版了,大家可以看一下樣章)。下圖是一個網絡爬蟲的基本框架:

網絡爬蟲的基本工作流程如下:
1.首先選取一部分精心挑選的種子URL;
2.將這些URL放入待抓取URL隊列;
3.從待抓取URL隊列中取出待抓取在URL,解析DNS,並且得到主機的ip,並將URL對應的網頁下載下來,存儲進已下載網頁庫中。此外,將這些URL放進已抓取URL隊列。
4.分析已抓取URL隊列中的URL,分析其中的其他URL,並且將URL放入待抓取URL隊列,從而進入下一個循環。
網絡爬蟲本質其實就是一些網絡請求和響應,只不過爬蟲把這些有效的整合起來做一些重復性勞動。
大家如果想切實的感受一下網絡爬蟲,看一下我之前寫的python爬蟲:爬取慕課網視頻,大家會對爬蟲的基本工作原理有比較深的了解。
說到爬蟲,不得不提及一下Scrapy的爬蟲架構。crapy,是Python開發的一個快速,高層次的爬蟲框架,用於抓取web站點並從頁面中提取結構化的數據。Scrapy用途廣泛,可以用於數據挖掘、監測和自動化測試。Scrapy吸引人的地方在於它是一個框架,任何人都可以根據需求方便的修改。它也提供了多種類型爬蟲的基類,如BaseSpider、sitemap爬蟲等。下面是Scrapy爬蟲框架圖:

綠線是數據流向,首先從初始 URL 開始,Scheduler 會將其交給 Downloader 進行下載,下載之后會交給 Spider 進行分析,Spider 分析出來的結果有兩種:一種是需要進一步抓取的鏈接,例如之前分析的“下一頁”的鏈接,這些東西會被傳回 Scheduler ;另一種是需要保存的數據,它們則被送到 Item Pipeline 那里,那是對數據進行后期處理(詳細分析、過濾、存儲等)的地方。另外,在數據流動的通道里還可以安裝各種中間件,進行必要的處理。
之后咱們就使用Scrapy框架來爬取音樂資源,下面給大家介紹一個Scrapy入門知識。我們假定您已經安裝好Scrapy,如果不會安裝,請百度一下scrapy安裝,很多,咱們不詳細說了。接下來以 Open Directory Project(dmoz) (dmoz) 為例來講述爬取。
本篇教程中將帶您完成下列任務:
- 創建一個Scrapy項目
- 定義提取的Item
- 編寫爬取網站的 spider 並提取 Item
- 編寫 Item Pipeline 來存儲提取到的Item(即數據)
創建項目
在開始爬取之前,您必須創建一個新的Scrapy項目。 進入您打算存儲代碼的目錄中,運行下列命令:
scrapy startproject tutorial
該命令將會創建包含下列內容的 tutorial 目錄:
tutorial/
scrapy.cfg
tutorial/
__init__.py
items.py
pipelines.py
settings.py
spiders/
__init__.py
...
我推薦將生成的代碼由pycharm打開,進行開發,IDE相對開發快一些。
定義Item
Item 是保存爬取到的數據的容器;其使用方法和python字典類似。雖然您也可以在Scrapy中直接使用dict,但是 Item 提供了額外保護機制來避免拼寫錯誤導致的未定義字段錯誤。
您可以通過創建一個 scrapy.Item 類, 並且定義類型為 scrapy.Field 的類屬性來定義一個Item。
首先根據需要從dmoz.org獲取到的數據對item進行建模。 我們需要從dmoz中獲取名字,url,以及網站的描述。 對此,在item中定義相應的字段。編輯 tutorial 目錄中的 items.py 文件:
import scrapy class DmozItem(scrapy.Item): title = scrapy.Field() link = scrapy.Field() desc = scrapy.Field()
一開始這看起來可能有點復雜,但是通過定義item, 您可以很方便的使用Scrapy的其他方法。而這些方法需要知道您的item的定義。
編寫第一個爬蟲(Spider)
Spider是用戶編寫用於從單個網站(或者一些網站)爬取數據的類。
其包含了一個用於下載的初始URL,如何跟進網頁中的鏈接以及如何分析頁面中的內容, 提取生成item 的方法。
為了創建一個Spider,您必須繼承 scrapy.Spider 類, 且定義一些屬性:
name: 用於區別Spider。 該名字必須是唯一的,您不可以為不同的Spider設定相同的名字。start_urls: 包含了Spider在啟動時進行爬取的url列表。 因此,第一個被獲取到的頁面將是其中之一。 后續的URL則從初始的URL獲取到的數據中提取。parse()是spider的一個方法。 被調用時,每個初始URL完成下載后生成的Response對象將會作為唯一的參數傳遞給該函數。 該方法負責解析返回的數據(response data),提取數據(生成item)以及生成需要進一步處理的URL的Request對象。
以下為我們的第一個Spider代碼,保存在 tutorial/spiders 目錄下的 dmoz_spider.py 文件中:
import scrapy class DmozSpider(scrapy.Spider): name = "dmoz" allowed_domains = ["dmoz.org"] start_urls = [ "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/", "http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/" ] def parse(self, response): filename = response.url.split("/")[-2] + '.html' with open(filename, 'wb') as f: f.write(response.body)
爬取
進入項目的根目錄,執行下列命令啟動spider:
scrapy crawl dmoz
該命令啟動了我們剛剛添加的 dmoz spider, 向 dmoz.org 發送一些請求。 您將會得到類似的輸出:
2014-01-23 18:13:07-0400 [scrapy] INFO: Scrapy started (bot: tutorial)
2014-01-23 18:13:07-0400 [scrapy] INFO: Optional features available: ...
2014-01-23 18:13:07-0400 [scrapy] INFO: Overridden settings: {}
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled extensions: ...
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled downloader middlewares: ...
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled spider middlewares: ...
2014-01-23 18:13:07-0400 [scrapy] INFO: Enabled item pipelines: ...
2014-01-23 18:13:07-0400 [scrapy] INFO: Spider opened
2014-01-23 18:13:08-0400 [scrapy] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/> (referer: None)
2014-01-23 18:13:09-0400 [scrapy] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (referer: None)
2014-01-23 18:13:09-0400 [scrapy] INFO: Closing spider (finished)
現在,查看當前目錄,您將會注意到有兩個包含url所對應的內容的文件被創建了: Book , Resources,正如我們的 parse 方法里做的一樣。
剛才發生了什么?
Scrapy為Spider的 start_urls 屬性中的每個URL創建了 scrapy.Request 對象,並將 parse 方法作為回調函數(callback)賦值給了Request。
Request對象經過調度,執行生成 scrapy.http.Response 對象並送回給spider parse() 方法。
提取Item
Selectors選擇器簡介
從網頁中提取數據有很多方法。Scrapy使用了一種基於 XPath 和 CSS 表達式機制: Scrapy Selectors。 關於selector和其他提取機制的信息請參考 Selector文檔 。
這里給出XPath表達式的例子及對應的含義:
/html/head/title: 選擇HTML文檔中<head>標簽內的<title>元素/html/head/title/text(): 選擇上面提到的<title>元素的文字//td: 選擇所有的<td>元素//div[@class="mine"]: 選擇所有具有class="mine"屬性的div元素
上邊僅僅是幾個簡單的XPath例子,XPath實際上要比這遠遠強大的多。如果想學習Xpath,請到W3CSchool
為了配合CSS與XPath,Scrapy除了提供了 Selector 之外,還提供了方法來避免每次從response中提取數據時生成selector的麻煩。
Selector有四個基本的方法(點擊相應的方法可以看到詳細的API文檔):
xpath(): 傳入xpath表達式,返回該表達式所對應的所有節點的selector list列表 。css(): 傳入CSS表達式,返回該表達式所對應的所有節點的selector list列表.extract(): 序列化該節點為unicode字符串並返回list。re(): 根據傳入的正則表達式對數據進行提取,返回unicode字符串list列表。
在Shell中嘗試Selector選擇器
為了介紹Selector的使用方法,接下來我們將要使用內置的 Scrapy shell 。Scrapy Shell需要您預裝好IPython (一個擴展的Python終端)。
您需要進入項目的根目錄,執行下列命令來啟動shell:
scrapy shell "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/"
shell的輸出類似:
[ ... Scrapy log here ... ]
2014-01-23 17:11:42-0400 [scrapy] DEBUG: Crawled (200) <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/> (referer: None)
[s] Available Scrapy objects:
[s] crawler <scrapy.crawler.Crawler object at 0x3636b50>
[s] item {}
[s] request <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
[s] response <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
[s] settings <scrapy.settings.Settings object at 0x3fadc50>
[s] spider <Spider 'default' at 0x3cebf50>
[s] Useful shortcuts:
[s] shelp() Shell help (print this help)
[s] fetch(req_or_url) Fetch request (or URL) and update local objects
[s] view(response) View response in a browser
In [1]:
當shell載入后,您將得到一個包含response數據的本地 response 變量。輸入 response.body 將輸出response的包體, 輸出 response.headers 可以看到response的包頭。
#TODO.. 更為重要的是, response 擁有一個 selector 屬性, 該屬性是以該特定 response 初始化的類Selector 的對象。 您可以通過使用 response.selector.xpath() 或 response.selector.css() 來對response 進行查詢。 此外,scrapy也對 response.selector.xpath() 及 response.selector.css() 提供了一些快捷方式, 例如 response.xpath() 或 response.css() ,
同時,shell根據response提前初始化了變量 sel 。該selector根據response的類型自動選擇最合適的分析規則(XML vs HTML)。
讓我們來試試:
In [1]: response.xpath('//title')
Out[1]: [<Selector xpath='//title' data=u'<title>Open Directory - Computers: Progr'>]
In [2]: response.xpath('//title').extract()
Out[2]: [u'<title>Open Directory - Computers: Programming: Languages: Python: Books</title>']
In [3]: response.xpath('//title/text()')
Out[3]: [<Selector xpath='//title/text()' data=u'Open Directory - Computers: Programming:'>]
In [4]: response.xpath('//title/text()').extract()
Out[4]: [u'Open Directory - Computers: Programming: Languages: Python: Books']
In [5]: response.xpath('//title/text()').re('(\w+):')
Out[5]: [u'Computers', u'Programming', u'Languages', u'Python']
提取數據
現在,我們來嘗試從這些頁面中提取些有用的數據。
您可以在終端中輸入 response.body 來觀察HTML源碼並確定合適的XPath表達式。不過,這任務非常無聊且不易。您可以考慮使用Firefox的Firebug擴展來使得工作更為輕松。詳情請參考 使用Firebug進行爬取 和 借助Firefox來爬取 。
在查看了網頁的源碼后,您會發現網站的信息是被包含在 第二個 <ul> 元素中。
我們可以通過這段代碼選擇該頁面中網站列表里所有 <li> 元素:
response.xpath('//ul/li')
網站的描述:
response.xpath('//ul/li/text()').extract()
網站的標題:
response.xpath('//ul/li/a/text()').extract()
以及網站的鏈接:
response.xpath('//ul/li/a/@href').extract()
之前提到過,每個 .xpath() 調用返回selector組成的list,因此我們可以拼接更多的 .xpath() 來進一步獲取某個節點。我們將在下邊使用這樣的特性:
for sel in response.xpath('//ul/li'): title = sel.xpath('a/text()').extract() link = sel.xpath('a/@href').extract() desc = sel.xpath('text()').extract() print title, link, desc
在我們的spider中加入這段代碼:
import scrapy class DmozSpider(scrapy.Spider): name = "dmoz" allowed_domains = ["dmoz.org"] start_urls = [ "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/", "http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/" ] def parse(self, response): for sel in response.xpath('//ul/li'): title = sel.xpath('a/text()').extract() link = sel.xpath('a/@href').extract() desc = sel.xpath('text()').extract() print title, link, desc
現在嘗試再次爬取dmoz.org,您將看到爬取到的網站信息被成功輸出:
scrapy crawl dmoz
使用item
Item 對象是自定義的python字典。 您可以使用標准的字典語法來獲取到其每個字段的值。(字段即是我們之前用Field賦值的屬性):
>>> item = DmozItem() >>> item['title'] = 'Example title' >>> item['title'] 'Example title'
為了將爬取的數據返回,我們最終的代碼將是:
import scrapy from tutorial.items import DmozItem class DmozSpider(scrapy.Spider): name = "dmoz" allowed_domains = ["dmoz.org"] start_urls = [ "http://www.dmoz.org/Computers/Programming/Languages/Python/Books/", "http://www.dmoz.org/Computers/Programming/Languages/Python/Resources/" ] def parse(self, response): for sel in response.xpath('//ul/li'): item = DmozItem() item['title'] = sel.xpath('a/text()').extract() item['link'] = sel.xpath('a/@href').extract() item['desc'] = sel.xpath('text()').extract() yield item
現在對dmoz.org進行爬取將會產生 DmozItem 對象:
[scrapy] DEBUG: Scraped from <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
{'desc': [u' - By David Mertz; Addison Wesley. Book in progress, full text, ASCII format. Asks for feedback. [author website, Gnosis Software, Inc.\n],
'link': [u'http://gnosis.cx/TPiP/'],
'title': [u'Text Processing in Python']}
[scrapy] DEBUG: Scraped from <200 http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
{'desc': [u' - By Sean McGrath; Prentice Hall PTR, 2000, ISBN 0130211192, has CD-ROM. Methods to build XML applications fast, Python tutorial, DOM and SAX, new Pyxie open source XML processing library. [Prentice Hall PTR]\n'],
'link': [u'http://www.informit.com/store/product.aspx?isbn=0130211192'],
'title': [u'XML Processing with Python']}
追蹤鏈接(Following links)
接下來, 不僅僅滿足於爬取 Books 及 Resources 頁面, 您想要獲取獲取所有 Python directory 的內容。
既然已經能從頁面上爬取數據了,為什么不提取您感興趣的頁面的鏈接,追蹤他們, 讀取這些鏈接的數據呢?
下面是實現這個功能的改進版spider:
import scrapy from tutorial.items import DmozItem class DmozSpider(scrapy.Spider): name = "dmoz" allowed_domains = ["dmoz.org"] start_urls = [ "http://www.dmoz.org/Computers/Programming/Languages/Python/", ] def parse(self, response): for href in response.css("ul.directory.dir-col > li > a::attr('href')"): url = response.urljoin(response.url, href.extract()) yield scrapy.Request(url, callback=self.parse_dir_contents) def parse_dir_contents(self, response): for sel in response.xpath('//ul/li'): item = DmozItem() item['title'] = sel.xpath('a/text()').extract() item['link'] = sel.xpath('a/@href').extract() item['desc'] = sel.xpath('text()').extract() yield item
現在, parse() 僅僅從頁面中提取我們感興趣的鏈接,使用 response.urljoin 方法構造一個絕對路徑的URL(頁面上的鏈接都是相對路徑的), 產生(yield)一個請求, 該請求使用 parse_dir_contents() 方法作為回調函數, 用於最終產生我們想要的數據.。
這里展現的即是Scrpay的追蹤鏈接的機制: 當您在回調函數中yield一個Request后, Scrpay將會調度,發送該請求,並且在該請求完成時,調用所注冊的回調函數。
基於此方法,您可以根據您所定義的跟進鏈接的規則,創建復雜的crawler,並且, 根據所訪問的頁面,提取不同的數據.
一種常見的方法是,回調函數負責提取一些item,查找能跟進的頁面的鏈接, 並且使用相同的回調函數yield一個 Request:
def parse_articles_follow_next_page(self, response): for article in response.xpath("//article"): item = ArticleItem() ... extract article data here yield item next_page = response.css("ul.navigation > li.next-page > a::attr('href')") if next_page: url = response.urljoin(next_page[0].extract()) yield scrapy.Request(url, self.parse_articles_follow_next_page)
上述代碼將創建一個循環,跟進所有下一頁的鏈接,直到找不到為止 – 對於爬取博客、論壇以及其他做了分頁的網站十分有效。
保存爬取到的數據
最簡單存儲爬取的數據的方式是使用 Feed exports:
scrapy crawl dmoz -o items.json
該命令將采用 JSON 格式對爬取的數據進行序列化,生成 items.json 文件。
在類似本篇教程里這樣小規模的項目中,這種存儲方式已經足夠。 如果需要對爬取到的item做更多更為復雜的操作,您可以編寫 Item Pipeline 。 類似於我們在創建項目時對Item做的,用於您編寫自己的 tutorial/pipelines.py 也被創建。 不過如果您僅僅想要保存item,您不需要實現任何的pipeline。
大家可以看看我寫的Scrapy爬取美女圖片 (原創)和Scrapy爬取美女圖片續集 (原創),肯定能實現Scrapy的入門。
代碼上傳到github上了-- https://github.com/qiyeboy/
今天的分享就到這里,如果大家覺得還可以呀,記得打賞呦。

歡迎大家支持我公眾號:

本文章屬於原創作品,歡迎大家轉載分享。尊重原創,轉載請注明來自:七夜的故事 http://www.cnblogs.com/qiyeboy/
