2 XPath介紹與lxml庫
參考連接:
XPath教程
https://www.w3school.com.cn/xpath/index.asp
lxml文檔
https://lxml.de/index.html#support-the-project
爬蟲專欄
https://blog.csdn.net/m0_38139250/category_12001010.html
2.1 XPath概述
XPath的中文名稱為XML路徑語言(XML Path Language),其最初的設(shè)計是用來搜索 XML 文檔,但也適用于HTML文檔搜索。1996年11月,XPath 成為W3C標準, XQuery 和 XPointer 都構(gòu)建于 XPath 表達之上。
XPath有著強大的搜索選擇功能,提供了簡潔的路徑選擇表達式, 提供了100+的內(nèi)建函數(shù),可以完成XML和HTML的絕大部分的定位搜索需求。
XML和HTML均可通過樹形結(jié)構(gòu)的DOM(文檔對象模型,Document Object Model)表示,DOM中包含元素節(jié)點,文本節(jié)點,屬性節(jié)點三種節(jié)點。
其中元素節(jié)點是DOM的基礎(chǔ),元素就是DOM中的標簽,
如<html>是根元素,代表整個文檔,其他的元素還包括<head>,<body>,<div>,<ul>,<span>等,元素節(jié)點之間可以相互包含。
文本節(jié)點:包含在元素節(jié)點中,
比如<span>文本節(jié)點</span>。
屬性節(jié)點:元素節(jié)點可以包含一些屬性,屬性的作用是對元素做出更具體的描述,
如<span class="屬性節(jié)點值">文本節(jié)點</span>。
XPath的核心思想就是寫地址,通過地址查找到XML和HTML中的元素,文本,屬性等信息。
獲取元素n:
//標簽[@屬性1="屬性值1"]/標簽[@屬性2="屬性值2"]/.../標簽n
獲取文本:
//標簽[@屬性1="屬性值1"]/標簽[@屬性2="屬性值2"]/.../text()
獲取屬性n的值:
//標簽[@屬性1="屬性值1"]/標簽[@屬性2="屬性值2"]/.../@屬性n
[@屬性1=“屬性值1”]是謂語,用于過濾相同的標簽,如果不需要通過屬性過濾標簽,可以不加謂語過濾。
下面介紹XPath的節(jié)點類型和常用語法。
1)節(jié)點(Node): XPath包括元素、屬性、文本、命名空間、處理指令、注釋以及文檔(根)等七種類型的節(jié)點。XML 文檔是被作為節(jié)點樹來對待的。樹的根被稱為文檔節(jié)點或者根節(jié)點。節(jié)點之間的關(guān)系包括父(Parent),子(Children),同胞(Sibling),先輩(Ancestor),后代(Descendant)。
2)語法:
XPath中,通過路徑(Path)和步(Step)在XML文檔中獲取節(jié)點。
a.常用的路徑表達式
常見的路徑表達式如下表所示:
表 XPath表達式與示例b.謂語(Predicates)
為查找特點節(jié)點或包含某個指定值的節(jié)點,可以使用謂語(Predicates),謂語用方括號[]表示,如:
//div[@class=‘useful’]
表示選取所有div 元素,且這些元素擁有值為 useful的 class屬性。
//div[@class=‘useful’]//li[last()]
表示選取具有class值為useful的div標簽下的任意li元素的最后一個li元素。
c.選取未知節(jié)點
XPath可以通過通配符搜索未知節(jié)點,如*表示匹配任何元素,@*表示匹配任何帶有屬性的節(jié)點,node()表示匹配任何類型的節(jié)點。如:
//title[@*]
表示選取所有帶有屬性的title元素。
d.選取若干路徑
XPath可以通過“|”運算符表示選取若干路徑。如
//title | //price
表示選取文檔中的所有 title 和 price 元素
3)軸與步:
a.XPath軸(axis)
軸表示當前節(jié)點的節(jié)點集XPath軸的名稱見表13-2所示:
表13-2 XPath軸名稱與結(jié)果
b.步(Step)
步可以根據(jù)當前節(jié)點集中的節(jié)點來進行計算搜索。
步的語法:
軸名稱::節(jié)點測試[謂語]
其中,軸(axis)表示所選節(jié)點與當前節(jié)點之間的關(guān)系,節(jié)點測試(node-test)表示是某給定軸內(nèi)部的節(jié)點,謂語(predicate)用于搜索特定的節(jié)點集。
步的使用如表13-3所示:
步的使用案例如下:
//div[@class=“useless”]/descendant::a’)
獲取任意class屬性值為useless的div標簽下得所有子孫a標簽節(jié)點。
2.2 lxml庫介紹
Web數(shù)據(jù)展示都通過HTML格式,如果采用正則表達式匹配lxml是Python中的第三方庫,主要用于處理搜索XML和HTML格式數(shù)據(jù)。
2.2.1 lxml庫安裝
安裝lxml:
pip install lxml==4.8.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
如果安裝不成,可以在
https://www.lfd.uci.edu/~gohlke/pythonlibs/
下載對應(yīng)的whl安裝包,然后安裝即可。
如果部分讀者還是安裝不成,可以把whl包解壓,然后把解壓后的兩個文件夾放在python安裝文件夾下的Lib\site-packages目錄下即可。
2.2.2 lxml庫基本使用
lxml的使用首先需要導(dǎo)入lxml的etree模塊:
from lxml import etree
etree模塊可以對HTML文件進行自動修正,lxml中的相關(guān)使用方法如下:
讀取數(shù)據(jù):
etree.HTML(text, parser=None, base_url=None,)
第一個參數(shù)text為一個字符串,字符串應(yīng)該可以轉(zhuǎn)換為HTML或XML文檔,如果字符串中的標簽存在不閉合等問題,本方法會自動修正,并把文本轉(zhuǎn)換成為HTML格式文檔。返回結(jié)果類型為’lxml.etree._Element’。
etree.fromstring(text, parser=None, base_url=None)
與etree.HTML()類似,但轉(zhuǎn)換過程中,要求text字符串為標準的XML或HTML格式,否則會拋出異常。返回結(jié)果類型為’lxml.etree._Element’。
etree.parse(source, parser=None, base_url=None)
可如果沒有解析器作為第二個參數(shù)提供,則使用默認解析器。返回一個加載了源元素的ElementTree對象,返回結(jié)果類型為’lxml.etree._ElementTree’。
etree.tostring(element_or_tree, encoding=None,)
輸出修正后的HTML代碼,返回結(jié)果為bytes類型。
搜索數(shù)據(jù):
假定有變量html為etree模塊讀取數(shù)據(jù)后返回’lxml.etree._Element’或’lxml.etree._ElementTree’類型,可以調(diào)用:
html.xpath(self, _path, namespaces=None, extensions=None, smart_strings=True, **_variables)
_path為xpath中的路徑表達式和步,xpath函數(shù)可以通過_path參數(shù)值實現(xiàn)對文檔的搜索。
2.2.3 lxml案例
下面根據(jù)具體案例來介紹lxml的基本使用。
a.讀取數(shù)據(jù)并補全
from lxml import etree
# 定義一個不規(guī)則的html文本
text = '''
<html><body><div><ul>
<li class="item-0">01 item</a></li>
'''
html = etree.HTML(text) # etree把不規(guī)則文本進行修正
complete_html = etree.tostring(html) # toString可輸出修正后的HTML代碼,返回結(jié)果為bytes
print("原數(shù)據(jù)------->", text)
print("修正后的數(shù)據(jù)--->\n",complete_html.decode('utf-8')) # 輸出修正后的html
輸出結(jié)果如下:
原數(shù)據(jù)------->
<html><body><div><ul>
<li class="item-0">01 item</a></li>
修正后的數(shù)據(jù)--->
<html><body><div><ul>
<li class="item-0">01 item</li>
</ul></div></body></html>
從輸出結(jié)果可以看出,etree.toString()可以對缺少閉合標簽的HTML文檔進行自動修正。
etree模塊可以調(diào)用HTML讀取字符串,也可以調(diào)用parse()方法讀取一個HTML格式的文件。把上面代碼中的text變量保存在文本文件中,文件命名為lxml.html。
from lxml import etree
# 讀取html文件
html = etree.parse("./lxml.html",etree.HTMLParser()) # etree把不規(guī)則文本進行修正
complete_html = etree.tostring(html) # toString可輸出修正后的HTML代碼,返回結(jié)果為bytes
print(complete_html.decode('utf-8'))
輸出結(jié)果如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<div><ul>
<li class="item-0"><a href="/link1.html">01 item</a></li></ul></div></body></html>
從輸出結(jié)果看可以看出,etree完成了HTML自動修正,同時還加上了!DOCTYPE標簽。
b.讀取數(shù)據(jù)并選取節(jié)點:
創(chuàng)建Demo11-03.html文件,內(nèi)容如下:
<!DOCTYPE html>
<html>
<body>
<div class="useful">
<ul>
<li class="cla-0" id="id-0"><a href="/link1">01</a></li>
<li class="cla-1"><a href="/link2">02</a></li>
<li><strong><a href="/link3">03</a></strong></li>
<li class="cla-1"><a href="/link4">04</a></li>
<li class="cla-0"><a href="/link5">05</a></li>
</ul>
</div>
<div class="useless">
<ul>
<li class="cla-0"><a href="/link1">useless-01</a></li>
<li class="cla-1"><a href="/link2">useless-02</a></li>
</ul>
</div>
</body>
</html>
導(dǎo)入庫,并通過etree讀取html文檔:
from lxml import etree
# 加載HTML文檔
html = etree.parse("./Demo11-03.html",etree.HTMLParser())
00.獲取根路徑的div元素:
print('--result00----/div-----')
result00 = html.xpath('/div') # 匹配/div節(jié)點
print(result00)
輸出如下:
--result00----/div-----
[]
因為根路徑下標簽為,所以無法匹配度根路徑下的div標簽。
01.獲取任意路徑的div元素:
print('--result01----/div-----')
result00 = html.xpath('/div') # 匹配所有div節(jié)點
print(result01)
輸出如下:
--result01----//div-----
[<Element div at 0x182e1169e80>, <Element div at 0x182e1169e00>]
匹配到兩個div元素。//表示任意路徑。
02.獲取任意路徑div元素的所以子節(jié)點:
print('--result02----//div/*-----')
result02 = html.xpath('//div/*') # 匹配所有div節(jié)點的子節(jié)點
print(result02)
輸出如下:
--result02----//div/*-----
[<Element ul at 0x182e1169f80>, <Element ul at 0x182e1169fc0>]
*表示匹配任意節(jié)點。
03.匹配所有class屬性的值:
print('--result03----//@class-----')
result03 = html.xpath('//@class') # 匹配所有class屬性的值
print(result03)
輸出如下:
--result03----//@class-----
['useful', 'cla-0', 'cla-1', 'cla-1', 'cla-0', 'useless', 'cla-0', 'cla-1']
@class表示獲取屬性class的值。
04.獲取任意路徑下li標簽的a標簽子節(jié)點:
print('--result04----//li/a-----')
result04 = html.xpath('//li/a') # 匹配所有l(wèi)i標簽下的子節(jié)點a標簽
print(result04)
輸出如下:
--result04----//li/a-----
[<Element a at 0x182e116a400>, <Element a at 0x182e116a480>, <Element a at 0x182e116a4c0>, <Element a at 0x182e116a500>, <Element a at 0x182e116a540>, <Element a at 0x182e116a5c0>]
原始數(shù)據(jù)中一共7個a標簽,返回值為6個a標簽,是因為如下原始數(shù)據(jù)
<li><strong><a href="/link3">03</a></strong></li>
a標簽不是li標簽的子節(jié)點。
05.獲取任意路徑下li標簽的任意a標簽子孫節(jié)點:
print('--result05----//li//a-----')
result05 = html.xpath('//li//a') # 匹配所有l(wèi)i標簽下的所有a標簽
print(result05)
輸出如下:
--result05----//li//a-----
[<Element a at 0x182e116a400>, <Element a at 0x182e116a480>, <Element a at 0x182e116a600>, <Element a at 0x182e116a4c0>, <Element a at 0x182e116a500>, <Element a at 0x182e116a540>, <Element a at 0x182e116a5c0>]
原始數(shù)據(jù)中一共7個a標簽,返回值為7個a標簽,全部獲取到。
06. 匹配具有herf屬性為/link2的元素的父元素的class屬性的值:
print('--result06----//a[@href="/link2"]/../@class-----')
result06 = html.xpath('//a[@href="/link2"]/../@class')print(result06)
輸出如下:
--result06----//a[@href="/link2"]/../@class-----
['cla-1', 'cla-1']
…表示當前節(jié)點的父元素。
07.查找所有class="cla-0"的li節(jié)點:
print('--result07----//li[@class="cla-0"]-----')
result07 = html.xpath('//li[@class="cla-0"]') # 查找所有class="cla-0"的li節(jié)點:
print(result07)
輸出如下:
--result07----//li[@class="cla-0"]-----
[<Element li at 0x182e116a140>, <Element li at 0x182e116a2c0>, <Element li at 0x182e116a3c0>]
//表示匹配任意路徑,[]代表謂語,@class="cla-0"代表過濾出class屬性值為cla-0的元素。
08.獲取a節(jié)點下的文本:
print('--result08----//li[@class="cla-0"]/a/text()-----')
result08_1 = html.xpath('//li[@class="cla-0"]/a/text()') # 先選取a節(jié)點,再獲取a節(jié)點下的文本
print(result08_1)
輸出如下:
--result08----//li[@class="cla-0"]/a/text()-----
['01', '05', 'useless-01']
text()表示獲取匹配節(jié)點的文本內(nèi)容。
09.獲取li節(jié)點下a節(jié)點的href屬性:
print('--result09----//li/a/@href-----')
result09 = html.xpath('//li/a/@href') # 獲取li節(jié)點下a節(jié)點的href屬性
print(result09)
輸出如下:
--result09----//li/a/@href-----
['/link1', '/link2', '/link4', '/link5', '/link1', '/link2']
//li/a/@href表示匹配任意路徑下的li元素的a標簽子節(jié)點的href屬性值。
10.獲取li節(jié)點下所有a節(jié)點的href屬性:
print('--result10----//li//a/@href-----')
result10 = html.xpath('//li//a/@href') # 獲取li節(jié)點下所有a節(jié)點的href屬性
print(result10)
輸出如下:
--result10----//li//a/@href-----
['/link1', '/link2', '/link3', '/link4', '/link5', '/link1', '/link2']
相比result9,本次結(jié)果匹配到了/link3。
11.獲取class屬性值包含-0的li元素下的a標簽的文本:
print('--result11----//li[contains(@class,"-0")]/a/text()-----')
result11 = html.xpath('//li[contains(@class,"-0")]/a/text()') # 獲取class屬性值包含-0的li元素下的a標簽的文本
print(result11)
輸出如下:
--result11----//li[contains(@class,"-0")]/a/text()-----
['01', '05', 'useless-01']
contains(@class,“-0”)表示過濾條件為class屬性包含-0。于此類似的還有starts-with,starts-with表示以什么開頭。
12.用多個屬性獲?。?/strong>
print('--result12----//li[contains(@class,"-0") and @id="id-0"]/a/text()-----')
result12 = html.xpath('//li[contains(@class,"-0") and @id="id-0"]/a/text()') # 多個屬性用and運算符來連接
print(result12)
輸出如下:
--result12----//li[contains(@class,"-0") and @id="id-0"]/a/text()-----
['01']
contains(@class,“-0”) and @id="id-0"表示待匹配的元素需要具有滿足以上兩種條件。and 操作符也可以替換為or 操作符。由于同時包含兩種屬性條件的a標簽只有一個,所以返回的文本只有01。
13.按照順序獲取節(jié)點:
print('--result13----//li[last()]/a/text()-----')
result13 = html.xpath('//li[last()]/a/text()') # 取最后一個li節(jié)點下a節(jié)點的文本
print(result13)
輸出如下:
--result13----//li[last()]/a/text()-----
['05', 'useless-02']
返回結(jié)果表示,通過last()返回了兩個li列表中的最后一個節(jié)點。
14.通過ancestor軸獲取所有的祖先節(jié)點:
print('--result14----//li[1]/ancestor::*-----')
result14 = html.xpath('//li[1]/ancestor::*') # ancestor軸可以獲取所有的祖先節(jié)點
print(result14)
輸出如下:
--result14----//li[1]/ancestor::*-----
[<Element html at 0x182e0de9c80>, <Element body at 0x182e116abc0>, <Element div at 0x182e1169e80>, <Element ul at 0x182e1169f80>, <Element div at 0x182e1169e00>, <Element ul at 0x182e1169fc0>]
//li[1]表示獲取任意路徑的li中的第一個元素,/ancestor::*表示獲取當前節(jié)點的任意祖先節(jié)點。
15.通過ancestor軸獲取祖先div節(jié)點:
print('--result15----//li[1]/ancestor::div-----')
# 只獲取div這個祖先節(jié)點
result15 = html.xpath('//li[1]/ancestor::div')
print(result15)
for result15_1 in result15:
print(result15_1.xpath('.//li[contains(@class,"-0")]/a/text()'))
輸出如下:
--result15----//li[1]/ancestor::div-----
[<Element div at 0x182e1169e80>, <Element div at 0x182e1169e00>]
['01', '05']
['useless-01']
result15的返回結(jié)果為div節(jié)點,然后對result15進行遍歷,在遍歷中,通過xpath路徑進一步獲取a標簽的文本。這里需要注意的是循環(huán)內(nèi)的xpath路徑以“.”開頭,表示相對于當前div元素下,第一次輸出為[‘01’, ‘05’],第二次輸出為[‘useless-01’]。如果循環(huán)內(nèi)的xpath路徑去掉“.”,則循環(huán)內(nèi)的兩次輸出是一致,應(yīng)該都為[‘01’, ‘05’, ‘useless-01’]。
16.獲取所有屬性值:
print('--result16----//li[1]/attribute::*-----')
result16 = html.xpath('.//li[1]/attribute::*')
print(result16)
輸出如下:
--result16----//li[1]/attribute::*-----
['cla-0', 'id-0', 'cla-0']
輸出結(jié)果為所有l(wèi)i中的第1個節(jié)點的屬性值。
17.獲取所有子孫節(jié)點a標簽:
print('--result17----//div/descendant::a-----')
result17 = html.xpath('//div[@class="useless"]/descendant::a')
print(result17)
輸出如下:
--result17----//div/descendant::a-----
[<Element a at 0x1f34cf2a540>, <Element a at 0x1f34cf2a5c0>]
descendant表示匹配子孫節(jié)點。
以上就是lxml的基本操作,更多操作可以自行組合或參考官網(wǎng),需要說明的是,在瀏覽器端通過開發(fā)者工具–查看器–選擇元素–右鍵復(fù)制–選擇XPath路徑,可以獲取選擇元素的XPath路徑,通過這種方法可以加快XPath路徑構(gòu)建。
另外需要注意的是,xpath()函數(shù)的返回值為列表,可以通過先抓取外層的數(shù)據(jù),然后通過遍歷或是索引的方式獲取節(jié)點數(shù)據(jù),然后通過相對路徑的方式進一步讀取內(nèi)層元素節(jié)點。案例如下:
18.先獲取外層元素,再通過相對路徑的方式獲取內(nèi)部元素:
print('--result18----//li[1]/ancestor::div-----')
result18 = html.xpath('//li[1]/ancestor::div')
print(result18)
print(result18[0].xpath('./ul/li/a/text()'))
在上面代碼中 ,result18[0]表示獲取列表中的第一個Element 類型元素,然后對Element 類型元素進行xpath操作。./ul/li/a/text()中的“.”表示相對當前節(jié)點。
輸出為:
--result18----//li[1]/ancestor::div-----
[<Element div at 0x28e3b83a000>, <Element div at 0x28e3b83a0c0>]
['01', '02', '04', '05']
2.3 urllib整合lxml
urllib獲取百度數(shù)據(jù)
from urllib import parse
from urllib import request
url='http://www.baidu.com/s?'
dict_data={'wd':'百度翻譯'}
#unlencode() 將字典{k 1:v 1,k2:v2}轉(zhuǎn)化為k1=v1&k2=v2
url_data=parse.urlencode(dict_data)
#urldata:wd=%E7%99%BE%E5%BA%A6%E7%BF%BB%E8%AF%91
print(url_data)#讀取URL響應(yīng)結(jié)果
response_data=request.urlopen((url+url_data))#用utf 8對響應(yīng)結(jié)果編碼
data=response_data.read().decode('utf-8')
print(data)
輸出為:
wd=%E7%99%BE%E5%BA%A6%E7%BF%BB%E8%AF%91
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>百度安全驗證</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
<meta name="format-detection" content="telephone=no, email=no">
<link rel="shortcut icon" type="image/x-icon">
<link rel="icon" sizes="any" mask >
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
<link rel="stylesheet" />
</head>
<body>
<div class="timeout hide-callback">
<div class="timeout-img"></div>
<div class="timeout-title">網(wǎng)絡(luò)不給力,請稍后重試</div>
<button type="button" class="timeout-button">返回首頁</button>
</div>
<div class="timeout-feedback hide-callback">
<div class="timeout-feedback-icon"></div>
<p class="timeout-feedback-title">問題反饋</p>
</div>
<script src="https://ppui-static-wap.cdn.bcebos.com/static/touch/js/mkdjump_v2_21d1ae1.js"></script>
</body>
</html>
基于lxml進行解析百度數(shù)據(jù)
from lxml import etree
# 定義一個不規(guī)則的html文本
html = etree.HTML(data) # etree把不規(guī)則文本進行修正
res = html.xpath("http://body[1]//div[@class='timeout-title']/text()")
print(res)
輸出為:文章來源:http://www.zghlxwxcb.cn/news/detail-731301.html
[‘網(wǎng)絡(luò)不給力,請稍后重試’]文章來源地址http://www.zghlxwxcb.cn/news/detail-731301.html
到了這里,關(guān)于Python爬蟲技術(shù)系列-02HTML解析-xpath與lxml的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!