文主要圍繞以xpath和lxml庫進行展開:
一、xpath 概念、xpath節點、xpath語法、xpath軸、xpath運算符
二、lxml的安裝、lxml的使用、lxml案例
一、xpath
1.xpath概念
XPath 是一門在 XML 文檔中查找信息的語言。XPath 使用路徑表達式在 XML 文檔中進行導航 。XPath 包含一個標準函數庫 。XPath 是 XSLT 中的主要元素 。XPath 是一個 W3C 標準 。
2.xpath節點
xpath有七種類型的節點:元素、屬性、文本、命名空間、處理指令、注釋以及文檔(根)節點。
節點關系:父、子、兄弟、先輩、后輩。
3.xpath語法
xpath語法在W3c網站上有詳細的介紹,這里截取部分知識,供大家學習。
XPath 使用路徑表達式在 XML 文檔中選取節點。節點是通過沿著路徑或者 step 來選取的。下面列出了最有用的路徑表達式:
表達式 | 描述 |
nodename | 選取此節點的所有子節點。 |
/ | 從根節點選取。 |
// | 從匹配選擇的當前節點選擇文檔中的節點,而不考慮它們的位置。 |
. | 選取當前節點。 |
.. | 選取當前節點的父節點。 |
@ | 選取屬性。 |
在下面的表格中,我們已列出了一些路徑表達式以及表達式的結果:
路徑表達式 | 結果 |
bookstore | 選取 bookstore 元素的所有子節點。 |
/bookstore | 選取根元素 bookstore。注釋:假如路徑起始于正斜杠( / ),則此路徑始終代表到某元素的絕對路徑! |
bookstore/book | 選取屬于 bookstore 的子元素的所有 book 元素。 |
//book | 選取所有 book 子元素,而不管它們在文檔中的位置。 |
bookstore//book | 選擇屬于 bookstore 元素的后代的所有 book 元素,而不管它們位于 bookstore 之下的什么位置。 |
//@lang | 選取名為 lang 的所有屬性。 |
謂語用來查找某個特定的節點或者包含某個指定的值的節點。
謂語被嵌在方括號中。
在下面的表格中,我們列出了帶有謂語的一些路徑表達式,以及表達式的結果:
路徑表達式 | 結果 |
/bookstore/book[1] | 選取屬于 bookstore 子元素的第一個 book 元素。 |
/bookstore/book[last()] | 選取屬于 bookstore 子元素的最后一個 book 元素。 |
/bookstore/book[last()-1] | 選取屬于 bookstore 子元素的倒數第二個 book 元素。 |
/bookstore/book[position()<3] | 選取最前面的兩個屬于 bookstore 元素的子元素的 book 元素。 |
//title[@lang] | 選取所有擁有名為 lang 的屬性的 title 元素。 |
//title[@lang='eng'] | 選取所有 title 元素,且這些元素擁有值為 eng 的 lang 屬性。 |
/bookstore/book[price>35.00] | 選取 bookstore 元素的所有 book 元素,且其中的 price 元素的值須大于 35.00。 |
/bookstore/book[price>35.00]/title | 選取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值須大于 35.00。 |
XPath 通配符可用來選取未知的 XML 元素。
通配符 | 描述 |
* | 匹配任何元素節點。 |
@* | 匹配任何屬性節點。 |
node() | 匹配任何類型的節點。 |
在下面的表格中,我們列出了一些路徑表達式,以及這些表達式的結果:
路徑表達式 | 結果 |
/bookstore/* | 選取 bookstore 元素的所有子元素。 |
//* | 選取文檔中的所有元素。 |
//title[@*] | 選取所有帶有屬性的 title 元素。 |
通過在路徑表達式中使用"|"運算符,您可以選取若干個路徑。
在下面的表格中,我們列出了一些路徑表達式,以及這些表達式的結果:
路徑表達式 | 結果 |
//book/title | //book/price | 選取 book 元素的所有 title 和 price 元素。 |
//title | //price | 選取文檔中的所有 title 和 price 元素。 |
/bookstore/book/title | //price | 選取屬于 bookstore 元素的 book 元素的所有 title 元素,以及文檔中所有的 price 元素。 |
4.xpath 軸
軸可定義相對于當前節點的節點集。
軸名稱 | 結果 |
ancestor | 選取當前節點的所有先輩(父、祖父等)。 |
ancestor-or-self | 選取當前節點的所有先輩(父、祖父等)以及當前節點本身。 |
attribute | 選取當前節點的所有屬性。 |
child | 選取當前節點的所有子元素。 |
descendant | 選取當前節點的所有后代元素(子、孫等)。 |
descendant-or-self | 選取當前節點的所有后代元素(子、孫等)以及當前節點本身。 |
following | 選取文檔中當前節點的結束標簽之后的所有節點。 |
namespace | 選取當前節點的所有命名空間節點。 |
parent | 選取當前節點的父節點。 |
preceding | 選取文檔中當前節點的開始標簽之前的所有節點。 |
preceding-sibling | 選取當前節點之前的所有同級節點。 |
self | 選取當前節點。 |
5.xpath運算符
下面列出了可用在 XPath 表達式中的運算符:
運算符 | 描述 | 實例 | 返回值 |
| | 計算兩個節點集 | //book | //cd | 返回所有擁有 book 和 cd 元素的節點集 |
+ | 加法 | 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。 |
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 | 計算除法的余數 | 5 mod 2 | 1 |
好了,xpath的內容就這么多了。接下來我們要介紹一個神器lxml,他的速度很快,曾經一直是我使用beautifulsoup時最鐘愛的解析器,沒有之一,因為他的速度的確比其他的html.parser 和html5lib快了許多。
二、lxml
1.lxml安裝
lxml 是一個xpath格式解析模塊,安裝很方便,直接pip install lxml 或者easy_install lxml即可。
2.lxml 使用
lxml提供了兩種解析網頁的方式,一種是你解析自己寫的離線網頁時,另一種 則是解析線上網頁。
導入包:
from lxml import etree
1.解析離線網頁:
html=etree.parse('xx.html',etree.HTMLParser())
aa=html.xpath('//*[@id="s_xmancard_news"]/div/div[2]/div/div[1]/h2/a[1]/@href')
print(aa)
2.解析在線網頁:
from lxml import etree
import requests
rep=requests.get('https://www.baidu.com')
html=etree.HTML(rep.text)
aa=html.xpath('//*[@id="s_xmancard_news"]/div/div[2]/div/div[1]/h2/a[1]/@href')
print(aa)
那么我們怎么獲取這些標簽和標簽對應的屬性值了,很簡單,首先獲取標簽只需你這樣做:
然后我們可以,比方說,你要獲取a標簽內的文本和它的屬性href所對應的值,有兩種方法,
1.表達式內獲取
aa=html.xpath('//*[@id="s_xmancard_news"]/div/div[2]/div/div[1]/h2/a[1]/text()')
ab=html.xpath('//*[@id="s_xmancard_news"]/div/div[2]/div/div[1]/h2/a[1]/@href')
2.表達式外獲取
aa=html.xpath('//*[@id="s_xmancard_news"]/div/div[2]/div/div[1]/h2/a[1]')
aa.text
aa.attrib.get('href')
這樣就完成了獲取,怎么樣,是不是很簡單了,哈哈哈。
下面再來lxml的解析規則:
表達式 | 描述 |
nodename | 選取此節點的所有子節點 |
/ | 從當前節點選取直接子節點 |
// | 從當前節點選取子孫節點 |
. | 選取當前節點 |
.. | 選取當前節點的父節點 |
@ | 選取屬性 |
html=lxml.etree.HTML(text)
#使用text構造一個XPath解析對象,etree模塊可以自動修正HTML文本
html=lxml.etree.parse('./ex.html',etree.HTMLParser())
#直接讀取文本進行解析
from lxml import etree
result=html.xpath('//*')
#選取所有節點
result=html.xpath('//li')
#獲取所有li節點
result=html.xpath('//li/a')
#獲取所有li節點的直接a子節點
result=html.xpath('//li//a')
#獲取所有li節點的所有a子孫節點
result=html.xpath('//a[@href="link.html"]/../@class')
#獲取所有href屬性為link.html的a節點的父節點的class屬性
result=html.xpath('//li[@class="ni"]')
#獲取所有class屬性為ni的li節點
result=html.xpath('//li/text()')
#獲取所有li節點的文本
result=html.xpath('//li/a/@href')
#獲取所有li節點的a節點的href屬性
result=html.xpath('//li[contains(@class,"li")]/a/text())
#當li的class屬性有多個值時,需用contains函數完成匹配
result=html.xpath('//li[contains(@class,"li") and @name="item"]/a/text()')
#多屬性匹配
result=html.xpath('//li[1]/a/text()')
result=html.xpath('//li[last()]/a/text()')
result=html.xpath('//li[position()<3]/a/text()')
result=html.xpath('//li[last()-2]/a/text()')
#按序選擇,中括號內為XPath提供的函數
result=html.xpath('//li[1]/ancestor::*')
#獲取祖先節點
result=html.xpath('//li[1]/ancestor::div')
result=html.xpath('//li[1]/attribute::*')
#獲取屬性值
result=html.xpath('//li[1]/child::a[@href="link1.html"]')
#獲取直接子節點
result=html.xpath('//li[1]/descendant::span')
#獲取所有子孫節點
result=html.xpath('//li[1]/following::*[2]')
#獲取當前節點之后的所有節點的第二個
result=html.xpath('//li[1]/following-sibling::*')
#獲取后續所有同級節點
3.lxml案例
為了偷懶,小編決定還是采用urllib那篇文章的代碼,哈哈哈,機智如我。
好了,今天就講這么多,大家感興趣的話可以多多關注哦,精彩不停息!!!!
本文參考文獻:
https://www.w3school.com.cn/
想學習更多前端、Python爬蟲、大數據等計算機知識,請前往:http://pdcfighting.com/
XPath 全稱為 Xml Path Language,即 Xml 路徑語言,是一種在 Xml 文檔中查找信息的語言。它提供了非常簡潔的路徑選擇表達式,幾乎所有的節點定位都可以用它來選擇。
XPath 可以用于 Xml 和 Html,在爬蟲中經常使用 XPath 獲取 Html 文檔內容。
lxml 是 Python 語言用 Xpath 解析 XML、Html文檔功能最豐富的、最容易的功能模塊。
在 XPath 中有七種節點分別是元素、屬性、文本、文檔、命名空間、處理指令、注釋,前3種節點為常用節點
請看下面的 Html 例子,(注:這個例子全文都需要使用)
<!DOCTYPE html>
<html>
<body>
<div>
<!-- 這里是注釋 -->
<h4>手機品牌商<span style="margin-left:10px">4</span></h4>
<ul>
<li>小米</li>
<li>華為</li>
<li class='blank'> OPPO </li>
<li>蘋果</li>
</ul>
</div>
<div>
<h4>電腦品牌商<span style="margin-left:10px">3</span></h4>
<ul class="ul" style="color:red">
<li>戴爾</li>
<li>機械革命</li>
<li>ThinkPad</li>
</ul>
</div>
</body>
</html>
在上面的例子中
<html> 為文檔節點
<li>小米</li> 為元素節點
class='blank' 為屬性節點
<!-- 這里是注釋 --> 為注釋節點
在 XPath中有多中節點關系分別是父節點、子節點、同胞節點、先輩節點、后代節點
在上面的例子中
表達式描述nodeName選擇nodeName節點的所有子節點/從根節點開始//從匹配的節點開始選擇節點.選擇當前節點..選擇當前節點的父節點@選擇元素*匹配任意元素節點@*匹配任意屬性節點
用上面的 Html 文檔舉個例子
路徑表達式描述body選取 body 的所有子節點/html選取 html 節點//div選取所有 div 節點//div/./h4div 節點下的 h4 節點../div選取當前節點的父節點下的所有 div 節點//@class所有帶有 class 元素的節點//*選擇所有節點//@*選擇所有屬性節點
表達式描述position()返回節點的 index 位置last()返回節點的個數contains(string1,string2)string1 是否包含 string2text()返回文本節點comment()返回注釋節點normalize-space(string)去除首位空格,中間多個空格用一個空格代替substring(string,start,len)返回從 start 位置開始的指定長度的子字符串,第一個字符下標為1substring-before(string1,string2)返回string1中位于第一個string2之前的部分substring-after(string1,string2)返回string1中位于第一個string2之后的部分
同樣用上面的Html文檔舉個例子
路徑表達式描述//div[position()>1]選擇第二個 div 節點//div[last()]選擇最后一個 div 節點contains(//h4[2],’手機’)第二個 h4 標簽是否包含手機字符串//li/text()li 節點中的文本內容//div/comment()div 節點下的 html 注釋normalize-space(//li[@class=’blank’])li 節點下 class屬性為 blank 的文本去掉空格substring(//h4[1],1,2)第一個 h4 節點的前2個字substring-before(//h4[1],’品牌商’)第一個 h4 節點的品牌商字符串之前的字符串substring-after(//h4[1],’品牌商’)第一個 h4 節點的品牌商字符串之后的字符串
XPath 中的謂語就是刪選表達式,相當于 SQL 中的 Where 條件,謂語被嵌在 [ ] 中
路徑表達式描述//div[1]選擇第一個 div 節點//div[2]/ul/li[last()]選擇第二個 div 節點下的最后一個 li 節點//div[2]/ul/li[position()>3]選擇第二個 div 節點下的前兩個 li 節點//ul[@class]選擇所有帶 class 屬性的 ul 節點//ul[@class=’computer’]選擇 class 屬性為 computer 的 ul 節點//h4[span=4]選擇 h4 節點下 span 值等于4的節點
以上內容介紹了 XPath 的基本語法,下面將介紹 XPath 如何在 Python 中使用。
sudo pip3 install lxml==4.4.1
lxml.etree 一個強大的 Xml 處理模塊,etree 中的 ElementTree 類是一個主要的類,用于對XPath的解析、增加、刪除和修改節點。
from lxml import etree
etree.parse() 函數可以解析一個網頁文件還可以解析字符串, 在網頁中下載的數據一般都是字符串形式的,使用 parse(StringIO(str)) 將整個頁面內容解析加載構建一個 ElementTree 對象,ElementTree 可以使用 XPath 語法精準找到需要的數據。
1.加載頁面到內存
from lxml import etree
from io import StringIO
test_html='''
<html>
<body>
<div>
<!-- 這里是注釋 -->
<h4>手機品牌商<span style="margin-left:10px">4</span></h4>
<ul>
<li>小米</li>
<li>華為</li>
<li class='blank'> OPPO </li>
<li>蘋果</li>
</ul>
</div>
<div>
<h4>電腦品牌商<span style="margin-left:10px">3</span></h4>
<ul class="ul" style="color:red">
<li>戴爾</li>
<li>機械革命</li>
<li>ThinkPad</li>
</ul>
</div>
</body>
</html>'''
html=etree.parse(StringIO(test_html))
print(html)
結果:
<lxml.etree._ElementTree object at 0x10bd6b948>
2.獲取所有 li 標簽數據
li_list=html.xpath('//li')
print("類型:")
print(type(li_list))
print("值:")
print(li_list)
print("個數:")
print(len(li_list))
for l in li_list:
print("li文本為:" + l.text)
結果:
類型:
<class 'list'>
值:
[<Element li at 0x10543c9c8>, <Element li at 0x10543ca08>, <Element li at 0x10543ca48>, <Element li at 0x10543ca88>, <Element li at 0x10543cac8>, <Element li at 0x10543cb48>, <Element li at 0x10543cb88>]
個數:
7
li文本為:小米
li文本為:華為
li文本為: OPPO
li文本為:蘋果
li文本為:戴爾
li文本為:機械革命
li文本為:ThinkPad
3.獲取帶 class=’blank’ 屬性數據
blank_li_list=html.xpath('//li[@class="blank"]')
print("類型:")
print(type(blank_li_list))
print("值:")
print(blank_li_list)
print("個數:")
print(len(blank_li_list))
for l in blank_li_list:
print("li文本為:" + l.text)
結果:
類型:
<class 'list'>
值:
[<Element li at 0x105253a48>]
個數:
1
li文本為: OPPO
4.屬性操作
ul=html.xpath('//ul')[1]
#遍歷屬性
for name, value in ul.attrib.items():
print('{0}="{1}"'.format(name, value))
#添加新的屬性
ul.set("new_attr", "true")
# 獲取單個屬性
new_attr=ul.get('new_attr')
print(new_attr)
結果:
class="ul"
style="color:red"
true
5.獲取最后一個div標簽數據
last_div=html.xpath('//div[last()]')
print("TAG:")
print(last_div.tag)
print("值:")
print(last_div.text)
結果
div
值:
6.添加子節點
child=etree.Element("child")
child.text="這里是新的子元素"
last_div.append(child)
# 在最后一個 div 標簽查找新的子元素
clild_text=last_div.find("child").text
print(clild_text)
7.刪除子元素
# 查找并設置第一個查詢到的元素
first_ul=html.find("//ul")
ul_li=first_ul.xpath("li")
for li in ul_li:
# 刪除元素
first_ul.remove(li)
ul_li=first_ul.xpath("li")
if len(ul_li)==0:
print("元素被刪除了")
8.遍歷元素后代
body=html.find("body")
for sub in body.iter():
print(sub.tag)
print(sub.text)
結果
body
div
<cyfunction Comment at 0x10c374b10>
這里是注釋
h4
手機品牌商
span
4
ul
...
慶才,Python技術控,爬蟲博文訪問量已過百萬。喜歡鉆研,熱愛生活,樂于分享。
個人博客:靜覓 | http://cuiqingcai.com/
XPath,全稱 XML Path Language,即 XML 路徑語言,它是一門在XML文檔中查找信息的語言。XPath 最初設計是用來搜尋XML文檔的,但是它同樣適用于 HTML 文檔的搜索。
所以在做爬蟲時,我們完全可以使用 XPath 來做相應的信息抽取,本節我們來介紹一下 XPath 的基本用法。
XPath 的選擇功能十分強大,它提供了非常簡潔明了的路徑選擇表達式,另外它還提供了超過 100 個內建函數用于字符串、數值、時間的匹配以及節點、序列的處理等等,幾乎所有我們想要定位的節點都可以用XPath來選擇。
XPath 于 1999 年 11 月 16 日 成為 W3C 標準,它被設計為供 XSLT、XPointer 以及其他 XML 解析軟件使用,更多的文檔可以訪問其官方網站:https://www.w3.org/TR/xpath/。
我們現用表格列舉一下幾個常用規則:
表達式描述
nodename選取此節點的所有子節點
/從當前節點選取直接子節點
//從當前節點選取子孫節點
.選取當前節點
..選取當前節點的父節點
@選取屬性
在這里列出了XPath的常用匹配規則,例如 / 代表選取直接子節點,// 代表選擇所有子孫節點,. 代表選取當前節點,.. 代表選取當前節點的父節點,@ 則是加了屬性的限定,選取匹配屬性的特定節點。
例如:
//title[@lang=’eng’]
這就是一個 XPath 規則,它就代表選擇所有名稱為 title,同時屬性 lang 的值為 eng 的節點。
在后文我們會介紹 XPath 的詳細用法,通過 Python 的 LXML 庫利用 XPath 進行 HTML 的解析。
在使用之前我們首先要確保安裝好了 LXML 庫,如沒有安裝可以參考第一章的安裝過程。
我們現用一個實例來感受一下使用 XPath 來對網頁進行解析的過程,代碼如下:
from lxml import etree
text='''
<div>
<ul>
<li><a >first item</a></li>
<li><a >second item</a></li>
<li><a >third item</a></li>
<li><a >fourth item</a></li>
<li><a >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 代碼,內容如下:
<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>
這次的輸出結果略有不同,多了一個 DOCTYPE 的聲明,不過對解析無任何影響,結果如下:
<!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>
我們一般會用 // 開頭的 XPath 規則來選取所有符合要求的節點,以上文的 HTML 文本為例,如果我們要選取所有節點,可以這樣實現:
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])
運行結果:
[<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]。
我們通過 / 或 // 即可查找元素的子節點或子孫節點,加入我們現在想選擇 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 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 是 link4.html 的 a 節點,然后再獲取其父節點,然后再獲取其 class 屬性,代碼如下:
from lxml import etree
html=etree.parse('./test.html', etree.HTMLParser())
result=html.xpath('//a[@]
檢查一下結果,正是我們獲取的目標 li 節點的 class,獲取父節點成功。
同時我們也可以通過 parent:: 來獲取父節點,代碼如下:
from lxml import etree
html=etree.parse('./test.html', etree.HTMLParser())
result=html.xpath('//a[@)
print(result)
8. 屬性匹配
在選取的時候我們還可以用 @ 符號進行屬性過濾,比如在這里如果我們要選取 class 為 item-1 的 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 0x10a399288>, <Element li at 0x10a3992c8>]可見匹配結果結果正是兩個,至于是不是那正確的兩個,我們在后面驗證一下。
9. 文本獲取
我們用 XPath 中的 text() 方法可以獲取節點中的文本,我們接下來嘗試獲取一下上文 li 節點中的文本,代碼如下:
from lxml import etree
html=etree.parse('./test.html', etree.HTMLParser())
result=html.xpath('//li[@class="item-0"]/text()')
print(result)
運行結果如下:
['\n ']很奇怪的是我們并沒有獲取到任何文本,而是只獲取到了一個換行符,這是為什么呢?因為 XPath 中 text() 前面是 /,而此 / 的含義是選取直接子節點,而此處很明顯 li 的直接子節點都是 a 節點,文本都是在 a 節點內部的,所以這里匹配到的結果就是被修正的 li 節點內部的換行符,因為自動修正的li節點的尾標簽換行了。
即選中的是這兩個節點:
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a>
</li>
其中一個節點因為自動修正,li 節點的尾標簽添加的時候換行了,所以提取文本得到的唯一結果就是 li 節點的尾標簽和 a 節點的尾標簽之間的換行符。
因此,如果我們想獲取 li 節點內部的文本就有兩種方式,一種是選取到 a 節點再獲取文本,另一種就是使用 //,我們來看下二者的區別是什么。
首先我們選取到 a 節點再獲取文本,代碼如下:
from lxml import etree
html=etree.parse('./test.html', etree.HTMLParser())
result=html.xpath('//li[@class="item-0"]/a/text()')
print(result)
運行結果:
['first item', 'fifth item']可以看到這里返回值是兩個,內容都是屬性為 item-0 的 li 節點的文本,這也印證了我們上文中屬性匹配的結果是正確的。
在這里我們是逐層選取的,先選取了 li 節點,又利用 / 選取了其直接子節點 a,然后再選取其文本,得到的結果恰好是符合我們預期的兩個結果。
我們再來看下用另一種方式 // 選取的結果,代碼如下:
from lxml import etree
html=etree.parse('./test.html', etree.HTMLParser())
result=html.xpath('//li[@class="item-0"]//text()')
print(result)
運行結果:
['first item', 'fifth item', '\n ']不出所料,這里返回結果是三個,可想而知這里是選取所有子孫節點的文本,其中前兩個就是 li 的子節點 a 節點內部的文本,另外一個就是最后一個 li 節點內部的文本,即換行符。
所以說,如果我們要想獲取子孫節點內部的所有文本,可以直接用 // 加 text() 的方式獲取,這樣可以保證獲取到最全面的文本信息,但是可能會夾雜一些換行符等特殊字符。如果我們想獲取某些特定子孫節點下的所有文本,可以先選取到特定的子孫節點,然后再調用 text() 方法獲取其內部文本,這樣可以保證獲取的結果是整潔的。
10. 屬性獲取
我們知道了用 text() 可以獲取節點內部文本,那么節點屬性該怎樣獲取呢?其實還是用 @ 符號就可以,例如我們想獲取所有 li 節點下所有 a 節點的 href 屬性,代碼如下:
from lxml import etree
html=etree.parse('./test.html', etree.HTMLParser())
result=html.xpath('//li/a/@href')
print(result)
在這里我們通過 @href 即可獲取節點的 href 屬性,注意此處和屬性匹配的方法不同,屬性匹配是中括號加屬性名和值來限定某個屬性,如 [@],而此處的 @href 指的是獲取節點的某個屬性,二者需要做好區分。
運行結果:
['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']可以看到我們成功獲取了所有 li 節點下的 a 節點的 href 屬性,以列表形式返回。
11. 屬性多值匹配
有時候某些節點的某個屬性可能有多個值,例如下面例子:
from lxml import etree
text='''
<li class="li li-first"><a >first item</a></li>
'''
html=etree.HTML(text)
result=html.xpath('//li[@class="li"]/a/text()')
print(result)
在這里 HTML 文本中的 li 節點的 class 屬性有兩個值 li 和 li-first,但是此時如果我們還想用之前的屬性匹配獲取就無法匹配了,代碼運行結果:
[]這時如果屬性有多個值就需要用 contains() 函數了,代碼可以改寫如下:
from lxml import etree
text='''
<li class="li li-first"><a >first item</a></li>
'''
html=etree.HTML(text)
result=html.xpath('//li[contains(@class, "li")]/a/text()')
print(result)
這樣我們通過 contains() 方法,第一個參數傳入屬性名稱,第二個參數傳入屬性值,這樣只要此屬性包含所傳入的屬性值就可以完成匹配了。
運行結果:
['first item']此種選擇方式在某個節點的某個屬性有多個值的時候經常會用到,如某個節點的 class 屬性通常有多個。
12. 多屬性匹配
另外我們可能還遇到一種情況,我們可能需要根據多個屬性才能確定一個節點,這是就需要同時匹配多個屬性才可以,那么這里可以使用運算符 and 來連接,示例如下:
from lxml import etree
text='''
<li class="li li-first" name="item"><a >first item</a></li>
'''
html=etree.HTML(text)
result=html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()')
print(result)
在這里 HTML 文本的 li 節點又增加了一個屬性 name,這時候我們需要同時根據 class 和 name 屬性來選擇,就可以 and 運算符連接兩個條件,兩個條件都被中括號包圍,運行結果如下:
['first item']這里的 and 其實是 XPath 中的運算符,另外還有很多運算符,如 or、mod 等等,在此總結如下:
運算符描述實例返回值
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計算除法的余數5 mod 21
\計算兩個節點集//book \//cd返回所有擁有 book 和 cd 元素的節點集
+加法6 + 410
-減法6 - 42
*乘法6 * 424
div除法8 div 42
=等于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/xpath/xpath_operators.asp。
13. 按序選擇
有時候我們在選擇的時候可能某些屬性同時匹配了多個節點,但是我們只想要其中的某個節點,如第二個節點,或者最后一個節點,這時該怎么辦呢?
這時可以利用中括號傳入索引的方法獲取特定次序的節點,示例如下:
from lxml import etree
text='''
<div>
<ul>
<li><a >first item</a></li>
<li><a >second item</a></li>
<li><a >third item</a></li>
<li><a >fourth item</a></li>
<li><a >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)
第一次選擇我們選取了第一個 li 節點,中括號中傳入數字1即可,注意這里和代碼中不同,序號是以 1 開頭的,不是 0 開頭的。
第二次選擇我們選取了最后一個 li 節點,中括號中傳入 last() 即可,返回的便是最后一個 li 節點。
第三次選擇我們選取了位置小于 3 的 li 節點,也就是位置序號為 1 和 2 的節點,得到的結果就是前 2 個 li 節點。
第四次選擇我們選取了倒數第三個 li 節點,中括號中傳入 last()-2即可,因為 last() 是最后一個,所以 last()-2 就是倒數第三個。
運行結果如下:
['first item']
['fifth item']
['first item', 'second item']
['third item']
在這里我們使用了 last()、position() 等函數,XPath 中提供了 100 多個函數,包括存取、數值、字符串、邏輯、節點、序列等處理功能,具體所有的函數作用可以參考:http://www.w3school.com.cn/xpath/xpath_functions.asp。
14. 節點軸選擇
XPath 提供了很多節點軸選擇方法,英文叫做 XPath Axes,包括獲取子元素、兄弟元素、父元素、祖先元素等等,在一定情況下使用它可以方便地完成節點的選擇,我們用一個實例來感受一下:
from lxml import etree
text='''
<div>
<ul>
<li><a ><span>first item</span></a></li>
<li><a >second item</a></li>
<li><a >third item</a></li>
<li><a >fourth item</a></li>
<li><a >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[@)
print(result)
運行結果:
[<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>]
第一次選擇我們調用了 ancestor 軸,可以獲取所有祖先節點,其后需要跟兩個冒號,然后是節點的選擇器,這里我們直接使用了 *,表示匹配所有節點,因此返回結果是第一個 li 節點的所有祖先節點,包括 html,body,div,ul。
第二次選擇我們又加了限定條件,這次在冒號后面加了 div,這樣得到的結果就只有 div 這個祖先節點了。
第三次選擇我們調用了 attribute 軸,可以獲取所有屬性值,其后跟的選擇器還是 *,這代表獲取節點的所有屬性,返回值就是 li 節點的所有屬性值。
第四次選擇我們調用了 child 軸,可以獲取所有直接子節點,在這里我們又加了限定條件選取 href 屬性為 link1.html 的 a 節點。
第五次選擇我們調用了 descendant 軸,可以獲取所有子孫節點,這里我們又加了限定條件獲取 span 節點,所以返回的就是只包含 span 節點而沒有 a 節點。
第六次選擇我們調用了 following 軸,可以獲取當前節點之后的所有節點,這里我們雖然使用的是 * 匹配,但又加了索引選擇,所以只獲取了第二個后續節點。
第七次選擇我們調用了 following-sibling 軸,可以獲取當前節點之后的所有同級節點,這里我們使用的是 * 匹配,所以獲取了所有后續同級節點。
以上是XPath軸的簡單用法,更多的軸的使用可以參考:http://www.w3school.com.cn/xpath/xpath_axes.asp。
End.
運行人員:中國統計網小編(微信號:itongjilove)
微博ID:中國統計網
中國統計網,是國內最早的大數據學習網站,公眾號:中國統計網
http://www.itongji.cn
*請認真填寫需求信息,我們會在24小時內與您取得聯系。