Python3網(wǎng)絡(luò)爬蟲實(shí)戰(zhàn)-28、解析庫(kù)的使用:XPath

上一節(jié)我們實(shí)現(xiàn)了一個(gè)最基本的爬蟲,但提取頁面信息時(shí)我們使用的是正則表達(dá)式,用過之后我們會(huì)發(fā)現(xiàn)構(gòu)造一個(gè)正則表達(dá)式還是比較的繁瑣的,而且萬一有一點(diǎn)地方寫錯(cuò)了就可能會(huì)導(dǎo)致匹配失敗,所以使用正則來提取頁面信息多多少少還是有些不方便的。

創(chuàng)新互聯(lián)公司主營(yíng)樺甸網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,App定制開發(fā),樺甸h5成都小程序開發(fā)搭建,樺甸網(wǎng)站營(yíng)銷推廣歡迎樺甸等地區(qū)企業(yè)咨詢

對(duì)于網(wǎng)頁的節(jié)點(diǎn)來說,它可以定義 id、class 或其他的屬性,而且節(jié)點(diǎn)之間還具有層次關(guān)系,在網(wǎng)頁中可以通過 XPath 或 CSS 選擇器來定位一個(gè)或多個(gè)節(jié)點(diǎn)。那么在頁面解析時(shí),我們利用 XPath 或 CSS 選擇器來提取到某個(gè)節(jié)點(diǎn),然后再調(diào)用相應(yīng)的方法去獲取它的正文內(nèi)容或者屬性不就可以提取我們想要的任意信息了嗎?

在 Python 中,我們?cè)鯓觼韺?shí)現(xiàn)這個(gè)操作呢?不用擔(dān)心,這種解析庫(kù)已經(jīng)非常多了,其中比較強(qiáng)大的庫(kù)有 LXML、BeautifulSoup、PyQuery 等等,本章我們就來介紹一下這三個(gè)解析庫(kù)的使用,有了它們,我們不用再為正則發(fā)愁,而且解析效率也會(huì)大大提高,實(shí)為爬蟲必備利器。

XPath的使用

XPath,全稱 XML Path Language,即 XML 路徑語言,它是一門在XML文檔中查找信息的語言。XPath 最初設(shè)計(jì)是用來搜尋XML文檔的,但是它同樣適用于 HTML 文檔的搜索。

所以在做爬蟲時(shí),我們完全可以使用 XPath 來做相應(yīng)的信息抽取,本節(jié)我們來介紹一下 XPath 的基本用法。

1. XPath概覽

XPath 的選擇功能十分強(qiáng)大,它提供了非常簡(jiǎn)潔明了的路徑選擇表達(dá)式,另外它還提供了超過 100 個(gè)內(nèi)建函數(shù)用于字符串、數(shù)值、時(shí)間的匹配以及節(jié)點(diǎn)、序列的處理等等,幾乎所有我們想要定位的節(jié)點(diǎn)都可以用XPath來選擇。

XPath 于 1999 年 11 月 16 日 成為 W3C 標(biāo)準(zhǔn),它被設(shè)計(jì)為供 XSLT、XPointer 以及其他 XML 解析軟件使用,更多的文檔可以訪問其官方網(wǎng)站:https://www.w3.org/TR/xpath/。

2. XPath常用規(guī)則

我們現(xiàn)用表格列舉一下幾個(gè)常用規(guī)則:

表達(dá)式描述
nodename 選取此節(jié)點(diǎn)的所有子節(jié)點(diǎn)
/ 從當(dāng)前節(jié)點(diǎn)選取直接子節(jié)點(diǎn)
// 從當(dāng)前節(jié)點(diǎn)選取子孫節(jié)點(diǎn)
. 選取當(dāng)前節(jié)點(diǎn)
.. 選取當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn)
@ 選取屬性

在這里列出了XPath的常用匹配規(guī)則,例如 / 代表選取直接子節(jié)點(diǎn),// 代表選擇所有子孫節(jié)點(diǎn),. 代表選取當(dāng)前節(jié)點(diǎn),.. 代表選取當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn),@ 則是加了屬性的限定,選取匹配屬性的特定節(jié)點(diǎn)。

