對於網頁的節點來講,它能夠定義 id 、class 或其餘屬性。 並且節點之間還有層次關係,在網頁 中能夠經過XPath 或 css 選擇器來定位一個或多個節點。因此在頁面解析時,利用 XPath 或 css 選擇器來提取某個節點,而後再調用相應方法獲取它的正文內容或者屬性,也能夠提取咱們想要的任意信息。css
XPath的使用html
//title[@lang='eng']
此XPath表明選擇全部名稱爲title,同時屬性lang的值爲eng的節點。node
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> </ul> </div> </body></html>
通過處理以後, li 節點標籤被補全,而且還向動添加了 body、 html 節點。 spa
另外,也能夠直接讀取文本文件進行解析,示例以下:code
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = etree.tostring(html) print(result.decode('utf-8'))
其中 test.html 的內容就是上面例子中的 HTML代碼,輸出結果會多一個DOCTYPE聲明,對解析無任何影響。xml
咱們通常會用 // 開頭的 XPath 規則來選取全部符合要求的節點。 之前面的 HTML 文本爲例,若是要選取全部節點:htm
from lxml import etree html = etree.parse('./test.html',etree.HTMLParser()) result = html.xpath('//*') print(result) 輸出: [<Element html at 0x245a651e508>, <Element body at 0x245a651e608>, <Element p at 0x245a651e648>, <Element div at 0x245a651e688>, <Element ul at 0x245a651e6c8>, <Element li at 0x245a651e748>, <Element a at 0x245a651e788>, <Element li at 0x245a651e7c8>, <Element a at 0x245a651e808>, <Element li at 0x245a651e708>, <Element a at 0x245a651e848>, <Element li at 0x245a651e888>, <Element a at 0x245a651e8c8>, <Element li at 0x245a651e908>, <Element a at 0x245a651e948>]
這裏使用*表明匹配全部節點,整個 HTML 文本中的全部節點都會被獲取。返回形式是一個列表,每一個元素是 Element 類型,後面跟了節點名稱,如 html、 body、 div 、 ul、 li 、a 等,全部節點都包含在列表中。對象
此處匹配也能夠指定節點名稱。 若是想獲取全部 li 節點,例:blog
from lxml import etree html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//li') print(result) print(result[0]) 輸出: [<Element li at 0x1e80856c608>, <Element li at 0x1e80856c648>, <Element li at 0x1e80856c688>, <Element li at 0x1e80856c6c8>, <Element li at 0x1e80856c708>] <Element li at 0x1e80856c608>
提取結果是一個列表形式,其中每一個元素都是一個 Element 對象。 若是要取出其中 一個對象,能夠直接用中括號加索引,如 [0].
經過/或//便可查找元素的子節點或子孫節點。若是如今想選擇 li 節點的全部直接 a 子節點,例:
from lxml import etree html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//li/a') print(result) 輸出: [<Element a at 0x2af0834e608>, <Element a at 0x2af0834e648>, <Element a at 0x2af0834e688>, <Element a at 0x2af0834e6c8>, <Element a at 0x2af0834e708>]
經過追加/a 即選擇了全部 li 節點的全部直接 a 子節點。由於//li 用於選中全部 li 節點,/a 用於選中 li 節點的全部直接子節點 a, 兩者組合在一塊兒即獲取全部 li 節點的全部直接 a 子節點。
此處的/用於選取直接子節點 ,若是要獲取全部子孫節點,就可使用//。 例如,要獲取 ul 節點 下的全部子孫 a 節點,能夠這樣實現:
from lxml import etree html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//ul//a') print(result)
若是這裏用//ul閉,就沒法獲取任何結果了。 由於/用於獲取直接子節點,而在 ul 節點下沒 有直接的 a 子節點,只有 li 節點,因此沒法獲取任何匹配結果。
所以,這裏咱們要注意/和//的區別,其中/用於獲取直接子節點,//用於獲取子孫節點。
父節點
連續的/或//能夠查找子節點或子孫節點,若是知道了子節點,查找父節點能夠用..來實現。
在首先選中 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)
屬性匹配
在選取的時候,還能夠用@符號進行屬性過濾。 好比,這裏若是要選取 class 爲 item-1 的 li 節點,例:
from lxml import etree html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//li[@class="item-0"]') print(result) 輸出: [<Element li at 0x2675c1f5608>, <Element li at 0x2675c1f5648>]
經過加入[@class="item-0」],限制了節點的 class 屬性爲 item-0,而 HTML 文本中符合 條件的 li 節點有兩個,因此結果應該返回兩個匹配到的元素。
from lxml import etree html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//li[@class="item-0"]/text()') print(result) 輸出: ['\r\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 節點的尾標籤之間的換行符。
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', '\r\n']
這裏是選取全部子孫節點的文本,其中前兩個就 是 li 的子節點 a 節點內部的文本,另一個就是最後一個 li 節點內部的文本,即換行符。
若是要想獲取子孫節點內部的全部文本,能夠直接用//加 text()的方式,這樣能夠保證 獲取到最全面的文本信息,可是可能會夾雜一些換行符等特殊字符。 若是想獲取某些特定子孫節點下的全部文本,能夠先選取到特定的子孫節點,而後再調用 text()方法獲取其內部文本,這樣能夠保證 獲取的結果是整潔的。
例:想獲取全部 li 節點下全部 a 節點的 href 屬性,示例:
from lxml import etree html = etree.parse('./test.html', etree.HTMLParser()) result = html.xpath('//li/a/@href') print(result) 輸出: ['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
這裏經過@href 便可獲取節點的 href 屬性。
注意,此處和屬性匹配的方法不一樣,屬性匹配是中括號加屬性名和值來限定某個屬性,如[@href="link1.html」],而此處的@href 指的是獲取節點的某 個屬性
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 節點的 class 屬性有兩個值 li 和 li-first,此時若是還想用以前的屬性匹配獲取,就沒法匹配了。因此輸出爲空。 這時須要用 contains()函數,代碼修改以下:
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) 輸出: ['first item']
經過 contains()方法,第一個參數傳人屬性名稱,第二個參數傳人屬性值,只要此屬性包含 所傳人的屬性值,就能夠完成匹配了。此方式在某個節點的某個屬性有多個值時常常用到,如某個節點的 class 屬性一般有多個。
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) 輸出: ['first item']
li 節點又增長了一個屬性 name。 要肯定這個節點 , 須要同時根據 class 和 name 屬性來選 擇,一個條件是 class 屬性裏面包含 li 字符串,另外一個條件是 name 屬性爲 item 字符串,兩者須要同 時知足,須要用 and 操做符相連,相連以後置於中括號內進行條件篩選。
此處 and 是 XPath 中的運算符。 還有不少運算符,如 or、 mod 等,以下:
按序選擇
有時候,在選擇的時候某些屬性可能同時匹配了多個節點,若是隻想要其中的某個節點,能夠利用中括號傳入索引的方法獲取特定次序的節點。
例:須要第二個節點或者最後一個節點:
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) 輸出: ['first item'] ['fifth item'] ['first item', 'second item'] ['third item']
這裏使用了 last()、position()等函數。 在 XPath 中,提供了 100 多個函數,包括存取、 數值、字符串、邏輯、節點、序列等處理功能,具體做用能夠參考 : http://www.w3school.com.cn/xpath/xpath _functions.asp。
節點軸選擇
XPath 提供了不少節點軸選擇方法,包括獲取子元素、兄弟元素、父元素、祖先元素等,示例:
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) 輸出: [<Element html at 0x2d9a83c5608>, <Element body at 0x2d9a83c5588>, <Element div at 0x2d9a83c5548>, <Element ul at 0x2d9a83c5648>] [<Element div at 0x2d9a83c5548>] ['item-0'] [<Element a at 0x2d9a83c5648>] [<Element span at 0x2d9a83c5548>] [<Element a at 0x2d9a83c5648>] [<Element li at 0x2d9a83c5588>, <Element li at 0x2d9a83c5688>, <Element li at 0x2d9a83c56c8>, <Element li at 0x2d9a83c5708>]