在學習xpath提取資料之前,得先了解下解析HTML代碼的一些方法,如果讀者想更加深入學習HTML代碼等相關内容,需要去檢視下前端HTML相關内容,本文僅介紹網絡爬蟲需要用到的部分内容。
本文介紹使用lxml子產品解析HTML與XML,因其支援XPath解析方式,且在解析效率方面非常優秀。
Tips: 你可以通過指令安裝
pip install lxml
lxml子產品
解析HTML
parse()
etree.parse(source, parser=None, *, base_url=None)
複制
傳回加載源元素的ElementTree對象。如果沒有解析器作為第二個參數提供,則使用預設解析器。
source: 可以是下列任何一種:
- 檔案名/路徑
- 檔案對象
- 一個類似檔案的對象
- 使用HTTP或FTP協定的URL
注意,從檔案路徑或URL解析通常更快,而不是從打開的檔案對象或類檔案對象。支援從gzip壓縮源透明解壓(除非在libxml2中顯式禁用)。
base_url:
關鍵字允許為文檔設定URL從類檔案對象進行解析時。這是在尋找時需要的具有相對路徑的外部實體(DTD, XInclude,…)。
fromstring()
如果要解析字元串,請使用
'fromstring()'
函數。fromstring函數可以把一串xml解析為一個xml元素(傳回值類型和etree.Element一樣,是lxml.etree._Element類)。
>>> some_xml_data = "<root>data</root>"
>>> root = etree.fromstring(some_xml_data)
>>> etree.tostring(root)
b'<root>data</root>'
複制
1、解析本地的HTML檔案
# 'studio.html'
from lxml import etree
parser = etree.HTMLPaser() # 建立HTMLPaser對象
html = etree.parse('studio.html', parser=parser) # 解析本地的HTML檔案
html_txt = etree.tostring(html, encoding='utf-8')# 轉換字元串類型,并進行編碼
html_txt.decode('utf-8')
複制
使用tostring()可以提取出xml中所含的全部文本。
HTML()
HTML函數會自動加上html和body元素(如果原字元串沒有的話),同樣是傳回Element類。
>>> root = etree.HTML("<p>data</p>")
>>> etree.tostring(root)
b'<html><body><p>data</p></body></html>'
複制
注意:HTML函數的傳回值依然會被當成标準XML處理。
2、解析字元串類型的HTML代碼
>>> root = etree.HTML('<head/><p>Hello<br/>World</p>')
# 沒有XML聲明, 預設為ASCII編碼。
>>> etree.tostring(root)
b'<html><head/><body><p>Hello<br/>World</p></body></html>'
>>> etree.tostring(root, method='html')
b'<html><head></head><body><p>Hello<br>World</p></body></html>'
# 換行
>>> html_txt = etree.tostring(root, method='html', encoding='uft-8')
>>> html_txt.decode('utf-8')
'<html><head></head><body><p>Hello<br>World</p></body></html>'
複制
lxml.etree.HTML(),lxml.etree.fromstring()和lxml.etree.tostring()
三者之間的差別和聯系
文檔格式化方法 | 類型type | 根節點 | 編碼方式 | XPath |
---|---|---|---|---|
etree.HTML() | <class 'lxml.etree._Element'> | html | (X.encode('utf-8')) | 支援 |
etree.fromstring() | <class 'lxml.etree._Element'> | 原文檔根節點 | (X.encode('utf-8')) | 支援 |
etree.tostring() | <class 'bytes'> | 無 | 無 | 不支援 |
- 從類型上看,
都是屬于同一種etree.HTML()和etree.fromstring()
,這個類型才會支援使用xpath。也就說"class類"
,不能使用xpath!etree.tostring()是"位元組bytes類"
- 從根節點看,
的文檔格式已經變成etree.HTML()
,是以根節點自然就是html類型
]。但是,html标簽
的根節點還是原文檔中的根節點,說明這種格式化方式并不改變原文檔的整體結構,這樣有利于使用xpath的絕對路徑方式查找資訊!而etree.fromstring()
是沒有所謂的根節點的,因為這個方法得到的文檔類型是etree.tostring()
。"bytes類"
- 從編碼方式上看,
的括号内參數都要以etree.HTML()和etree.fromstring()
!表格中的"utf-8"的方式進行編碼
。X是表示用read()方法之後的原文檔内容
3、解析伺服器傳回的HTML代碼
發送網絡請求後傳回的響應結果轉為字元串類型,如果傳回的結果是HTML代碼,則需要解析HTML代碼。
from lxml import etree
import requests
from requests.auth import HTTPBasicAuth
url = 'http://'
auth = HTTPBasicAuth('username', 'password')
response = requests.get(url=url, auth=auth)
if response.status_code == 200:
html = etree.HTML(response.text)
html_txt = etree.tostring(html, encoding='utf-8')
print(html_txt.decode('utf-8'))
複制
XPath解析方式
官方網站( https://www.w3.org/TR/xpath/all/
)
XPath
是一門路徑提取語言,常用于從
html/xml
檔案中提取資訊。它的基規則如下.
選取節點
表達式 | 描述 |
---|---|
nodename | 選取此節點的所有子節點 |
/ | 從根節點選取 |
// | 從比對選擇的目前節點選擇文檔中的節點,而不考慮他們的位置 |
. | 選取目前節點 |
.. | 選取目前節點的父節點 |
@ | 選取屬性 |
* | 選取所有節點 |
下面為一些路徑表達式及表達式結果:
路徑表達式 | 結果 |
---|---|
petstore | 選取 petstore 元素的所有子節點 |
/petstore | 選取根元素 petstore。注釋:假如路徑起始于正斜杠( / ),則此路徑始終代表到某元素的絕對路徑! |
petstore/corgi | 選取屬于 petstore 的子元素的所有 corgi 元素 |
//corgi | 選取所有 corgi 子元素,而不管它們在文檔中的位置。 |
petstore//corgi | 選擇屬于 petstore 元素的後代的所有 corgi 元素,而不管它們位于 petstore 之下的什麼位置。 |
//@dog | 選取名為 dog 的所有屬性。 |
謂語(Predicates)
謂語用來查找某個特定的節點或者包含某個指定的值的節點。謂語被嵌在方括号中。下面為一些帶有謂語的路徑表達式,及表達式結果。
路徑表達式 | 結果 |
---|---|
/petstore/corgi[1] | 選取屬于 petstore 子元素的第一個 corgi 元素。 |
/petstore/corgi[last()] | 選取屬于 petstore 子元素的最後一個 corgi 元素。 |
/petstore/corgi[last()-1] | 選取屬于 petstore 子元素的倒數第二個 corgi 元素。 |
/petstore/corgi[position()<3] | 選取最前面的兩個屬于 petstore 元素的子元素的 corgi 元素。 |
//title[@dog] | 選取所有擁有名為 dog 的屬性的 title 元素。 |
//title[@dog='female'] | 選取所有 title 元素,且這些元素擁有值為 female 的 dog 屬性。 |
/petstore/corgi[price>2500.00] | 選取 petstore 元素的所有 corgi 元素,且其中的 price 元素的值須大于 2500.00。 |
/petstore/corgi[price>2500.00]/title | 選取 petstore 元素中的 corgi 元素的所有 title 元素,且其中的 price 元素的值須大于 2500.00。 |
//div[contains(@class,"f1")] | 選擇div屬性包含"f1"的元素 |
選取未知節點
XPath 通配符可用來選取未知的 XML 元素。
通配符 | 描述 |
---|---|
* | 比對任何元素節點。 |
@* | 比對任何屬性節點。 |
node() | 比對任何類型的節點。 |
在下面的表格中,我們列出了一些路徑表達式,以及這些表達式的結果:
路徑表達式 | 結果 |
---|---|
/petstore/* | 選取 petstore 元素的所有子元素。 |
//* | 選取文檔中的所有元素。 |
html/node()/meta/@* | 選擇html下面任意節點下的meta節點的所有屬性 |
//title[@*] | 選取所有帶有屬性的 title 元素。 |
contains()方法
實作屬性多值比對
contains(指定屬性名稱, 指定屬性值)
如需既擷取class=class="main-hd",又要擷取class="main"的節點時,如果HTML代碼中包含指定的屬性值,就可以比對成功。
html = etree.HTML(html_str)
div_all = html.xpath('//div[contains(@lcass, "main")]/text()') # text()擷取問題,下面介紹
複制
and
多屬性比對
在一個節點中出現多個屬性,這時就需要同時多個屬性,以便更加精确地擷取指定節點中的資料。
>>> from lxml import etree
>>> html_str = '''
<div class="main-hd">
<div class="main review-item" id="13045497">雲朵</div>
<div class="main review-item" id="13054201">資料STUDIO</div>
</div>
'''
>>> html = etree.HTML(html_str)
>>> div_all = html.xpath('//div[@class="main review-item" and @id="13054201"]/text()')
>>> print(div_all)
['資料STUDIO']
複制
擷取文本
可以使用XPath的
text()
方法擷取HTML代碼中的文本。
>>> from lxml import etree
>>> html_str = '''
<header class="main-hd">
<a href="https://www.douban.com/people/162291901/" class="name">蒼華</a>
<span class="allstar10 main-title-rating" title="很差"/>
<span content="2020-12-10" class="main-meta">2020-12-10 01:29:29</span>
</header>
'''
>>> html = etree.HTML(html_str)
>>> a_text = html.xpath('//a/text()')
>>> print(f'所有a下節點文本資訊:{a_text}')
所有a下節點文本資訊:['蒼華']
複制
XPath表達式中運算符:
運算符 | 描述 | 執行個體 | 傳回值 |
---|---|---|---|
+ | 加法 | 5 + 4 | 9 |
– | 減法 | 5 – 4 | 1 |
* | 乘法 | 5 * 4 | 20 |
div | 除法 | 5 div 5 | 1 |
= | 等于 | price=100.0 | 如果 price 是 100.0,則傳回 true。如果 price 是 110.0,則傳回 false。 |
!= | 不等于 | price!=100.0 | 如果 price 是 100.0,則傳回 true。如果 price 是 99.0,則傳回 false。 |
< | 小于 | price<100.0 | 如果 price 是 99.0,則傳回 true。如果 price 是 100.0,則傳回 false。 |
<= | 小于或等于 | price<=100.0 | 如果 price 是 100.0,則傳回 true。如果 price 是 99.0,則傳回 false。 |
> | 大于 | price>100.0 | 如果 price 是 110.0,則傳回 true。如果 price 是 99.0,則傳回 false。 |
>= | 大于或等于 | price>=100.0 | 如果 price 是 110.0,則傳回 true。如果 price 是 99.0,則傳回 false。 |
or | 或 | price=100.0or price=99.0 | 如果 price 是 99.0,則傳回 true。如果 price 是 99.8,則傳回 false。 |
and | 與 | price>99.0 and price<100.0 | 如果 price 是 99.8,則傳回 true。如果 price 是 88.0,則傳回 false。 |
mod | 計算除法的餘數 | 6 mod 4 | 2 |
| | 計算兩個節點集 | //div|//ul | 傳回所有div和a節點集 |
XPath 軸(Axes)
軸可定義相對于目前節點的節點集。
軸名稱 | 結果 |
---|---|
ancestor | 目前節點的所有先輩(父、祖父等)。 |
ancestor-or-self | 目前節點的所有先輩(父、祖父等)以及目前節點本身。 |
attribute | 目前節點的所有屬性。 |
child | 目前節點的所有子元素。 |
descendant | 目前節點的所有後代元素(子、孫等)。 |
descendant-or-self | 目前節點的所有後代元素(子、孫等)以及目前節點本身。 |
following | 文檔中目前節點的結束标簽之後的所有節點。 |
following-sibling | 目前節點之後的所有兄弟節點 |
namespace | 目前節點的所有命名空間節點。 |
parent | 目前節點的父節點。 |
preceding | 文檔中目前節點的開始标簽之前的所有節點。 |
preceding-sibling | 目前節點之前的所有同級節點。 |
self | 目前節點。 |
例
from lxml import etree
html_str = '''
<div class="main-hd">
<li><a href="https://www.douban.com/people/162291901/" class="name">蒼華</a>
</div>
'''
html = etree.HTML(html_str)
# 擷取li[0]所有祖先節點
html.xpath('//li[0]/ancestor::*')
# 擷取li[0]屬性為class="main"的祖先節點
class_div = html.xpath('//li[0]/ancestor::*[@class="main"]')
複制