例如:

//title[@lang=’eng’]
Python資源分享qun 784758214 ,內(nèi)有安裝包,PDF,學(xué)習(xí)視頻,這里是Python學(xué)習(xí)者的聚集地,零基礎(chǔ),進(jìn)階,都?xì)g迎

這就是一個(gè) XPath 規(guī)則,它就代表選擇所有名稱為 title,同時(shí)屬性 lang 的值為 eng 的節(jié)點(diǎn)。

在后文我們會(huì)介紹 XPath 的詳細(xì)用法,通過 Python 的 LXML 庫(kù)利用 XPath 進(jìn)行 HTML 的解析。

3. 準(zhǔn)備工作

在使用之前我們首先要確保安裝好了 LXML 庫(kù),如沒有安裝可以參考第一章的安裝過程。

4. 實(shí)例引入

我們現(xiàn)用一個(gè)實(shí)例來感受一下使用 XPath 來對(duì)網(wǎng)頁進(jìn)行解析的過程,代碼如下:

from lxml import etree
text = '''
<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>
     </ul>
 </div>
'''
html = etree.HTML(text)
result = etree.tostring(html)
print(result.decode('utf-8'))

在這里我們首先導(dǎo)入了 LXML 庫(kù)的 etree 模塊,然后聲明了一段 HTML 文本,調(diào)用 HTML 類進(jìn)行初始化,這樣我們就成功構(gòu)造了一個(gè) XPath 解析對(duì)象,在這里注意到 HTML 文本中的最后一個(gè) li 節(jié)點(diǎn)是沒有閉合的,但是 etree 模塊可以對(duì) HTML 文本進(jìn)行自動(dòng)修正。

在這里我們調(diào)用 tostring() 方法即可輸出修正后的 HTML 代碼,但是結(jié)果是 bytes 類型,在這里我們利用 decode() 方法轉(zhuǎn)成 str 類型,結(jié)果如下:

<html><body><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>
</body></html>

我們可以看到經(jīng)過處理之后 li 節(jié)點(diǎn)標(biāo)簽被補(bǔ)全,并且還自動(dòng)添加了 body、html 節(jié)點(diǎn)。

另外我們也可以直接讀取文本文件進(jìn)行解析,示例如下:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = etree.tostring(html)
print(result.decode('utf-8'))

其中 test.html 的內(nèi)容就是上面例子中的 HTML 代碼,內(nèi)容如下:

<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>
     </ul>
 </div>
Python資源分享qun 784758214 ,內(nèi)有安裝包,PDF,學(xué)習(xí)視頻,這里是Python學(xué)習(xí)者的聚集地,零基礎(chǔ),進(jìn)階,都?xì)g迎

這次的輸出結(jié)果略有不同,多了一個(gè) DOCTYPE 的聲明,不過對(duì)解析無任何影響,結(jié)果如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><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></body></html>

5. 所有節(jié)點(diǎn)

我們一般會(huì)用 // 開頭的 XPath 規(guī)則來選取所有符合要求的節(jié)點(diǎn),以上文的 HTML 文本為例,如果我們要選取所有節(jié)點(diǎn),可以這樣實(shí)現(xiàn):

from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//*')
print(result)

運(yùn)行結(jié)果:

[<Element html at?0x10510d9c8>, <Element body at?0x10510da08>, <Element div at?0x10510da48>, <Element ul at?0x10510da88>, <Element li at?0x10510dac8>, <Element a at?0x10510db48>, <Element li at?0x10510db88>, <Element a at?0x10510dbc8>, <Element li at?0x10510dc08>, <Element a at?0x10510db08>, <Element li at?0x10510dc48>, <Element a at?0x10510dc88>, <Element li at?0x10510dcc8>, <Element a at?0x10510dd08>]

我們?cè)谶@里使用 * 代表匹配所有節(jié)點(diǎn),也就是整個(gè) HTML 文本中的所有節(jié)點(diǎn)都會(huì)被獲取,可以看到返回形式是一個(gè)列表,每個(gè)元素是 Element 類型,其后跟了節(jié)點(diǎn)的名稱,如 html、body、div、ul、li、a 等等,所有的節(jié)點(diǎn)都包含在列表中了。

