XPath,全稱XML Path Language,即XML路徑語言,它是一門在XML文件中查詢資訊的語言。它最初是用來搜尋XML文件的,但是它同樣適用於HTML文件的搜尋。
所以在做爬蟲時,我們完全可以使用XPath來做相應的資訊抽取。本節中,我們就來介紹XPath的基本用法。
XPath於1999年11月16日成爲W3C標準,它被設計爲供XSLT、XPointer以及其他XML解析軟體使用,更多的文件可以存取其官方網站:https://www.w3.org/TR/xpath/。
表4-1 XPath常用規則
表達式
描述
nodename
選取此節點的所有子節點
/
從當前節點選取直接子節點
//
從當前節點選取子孫節點
.
選取當前節點
…
選取當前節點的父節點
@
選取屬性
這裏列出了XPath的常用匹配規則,範例如下:
//title[@lang=‘eng’]
這就是一個XPath規則,它代表選擇所有名稱爲title,同時屬性lang的值爲eng的節點。
後面會通過Python的lxml庫,利用XPath進行HTML的解析。
準備工作
使用之前,首先要確保安裝好lxml庫,若沒有安裝,可以參考第1章的安裝過程。
範例引入
現在通過範例來感受一下使用XPath來對網頁進行解析的過程,相關程式碼如下:
from lxml import etree
text = ‘’’
這裏首先匯入lxml庫的etree模組,然後宣告瞭一段HTML文字,呼叫HTML類進行初始化,這樣就成功構造了一個XPath解析物件。這裏需要注意的是,HTML文字中的最後一個li節點是沒有閉合的,但是etree模組可以自動修正HTML文字。
這裏我們呼叫tostring()方法即可輸出修正後的HTML程式碼,但是結果是bytes型別。這裏利用decode()方法將其轉成str型別,結果如下:
可以看到,經過處理之後,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的宣告,不過對解析無任何影響,結果如下:
from lxml import etree
html = etree.parse(’./test.html’, etree.HTMLParser())
result = html.xpath(’//*’)
print(result)
執行結果如下:
[<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>]
這裏使用*代表匹配所有節點,也就是整個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 0x105849208>, <Element li at 0x105849248>, <Element li at 0x105849288>, <Element li at 0x1058492c8>, <Element li at 0x105849308>]
<Element li at 0x105849208>
這裏可以看到提取結果是一個列表形式,其中每個元素都是一個 Element物件。如果要取出其中一個物件,可以直接用中括號加索引,如[0]。
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 0x106ee8688>, <Element a at 0x106ee86c8>, <Element a at 0x106ee8708>, <Element a at 0x106ee8748>, <Element a at 0x106ee8788>]
此處的/用於選取直接子節點,如果要獲取所有子孫節點,就可以使用//。例如,要獲取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節點,所以無法獲取任何匹配結果,程式碼如下:
from lxml import etree
html = etree.parse(’./test.html’, etree.HTMLParser())
result = html.xpath(’//ul/a’)
print(result)
因此,這裏我們要注意/和//的區別,其中/用於獲取直接子節點,//用於獲取子孫節點。
比如,現在首先選中href屬性爲https://blog.csdn.net/weixin_48794920/article/details/link4.html的a節點,然後再獲取其父節點,然後再獲取其class屬性,相關程式碼如下:
from lxml import etree
html = etree.parse(’./test.html’, etree.HTMLParser())
result = html.xpath(’//a[@href=「https://blog.csdn.net/weixin_48794920/article/details/link4.html」]/…/@class’)
print(result)
執行結果如下:
1
[‘item-1’]
檢查一下結果發現,這正是我們獲取的目標li節點的class。
同時,我們也可以通過parent::來獲取父節點,程式碼如下:
from lxml import etree
html = etree.parse(’./test.html’, etree.HTMLParser())
result = html.xpath(’//a[@href=「https://blog.csdn.net/weixin_48794920/article/details/link4.html」]/parent: