七夜在線音樂台開發 第三彈 爬蟲篇 (原創)


  上一篇咱們講到了七夜音樂台的需求和所需要的技術。咱們今天就講一下爬蟲,為什么要講爬蟲,因為音樂台的數據源需要通過爬蟲來獲取,不可能手動來下載。(我的新書《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) 為例來講述爬取。

本篇教程中將帶您完成下列任務:

  1. 創建一個Scrapy項目
  2. 定義提取的Item
  3. 編寫爬取網站的 spider 並提取 Item
  4. 編寫 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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM