lxml 學(xué)習(xí)筆記

Hallstatt 哈爾施塔特

人生苦短,我用Python。

起初,這篇文章是打算來寫 XPath 的,可是后來一想,我需要的僅是 XPath 的一部分,我僅僅是打算在寫爬蟲的時候,抓取特定數(shù)據(jù)的,并且這是需要結(jié)合 lxml 的,So ,索性就來寫 lxml 來了。(lxml supports XPath 1.0, XSLT 1.0 and the EXSLT extensions through libxml2 and libxslt in a standards compliant way.這是教程上的一段話,但是XPath現(xiàn)在是有2.0版本的。)

關(guān)于教程方面官方網(wǎng)站上給出了三個教程:

  • the lxml.etree tutorial for XML processing 這個是官方的教程。
  • John Shipman's tutorial on Python XML processing with lxml 這個是 New Mexico Tech 大學(xué)的一個教程。嗯 ,有點久了,最后更新時間是 2013-08-24 ,當(dāng)時應(yīng)該是 3.2 版本的。不過易讀性還是比官方的強。
  • Fredrik Lundh's tutorial for ElementTree 這個是 Fredrik Lundh 維護的一個第三方庫,后來也被整合到 Python 標準庫 xml 中了。這個沒時間可以不看,我感覺用不到應(yīng)該。

lxml is the most feature-rich and easy-to-use library for processing XML and HTML in the Python language.

lxml 是提供了一個 Pythonic API ,并且她也是幾乎完全兼容 ElementTree API 的。lxml 的開發(fā)團隊也是力求減少開發(fā)者的學(xué)習(xí)成本,不過 lxml 的教程感覺一點都不友好。

在看官方教程的時候,發(fā)現(xiàn)了一些小疑惑:

lxml 模塊并不僅僅是我們在網(wǎng)上搜到的 etree ,lxml 下面還有很多的模塊,有 etree 、html 、cssselect 等眾多模塊。另外我也看到了 BeautifulSoup 。

lxml can make use of BeautifulSoup as a parser backend, just like BeautifulSoup can employ lxml as a parser.

lxml interfaces with BeautifulSoup through the lxml.html.soupparser module.

BeautifulSoup 可以將 lxml 作為解析器,這個我是知道的,但是 lmxl.html.soupparser 可以調(diào)用 BeautifulSoup 我就不明白了。剛開始以為理解錯誤,但是確實在源碼中找到的證據(jù),soupparser 模塊中引入了 BeautifulSoup ,并且也調(diào)用了該函數(shù)。

兩者之間可以相互調(diào)用,并且兩者之間的函數(shù)也是很相似的,就連 css 選擇器兩個也都有。有意思了。難道 BeautifulSoup 是 lxml 的一部分?

BeautifulSoup 是在 2004 年發(fā)布的。lxml 大約是在 2005 年發(fā)布。

2008 年,lxml v2.0 發(fā)布,lxml.html 添加到庫中;在 v2.0.3 版本中 lxml.html.soupparser 才出現(xiàn)。

直到 2012 年 bs4 發(fā)布,BeautifulSoup 才可以選擇從內(nèi)置解析器、html5lib 、lxml 這三個中選一個作為解析器,而之前版本使用的是卻是另一個。

顯然,這兩個是兩碼事。可是兩者之間的區(qū)別以及聯(lián)系是什么(因為相似度有點高),特定的情況下又該做出何種選擇?這個疑問先立在這里,容我以后回答。我所知道的是 BeautifulSoup 強大檢測編碼能力,并且能夠以 utf-8 形式輸出;lxml 具有速度飛起的文本解析力。

本篇文章的介紹順序是 lxml.etree 、XPathlxml.html 。

lxml.etree

作為一個強大的 xml 處理模塊,該有的功能她都是有的。

