鍍金池/ 教程/ Python/ 選擇器(Selectors)
Benchmarking
命令行工具(Command line tools)
下載器中間件(Downloader Middleware)
信號(hào)(Signals)
Telnet 終端(Telnet Console)
初窺 Scrapy
數(shù)據(jù)收集(Stats Collection)
Scrapyd
通用爬蟲(chóng)(Broad Crawls)
Item Loaders
試驗(yàn)階段特性
Scrapy 入門(mén)教程
自動(dòng)限速(AutoThrottle)擴(kuò)展
Settings
Scrapy 終端(Scrapy shell)
下載項(xiàng)目圖片
DjangoItem
調(diào)試(Debugging)Spiders
選擇器(Selectors)
Feed exports
Spiders Contracts
借助 Firefox 來(lái)爬取
Logging
Spiders
Ubuntu 軟件包
實(shí)踐經(jīng)驗(yàn)(Common Practices)
安裝指南
Item Exporters
擴(kuò)展(Extensions)
Items
Spider 中間件(Middleware)
異常(Exceptions)
例子
發(fā)送 email
架構(gòu)概覽
常見(jiàn)問(wèn)題(FAQ)
Jobs:暫停,恢復(fù)爬蟲(chóng)
核心 API
使用 Firebug 進(jìn)行爬取
Item Pipeline
Link Extractors
Web Service
調(diào)試內(nèi)存溢出

選擇器(Selectors)

當(dāng)抓取網(wǎng)頁(yè)時(shí),你做的最常見(jiàn)的任務(wù)是從 HTML 源碼中提取數(shù)據(jù)?,F(xiàn)有的一些庫(kù)可以達(dá)到這個(gè)目的:

  • BeautifulSoup 是在程序員間非常流行的網(wǎng)頁(yè)分析庫(kù),它基于 HTML 代碼的結(jié)構(gòu)來(lái)構(gòu)造一個(gè) Python 對(duì)象, 對(duì)不良標(biāo)記的處理也非常合理,但它有一個(gè)缺點(diǎn):慢。
  • lxml 是一個(gè)基于 ElementTree(不是 Python 標(biāo)準(zhǔn)庫(kù)的一部分)的 python 化的 XML 解析庫(kù)(也可以解析 HTML)。

Scrapy 提取數(shù)據(jù)有自己的一套機(jī)制。它們被稱作選擇器(seletors),因?yàn)樗麄兺ㄟ^(guò)特定的 XPath 或者 CSS 表達(dá)式來(lái)“選擇” HTML 文件中的某個(gè)部分。

XPath 是一門(mén)用來(lái)在 XML 文件中選擇節(jié)點(diǎn)的語(yǔ)言,也可以用在 HTML 上。CSS 是一門(mén)將 HTML 文檔樣式化的語(yǔ)言。選擇器由它定義,并與特定的 HTML 元素的樣式相關(guān)連。

Scrapy 選擇器構(gòu)建于 lxml 庫(kù)之上,這意味著它們?cè)谒俣群徒馕鰷?zhǔn)確性上非常相似。

本頁(yè)面解釋了選擇器如何工作,并描述了相應(yīng)的 API。不同于 lxml API 的臃腫,該 API 短小而簡(jiǎn)潔。這是因?yàn)?lxml 庫(kù)除了用來(lái)選擇標(biāo)記化文檔外,還可以用到許多任務(wù)上。

選擇器 API 的完全參考詳見(jiàn) Selector reference

使用選擇器(selectors)

構(gòu)造選擇器(selectors)

Scrapy selector 是以 文字(text) 或 TextResponse 構(gòu)造的 Selector 實(shí)例。 其根據(jù)輸入的類型自動(dòng)選擇最優(yōu)的分析方法(XML vs HTML):

>>> from scrapy.selector import Selector
>>> from scrapy.http import HtmlResponse

以文字構(gòu)造:

>>> body = '<html><body><span>good</span></body></html>'
>>> Selector(text=body).xpath('//span/text()').extract()
[u'good']

以 response 構(gòu)造:

