XPath解析
XPath(XML Path Language)是一種用於在XML和HTML文件中查詢資訊的語言,其透過路徑表示式來定位節點,屬性和文字內容,並支援複雜查詢條件,XPath 是許多 Web 抓取工具如
Scrapy,Selenium
等的核心技術之一
XPath 解析的基本步驟
-
匯入lxml.etree
from lxml import etree
-
使用etree.parse(filename, parser=None)函式返回一個樹形結構
etree.parse()
用於解析本地XML或HTML檔案,並將其轉換為一個樹形結構即ElementTree
物件,可以透過該物件訪問文件的各個節點filename
:要解析的檔案路徑parser
(可選):預設情況下,parser()會根據副檔名自動選擇合適的解析器,如.xml
檔案使用XML解析器,.html使用HTML解析器
-
使用etree.HTML(html_string, parser=None)解析網路html字串
html_string
:要解析的HTML字串parser
:(可選):預設情況下etree.HTML()
使用etree.HTMLparser()
進行解析- 返回值:etree.HTML()返回一個
ELement
物件,表示HTML文件的根元素,可以透過該物件訪問文件各個節點
-
使用.xpath(xpath_expression)在已經解析好的HTML文件中執行XPath查詢
result = html_tree.xpath(xpath_expression)
xpath_expression
:XPath表示式,用於在文件中查詢節點,XPath表示式可以是絕對路徑或相對路徑,也可以包含謂詞,函式和軸操作,主要的XPath語法下面會展開講解html_tree
:可以是ElementTree
物件(由 etree.parse() 返回)或Element
物件(由 etree.HTML() 返回)
from lxml import etree
# 使用etree.parser()解析檔案路徑
parser = etree.HTMLParser(encoding='utf-8') # 以utf8進行編碼
tree = etree.parse('../Learning02/三國演義.html', parser=parser)
print(tree)
#output-> <lxml.etree._ElementTree object at 0x000001A240107000>
# 使用etree.HTML()解析本地檔案或網路動態HTML
# 讀取檔案 解析為字串
file = open('../Learning02/三國演義.html', 'r', encoding='utf-8')
data = file.read()
root = etree.HTML(data)
print(root)
#整合
root = etree.HTML(open('../Learning02/三國演義.html', 'r', encoding='utf-8').read())
print(root)
#output-> <Element html at 0x1a23e462880>
XPath語法
XPath
語法可以用於在XML與HTML文件中查詢資訊的語言
路徑表示式
XPath使用路徑表示式來定位文件中的節點,路徑也可以分為絕對路徑與相對路徑
絕對路徑
/
:表示從根節點開始選擇,其用於定義一個絕對路徑
從根節點html開始查詢到head,再從head下找出title標籤
root = etree.HTML(open('../Learning02/三國演義.html', 'r', encoding='utf-8').read())
all_titles = root.xpath('/html/head/title')
for title in all_titles:
print(etree.tostring(title, encoding='utf-8').decode('utf-8'))
#output-> <title>《三國演義》全集線上閱讀_史書典籍_詩詞名句網</title>
相對路徑
相比與絕對路徑,相對路徑使用率更好,更好用
//
:表示從當前節點開始,選擇文件中所有符合條件的節點,並且不考慮他們的位置
root = etree.HTML(open('../Learning02/三國演義.html', 'r', encoding='utf-8').read())
all_a = root.xpath('//a')
for a in all_a:
print(a.text)
#None
#首頁
#分類
#作者
#...
當前節點
./
:表示當前節點,通常用於指明當前節點本身,避免混淆
all_a = root.xpath('//a')
print(all_a[1].xpath('./text()')) #./表示當前的a標籤
#output-> ['首頁']
選擇屬性
@
:用於選擇元素的屬性,而不是元素本身
# 使用 @ 選擇 <a> 標籤的 href 屬性
all_hrefs = root.xpath('//a[@href]')
for hrefs in all_hrefs:
print(etree.tostring(hrefs, encoding='unicode'))
XPath謂語
謂語是
xpath
中用於進一步篩選節點的表示式,通常放在方括號[]
內,其可以基於節點的位置,屬性值,文字內容或其他條件來選擇特定的節點,謂語可以巢狀使用,也可以與其他謂語組合使用
-
基本語法
//element[condition]
element
:要選擇的元素condition
:謂語中的條件,用於進一步篩選符合條件的元素
位置謂語
位置謂語用於根據節點在兄弟節點中的位置進行選擇,可以使用
position()
或直接指定位置編號
-
獲取第一個
ul
標籤中的第一個li
標籤#//ul獲取的是所有ul,[0]選擇第一個 lis = root.xpath('//ul')[0].xpath('./li[1]') for li in lis: print(etree.tostring(li, encoding='unicode')) #output-> <li><a href="/">首頁</a></li>
-
使用
last()
獲取最後第一個節點,和導數第二個節點# 倒一個 last_li = root.xpath('//ul')[0].xpath('./li[last()]') print(etree.tostring(last_li[0], encoding='unicode')) # 倒二個 last_second_li = root.xpath('//ul')[0].xpath('./li[last()-1]') print(etree.tostring(last_second_li[0], encoding='unicode')) #output-> <li><a href="/app/">安卓下載</a></li> #<li><a href="/book/">古籍</a></li>
-
使用
position()
獲取位置進行篩選# 獲取前兩個li標籤 last_li = root.xpath('//ul')[0].xpath('./li[position()<3]') for li in last_li: print(etree.tostring(li, encoding='unicode')) # 獲取偶數位標籤 lis = root.xpath('//ul')[0].xpath('./li[position() mod 2=0]') for li in lis: print(etree.tostring(li, encoding='unicode'))
-
屬性謂語
屬性謂語用於選擇具體特定屬性的節點
- 使用
@attribute
來獲取屬性名稱,結合條件進行篩選
# 選取所有具有 href 屬性的 a 元素 hrefs = root.xpath("//a[@href]") for href in hrefs: print(etree.tostring(href, encoding='unicode'))
- 查詢
class
屬性值
all_class = root.xpath('//@class') print(all_class)
- 使用
-
組合謂語
將多個條件組合在一起,使用邏輯運算子
and,or
等來建立更復雜的謂語#選取href屬性值為https://example.com且class屬性值為link的a元素 //a[@href='https://example.com' and @class='link']
#選取href屬性值為https://example.com或https://another.com的a 元素 //a[@href='https://example.com' or @href='https://another.com']
-
函式謂語
Xpath提供了許多內建函式,來應對更復雜的篩選條件
-
contains((string1, string2)
函式:string1
:要搜尋的字串string2
:要查詢的字串
# 選取class包含"book"的img標籤 images = root.xpath('//img[contains(@src,"book")]') for image in images: print(etree.tostring(image, encoding='unicode'))
-
starts-with(string1, string2)
函式:檢查一個字串是否以指定字元的字首開始,是返回
true
,否返回false
string1:
要檢查的字串string2:
作為字首的字串
# 選取所有href以https://開頭的a標籤 all_a = root.xpath('//a[starts-with(@href,"https:")]') for a in all_a: print(etree.tostring(a, encoding='unicode'))
-
-
文字內容謂語
用於選擇包含特定文字內容的節點,可以使用
text()
函式來提取節點的文字內容# 選擇使用包含"三國"文字的p標籤 paragraphs = root.xpath('//p[contains(text(),"三國")]') for p in paragraphs: print(etree.tostring(p, encoding='unicode'))
萬用字元
xpath提供了多種萬用字元,用於在路徑表示式中匹配未知的元素,屬性,或任何節點.這些萬用字元非常有用,尤其是當不確定具體節點名稱和結構的情況下
萬用字元 | 描述 |
---|---|
* | 匹配任何元素節點。 一般用於瀏覽器copy xpath會出現 |
@* | 匹配任何屬性節點。 |
node() | 匹配任何型別的節點。 |
使用*
匹配任何元素節點
*
是最常用的萬用字元之一,其可以匹配任何元素,而不需要具體標籤名.這在不確定元素名稱或希望選擇所有型別的元素時非常有用
# 選擇所有 div 下的所有子元素
divs = root.xpath("//div/*")
for div in divs:
print(etree.tostring(div, encoding='unicode'))
使用@*
匹配任何屬性節點
@*
用於匹配任何屬性節點,而不用指定具體屬性名稱,在你不確定屬性名稱或希望選擇所有屬性時非常有用
# 選擇所有 a 元素的所有屬性
all_a = root.xpath('//a/@*')
for a in all_a:
print(a)
使用node()
匹配任何型別的節點
node()
是一個更通用的萬用字元,其能匹配任何型別節點,包括元素節點,文字節點,屬性節點,註釋節點等等,其在需要選擇不僅僅是元素節點是十分有用
# 選擇所有 ul 下的所有子節點(包括文字節點)
nodes = root.xpath('//ul/node()')
print(nodes)
#output-> ['\n ', <Element li at 0x2009621d800>, '\n,...]
XPath,re正則,BeautifulSoup對比
在之前的學習中我們首先學習了re正規表示式,其次學習了更加便捷的bs4,哪為何還要學習XPath解析呢,接下來我們將它們的優點和適用場景進行對比學習
工具 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
XPath |
強大的路徑表達能力,支援層級結構和條件查詢 | 學習曲線較陡,對不規範 HTML 容錯性較差 | 結構化良好的 XML/HTML,複雜查詢 |
re |
靈活性高,適合處理純文字中的模式匹配 | 不適合解析 HTML/XML,可讀性差 | 從純文字中提取特定模式的資料 |
BeautifulSoup |
易於使用,容錯性強,適合初學者 | 效能稍低,功能有限 | 不規範的 HTML,簡單資料提取,網頁抓取 |
- 總結
- 若需要處理結構良好的XML或HTML文件,並需要進行復雜查詢,那麼XPath解析是最佳選擇
- 若需要從純文字中提取特定模式的資料時,如從日誌中提取日期,IP地址的,re正規表示式是最佳選擇
- 需要解析不規範的 HTML 或者只需要進行簡單的資料提取,BeautifulSoup 是最友好的選擇