若操作一個 XML ,首先要能讀寫,能將字符串或流讀進來解析成 ElementTree ,又能將 ElementTree 轉(zhuǎn)換為字符串或流。在這方面 lxml 提供了幾個可用的函數(shù):

  • etree.fromstring( string )

    將 string 解析為 Element 或者 ElementTree 。

  • etree.parse( file )

    將文件或者是 file_like 對象解析為 ElementTree (not an Element object),因為 parse() 一般解析整篇文檔,字符串解析函數(shù)一般只解析片段。其中 file 還可以是 HTTP/FTP URL ,也就是說,file 應(yīng)該是一個 Bytes 流。不過 libxml2 中的 HTTP/FTP client 比較簡單,所以可以考慮使用第三方庫。

  • XML( text ) / HTML( text )

    行為比較像 fromstring() ,比較直接的對 XML 和 HTML 文檔進行特定解析,可以修改解析器 parser 參數(shù),不同的解析器設(shè)置區(qū)別還是蠻大的。其中 parser 可以由相應(yīng)的 XMLParser() / HTMLParser() 函數(shù)生成,可設(shè)置項有很多不僅限于 encoding 、recover 、remove_blank_text 、remove_comments ,但是默認值是存在不同的。比如 recover 在 XML 中是 False ,而在 HTML 中是 True 。在 HTML 中會使用 libxml2 的 recover 算法對不盒飯的標簽進行修復(fù)。

  • etree.tostring( elt )

    將一個 Element 或者 ElementTree 轉(zhuǎn)換為 string 形式。這里面有幾個可選參數(shù):pretty_print=False 表示是否格式化提高可讀性;method="xml" 選擇輸出后的文檔格式,不同的選擇,做的修改也不相同,可選參數(shù)有 xml 、html 、text (文本內(nèi)容不包括標簽,就是純文本內(nèi)容,tail也是) 、c14n (規(guī)范化 xml );encoding=None 表示以什么編碼的字符串輸出,在無 xml 文檔聲明情況下默認是 ASCⅡ ,可通過 encoding=None 進行修改,但是如果所改編碼不是 utf-8 兼容,那么將會啟用默認聲明。

  • ElementTree.write( file )

    這個是 ElementTree 特有的方法,是將 ElementTree 寫到 a file, a file-like object, or a URL (via FTP PUT or HTTP POST) ??蛇x參數(shù)和 tostring() 差不多,也有不同。

剩下就是操作了,增刪查改一樣也不少,而查找是剩下三個的基礎(chǔ)。

etree 對于每種元素都有一個創(chuàng)建函數(shù),比如 Element() 、ElementTree() 、SubElement() 以及 Comment() ?;舅械暮瘮?shù)都是基于這些實例對象的,lxml 的 API 是比較 Pythonic 的,Element 的行為和 list 比較像可以使用 len() 函數(shù)切片等,Element 的 attrib 就是一個 dist ;其中 Element 實例擁有 41 個屬性和方法,包含了所有:

append() insert()

clear() remove()

find*() get*() iter*() xpath()

set() 以及屬性 .tag .attrib .text .tail等的操作

ElementTree 實例擁有的方法較少,常用的大概有 7 種,主要是查找find*() get*() xpath()。

Element 的屬性介紹:

  • .tag

    元素的名稱

  • .attrib

    一個包含元素屬性的字典,key 是屬性名,value 是對應(yīng)的值。

  • .text

    Element 的文本均為直接子文本,不包含子元素中的文本,這其中又包含兩部分 .text 和 .tail 。.text 是第一個子元素標簽之前的,如果沒有則為 None 。

  • .tail

    .tail 為 Element 的關(guān)閉標簽之后的文本,并且是在下一個兄弟子標簽之前的部分。沒有則為 None 。