>>> response = HtmlResponse(url='http://example.com', body=body)
>>> Selector(response=response).xpath('//span/text()').extract()
[u'good']

為了方便起見(jiàn),response 對(duì)象以.selector 屬性提供了一個(gè) selector,您可以隨時(shí)使用該快捷方法:

>>> response.selector.xpath('//span/text()').extract()
[u'good']

使用選擇器(selectors)

我們將使用 Scrapy shell (提供交互測(cè)試)和位于 Scrapy 文檔服務(wù)器的一個(gè)樣例頁(yè)面,來(lái)解釋如何使用選擇器:

http://doc.scrapy.org/en/latest/_static/selectors-sample1.html

這里是它的 HTML 源碼:

<html>
 <head>
  <base  />
  <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>

首先,我們打開(kāi) shell:

scrapy shell http://doc.scrapy.org/en/latest/_static/selectors-sample1.html

接著,當(dāng) shell 載入后,您將獲得名為 response 的 shell 變量,其為響應(yīng)的 response,并且在其 response.selector 屬性上綁定了一個(gè) selector。

因?yàn)槲覀兲幚淼氖?HTML,選擇器將自動(dòng)使用 HTML 語(yǔ)法分析。

那么,通過(guò)查看 HTML code 該頁(yè)面的源碼,我們構(gòu)建一個(gè) XPath 來(lái)選擇 title 標(biāo)簽內(nèi)的文字:

>>> response.selector.xpath('//title/text()')
[<Selector (text) xpath=//title/text()>]

由于在 response 中使用 XPath、CSS 查詢十分普遍,因此,Scrapy 提供了兩個(gè)實(shí)用的快捷方式: response.xpath()及 response.css():

>>> response.xpath('//title/text()')
[<Selector (text) xpath=//title/text()>]
>>> response.css('title::text')
[<Selector (text) xpath=//title/text()>]

如你所見(jiàn),.xpath()及.css()方法返回一個(gè)類 SelectorList 的實(shí)例,它是一個(gè)新選擇器的列表。這個(gè) API 可以用來(lái)快速的提取嵌套數(shù)據(jù)。

為了提取真實(shí)的原文數(shù)據(jù),你需要調(diào)用.extract() 方法如下:

>>> response.xpath('//title/text()').extract()
[u'Example website']

注意 CSS 選擇器可以使用 CSS3 偽元素(pseudo-elements)來(lái)選擇文字或者屬性節(jié)點(diǎn):

>>> response.css('title::text').extract()
[u'Example website']

現(xiàn)在我們將得到根 URL(base URL)和一些圖片鏈接:

>>> response.xpath('//base/@href').extract()
[u'http://example.com/']

>>> response.css('base::attr(href)').extract()
[u'http://example.com/']

>>> response.xpath('//a[contains(@href, "image")]/@href').extract()
[u'image1.html',
 u'image2.html',
 u'image3.html',
 u'image4.html',
 u'image5.html']

>>> response.css('a[href*=image]::attr(href)').extract()
[u'image1.html',
 u'image2.html',
 u'image3.html',
 u'image4.html',
 u'image5.html']

>>> response.xpath('//a[contains(@href, "image")]/img/@src').extract()
[u'image1_thumb.jpg',
 u'image2_thumb.jpg',
 u'image3_thumb.jpg',
 u'image4_thumb.jpg',
 u'image5_thumb.jpg']

>>> response.css('a[href*=image] img::attr(src)').extract()
[u'image1_thumb.jpg',
 u'image2_thumb.jpg',
 u'image3_thumb.jpg',
 u'image4_thumb.jpg',
 u'image5_thumb.jpg']

嵌套選擇器(selectors)

選擇器方法( .xpath() or .css() )返回相同類型的選擇器列表,因此你也可以對(duì)這些選擇器調(diào)用選擇器方法。下面是一個(gè)例子:

>>> links = response.xpath('//a[contains(@href, "image")]')
>>> links.extract()
[u'<a href="image1.html">Name: My image 1 <br><img src="image1_thumb.jpg"></a>',
 u'<a href="image2.html">Name: My image 2 <br><img src="image2_thumb.jpg"></a>',
 u'<a href="image3.html">Name: My image 3 <br><img src="image3_thumb.jpg"></a>',
 u'<a href="image4.html">Name: My image 4 <br><img src="image4_thumb.jpg"></a>',
 u'<a href="image5.html">Name: My image 5 <br><img src="image5_thumb.jpg"></a>']

>>> for index, link in enumerate(links):
        args = (index, link.xpath('@href').extract(), link.xpath('img/@src').extract())
        print 'Link number %d points to url %s and image %s' % args

Link number 0 points to url [u'image1.html'] and image [u'image1_thumb.jpg']
Link number 1 points to url [u'image2.html'] and image [u'image2_thumb.jpg']
Link number 2 points to url [u'image3.html'] and image [u'image3_thumb.jpg']
Link number 3 points to url [u'image4.html'] and image [u'image4_thumb.jpg']
Link number 4 points to url [u'image5.html'] and image [u'image5_thumb.jpg']

結(jié)合正則表達(dá)式使用選擇器(selectors)

Selector 也有一個(gè).re()方法,用來(lái)通過(guò)正則表達(dá)式來(lái)提取數(shù)據(jù)。然而,不同于使用.xpath()或者.css()方法,.re()方法返回 unicode 字符串的列表。所以你無(wú)法構(gòu)造嵌套式的.re()調(diào)用。

下面是一個(gè)例子,從上面的 HTML code 中提取圖像名字:

>>> response.xpath('//a[contains(@href, "image")]/text()').re(r'Name:\s*(.*)')
[u'My image 1',
 u'My image 2',
 u'My image 3',
 u'My image 4',
 u'My image 5']

使用相對(duì) XPaths

記住如果你使用嵌套的選擇器,并使用起始為/的 XPath,那么該 XPath 將對(duì)文檔使用絕對(duì)路徑,而且對(duì)于你調(diào)用的 Selector 不是相對(duì)路徑。

比如,假設(shè)你想提取在<div>元素中的所有<p>元素。首先,你將先得到所有的<div>元素:

>>> divs = response.xpath('//div')

開(kāi)始時(shí),你可能會(huì)嘗試使用下面的錯(cuò)誤的方法,因?yàn)樗鋵?shí)是從整篇文檔中,而不僅僅是從那些<div> 元素內(nèi)部提取所有的<p>元素:

>>> for p in divs.xpath('//p'):  # this is wrong - gets all <p> from the whole document
...     print p.extract()

下面是比較合適的處理方法(注意.//p XPath 的點(diǎn)前綴):

>>> for p in divs.xpath('.//p'):  # extracts all <p> inside
...     print p.extract()

另一種常見(jiàn)的情況將是提取所有直系<p>的結(jié)果:

>>> for p in divs.xpath('p'):
...     print p.extract()

更多關(guān)于相對(duì) XPaths 的細(xì)節(jié)詳見(jiàn) XPath 說(shuō)明中的 Location Paths 部分。

使用 EXSLT 擴(kuò)展

因建于 lxml 之上,Scrapy 選擇器也支持一些 EXSLT 擴(kuò)展,可以在 XPath 表達(dá)式中使用這些預(yù)先制定的命名空間:

前綴 命名空間 用途
re http://exslt.org/regular-expressions 正則表達(dá)式
set http://exslt.org/sets 集合操作

正則表達(dá)式

例如在 XPath 的 starts-with()contains()無(wú)法滿足需求時(shí),test()函數(shù)可以非常有用。

例如在列表中選擇有”class”元素且結(jié)尾為一個(gè)數(shù)字的鏈接:

>>> from scrapy import Selector
>>> doc = """
... <div>
...     <ul>
...         <li class="item-0"><a href="link1.html">first item</a></li>
...         <li class="item-1"><a href="link2.html">second item</a></li>
...         <li class="item-inactive"><a href="link3.html">third item</a></li>
...         <li class="item-1"><a href="link4.html">fourth item</a></li>
...         <li class="item-0"><a href="link5.html">fifth item</a></li>
...     </ul>
... </div>
... """
>>> sel = Selector(text=doc, type="html")
>>> sel.xpath('//li//@href').extract()
[u'link1.html', u'link2.html', u'link3.html', u'link4.html', u'link5.html']
>>> sel.xpath('//li[re:test(@class, "item-\d$")]//@href').extract()
[u'link1.html', u'link2.html', u'link4.html', u'link5.html']
>>>

警告

C 語(yǔ)言庫(kù) libxslt 不原生支持 EXSLT 正則表達(dá)式,因此 lxml 在實(shí)現(xiàn)時(shí)使用了 Python re 模塊的鉤子。 因此,在 XPath 表達(dá)式中使用 regexp 函數(shù)可能會(huì)犧牲少量的性能。

集合操作

集合操作可以方便地用于在提取文字元素前從文檔樹(shù)中去除一些部分。

例如使用 itemscopes 組和對(duì)應(yīng)的 itemprops 來(lái)提取微數(shù)據(jù)(來(lái)自 http://schema.org/Product 的樣本內(nèi)容):

>>> doc = """
... <div itemscope itemtype="http://schema.org/Product">
...   <span itemprop="name">Kenmore White 17" Microwave</span>
...   <img src="kenmore-microwave-17in.jpg" alt='Kenmore 17" Microwave' />
...   <div itemprop="aggregateRating"
...     itemscope itemtype="http://schema.org/AggregateRating">
...    Rated <span itemprop="ratingValue">3.5</span>/5
...    based on <span itemprop="reviewCount">11</span> customer reviews
...   </div>
...
...   <div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
...     <span itemprop="price">$55.00</span>
...     <link itemprop="availability"  />In stock
...   </div>
...
...   Product description:
...   <span itemprop="description">0.7 cubic feet countertop microwave.
...   Has six preset cooking categories and convenience features like
...   Add-A-Minute and Child Lock.</span>
...
...   Customer reviews:
...
...   <div itemprop="review" itemscope itemtype="http://schema.org/Review">
...     <span itemprop="name">Not a happy camper</span> -
...     by <span itemprop="author">Ellie</span>,
...     <meta itemprop="datePublished" content="2011-04-01">April 1, 2011
...     <div itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">
...       <meta itemprop="worstRating" content = "1">
...       <span itemprop="ratingValue">1</span>/
...       <span itemprop="bestRating">5</span>stars
...     </div>
...     <span itemprop="description">The lamp burned out and now I have to replace
...     it. </span>
...   </div>
...
...   <div itemprop="review" itemscope itemtype="http://schema.org/Review">
...     <span itemprop="name">Value purchase</span> -
...     by <span itemprop="author">Lucas</span>,
...     <meta itemprop="datePublished" content="2011-03-25">March 25, 2011
...     <div itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">
...       <meta itemprop="worstRating" content = "1"/>
...       <span itemprop="ratingValue">4</span>/
...       <span itemprop="bestRating">5</span>stars
...     </div>
...     <span itemprop="description">Great microwave for the price. It is small and
...     fits in my apartment.</span>
...   </div>
...   ...
... </div>
... """
>>>
>>> for scope in sel.xpath('//div[@itemscope]'):
...     print "current scope:", scope.xpath('@itemtype').extract()
...     props = scope.xpath('''
...                 set:difference(./descendant::*/@itemprop,
...                                .//*[@itemscope]/*/@itemprop)''')
...     print "    properties:", props.extract()
...     print
...

current scope: [u'http://schema.org/Product']
    properties: [u'name', u'aggregateRating', u'offers', u'description', u'review', u'review']

current scope: [u'http://schema.org/AggregateRating']
    properties: [u'ratingValue', u'reviewCount']

current scope: [u'http://schema.org/Offer']
    properties: [u'price', u'availability']

current scope: [u'http://schema.org/Review']
    properties: [u'name', u'author', u'datePublished', u'reviewRating', u'description']

current scope: [u'http://schema.org/Rating']
    properties: [u'worstRating', u'ratingValue', u'bestRating']

current scope: [u'http://schema.org/Review']
    properties: [u'name', u'author', u'datePublished', u'reviewRating', u'description']

current scope: [u'http://schema.org/Rating']
    properties: [u'worstRating', u'ratingValue', u'bestRating']

>>>

在這里,我們首先在 itemscope 元素上迭代,對(duì)于其中的每一個(gè)元素,我們尋找所有的 itemprops 元素,并排除那些本身在另一個(gè) itemscope 內(nèi)的元素。

內(nèi)建選擇器的參考

class scrapy.selector.Selector(response=None, text=None, type=None)

Selector 的實(shí)例是對(duì)選擇某些內(nèi)容響應(yīng)的封裝。

response 是 HtmlResponse 或 XmlResponse 的一個(gè)對(duì)象,將被用來(lái)選擇和提取數(shù)據(jù)。

text 是在 response 不可用時(shí)的一個(gè) unicode 字符串或 utf-8 編碼的文字。將 text 和 response 一起使用是未定義行為。

type 定義了選擇器類型,可以是 "html","xml" or None (默認(rèn)).

如果 type 是 None ,選擇器會(huì)根據(jù) response 類型(參見(jiàn)下面)自動(dòng)選擇最佳的類型,或者在和 text 一起使用時(shí),默認(rèn)為 "html" 。

如果 type 是 None ,并傳遞了一個(gè) response ,選擇器類型將從 response 類型中推導(dǎo)如下:

  • "html" for HtmlResponse type
  • "xml" for XmlResponse type
  • "html" for anything else

其他情況下,如果設(shè)定了 type ,選擇器類型將被強(qiáng)制設(shè)定,而不進(jìn)行檢測(cè)。

xpath(query)

尋找可以匹配 xpath query 的節(jié)點(diǎn),并返回 SelectorList 的一個(gè)實(shí)例結(jié)果,單一化其所有元素。列表元素也實(shí)現(xiàn)了 Selector 的接口。

query 是包含 XPATH 查詢請(qǐng)求的字符串。

注解

為了方便起見(jiàn),該方法也可以通過(guò) response.xpath()調(diào)用

css(query)

應(yīng)用給定的 CSS 選擇器,返回 SelectorList 的一個(gè)實(shí)例。

query 是一個(gè)包含 CSS 選擇器的字符串。

在后臺(tái),通過(guò) cssselect 庫(kù)和運(yùn)行 .xpath() 方法,CSS 查詢會(huì)被轉(zhuǎn)換為 XPath 查詢。

注解

為了方便起見(jiàn),該方法也可以通過(guò) response.css() 調(diào)用

extract()

串行化并將匹配到的節(jié)點(diǎn)返回一個(gè) unicode 字符串列表。 結(jié)尾是編碼內(nèi)容的百分比。

re(regex)

應(yīng)用給定的 regex,并返回匹配到的 unicode 字符串列表。

regex 可以是一個(gè)已編譯的正則表達(dá)式,也可以是一個(gè)將被 re.compile(regex)編譯為正則表達(dá)式的字符串。

register_namespace(prefix, uri)

注冊(cè)給定的命名空間,其將在 Selector 中使用。 不注冊(cè)命名空間,你將無(wú)法從非標(biāo)準(zhǔn)命名空間中選擇或提取數(shù)據(jù)。參見(jiàn)下面的例子。

remove_namespaces()

移除所有的命名空間,允許使用少量的命名空間 xpaths 遍歷文檔。參加下面的例子。

__nonzero__()

如果選擇了任意的真實(shí)文檔,將返回 True ,否則返回 False 。 也就是說(shuō), Selector 的布爾值是通過(guò)它選擇的內(nèi)容確定的。

SelectorList 對(duì)象

class scrapy.selector.SelectorList

SelectorList 類是內(nèi)建 list 類的子類,提供了一些額外的方法。

xpath(query)

對(duì)列表中的每個(gè)元素調(diào)用.xpath()方法,返回結(jié)果為另一個(gè)單一化的 SelectorList。

query 和 Selector.xpath()中的參數(shù)相同。

css(query)

對(duì)列表中的各個(gè)元素調(diào)用.css() 方法,返回結(jié)果為另一個(gè)單一化的 SelectorList。

query 和 Selector.css() 中的參數(shù)相同。

extract()

對(duì)列表中的各個(gè)元素調(diào)用.extract()方法,返回結(jié)果為單一化的 unicode 字符串列表。

re()

對(duì)列表中的各個(gè)元素調(diào)用 .re() 方法,返回結(jié)果為單一化的 unicode 字符串列表。

__nonzero__()

列表非空則返回 True,否則返回 False。

在 HTML 響應(yīng)上的選擇器樣例

這里是一些 Selector 的樣例,用來(lái)說(shuō)明一些概念。在所有的例子中,我們假設(shè)已經(jīng)有一個(gè)通過(guò) HtmlResponse 對(duì)象實(shí)例化的 Selector,如下:

sel = Selector(html_response)
  1. 從 HTML 響應(yīng)主體中提取所有的<h1>元素,返回:class:Selector 對(duì)象(即 SelectorList 的一個(gè)對(duì)象)的列表:
sel.xpath("http://h1")
  1. 從 HTML 響應(yīng)主體上提取所有<h1>元素的文字,返回一個(gè) unicode 字符串的列表:
sel.xpath("http://h1").extract()         # this includes the h1 tag
sel.xpath("http://h1/text()").extract()  # this excludes the h1 tag
  1. 在所有<p>標(biāo)簽上迭代,打印它們的類屬性:
for node in sel.xpath("http://p"):
    print node.xpath("@class").extract()

在 XML 響應(yīng)上的選擇器樣例

這里是一些樣例,用來(lái)說(shuō)明一些概念。在兩個(gè)例子中,我們假設(shè)已經(jīng)有一個(gè)通過(guò) XmlResponse 對(duì)象實(shí)例化的 Selector ,如下:

sel = Selector(xml_response)
  1. 從 XML 響應(yīng)主體中選擇所有的 元素,返回 Selector 對(duì)象(即 SelectorList 對(duì)象)的列表:
sel.xpath("http://product")
  1. 從 Google Base XML feed 中提取所有的價(jià)錢,這需要注冊(cè)一個(gè)命名空間:
sel.register_namespace("g", "http://base.google.com/ns/1.0")
sel.xpath("http://g:price").extract()

移除命名空間

在處理爬蟲(chóng)項(xiàng)目時(shí),完全去掉命名空間而僅僅處理元素名字,寫(xiě)更多簡(jiǎn)單/實(shí)用的 XPath 會(huì)方便很多。你可以為此使用 Selector.remove_namespaces() 方法。

讓我們來(lái)看一個(gè)例子,以 Github 博客的 atom 訂閱來(lái)解釋這個(gè)情況。

首先,我們使用想爬取的 url 來(lái)打開(kāi) shell:

$ scrapy shell https://github.com/blog.atom

一旦進(jìn)入 shell,我們可以嘗試選擇所有的 <link> 對(duì)象,可以看到?jīng)]有結(jié)果(因?yàn)?Atom XML 命名空間混淆了這些節(jié)點(diǎn)):

>>> response.xpath("http://link")
[]

但一旦我們調(diào)用 Selector.remove_namespaces()方法,所有的節(jié)點(diǎn)都可以直接通過(guò)他們的名字來(lái)訪問(wèn):

>>> response.selector.remove_namespaces()
>>> response.xpath("http://link")
[<Selector xpath='//link' data=u'<link xmlns="http://www.w3.org/2005/Atom'>,
 <Selector xpath='//link' data=u'<link xmlns="http://www.w3.org/2005/Atom'>,
 ...

如果你對(duì)為什么命名空間移除操作并不總是被調(diào)用,而需要手動(dòng)調(diào)用有疑惑。這是因?yàn)榇嬖谌缦聝蓚€(gè)原因,按照相關(guān)順序如下:

  1. 移除命名空間需要迭代并修改文件的所有節(jié)點(diǎn),而這對(duì)于 Scrapy 爬取的所有文檔操作需要一定的性能消耗
  2. 會(huì)存在這樣的情況,確實(shí)需要使用命名空間,但有些元素的名字與命名空間沖突。盡管這些情況非常少見(jiàn)。