當(dāng)然此處匹配也可以指定節(jié)點(diǎn)名稱,如果我們想獲取所有 li 節(jié)點(diǎn),示例如下:

from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li')
print(result)
print(result[0])

在這里我們要選取所有 li 節(jié)點(diǎn)可以使用 //,然后直接加上節(jié)點(diǎn)的名稱即可,調(diào)用時(shí)直接調(diào)用 xpath() 方法即可提取。

運(yùn)行結(jié)果:

[<Element li at 0x105849208>, <Element li at 0x105849248>, <Element li at 0x105849288>, <Element li at 0x1058492c8>, <Element li at 0x105849308>]
<Element li at 0x105849208>

在這里我們可以看到提取結(jié)果是一個(gè)列表形式,其每一個(gè)元素都是一個(gè) Element 對(duì)象,如果要取出其中一個(gè)對(duì)象可以直接用中括號(hào)加索引即可取出,如 [0]。

6. 子節(jié)點(diǎn)

我們通過 / 或 // 即可查找元素的子節(jié)點(diǎn)或子孫節(jié)點(diǎn),加入我們現(xiàn)在想選擇 li 節(jié)點(diǎn)所有直接 a 子節(jié)點(diǎn),可以這樣來實(shí)現(xiàn):

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a')
print(result)

在這里我們通過追加一個(gè) /a 即選擇了所有 li 節(jié)點(diǎn)的所有直接 a 子節(jié)點(diǎn),因?yàn)?//li 是選中所有l(wèi)i節(jié)點(diǎn), /a 是選中l(wèi)i節(jié)點(diǎn)的所有直接子節(jié)點(diǎn) a,二者組合在一起即獲取了所有l(wèi)i節(jié)點(diǎn)的所有直接 a 子節(jié)點(diǎn)。

運(yùn)行結(jié)果:

[<Element a at?0x106ee8688>, <Element a at?0x106ee86c8>, <Element a at?0x106ee8708>, <Element a at?0x106ee8748>, <Element a at?0x106ee8788>]

但是此處的 / 是選取直接子節(jié)點(diǎn),如果我們要獲取所有子孫節(jié)點(diǎn)就該使用 // 了,例如我們要獲取 ul 節(jié)點(diǎn)下的所有子孫 a 節(jié)點(diǎn),可以這樣來實(shí)現(xiàn):

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//ul//a')
print(result)

運(yùn)行結(jié)果是相同的。

但是這里如果我們用 //ul/a 就無法獲取任何結(jié)果了,因?yàn)?/ 是獲取直接子節(jié)點(diǎn),而在 ul 節(jié)點(diǎn)下沒有直接的 a 子節(jié)點(diǎn),只有 li 節(jié)點(diǎn),所以無法獲取任何匹配結(jié)果,代碼如下:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//ul/a')
print(result)

運(yùn)行結(jié)果:

[]

因此在這里我們要注意 / 和 // 的區(qū)別,/ 是獲取直接子節(jié)點(diǎn),// 是獲取子孫節(jié)點(diǎn)。

7. 父節(jié)點(diǎn)

我們知道通過連續(xù)的 / 或 // 可以查找子節(jié)點(diǎn)或子孫節(jié)點(diǎn),那假如我們知道了子節(jié)點(diǎn)怎樣來查找父節(jié)點(diǎn)呢?在這里我們可以用 .. 來獲取父節(jié)點(diǎn)。

比如我們現(xiàn)在首先選中 href 是 link4.html 的 a 節(jié)點(diǎn),然后再獲取其父節(jié)點(diǎn),然后再獲取其 class 屬性,代碼如下:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/../@class')
print(result)

運(yùn)行結(jié)果:

['item-1']

檢查一下結(jié)果,正是我們獲取的目標(biāo) li 節(jié)點(diǎn)的 class,獲取父節(jié)點(diǎn)成功。

同時(shí)我們也可以通過 parent:: 來獲取父節(jié)點(diǎn),代碼如下:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//a[@href="link4.html"]/parent::*/@class')
print(result)

8. 屬性匹配

在選取的時(shí)候我們還可以用 @ 符號(hào)進(jìn)行屬性過濾,比如在這里如果我們要選取 class 為 item-1 的 li 節(jié)點(diǎn),可以這樣實(shí)現(xiàn):

from lxml import etree
html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]')
print(result)
Python資源分享qun 784758214 ,內(nèi)有安裝包,PDF,學(xué)習(xí)視頻,這里是Python學(xué)習(xí)者的聚集地,零基礎(chǔ),進(jìn)階,都?xì)g迎

在這里我們通過加入 [@class="item-0"] 就限制了節(jié)點(diǎn)的 class 屬性為 item-0,而 HTML 文本中符合條件的 li 節(jié)點(diǎn)有兩個(gè),所以返回結(jié)果應(yīng)該返回兩個(gè)匹配到的元素,結(jié)果如下:

[<Element li at?0x10a399288>, <Element li at?0x10a3992c8>]

可見匹配結(jié)果結(jié)果正是兩個(gè),至于是不是那正確的兩個(gè),我們?cè)诤竺骝?yàn)證一下。

9. 文本獲取

我們用 XPath 中的 text() 方法可以獲取節(jié)點(diǎn)中的文本,我們接下來嘗試獲取一下上文 li 節(jié)點(diǎn)中的文本,代碼如下:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/text()')
print(result)

運(yùn)行結(jié)果如下:

['\n ']

很奇怪的是我們并沒有獲取到任何文本,而是只獲取到了一個(gè)換行符,這是為什么呢?因?yàn)?XPath 中 text() 前面是 /,而此 / 的含義是選取直接子節(jié)點(diǎn),而此處很明顯 li 的直接子節(jié)點(diǎn)都是 a 節(jié)點(diǎn),文本都是在 a 節(jié)點(diǎn)內(nèi)部的,所以這里匹配到的結(jié)果就是被修正的 li 節(jié)點(diǎn)內(nèi)部的換行符,因?yàn)樽詣?dòng)修正的li節(jié)點(diǎn)的尾標(biāo)簽換行了。

即選中的是這兩個(gè)節(jié)點(diǎn):

<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</li>

其中一個(gè)節(jié)點(diǎn)因?yàn)樽詣?dòng)修正,li 節(jié)點(diǎn)的尾標(biāo)簽添加的時(shí)候換行了,所以提取文本得到的唯一結(jié)果就是 li 節(jié)點(diǎn)的尾標(biāo)簽和 a 節(jié)點(diǎn)的尾標(biāo)簽之間的換行符。

因此,如果我們想獲取 li 節(jié)點(diǎn)內(nèi)部的文本就有兩種方式,一種是選取到 a 節(jié)點(diǎn)再獲取文本,另一種就是使用 //,我們來看下二者的區(qū)別是什么。

首先我們選取到 a 節(jié)點(diǎn)再獲取文本,代碼如下:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]/a/text()')
print(result)

運(yùn)行結(jié)果:

['first item', 'fifth item']

可以看到這里返回值是兩個(gè),內(nèi)容都是屬性為 item-0 的 li 節(jié)點(diǎn)的文本,這也印證了我們上文中屬性匹配的結(jié)果是正確的。

在這里我們是逐層選取的,先選取了 li 節(jié)點(diǎn),又利用 / 選取了其直接子節(jié)點(diǎn) a,然后再選取其文本,得到的結(jié)果恰好是符合我們預(yù)期的兩個(gè)結(jié)果。

我們?cè)賮砜聪掠昧硪环N方式 // 選取的結(jié)果,代碼如下:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li[@class="item-0"]//text()')
print(result)

運(yùn)行結(jié)果:

['first item', 'fifth item', '\n ']

不出所料,這里返回結(jié)果是三個(gè),可想而知這里是選取所有子孫節(jié)點(diǎn)的文本,其中前兩個(gè)就是 li 的子節(jié)點(diǎn) a 節(jié)點(diǎn)內(nèi)部的文本,另外一個(gè)就是最后一個(gè) li 節(jié)點(diǎn)內(nèi)部的文本,即換行符。

所以說,如果我們要想獲取子孫節(jié)點(diǎn)內(nèi)部的所有文本,可以直接用 // 加 text() 的方式獲取,這樣可以保證獲取到最全面的文本信息,但是可能會(huì)夾雜一些換行符等特殊字符。如果我們想獲取某些特定子孫節(jié)點(diǎn)下的所有文本,可以先選取到特定的子孫節(jié)點(diǎn),然后再調(diào)用 text() 方法獲取其內(nèi)部文本,這樣可以保證獲取的結(jié)果是整潔的。

10. 屬性獲取

我們知道了用 text() 可以獲取節(jié)點(diǎn)內(nèi)部文本,那么節(jié)點(diǎn)屬性該怎樣獲取呢?其實(shí)還是用 @ 符號(hào)就可以,例如我們想獲取所有 li 節(jié)點(diǎn)下所有 a 節(jié)點(diǎn)的 href 屬性,代碼如下:

from lxml import etree

html = etree.parse('./test.html', etree.HTMLParser())
result = html.xpath('//li/a/@href')
print(result)

在這里我們通過 @href 即可獲取節(jié)點(diǎn)的 href 屬性,注意此處和屬性匹配的方法不同,屬性匹配是中括號(hào)加屬性名和值來限定某個(gè)屬性,如 [@href="link1.html"],而此處的 @href 指的是獲取節(jié)點(diǎn)的某個(gè)屬性,二者需要做好區(qū)分。

運(yùn)行結(jié)果:

['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']

可以看到我們成功獲取了所有 li 節(jié)點(diǎn)下的 a 節(jié)點(diǎn)的 href 屬性,以列表形式返回。

11. 屬性多值匹配

有時(shí)候某些節(jié)點(diǎn)的某個(gè)屬性可能有多個(gè)值,例如下面例子:

from lxml import etree
text = '''
<li class="li li-first"><a href="link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[@class="li"]/a/text()')
print(result)

在這里 HTML 文本中的 li 節(jié)點(diǎn)的 class 屬性有兩個(gè)值 li 和 li-first,但是此時(shí)如果我們還想用之前的屬性匹配獲取就無法匹配了,代碼運(yùn)行結(jié)果:

[]

這時(shí)如果屬性有多個(gè)值就需要用 contains() 函數(shù)了,代碼可以改寫如下:

from lxml import etree
text = '''
<li class="li li-first"><a href="link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li")]/a/text()')
print(result)

這樣我們通過 contains() 方法,第一個(gè)參數(shù)傳入屬性名稱,第二個(gè)參數(shù)傳入屬性值,這樣只要此屬性包含所傳入的屬性值就可以完成匹配了。

運(yùn)行結(jié)果:

['first item']

此種選擇方式在某個(gè)節(jié)點(diǎn)的某個(gè)屬性有多個(gè)值的時(shí)候經(jīng)常會(huì)用到,如某個(gè)節(jié)點(diǎn)的 class 屬性通常有多個(gè)。

12. 多屬性匹配

另外我們可能還遇到一種情況,我們可能需要根據(jù)多個(gè)屬性才能確定一個(gè)節(jié)點(diǎn),這是就需要同時(shí)匹配多個(gè)屬性才可以,那么這里可以使用運(yùn)算符 and 來連接,示例如下:

from lxml import etree
text = '''
<li class="li li-first" name="item"><a href="link.html">first item</a></li>
'''
html = etree.HTML(text)
result = html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()')
print(result)

在這里 HTML 文本的 li 節(jié)點(diǎn)又增加了一個(gè)屬性 name,這時(shí)候我們需要同時(shí)根據(jù) class 和 name 屬性來選擇,就可以 and 運(yùn)算符連接兩個(gè)條件,兩個(gè)條件都被中括號(hào)包圍,運(yùn)行結(jié)果如下:

['first item']

這里的 and 其實(shí)是 XPath 中的運(yùn)算符,另外還有很多運(yùn)算符,如 or、mod 等等,在此總結(jié)如下:

運(yùn)算符描述實(shí)例返回值
or price=9.80 or price=9.70 如果 price 是 9.80,則返回 true。如果 price 是 9.50,則返回 false。
and price>9.00 and price<9.90 如果 price 是 9.80,則返回 true。如果 price 是 8.50,則返回 false。
mod 計(jì)算除法的余數(shù) 5 mod 2 1
\ 計(jì)算兩個(gè)節(jié)點(diǎn)集 //book //cd 返回所有擁有 book 和 cd 元素的節(jié)點(diǎn)集
+ 加法 6 + 4 10
- 減法 6 - 4 2
* 乘法 6 * 4 24
div 除法 8 div 4 2
= 等于 price=9.80 如果 price 是 9.80,則返回 true。如果 price 是 9.90,則返回 false。
!= 不等于 price!=9.80 如果 price 是 9.90,則返回 true。如果 price 是 9.80,則返回 false。
< 小于 price<9.80 如果 price 是 9.00,則返回 true。如果 price 是 9.90,則返回 false。
<= 小于或等于 price<=9.80 如果 price 是 9.00,則返回 true。如果 price 是 9.90,則返回 false。
> 大于 price>9.80 如果 price 是 9.90,則返回 true。如果 price 是 9.80,則返回 false。
>= 大于或等于 price>=9.80 如果 price 是 9.90,則返回 true。如果 price 是 9.70,則返回 false。

此表參考來源:http://www.w3school.com.cn/xp...。

13. 按序選擇

有時(shí)候我們?cè)谶x擇的時(shí)候可能某些屬性同時(shí)匹配了多個(gè)節(jié)點(diǎn),但是我們只想要其中的某個(gè)節(jié)點(diǎn),如第二個(gè)節(jié)點(diǎn),或者最后一個(gè)節(jié)點(diǎn),這時(shí)該怎么辦呢?

這時(shí)可以利用中括號(hào)傳入索引的方法獲取特定次序的節(jié)點(diǎn),示例如下:

from lxml import etree

text = '''
<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>
     </ul>
 </div>
'''
html = etree.HTML(text)
result = html.xpath('//li[1]/a/text()')
print(result)
result = html.xpath('//li[last()]/a/text()')
print(result)
result = html.xpath('//li[position()<3]/a/text()')
print(result)
result = html.xpath('//li[last()-2]/a/text()')
print(result)

第一次選擇我們選取了第一個(gè) li 節(jié)點(diǎn),中括號(hào)中傳入數(shù)字1即可,注意這里和代碼中不同,序號(hào)是以 1 開頭的,不是 0 開頭的。

第二次選擇我們選取了最后一個(gè) li 節(jié)點(diǎn),中括號(hào)中傳入 last() 即可,返回的便是最后一個(gè) li 節(jié)點(diǎn)。

第三次選擇我們選取了位置小于 3 的 li 節(jié)點(diǎn),也就是位置序號(hào)為 1 和 2 的節(jié)點(diǎn),得到的結(jié)果就是前 2 個(gè) li 節(jié)點(diǎn)。

第四次選擇我們選取了倒數(shù)第三個(gè) li 節(jié)點(diǎn),中括號(hào)中傳入 last()-2即可,因?yàn)?last() 是最后一個(gè),所以 last()-2 就是倒數(shù)第三個(gè)。

運(yùn)行結(jié)果如下:

['first item']
['fifth item']
['first item', 'second item']
['third item']

在這里我們使用了 last()、position() 等函數(shù),XPath 中提供了 100 多個(gè)函數(shù),包括存取、數(shù)值、字符串、邏輯、節(jié)點(diǎn)、序列等處理功能,具體所有的函數(shù)作用可以參考:http://www.w3school.com.cn/xp...。

13. 節(jié)點(diǎn)軸選擇

XPath 提供了很多節(jié)點(diǎn)軸選擇方法,英文叫做 XPath Axes,包括獲取子元素、兄弟元素、父元素、祖先元素等等,在一定情況下使用它可以方便地完成節(jié)點(diǎn)的選擇,我們用一個(gè)實(shí)例來感受一下:

from lxml import etree

