一、xpath初步認識
1、xpath介紹
XPath(XML Path Language)是一種用於在 XML 文件中定位節點的語言。它是一種在 XML 文件中導航和查詢資訊的語言,類似於在關聯式資料庫中使用 SQL 查詢資料。XPath 提供了一種靈活的方式來定位和處理 XML 文件中的元素和屬性。
2、lxml的安裝
lxml是Python的一個第三方解析庫,支援HTML和XML解析,而且效率非常高,彌補了Python自帶的xml標準庫在XML解析方面的不足。
由於是第三方庫,所以在使用 lxml 之前需要先安裝:pip install lxml
from lxml import etree
# 將原始碼轉化為能被XPath匹配的格式
selector=etree.HTML(原始碼)
# 返回為一列表
selector.xpath(表示式)
3、xpath解析原理
XPath 使用路徑表示式來選取 XML 文件中的節點或節點集。這些路徑表示式類似於檔案系統中的路徑,可以沿著元素和屬性之間的層次結構前進,並選擇所需的節點。XPath 也支援使用謂詞來過濾和選擇節點,以便更精確地定位目標節點。
二、xpath的語法
XPath 語法 : XPath 參考手冊 ] - 線上原生手冊 - php中文網
1、選取節點
表示式 | 描述 |
---|---|
nodename | 選取此節點的所有子節點。 |
/ | 從根節點選取。 |
// | 從匹配選擇的當前節點選擇文件中的節點,而不考慮它們的位置。 |
. | 選取當前節點。 |
.. | 選取當前節點的父節點。 |
@ | 選取屬性。 |
在下面的表格中,我們已列出了一些路徑表示式以及表示式的結果:
路徑表示式 | 結果 |
---|---|
bookstore | 選取 bookstore 元素的所有子節點。 |
/bookstore | 選取根元素 bookstore。註釋:假如路徑起始於正斜槓( / ),則此路徑始終代表到某元素的絕對路徑! |
bookstore/book | 選取屬於 bookstore 的子元素的所有 book 元素。 |
//book | 選取所有 book 子元素,而不管它們在文件中的位置。 |
bookstore//book | 選擇屬於 bookstore 元素的後代的所有 book 元素,而不管它們位於 bookstore 之下的什麼位置。 |
//@lang | 選取名為 lang 的所有屬性。 |
2、謂語
謂語用來查詢某個特定的節點或者包含某個指定的值的節點。
謂語被嵌在方括號中。
在下面的表格中,我們列出了帶有謂語的一些路徑表示式,以及表示式的結果:
路徑表示式 | 結果 |
---|---|
/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。 |
3、選取未知節點
XPath 萬用字元可用來選取未知的 XML 元素。
萬用字元 | 描述 |
---|---|
* | 匹配任何元素節點。 |
@* | 匹配任何屬性節點。 |
node() | 匹配任何型別的節點。 |
在下面的表格中,我們列出了一些路徑表示式,以及這些表示式的結果:
路徑表示式 | 結果 |
---|---|
/bookstore/* | 選取 bookstore 元素的所有子元素。 |
//* | 選取文件中的所有元素。 |
//title[@*] | 選取所有帶有屬性的 title 元素。 |
4、選取若干路徑
透過在路徑表示式中使用"|"運算子,您可以選取若干個路徑。
在下面的表格中,我們列出了一些路徑表示式,以及這些表示式的結果:
路徑表示式 | 結果 |
---|---|
//book/title | //book/price | 選取 book 元素的所有 title 和 price 元素。 |
//title | //price | 選取文件中的所有 title 和 price 元素。 |
/bookstore/book/title | //price | 選取屬於 bookstore 元素的 book 元素的所有 title 元素,以及文件中所有的 price 元素。 |
(1)邏輯運算
//div[@id="head" and @class="s_down"] # 查詢所有id屬性等於head並且class屬性等於s_down的div標籤
//title | //price # 選取文件中的所有 title 和 price 元素,“|”兩邊必須是完整的xpath路徑
(2)屬性查詢
//div[@id] # 找所有包含id屬性的div節點
//div[@id="maincontent"] # 查詢所有id屬性等於maincontent的div標籤
//@class
//li[@name="xx"]//text() # 獲取li標籤name為xx的裡面的文字內容
(3)獲取第幾個標籤 索引從1開始
tree.xpath('//li[1]/a/text()') # 獲取第一個
tree.xpath('//li[last()]/a/text()') # 獲取最後一個
tree.xpath('//li[last()-1]/a/text()') # 獲取倒數第二個
(4)模糊查詢
//div[contains(@id, "he")] # 查詢所有id屬性中包含he的div標籤
//div[starts-with(@id, "he")] # 查詢所有id屬性中包以he開頭的div標籤
//div/h1/text() # 查詢所有div標籤下的直接子節點h1的內容
//div/a/@href # 獲取a裡面的href屬性值
//* #獲取所有
//*[@class="xx"] #獲取所有class為xx的標籤
# 獲取節點內容轉換成字串
c = tree.xpath('//li/a')[0]
result=etree.tostring(c, encoding='utf-8')
print(result.decode('UTF-8'))
5、示例
from lxml import etree
doc = '''
<html>
<head>
<base href='http://example.com/' /> <!-- 設定基準連結 -->
<title>Example website</title> <!-- 設定網頁標題 -->
</head>
<body>
<div id='images'>
<a href='image1.html' id='lqz'>Name: My image 1 <br /><img src='image1_thumb.jpg' /></a>
<a href='image2.html'>Name: My image 2 <br /><img src='image2_thumb.jpg' /></a>
<a href='image3.html'>Name: My image 3 <br /><img src='image3_thumb.jpg' /></a>
<a href='image4.html'>Name: My image 4 <br /><img src='image4_thumb.jpg' /></a>
<a href='image5.html' class='li li-item' name='items'>Name: My image 5 <br /><img src='image5_thumb.jpg' /></a>
<a href='image6.html' name='items'><span><h5>test</h5></span>Name: My image 6 <br /><img src='image6_thumb.jpg' /></a>
</div>
</body>
</html>
'''
# 將HTML字串轉為可解析的物件
html = etree.HTML(doc)
# 1. 獲取所有節點
all_nodes = html.xpath('//*')
print(all_nodes)
# 2. 指定節點(結果為列表)
head_node = html.xpath('//head')
print(head_node)
# 3. 子節點和子孫節點
child_nodes = html.xpath('//div/a') # 獲取div下的所有a標籤
descendant_nodes = html.xpath('//body//a') # 獲取body下的所有子孫a標籤
print(child_nodes)
print(descendant_nodes)
# 4. 父節點
parent_node = html.xpath('//body//a[1]/..') # 獲取第一個a標籤的父節點
print(parent_node)
# 5. 屬性匹配
matched_nodes = html.xpath('//body//a[@href="image1.html"]') # 獲取href屬性為"image1.html"的a標籤
print(matched_nodes)
# 6. 文字獲取
text = html.xpath('//body//a[@href="image1.html"]/text()') # 獲取第一個a標籤的文字內容
print(text)
# 7. 屬性獲取
href_attributes = html.xpath('//body//a/@href') # 獲取所有a標籤的href屬性值
print(href_attributes)
# 8. 屬性多值匹配
li_class_nodes = html.xpath('//body//a[contains(@class, "li")]') # 獲取class屬性包含"li"的a標籤
print(li_class_nodes)
# 9. 多屬性匹配
matched_nodes = html.xpath('//body//a[contains(@class, "li") and @name="items"]') # 獲取class屬性包含"li"和name屬性為"items"的a標籤
print(matched_nodes)
# 10. 按序選擇
second_a_text = html.xpath('//a[2]/text()') # 獲取第二個a標籤的文字內容
print(second_a_text)
# 11. 節點軸選擇
ancestors = html.xpath('//a/ancestor::*') # 獲取a標籤的所有祖先節點
div_ancestor_node = html.xpath('//a/ancestor::div') # 獲取a標籤的祖先節點中的div
attribute_values = html.xpath('//a[1]/attribute::*') # 獲取第一個a標籤的所有屬性值
child_nodes = html.xpath('//a[1]/child::*') # 獲取第一個a標籤的所有子節點
descendant_nodes = html.xpath('//a[6]/descendant::*') # 獲取第六個a標籤的所有子孫節點
following_nodes = html.xpath('//a[1]/following::*') # 獲取第一個a標籤之後的所有節點
following_sibling_nodes = html.xpath('//a[1]/following-sibling::*') # 獲取第一個a標籤之後的同級節點
print(ancestors)
print(div_ancestor_node)
print(attribute_values)
print(child_nodes)
print(descendant_nodes)
print(following_nodes)
print(following_sibling_nodes)
XPath 是一種強大的工具,廣泛用於 XML 文件的處理和解析。它在各種領域中都有廣泛的應用,包括 Web 開發、資料抓取、資料提取和資料轉換等方面。在 Web 開發中,XPath 經常與 XML、HTML 和 XSLT(Extensible Stylesheet Language Transformations)一起使用,用於從網頁中提取資料或進行資料轉換。
三、專案例項
1、例項一
需求:
爬取58同城二手房源資訊(以北京市為例)。解析出所有房源的名稱,並進行持久化儲存。
網址:https://bj.58.com/ershoufang/
思路:
主要就是觀察頁面的結構,看每個房源的名字所在的標籤是哪個。然後寫出xpath表示式即可。
import requests
from lxml import etree
from fake_useragent import UserAgent
url = 'https://bj.58.com/ershoufang/'
headers = {
"User-Agent": UserAgent().random
}
# 爬取頁面原始碼資料
page_text = requests.get(url=url, headers=headers).text
# 頁面解析
tree = etree.HTML(page_text)
div_list = tree.xpath('//*[@id="esfMain"]/section/section[3]/section[1]/section[2]/div')
with open('58.txt', 'w',encoding='utf8') as f:
for div in div_list:
# 區域性解析
# 一定要加 . 這個 . 表示的就是區域性定位到的標籤
title = div.xpath('./a/div[2]/div[1]/div[1]/h3')[0].text
print(title)
# 存入檔案
f.write(title + '\n')
2、案例二
需求:
爬取《紅樓夢》所有章節的標題。
網址:https://www.shicimingju.com/book/hongloumeng.html
思路:
主要是對xpath表示式的書寫。透過觀察標籤寫出xpath表示式。
import requests
from lxml import etree
from fake_useragent import UserAgent
url = 'https://www.shicimingju.com/book/hongloumeng.html'
headers = {
"User-Agent":UserAgent().random
}
page_text = requests.get(url=url,headers=headers).text
# 例項化etree物件
tree = etree.HTML(page_text)
li_list = tree.xpath('//*[@id="main_left"]/div/div[4]/ul/li')
with open('紅樓夢.txt', 'w',encoding='utf8') as f:
data_dic = []
for li in li_list:
title = li.xpath('./a/text()')[0] # 獲取標籤裡的文字值
href = li.xpath('./a/@href')[0] # 獲取標籤裡的href值
detail_url = 'https://www.shicimingju.com' + href
detail_page_text = requests.get(url=detail_url,headers=headers)
detail_page_text.encoding = 'utf8'
detail_page_text = detail_page_text.text
new_tree = etree.HTML(detail_page_text)
# 文章內容
p_list = new_tree.xpath('//*[@id="main_left"]/div[1]/div/p')
for p in p_list:
words = p.text
print(words)
data_dic.append({"title":title,"words":words})
f.write(str(data_dic))
3、例項三
需求:
解析下載圖片資料。
網址:https://pic.netbian.com/4kdongman/
思路:
主要是對 xpath表示式的書寫,和怎樣處理中文亂碼。
xpath表示式可以從< div class = “slist”>標籤開始,也可以從更上面的標籤開始,比如< idv id = “main” > 可以從這裡開始。
當然兩個寫法的含義是一樣的。
import requests
from lxml import etree
from fake_useragent import UserAgent
import os
if __name__ == '__main__':
url = 'https://pic.netbian.com/4kdongman/'
header = {
'User-Agent': UserAgent().random
}
# 爬取頁面原始碼資料 獲取相應物件
reponse = requests.get(url=url, headers=header)
# 手動設定響應資料編碼格式
# reponse.encoding = 'utf-8'
page_text = reponse.text
# 資料解析,解析src的屬性值,解析alt的屬性值
# 例項化etree
tree = etree.HTML(page_text)
# xpath表示式
# src_list = tree.xpath('//div[@id="main"]/div[3]/ul/li/a/img/@src')
# alt_list = tree.xpath('//div[@id="main"]/div[3]/ul/li/a/img/@alt')
# 也可以這樣寫
src_list = tree.xpath('//div[@class="slist"]/ul/li')
# 建立一個資料夾
if not os.path.exists('./tupian'):
os.mkdir('./tupian')
for li in src_list:
img_src = 'http://pic.netbian.com' + li.xpath('./a/img/@src')[0]
img_alt = li.xpath('./a/img/@alt')[0] + '.jpg'
# 統用處理解決中文亂碼的解決方法
img_alt = img_alt.encode('iso-8859-1').decode('gbk')
print(img_alt + " : " + img_src)
# 圖片地址轉化成二進位制
img_data = requests.get(url=img_src, headers=header).content
img_path = './tupian/' + img_alt
# 儲存
with open(img_path, 'wb') as fp:
fp.write(img_data)
4、案例四
需求:
解析出所給網址中全國城市的名稱。
網址:https://www.aqistudy.cn/historydata/
思路:
首先例項化xpath物件,然後根據熱門城市和全部城市的標籤層級關係寫出xpath表示式。解析表示式所對應的a標籤,然後xpath函式返回一個列表,列表中存的就是a標籤對應的城市。然後我們遍歷列表即可。
第一種寫法:分別解析熱門城市和所有城市,然後把這些城市的名字存入列表中。
import requests
from lxml import etree
import os
from fake_useragent import UserAgent
url = 'https://www.aqistudy.cn/historydata/'
headers = {
'User-Agent':UserAgent().random
}
# 爬取頁面原始碼資料,獲取響應物件
page_text = requests.get(url=url,headers=headers).text
# 資料解析
# 例項化物件
tree = etree.HTML(page_text)
all_city = [] # 所有的城市
# 熱門城市
# hot_city_list = tree.xpath('//div[@class="bottom"]/ul/li')
hot_city_list = tree.xpath('/html/body/div[3]/div/div[1]/div[1]/div[2]/ul/li')
for li in hot_city_list:
hot_city_name = li.xpath('./a/text()')[0]
all_city.append(hot_city_name)
all_city_list = tree.xpath('//div[@class="bottom"]/ul/div[2]/li')
for li in all_city_list:
all_city_name = li.xpath('./a/text()')[0]
all_city.append(all_city_name)
print(all_city,"一共有:",len(all_city),"個城市")
第二種寫法:用按位或將兩個層級關係連線。
我們無法只透過一共 xpath表示式,將兩個層級標籤都表示數量,但是我們可以將兩個層級標籤寫在一起,只需要用按位或 “ | ” 進行分割。這個意味著將第一個 xpath表示式或者第二個 xpath表示式,作用到 xpath函式當中。這樣可以解析第一個表示式所對應的a標籤定位到,也可以將第二個表示式所對應的a標籤定位到。
# 用按位或進行分割 “ | ” a_city_list = tree.xpath('//div[@class="bottom"]/ul/li/a | //div[@class="bottom"]/ul/div[2]/li/a')
xpath返回一個列表,這個列表裡面存的是熱門城市加全部城市a標籤所對應的一個列表。
然後遍歷列表即可。
import requests
from lxml import etree
if __name__ == '__main__':
url = 'https://www.aqistudy.cn/historydata/'
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4209.2 Safari/537.36'
}
# 爬取頁面原始碼資料 獲取相應物件
page_text = requests.get(url=url, headers=header).text
# 資料解析
# 例項化物件
tree = etree.HTML(page_text)
all_city = [] # 所有的城市
# 解析到熱門城市和所有城市對應的a標籤
# 熱門城市對應a標籤層級關係://div[@class="bottom"]/ul/li/a
# 所有城市對應a標籤層級關係://div[@class="bottom"]/ul/div[2]/li/a
# 用按位或進行分割 “ | ”
a_city_list = tree.xpath('//div[@class="bottom"]/ul/li/a | //div[@class="bottom"]/ul/div[2]/li/a')
for a in a_city_list:
city_name = a.xpath('./text()')[0]
all_city.append(city_name)
print(all_city, " 一共有:", len(all_city), "個城市")
5、案例五
需求:
獲取大學生求職簡歷模板的封面和名稱,並且列印下來。
網址:http://sc.chinaz.com/jianli/daxuesheng.html
思路:
首先例項化xpath物件,然後根據圖片和名稱的標籤層級關係寫出xpath表示式。解析表示式所對應的a標籤,然後xpath函式返回一個列表,列表中存的就是a標籤對應的圖片和名字。然後我們遍歷列表即可。
import requests
from fake_useragent import UserAgent
from lxml import etree
if __name__ == '__main__':
header = {
'User-Agent': UserAgent().random
}
# 第一頁
# 第一頁的url和其它頁的有所不同
url1 = 'http://sc.chinaz.com/jianli/daxuesheng.html'
# 爬取頁面原始碼資料 獲取相應物件
reponse = requests.get(url=url1, headers=header)
# 手動設定響應資料編碼格式
reponse.encoding = 'utf-8'
page_text = reponse.text
# 資料解析
# 例項化物件
tree = etree.HTML(page_text)
src_list = tree.xpath('//div[@id="main"]/div/div')
for a in src_list:
img_src = a.xpath('./a/img/@src')[0]
img_alt = a.xpath('./a/img/@alt')[0]
# 統用處理解決中文亂碼的解決方法
# 會報錯:UnicodeDecodeError: 'gbk' codec can't decode byte 0xbf in position 32: incomplete multibyte sequence
# img_alt = img_alt.encode('iso-8859-1').decode('gbk')
print(img_alt, ' : ', img_src)
# 其他頁(2到13頁)
url = 'http://sc.chinaz.com/jianli/daxuesheng'
for num in range(2, 13 + 1):
num = str(num)
new_url = url + '_' + num + '.html'
# 爬取頁面原始碼資料 獲取相應物件
reponse = requests.get(url=new_url, headers=header)
# 手動設定響應資料編碼格式
reponse.encoding = 'utf-8'
page_text = reponse.text
# 資料解析
# 例項化物件
tree = etree.HTML(page_text)
src_list = tree.xpath('//div[@id="main"]/div/div')
for a in src_list:
img_src = a.xpath('./a/img/@src')[0]
img_alt = a.xpath('./a/img/@alt')[0]
# 統用處理解決中文亂碼的解決方法
# 會報錯:UnicodeDecodeError: 'gbk' codec can't decode byte 0xbf in position 32: incomplete multibyte sequence
# img_alt = img_alt.encode('iso-8859-1').decode('gbk')
print(img_alt, ' : ', img_src)
具體流程:
當然我們發現每個模板的名字(中文資訊)列印出來是亂碼,這就需要我們更改編碼格式。由於統用處理解決中文亂碼的解決方法會報錯,無法正常使用,所以我們使用手動設定響應資料編碼格式。
reponse = requests.get(url=url,headers=header)
# 手動設定響應資料編碼格式
reponse.encoding = 'utf-8'
page_text = reponse.text
- 我們如何列印所有頁的資料呢?
- 我們觀察發現,模板只有13頁
- 而網址也很相似:
第一頁:http://sc.chinaz.com/jianli/daxuesheng.html
第二頁:http://sc.chinaz.com/jianli/daxuesheng_2.html
第三頁:http://sc.chinaz.com/jianli/daxuesheng_3.html
第四頁:http://sc.chinaz.com/jianli/daxuesheng_4.html
...
第十三頁:http://sc.chinaz.com/jianli/daxuesheng_13.html
我們觀察發現除了第一頁外其它12頁的url只有後面的數字不同,我們可以用字串拼接的方法將url拼接出來,而那個不同的數字用for迴圈即可,然後將迴圈變數強制轉換成str型的,最後拼接字串。而第一頁的特殊處理,獨立輸出,其他頁(2到13頁)的迴圈輸出。
四、亂碼解決辦法
解決方法一:
手動設定響應資料編碼格式。
reponse = requests.get(url=url,headers=header)
# 手動設定響應資料編碼格式
reponse.encoding = 'utf-8'
page_text = reponse.text
解決方法二:
找到發生亂碼對應的資料,對該資料進行:encode(‘iso-8859-1’).decode(‘gbk’) 操作。
# 統用處理解決中文亂碼的解決方法
img_alt = img_alt.encode('iso-8859-1').decode('gbk')
案例三和案例五就是對這兩種處理中文亂碼的方法的應用。
案例三:是方法二。統用處理解決中文亂碼的解決方法。
案例五:是方法一。手動設定響應資料編碼格式。