1. 程式人生 > >【Python3 爬蟲學習筆記】解析庫的使用 1 —— 使用XPath 1

【Python3 爬蟲學習筆記】解析庫的使用 1 —— 使用XPath 1

XPath,全稱XML Path Language,即XML路徑語言,它是一門在XML文件中查詢資訊的於洋。它最初是用來搜尋XML文件的,但它同樣適用於HTML文件的搜尋。

1. XPath概覽

XPath的選擇功能十分強大,它提供了非常簡潔明瞭的路徑選擇表示式。另外,它還提供了超過100個內建函式,用於字串、數值、時間的匹配以及節點、序列的處理等。
XPath於1999年11月16日成為W3C標準,它被設計為供XSLT、XPointer以及其他XML解析軟體使用。

2. XPath常用規則

下表列舉了XPath的幾個常用規則。

表示式 描述
nodename 選取此節點的所有子節點
/ 從當前節點選取直接子節點
// 從當前節點選取子孫節點
. 選取當前節點
選取當前節點的父節點
@ 選取屬性

XPath的常用匹配規則,示例如下:

//title[@lang='eng']

3.例項引入

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'))

這裡首先匯入lxml庫的etree模組,然後聲明瞭一段HTML文字,呼叫HTML類進行初始化,這樣就成功構造了一個XPath解析物件。這裡需要注意的是,HTML文字中的最後一個li節點是沒有閉合的,但是etree模組可以自動修正HTML文字。
這裡呼叫tostring()方法即可輸出修正後的HTML程式碼,但是結果是bytes型別。這裡利用decode()方法將其轉成str型別,結果如下:

<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>

可以看到,經過處理之後,li節點標籤被補全,並且還自動添加了body、html節點。
另外,也可以直接讀取文字檔案進行解析,示例如下:

from lxml import etree

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

其中test.html的內容就是上面例子中的HTML程式碼。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><div>&#13;
<ul>&#13;
<li class="item-0"><a href="link1.html">first item</a></li>&#13;
<li class="item-1"><a href="link2.html">second item</a></li>&#13;
<li class="item-inactive"><a href="link3.html">third item</a></li>&#13;
<li class="item-1"><a href="link4.html">fourth item</a></li>&#13;
<li class="item-0"><a href="link5.html">fifth item</a>&#13;
</li></ul>&#13;
</div></body></html>

可以看出,這次的輸出結果略有不同,多了一個DOCTYPE的宣告,不過解析無任何影響。

4. 所有節點

我們一般會用//開頭的XPath規則來選取所有符合要求的節點。這裡以前面的HTML文字為例,如果要選取所有節點,可以這樣實現:

from lxml import etree

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

執行結果如下:

[<Element html at 0x1f2b3c1d248>,
 <Element body at 0x1f2b3c00ac8>,
  <Element div at 0x1f2b3c00a88>,
   <Element ul at 0x1f2b3aee588>,
   <Element li at 0x1f2b3c1d288>, 
   <Element a at 0x1f2b3c1d308>, 
   <Element li at 0x1f2b3c1d348>, 
   <Element a at 0x1f2b3c1d388>, 
   <Element li at 0x1f2b3c1d3c8>, 
   <Element a at 0x1f2b3c1d2c8>, 
   <Element li at 0x1f2b3c1d408>, 
   <Element a at 0x1f2b3c1d448>, 
   <Element li at 0x1f2b3c1d488>, 
   <Element a at 0x1f2b3c1d4c8>]

這裡使用*代表匹配所有節點,也就是整個HTML文字中的所有節點都會被獲取。可以看到,返回形式是一個列表,每個元素是Element型別,其後跟了節點的名稱,如html、body、div、ul、li、a等,所有節點都包含在列表中了。
當然,此處匹配也可以指定節點名稱。如果想獲取所有li節點,示例如下:

from lxml import etree

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

這裡要選取所有li節點,可以使用//,然後直接上節點名稱即可,呼叫時直接使用xpath()方法即可。
執行結果:

[<Element li at 0x1f2b3c1d208>, 
<Element li at 0x1f2b3b992c8>, 
<Element li at 0x1f2b3acfa48>, 
<Element li at 0x1f2b3aea708>,
 <Element li at 0x1f2b3ac8148>]
<Element li at 0x1f2b3c1d208>

可以看到提取結果是一個列表形式,其中每個元素都是一個Element物件。如果要取出其中一個物件,可以直接用中括號加索引,如[0]。

5. 子節點

我們可以通過/或//即可查詢元素的子節點或子孫節點。加入現在想選擇li節點的所有直接a子節點,可以這樣實現:

from lxml import etree

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

這裡通過追加/a即選擇了所有li節點的所有直接a子節點。因為//li用於選中所有li節點,/a用於選中li節點的所有直接子節點a,二者組合在一起即獲取所有li節點的所有直接a子節點。
執行結果如下:

[<Element a at 0x1f2b3c31d88>, <Element a at 0x1f2b3c31dc8>, <Element a at 0x1f2b3ae4d08>, <Element a at 0x1f2b3aeea08>, <Element a at 0x1f2b3aeec48>]

此處的/用於選取直接子節點,如果要獲取所有子孫節點,就可以使用//。例如,要獲取ul節點下的所有子孫a節點,可以這樣實現:

from lxml import etree

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

執行結果是相同的。
但是如果這裡用//ul/a,就無法獲取任何結果了。因為/用於獲取直接子節點,而在ul節點下沒有直接的a子節點,只有li節點,所以無法獲取任何匹配結果。

6. 父節點

如果要選中href屬性為link4.html的a節點,然後再獲取其父節點,然後再獲取其class屬性,相關程式碼如下:

from lxml import etree

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

執行結果:

['item-1']

同時,我們也可以通過parent::來獲取父節點,程式碼如下:

from lxml import etree

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

7. 屬性匹配

在選取的時候,我們還可以通過使用@符合進行屬性過濾,如這裡要選取class為item-0的li節點,可以這樣實現:

from lxml import etree

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

這裡我們通過加入[@class=“item-0”],限制了節點的class屬性為item-0,而HTML文字中符合條件的li節點有兩個,所以結果應該返回兩個匹配的元素。結果如下:

[<Element li at 0x1f2b3ae3348>, <Element li at 0x1f2b3ae30c8>]