text = '''
<div>
    <ul>
         <li class="item-0"><a href="link1.html"><span>first item</span></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>
     </ul>
 </div>
'''
html = etree.HTML(text)
result = html.xpath('//li[1]/ancestor::*')
print(result)
result = html.xpath('//li[1]/ancestor::div')
print(result)
result = html.xpath('//li[1]/attribute::*')
print(result)
result = html.xpath('//li[1]/child::a[@href="link1.html"]')
print(result)
result = html.xpath('//li[1]/descendant::span')
print(result)
result = html.xpath('//li[1]/following::*[2]')
print(result)
result = html.xpath('//li[1]/following-sibling::*')
print(result)

運(yùn)行結(jié)果:

[<Element html at 0x107941808>, <Element body at 0x1079418c8>, <Element div at 0x107941908>, <Element ul at 0x107941948>]
[<Element div at 0x107941908>]
['item-0']
[<Element a at 0x1079418c8>]
[<Element span at 0x107941948>]
[<Element a at 0x1079418c8>]
[<Element li at 0x107941948>, <Element li at 0x107941988>, <Element li at 0x1079419c8>, <Element li at 0x107941a08>]
Python資源分享qun 784758214 ,內(nèi)有安裝包,PDF,學(xué)習(xí)視頻,這里是Python學(xué)習(xí)者的聚集地,零基礎(chǔ),進(jìn)階,都?xì)g迎

第一次選擇我們調(diào)用了 ancestor 軸,可以獲取所有祖先節(jié)點(diǎn),其后需要跟兩個(gè)冒號(hào),然后是節(jié)點(diǎn)的選擇器,這里我們直接使用了 *,表示匹配所有節(jié)點(diǎn),因此返回結(jié)果是第一個(gè) li 節(jié)點(diǎn)的所有祖先節(jié)點(diǎn),包括 html,body,div,ul。

第二次選擇我們又加了限定條件,這次在冒號(hào)后面加了 div,這樣得到的結(jié)果就只有 div 這個(gè)祖先節(jié)點(diǎn)了。

第三次選擇我們調(diào)用了 attribute 軸,可以獲取所有屬性值,其后跟的選擇器還是 *,這代表獲取節(jié)點(diǎn)的所有屬性,返回值就是 li 節(jié)點(diǎn)的所有屬性值。

第四次選擇我們調(diào)用了 child 軸,可以獲取所有直接子節(jié)點(diǎn),在這里我們又加了限定條件選取 href 屬性為 link1.html 的 a 節(jié)點(diǎn)。

第五次選擇我們調(diào)用了 descendant 軸,可以獲取所有子孫節(jié)點(diǎn),這里我們又加了限定條件獲取 span 節(jié)點(diǎn),所以返回的就是只包含 span 節(jié)點(diǎn)而沒有 a 節(jié)點(diǎn)。

第六次選擇我們調(diào)用了 following 軸,可以獲取當(dāng)前節(jié)點(diǎn)之后的所有節(jié)點(diǎn),這里我們雖然使用的是 * 匹配,但又加了索引選擇,所以只獲取了第二個(gè)后續(xù)節(jié)點(diǎn)。

第七次選擇我們調(diào)用了 following-sibling 軸,可以獲取當(dāng)前節(jié)點(diǎn)之后的所有同級(jí)節(jié)點(diǎn),這里我們使用的是 * 匹配,所以獲取了所有后續(xù)同級(jí)節(jié)點(diǎn)。

以上是XPath軸的簡(jiǎn)單用法

14. 結(jié)語

到現(xiàn)在為止我們基本上把可能用到的 XPath 選擇器介紹完了, XPath 功能非常強(qiáng)大,內(nèi)置函數(shù)非常多,熟練使用之后可以大大提升 HTML 信息的提取效率。

分享名稱:Python3網(wǎng)絡(luò)爬蟲實(shí)戰(zhàn)-28、解析庫(kù)的使用:XPath
當(dāng)前網(wǎng)址:http://bm7419.com/article14/jcidge.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導(dǎo)航、品牌網(wǎng)站設(shè)計(jì)、網(wǎng)站維護(hù)搜索引擎優(yōu)化、小程序開發(fā)、App設(shè)計(jì)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站建設(shè)