有同學說,我正則用的不好,處理HTML文檔很累,有沒有其他的方法?
有!那就是XPath,我們可以用先將HTML文檔轉換成XML文檔,然后用XPath查找HTML節點或元素。
什么是XML
- XML指可擴展標記語言(Extensible Markup Language)
- XML是一種標記語言,很類似HTML
- XML的設計宗旨是傳輸數據,而非顯示數據。
- XML的標簽需要我們自行定義。
- XML被設計為具有自我描述性。
- XML是W3C的推薦標准。
W3School官方文檔:http://www.w3school.com.cn/xml/index.asp
XML和HTML的區別
數據格式 | 描述 | 設計目標 |
---|---|---|
XML | Extensible Markup Language (可擴展標記語言) | 被設計為傳輸和存儲數據,其焦點是數據的內容。 |
HTML | HyperText Markup Language(超文本標記語言) | 顯示數據以及如何更好顯示數據。 |
HTML DOM | Document Object Model for HTML (文檔對象模型) | 通過 HTML DOM,可以訪問所有的 HTML 元素,連同它們所包含的文本和屬性。可以對其中的內容進行修改和刪除,同時也可以創建新的元素。 |
XML文檔實例
<?xml version="1.0" encoding="utf-8"?>
<bookstore>
<book category="cooking">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="children">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="web">
<title lang="en">XQuery Kick Start</title>
<author>James McGovern</author>
<author>Per Bothner</author>
<author>Kurt Cagle</author>
<author>James Linn</author>
<author>Vaidyanathan Nagarajan</author>
<year>2003</year>
<price>49.99</price>
</book>
<book category="web" cover="paperback">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
HTML DOM模型示例
HTML DOM定義了訪問和操作HTML文檔的標准方法,以樹結構方式表達了HTML文檔。
XML的節點關系
1.父(Parent)
每個元素以及屬性都有一個父。
下面是一個簡單的XML例子中,book元素時title、author、year以及price
<?xml version="1.0" encoding="utf-8"?>
<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
2.子(Children)
元素節點可能有零個、一個或多個子。
在下面的例子中,title、author、year以及price元素都是book元素的子:
<?xml version="1.0" encoding="utf-8"?>
<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
3.同胞(Sibling)
擁有相同的父的節點
在下面的例子中,title、author、year以及price元素都是同胞:
<?xml version="1.0" encoding="utf-8"?>
<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
4.先輩(Ancestor)
某節點的父、父的父,等等。
在下面的例子中,title元素的先輩是book元素和bookstore元素:
<?xml version="1.0" encoding="utf-8"?>
<bookstore>
<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
</bookstore>
5.后代
某個節點的子,子的子,等等。
在下面的例子中,bookstore的后代是book、title、author、year以及price元素:
<?xml version="1.0" encoding="utf-8"?>
<bookstore>
<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
</bookstore>
什么是XPath?
XPath(XML Path Language)是一門在XML文檔中查找信息的語言,可用來在XML中對元素和屬性進行遍歷。
W3School官方文檔:http://www.w3school.com.cn/xpath/index.asp
XPath 開發工具
- 開源的XPath表達式編輯工具:XML Quire(XML格式文件可用)
- Chrome插件Xpath Helper
- Firefox插件Xpath Checker
選取節點
XPath使用路徑表達式來選取XML文檔中的節點或者節點集。這些路徑表達式和我們常規的電腦文件系統中看到的表達式非常相似。
下面列出了最常用的路徑表達式:
表達式 | 描述 |
---|---|
nodename | 選取此節點的所有子節點 |
/ | 從根節點選取 |
// | 從匹配選擇的當前節點選擇文檔中的節點,而不考慮它們的位置。 |
. | 選取當前節點。 |
.. | 選取當前節點的父節點 |
@ | 選取屬性 |
在下面的表格中,我們已列出了一些路徑表達式以及表達式的結果:
路徑表達式 | 描述 |
---|---|
bookstore | 選取bookstore元素的所有子節點。 |
/bookstore | 選取根元素 bookstore。注釋:假如路徑起始於正斜杠( / ),則此路徑始終代表到某元素的絕對路徑! |
bookstore/book | 選取屬於bookstore的子元素的所有book元素 |
//book | 選取所有book子元素,而不管它們在文檔中的位置 |
bookstore//book | 選擇屬於bookstore元素的后代的所有bok元素,而不管它們位於bookstore之下的什么位置 |
//@lang | 選取名為lang的所有屬性。 |
謂語(Predicates)
謂語用來查找某個特定的節點或者包含某個特定的值的節點,被嵌在方括號中。
在下面的表格中,我們列出了帶有謂語的一些路徑表達式,以及表達式的結果:
路徑表達式 | 結果 |
---|---|
/bookstore/book[1] | 選取屬於bookstore子元素的第一個book元素。 |
/bookstore/book[last()] | 選取數據bookstore子元素的最后一個book元素 |
/bookstore/book[last()-1] | 選取屬於bookstore元素的倒數第二個book元素 |
/bookstore/book[position()❤️] | 選取最前面的兩個屬於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元素 |
XPath的運算符
以上就是XPath的語法內容,在運用到Python抓取時要先轉換為xml.
lxml庫
lxml是一個HTML/XML的解析器,主要的功能是如何提取和解析HTML/XML數據。
lxml和正則一樣,也是用C實現,是一款高性能的Python HTML/XML解析器,我們可以利用之前學習的XPath語法,來快速的定位特定元素以及節點信息。
lxml python官方文檔:http://lxml.de/index.html
需要安裝C語言庫,可使用pip安裝:pip install lxml(或通過wheel方式安裝)
初步使用
我們利用它來解析HTML代碼,簡單實例:
#-*- coding:utf-8 -*-
#lxml_test.py
#使用lxml的etree庫
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> # 注意,此處缺少一個 </li> 閉合標簽
</ul>
</div>
'''
#利用etree.HTML,將字符串解析為HTML文檔
html = etree.HTML(text)
#按字符串序列化為HTML文檔
result = etree.tostring(html)
print(result)
輸出結果:
<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>
lxml可以自動修正html代碼,例子里不僅補全里li標簽,還添加了body/html標簽
文件讀取:
除了直接讀取字符串,lxml還支持從文件里讀取內容。我們新建一個hello.html文檔:
<!--hello.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"><span class="bold">third item</span></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>
再利用etree.parse()方法來讀取文件。
#lxml_parse.py
from lxml import etree
#讀取外部文件hello.html
html = etree.parse('./hello.html')
result = etree.tostring(html, pretty_print=True)
print(result)
輸出結果與之前相同:
<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實例測試
1.獲取所有的<li>
標簽
#xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
print type(html) #顯示etree.parse()返回類型
result = html.xpath('//li')
print result #打印<li>標簽的的元素集合
print len(result)
print type(result)
print type(result[0])
輸出結果:
<type 'lxml.etree._ElementTree'>
[<Element li at 0x1014e0e18>, <Element li at 0x1014e0ef0>, <Element li at 0x1014e0f38>, <Element li at 0x1014e0f80>, <Element li at 0x1014e0fc8>]
5
<type 'list'>
<type 'lxml.etree._Element'>
2.繼續獲取<li>
標簽的所有class
屬性
#xpath_li.py
from lxml import etree
html = etree.parse('htllo.html')
result = html.xpath('//li/@class')
print result
運行結果:
['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']
3.繼續獲取<li>
標簽下href
為link1.html
的<a>
標簽
#xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li/a[@href="link1.html"]')
print result
運行結果:
[<Element a at 0x10ffaae18>]
4.獲取<li>
標簽下的所有<span>
標簽
#xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
#result = html.xpath('//li/span')
#注意這么寫是不對的
#因為/是用來獲取子元素的,而<span>不是<li>的子元素,所以,要用雙斜杠
result = html.xpath('//li//span')
print result
運行結果:
[<Element span at 0x10d698e18>]
5.獲取<li>
標簽下的<a>
標簽里的所有class
from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li/a//@class')
print result
運行結果
['blod']
6.獲取最后一個<li>
的<a>
的href
#xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li[last()]/a/@href')
#謂語[last()]可以找到最后一個元素
print result
運行結果
['link5.html']
7.獲取倒數第二個元素的內容
#xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//li[last()-1]/a')
#text方法可以獲取元素內容
print(result[0].text)
運行結果
fourth item
8.獲取class
值為bold
的標簽名
#xpath_li.py
from lxml import etree
html = etree.parse('hello.html')
result = html.xpath('//*[@class="bold"]')
#tag方法可以獲取標簽名
print result[0].tag
運行結果
span