正則表達式 筆記整理html
在用 Python 實現爬蟲時,可使用 requests 庫訪問資源,而後用正則表達式提取信息。python
可是,這裏會有一些繁瑣,由於正則表達式的書寫是比較嚴格的,萬一有一個地方寫錯了,可能會致使匹配失敗沒法提取須要的信息。正則表達式
對於網頁的節點來講,能夠定義 id、class 或其餘屬性。節點之間有層次關係,在網頁中,其實能夠經過 Xpath 定位一個或多個節點。微信
那麼相應的,在頁面解析的時候,利用 Xpath 定位節點,調用相應的方法獲取正文或者屬性,那麼徹底能夠獲取須要的信息。函數
在 Python 中,這個解析庫叫 lxml,下面來介紹這個解析庫的用法。測試
lxml 是 Python 的一個解析庫,支持 HTML 和 XML 的解析,支持 XPath 解析方式,效率很是高。編碼
使用 lxml 以前,須要先安裝,可使用以下命令:.net
$ pip install lxml
Xpath,全稱 XML Path Language,便是 XML 路徑語言。Xpath 是一門在 XML 文檔中查找信息的語言,用於在 XML 文檔中經過元素和屬性進行導航,但一樣適用於 HTML 文檔的搜索。設計
在實現爬蟲時,徹底能夠經過 Xpath 進行信息提取。
Xpath 的功能強大,使用路徑表達式來選取 XML 或 HTML 文檔中的節點或者節點集。Xpath 有超過 100 個內建的函數。這些函數可用於字符串、數值、日期和時間比較、節點、序列處理和邏輯值等等。
Xpath 於 1999 年 11 月 16 日成爲 W3C 標準,被設計爲供 XSLT、XPointer 以及其餘 XML 解析軟件使用。
前面說起了,Xpath 使用路徑表達式選取文檔中的節點或節點集。
下面羅列經常使用的路徑表達式:
表達式 | 描述說明 |
---|---|
nodename | 選取此節點的全部子節點 |
/ | 從根節點選取 |
// | 從當前節點選擇子孫節點(不考慮它們的位置) |
. | 選取當前節點 |
.. | 選取當前節點的父節點 |
@ | 選取屬性 |
上面羅列的內容屬於經常使用部分,用示例來講明下具體的用法:
//div[@class="document"]
這就是一個 Xpath 路徑表達式,表明的是選擇名稱爲 div,屬性 class 的值爲 document 的節點。
在 Python 中,會經過 lxml 庫,利用 Xpath 進行解析。
經過實例瞭解使用 Xpath 對網頁進行解析的過程,代碼以下(下面 HTML 內容節選自豆瓣,稍做更改):
# 先導入 lxml 庫 from lxml import etree text = """ <div> <ul> <li class="pl2"><a href="https://book.douban.com/subject/1007305/">紅樓夢</a> <li class="pl2"><a href="https://book.douban.com/subject/4913064/">活着</a></li> <li class="pl2"><a href="https://book.douban.com/subject/6082808/">百年孤獨</a></li> <li class="pl1"><a href="https://book.douban.com/subject/4820710/">1984</a></li> </ul> </div> """ html = etree.HTML(text) result = etree.tostring(html) print(result.decode('utf-8'))
在上面的實例中,先導入 lxml 庫中的 etree 模塊,聲明一段 HTML 文本,而後使用 etree 的 HTML 類進行初始化,構造一個 Xpath 解析對象。在這裏須要注意一點,實例中,聲明的 HTML 文本第 1 個節點沒有閉合,可是 etree 模塊會自動修正。
etree.toString() 方法用於輸出修正後的 HTML 內容,不過該方法返回的是 byte 類型,輸出的時候須要進行解碼轉換爲 str 類型。
上面的輸出結果以下:
<html><body><div> <ul> <li class="pl2"><a href="https://book.douban.com/subject/1007305/">红楼梦</a> </li><li class="pl2"><a href="https://book.douban.com/subject/4913064/">活着</a></li> <li class="pl2"><a href="https://book.douban.com/subject/6082808/">百年孤独</a></li> <li class="pl1"><a href="https://book.douban.com/subject/4820710/">1984</a></li> </ul> </div> </body></html>
在這裏能夠看到 li 節點標籤已經補全,同時自動添加了 body、html 節點。
上面的代碼中,中文沒有正常顯示。這裏屬於編碼的問題,能夠將上面的代碼稍微修改一下:
result = etree.tostring(html, encoding='gbk') print(result.decode('gbk'))
再看輸出結果:
<?xml version='1.0' encoding='gbk'?> <html><body><div> <ul> <li class="pl2"><a href="https://book.douban.com/subject/1007305/">紅樓夢</a> </li><li class="pl2"><a href="https://book.douban.com/subject/4913064/">活着 </a></li> <li class="pl2"><a href="https://book.douban.com/subject/6082808/">百年孤獨< /a></li> <li class="pl1"><a href="https://book.douban.com/subject/4820710/">1984</a>< /li> </ul> </div> </body></html>
這裏有所不一樣,前面多了個聲明,同時標記編碼方式爲 GBK。
另外, lxml 庫也能夠直接讀取文件進行解析,示例以下(先將上面的未修正的 HTML 內容放到 example.html 文件中):
from lxml import etree html = etree.parse('./example.html', etree.HTMLParser()) result = etree.tostring(html) print(result.decode('utf-8'))
這個時候輸出的結果會多一個 DOCTYPE 的聲明。
以 //
開頭的 Xpath 表達式爲選取全部符合要求的節點,沿用上面的例子:
... result = html.xpath('//*') print(result)
運行結果:
[<Element html at 0x4b34fc8>, <Element body at 0x4b3b108>, <Element div at 0x4b3b088>, <Element ul at 0x4b3b148>, <Element li at 0x4b3b188>, <Element a at 0x4b3b208>, <Element li at 0x4b3b248>, <Element a at 0x4b3b288>, <Element li at 0x4b3b2c8>, <Element a at 0x4b3b1c8>, <Element li at 0x4b3b308>, <Element a at 0x4b3b588>]
在這裏, *
表示匹配全部的節點,由運行結果能夠看出,返回的列表中,包括了 html, body, div, ul, li, a
全部節點。
固然 //
後面能夠跟特定的節點,例如:
... result = html.xpath('//a') print(result)
運行結果:
[<Element a at 0x2d1d688>, <Element a at 0x2d1d648>, <Element a at 0x2d1d748>, <Element a at 0x2d1d788>]
/
或者 //
能夠用來定位子節點或者子孫節點,例如定位 li 節點的全部 a 節點:
... result = html.xpath('//li/a') print(result)
運行結果:
[<Element a at 0x2cfd688>, <Element a at 0x2cfd648>, <Element a at 0x2cfd748>, <Element a at 0x2cfd788>]
在這裏能夠看到,與上面直接用 //a
表達式獲取的結果相同,但這裏有所區別,//a
表達式找的全部的 a 節點,//li/a
這裏找的是全部 li 節點的全部直接 a 子節點。
好比,有以下標籤內容:
<title><a href="link.html">Title</a></title>
用這個示例來區分,根據上面的區分解釋,在這裏用 //a
是能夠匹配到這項內容,可是 //li/a
則匹配不到,由於示例中 a 節點並不是 li 節點的直接子節點。
在原來的 HTML 文檔內容中,a 是 li 的直接節點,也是 ul 的子孫節點,那麼要定位 a 節點,也能夠按照以下的表達式來寫:
... result = html.xpath('//ul//a') print(result)
這裏獲得的結果跟上面是一致的:
[<Element a at 0x2cfd688>, <Element a at 0x2cfd648>, <Element a at 0x2cfd748>, <Element a at 0x2cfd788>]
可是要注意,不可以寫成 //ul/a
,由於 a 並不是 ul 的直接子節點,若是這樣寫則沒法匹配,返回空列表。
因此要對 /
和 //
加以區分,/
用於獲取直接子節點,//
用於獲取子孫節點。
獲取父節點的信息,用 ..
來實現,例如:
<li class="p12"><a href="https://book.douban.com/subject/1007305/"></a>紅樓夢</li>
想要獲取 href 屬性爲 "https://book.douban.com/subject/1007305/"
的 a 節點的父節點屬性。
代碼以下:
... result = html.xpath('//a[@href="https://book.douban.com/subject/1007305/"]/../@class') print(result)
運行結果:
['pl2']
這個結果正是父節點的屬性。
節點中,屬性可存在單值或多值的狀況,一個節點也能夠有多個屬性,當出現這些狀況時,使用的表達式每每不可以一成不變,須要針對性進行書寫。
在上面的例子中,其實已經使用屬性匹配,@
符號用於屬性過濾。在上面的例子當中,有一個屬性跟其餘的不一樣,如今將其定位,代碼實現:
... result = html.xpath('//li[@class="pl1"]') print(result)
運行結果:
[<Element li at 0x2cfd688>]
[@class="pl1"]
這部分對定位進行了限制,找的是 class 屬性值爲 pl1 的節點。
屬性有時候可能不止 1 個,以下示例:
<li class="pl1 pl2"><a href="https://book.douban.com/subject/4820710/">1984</a></li>
將 li 的屬性值改成 pl1 pl2
,若是仍是用原來的表達式的話:
... result = html.xpath('//li[@class="pl1"]') print(result)
獲得的是空列表:
[]
這個時候,要考慮使用 contains()
方法,這個方法須要的參數有:第一個參數是屬性名稱,第二個參數是屬性值。該方法的實現過程是,若第一個參數屬性包含第二個參數中的屬性值,則能夠匹配成功。例如:
... result = html.xpath('//li[contains(@class, "pl1")]') print(result)
運行結果:
[<Element li at 0x2d1d648>]
這個方法在屬性值不止 1 個的狀況下,很是有用。
在節點中,除了單個屬性能夠有多個值以外,也能夠有多個屬性。假設有以下節點:
<li class="pl1 pl2" name="item"><a href="https://book.douban.com/subject/4820710/">1984</a></li>
這種狀況要用到 Xpath 運算符,下面羅列經常使用的運算符:
運算符 | 描述 | 實例 | 返回值 |
---|---|---|---|
丨 | 計算兩個節點集 | //book 丨 //cd | 返回擁有 book 和 cd 元素的節點 |
+ | 加法 | 6 + 4 | 10 |
- | 減法 | 6 - 4 | 2 |
* | 乘法 | 6 * 4 | 24 |
div | 除法 | 9 div 3 | 3 |
= | 等於 | stature=178 | 當 stature 爲 178 時,返回 true;不然,返回 false. |
!= | 不等於 | stature!=178 | 當 stature 不是 178 時,返回 true;不然,返回 false |
< | 小於 | stature<178 | 當 stature 爲 177 時,返回 true;當 stature 爲 179 時,返回 false |
<= | 小於或等於 | stature<=178 | 當 stature 爲 177 時,返回 true;當 stature 爲 179 時,返回 false |
> | 大於 | stature>178 | 當 stature 爲 179 時,返回 true;當 stature 爲 177 時,返回 false |
>= | 大於 | stature>=178 | 當 stature 爲 179 時,返回 true;當 stature 爲 177 時,返回 false |
or | 或 | stature=178 or stature=179 | 當 stature=178 時,返回 true;當 stature=175 時,返回 false |
and | 與 | stature>175 and stature<178 | 當 stature=178 時,返回 true;當 stature=165 時,返回 false |
mod | 取餘 | 5 mod 2 | 1 |
在這裏,使用 and 運算符將多個屬性鏈接:
... result = html.xpath('//li[contains(@class, "pl1") and @name="item"]') print(result)
運算結果:
[<Element li at 0x2cfd688>]
這裏要與上面區分開,上面都是根據屬性去定位節點。如今是想查找某個節點的確切屬性。例如查找 li 下 a 節點的 href 屬性:
... result = html.xpath('//li/a/@href') print(result)
返回結果:
['https://book.douban.com/subject/1007305/', 'https://book.douban.com/subject/4913064/', 'https://book.douban.com/subject/6082808/', 'https://book.douban.com/subject/4820710/']
這裏 /@href
是爲了獲取節點屬性,上面 [@class="pl1"]
是爲了限定屬性查找節點,要加以區分。
Xpath 用 text() 方法獲取文本,如今嘗試獲取上面屬性所演示的示例,獲取節點中的文本,同時驗證上面定位的是不是屬性值爲 pl1 的節點:
... result = html.xpath('//li[@class="pl1"]/a/text()') print(result)
運行結果:
['1984']
從結果來看,上面屬性示例中返回的節點,的確是屬性值爲 pl1 的節點。這裏須要注意,由於文本是被 a 節點包裹着的,若是直接在 li 節點下使用 /text()
是獲取不到想要的信息的。若是改爲 //text()
表達式,則能夠獲取全部子孫節點的文本,但這裏可能獲取的內容會有些誤差,有可能會獲取到換行符,這個並非想要的信息。以下示例:
result = html.xpath('//li[@class="pl1"]//text()') print(result) # 輸出結果: # ['\n ', '1984', '\n ']
這裏就是須要注意的地方,若是要想獲取特定子節點的文本,首先建議先找到特定的子節點,而後在子節點下使用 text()
方法,這樣確保獲取的信息是整潔的。
軸可定義相對當前節點的節點集。
先羅列一些簡單的軸及其含義:
軸名稱 | 含義 |
---|---|
ancestor | 選取當前節點的全部祖先節點 |
attribute | 選取當前節點的全部屬性 |
child | 選取當前節點的全部直接子節點 |
descendant | 選取當前節點的全部子孫節點 |
following | 選取當前節點以後的全部節點 |
更多軸的詳細用法可參考:https://www.w3school.com.cn/xpath/xpath_axes.asp
使用軸的語法:
軸名稱::節點測試[謂語]
沿用上面的例子,關於軸的簡單實例:
例子 | 結果 |
---|---|
//li/ancestor:: * | 選取 li 節點的全部祖先節點 |
//li/ancestor::div | 這裏加了 div 加以限定,因此僅返回 div 節點 |
//li/attribute:: * | 獲取 li 節點的全部屬性 |
//li/child::a[@href="#"] | 這裏加了限定條件,因此僅返回 href 屬性爲 # 的 a 節點 |
//li/descentdant:: * | 獲取 li 節點的全部子孫節點 |
//li/following:: * | 獲取 li 節點後續的全部節點 |
以上就是關於 Xpath 的內容, Xpath 還有一些函數,文章未說起,若是有興趣的話,能夠參考:https://www.w3school.com.cn/xpath/xpath_functions.asp
歡迎關注微信公衆號《書所集錄》