Element 的方法介紹:

  • append( child )

    添加一個新的子節(jié)點(可以是 Element 、Comment)到當(dāng)前 Element 中。

  • insert( index, elt)

    將子元素 elt 插入到指定位置,這個 index 的隨意性比較大,如果是正數(shù)但是超過了最大值,那么 lxml 會直接將這個元素插到末尾,如果是負數(shù),但是這個負數(shù)所指位置不存在,那么就插到末尾,這個負數(shù)的位置計算規(guī)則和列表的那個不太一樣,不知道正確的規(guī)律是什么,但是經(jīng)過測試,-n所插的位置,后面有 n (以變化前計算)個元素。如果對 Element 中的子元素執(zhí)行 insert() 操作,那么子元素位置會按 index 指定的變換。

  • clear()

    調(diào)用該函數(shù),將移除所有內(nèi)容 .attrib 將被移除 .text 和 .tail 將被設(shè)置為 None 所有的子節(jié)點將被刪除。

  • remove( child )

    將子節(jié)點 child 從Element 中移除 ,如果child 不是 Element 的子節(jié)點,將會引發(fā) ValueError 異常。

  • find( path )

    從 Element 的子元素及后代元素中查找第一個符合 path 的 subelement 。如果沒有返回 None 。

    ElementPath 是 ElementTree 自帶的一個 XPath-like 的路徑語言,和 XPath 差不太多,主要區(qū)別是 ElementPath 能用 {namespace}tag,但是 ElementPath 不能使用值比較和函數(shù)。

  • findall( path )

    返回一個匹配的 Element 的列表。

  • findtext( path, default=None )

    返回第一個匹配元素的 .text 內(nèi)容,如果存在匹配,但是沒有 .text 內(nèi)容,那么將返回一個空字符串,如果沒有一個匹配的元素,那么將會返回一個 None ,但是有 default 參數(shù),返回 default 所指定的。

  • get( key, default=None )

    返回字符串形式的 屬性 key 的值,沒有返回 None 或者 default 指定的。

  • getchildren()

    返回一個包含 Element 子元素的列表。

  • getiterator( tag=None, *tags )

    返回元素的一個生成器,返回元素類別取決于參數(shù) tag ,生成順序是in document order (depth first pre-order) 深度優(yōu)先的先根遍歷。如果沒有參數(shù)的話,則第一個就是元素本身。如果想使用一個高效的生成器,可以使用 .iter() 。

  • getroottree()

    返回該元素的 ElementTree 。

  • iter( tag=None, *tags )

    過濾特定標簽,生成迭代器。默認情況下,iter() 迭代所有的節(jié)點,包括PI(處理指令) 、Comment(注釋) 等,如果只想迭代標簽元素,可以使用 Element factory 做參數(shù)e.iter(tag = etree.Element)。

  • iterfind( path )

    迭代所有匹配 ElementPath 的 Element 。

  • itertext( tag=None, *tags, with_tail=True )

    迭代 Element 元素的文本內(nèi)容,with_tail 參數(shù)決定是否迭代子元素的 tail 。Element 的tail 不會進行迭代。

  • iterancestors( tag=None )

    如果忽略參數(shù),那么將會迭代所有先輩,加標簽名可以只迭代該標簽先輩。

  • iterchildren( reversed=False, tag=None )

    通過設(shè)置 reversed=True 可以以反向的順序迭代子元素。

  • itersiblings( preceding=False )

    迭代 Element 之后的兄弟元素,可以通過設(shè)置 preceding=True 僅迭代 Element 之前的兄弟元素。

  • iterdescendants( tag=None )

    同 iterancestors()

  • items()

    返回由元素屬性的鍵值所構(gòu)成的( name, value)元組的列表。

  • keys()

    返回一個沒有特定順序的元素屬性名的列表。

  • set(A, V)

    創(chuàng)建或者改變屬性 A 的值為 V。

  • xpath()

ElementTree 方法介紹:

沒有介紹的方法,是前面的類似,或者已經(jīng)介紹過。

  • find( path )

  • findall( path )

  • findtext( path )

  • getiterator( tag=None, *tags )

  • getroot()

    獲得 ElementTree 的 root 元素,一般情況下是樹的根節(jié)點的 Element 的實例,但是 ElementTree 是經(jīng)沒有任何東西創(chuàng)建出來的,將返回 None 。

  • xpath()

  • write()

XPath

XPath 是一門在 XML 文檔中查找信息的路徑語言,W3C 文檔:XML Path Language (XPath), Version 1.0。

所有的查找都是基于參考定位的,依靠兩者之間存在的某種關(guān)系,或主從,或平級,這樣一步一步的查到。

在 XPath 中,有七種類型的節(jié)點:元素、屬性、文本、命名空間、處理指令、注釋以及文檔節(jié)點(或稱為根節(jié)點)。

XPath 使用路徑表達式在 XML 文檔中選取節(jié)點。節(jié)點是通過沿著路徑或者 step 來選取的。

下面列出了最有用的路徑表達式:

表達式 描述
nodename 選取此節(jié)點的所有子節(jié)點。
/ 從根節(jié)點選取。
// 從匹配選擇的當(dāng)前節(jié)點選擇文檔中的節(jié)點,而不考慮它們的位置。
. 選取當(dāng)前節(jié)點。
.. 選取當(dāng)前節(jié)點的父節(jié)點。
@ 選取屬性。

XPath 通配符可用來選取未知的 XML 元素。

通配符 描述
* 匹配任何元素節(jié)點。
@* 匹配任何屬性節(jié)點。
node() 匹配任何類型的節(jié)點。

通過在路徑表達式中使用“|”運算符,可以選取若干個路徑,“|”兩邊的表達式是互相獨立的,并非是一個表達式下。

除了上面介紹的還有兩個概念需要知道:軸(Axes) 、謂語

通過以上介紹我們基本可以進行選取節(jié)點了,但是有可能選的節(jié)點有點多,那么這時候就需要用謂語了。謂語就是用來做過濾的,過濾條件均寫在[]中。

這個過濾條件可寫內(nèi)容有很多:運算符表達式、數(shù)字、函數(shù)以及文字( Literal ,W3C 的英文介紹沒仔細看,沒太懂 Literal 說的是什么)。

w3school 給的一個例子,比較有代表性吧。

路徑表達式 結(jié)果
/bookstore/book[1] 選取屬于 bookstore 子元素的第一個 book 元素。
/bookstore/book[last()] 選取屬于 bookstore 子元素的最后一個 book 元素。
/bookstore/book[last()-1] 選取屬于 bookstore 子元素的倒數(shù)第二個 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 的軸定于的是相對于當(dāng)前節(jié)點具有某種相同特征的節(jié)點集。

軸名稱 結(jié)果
ancestor 選取當(dāng)前節(jié)點的所有先輩(父、祖父等)。
ancestor-or-self 選取當(dāng)前節(jié)點的所有先輩(父、祖父等)以及當(dāng)前節(jié)點本身。
attribute 選取當(dāng)前節(jié)點的所有屬性。
child 選取當(dāng)前節(jié)點的所有子元素。
descendant 選取當(dāng)前節(jié)點的所有后代元素(子、孫等)。
descendant-or-self 選取當(dāng)前節(jié)點的所有后代元素(子、孫等)以及當(dāng)前節(jié)點本身。
following 選取文檔中當(dāng)前節(jié)點的結(jié)束標簽之后的所有節(jié)點。
namespace 選取當(dāng)前節(jié)點的所有命名空間節(jié)點。
parent 選取當(dāng)前節(jié)點的父節(jié)點。
preceding 選取文檔中當(dāng)前節(jié)點的開始標簽之前的所有節(jié)點。
preceding-sibling 選取當(dāng)前節(jié)點之前的所有同級節(jié)點。
self 選取當(dāng)前節(jié)點。

每一個step都是由軸名稱::NodeTest[謂語]構(gòu)成。比如:child::*/child::pricechild::*child::price都是一個step。

lxml.html

Since version 2.0, lxml comes with a dedicated Python package for dealing with HTML: lxml.html. It is based on lxml's HTML parser, but provides a special Element API for HTML elements, as well as a number of utilities for common HTML processing tasks.

The main API is based on the lxml.etree API, and thus, on the ElementTree API.

lxml.html 是一個專門用來處理 HTML 文檔的,雖然他依舊是基于 HTML parser 的,但是她提供了很多有針對性的 API 。這里只作一個簡單介紹,深入了解,請查閱官方文檔。

首先在解析 HTML 文本上增加了幾個方法:

  • document_fromstring(string)
  • fragment_fromstring(string, create_parent=False)
  • fragments_fromstring(string)

其次對于 HTML 元素,除了之前的方法之外又加了一些:

  • .drop_tree()

    刪除該元素及其所有子元素,就像 el.getparent().remove(el),但是不會刪除該元素的 .tail ,.tail 將會與前一個元素合并。

  • .drop_tag()

    刪除該標簽,但是會保留該元素的子元素與文本。

  • .find_class(class_name)

    返回一個含有 class_name CSS 的元素列表。

  • .find_rel_links(rel)

    返回一個<a rel="{rel}">的元素列表

  • .get_element_by_id(id, default=None)

    通過 id 查找元素,如果沒有返回默認,假如文檔不合規(guī)出現(xiàn)多個同 id 的元素,僅返回第一個。

  • .text_content()

    返回元素的文本內(nèi)容,包括其子代的文本內(nèi)容。

  • .cssselect(expr)

    通過 CSS 選擇器選擇元素。

  • .set(key, value=None)

    設(shè)置元素屬性,如果 value 沒有給或者是 None ,那么將創(chuàng)建一個 boolean attribute (屬性只要出現(xiàn)即表示真,無論 value 是什么,哪怕是個空串) E.g. <div checked></div>。

正如文章開頭說的那個, lxml.html.soupparser 可以引入 BeautifulSoup ,甚至可以僅使用 BeautifulSoup 的編碼檢測模塊 UnicodeDammit 。

最后就是可以使用 lxml.html.open_in_browser(lxml_doc) 直接在瀏覽器中顯示文檔,甚至可以對文檔的表單進行提交操作。

以上都是據(jù)自己的理解而寫,如有錯誤之處,歡迎交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容