Python中xpath解析
阿新 • • 發佈:2022-01-14
目錄
簡介
XPath 是一門在 XML 文件中查詢資訊的語言。XPath 可用來在 XML 文件中對元素和屬性進行遍歷。XPath 是 W3C XSLT 標準的主要元素,並且 XQuery 和 XPointer 都構建於 XPath 表達之上。
安裝
pip install lxml
本文示例的html程式碼
<div> <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> </ul> </div> <div id="111"> <div class="item-1"> <a href="www.qq.com">qq.com</a> <p>this is p label</p> <ul> <li class="item-2"> <a href="link1.html">first item1</a> </li> <li class="item-3"> <a href="link2.html">second item2</a> </li> </ul> </div> <a href="www.baidu.com">baidu.com</a> </div> </div>
使用
例項化etree
from lxml import etree
# 將本地的html檔案載入etree物件中
html = etree.parse("file_path")
# 將網際網路上獲取的原始碼資料載入到該物件中
html = etree.HTML(resp.text)
result = etree.tostring(html) # 格式化html程式碼
xpth表示式
定位
根據層級定位
- / :表示從根節點開始定位
- // :表示多個層級,可以從任意位置開始定位
- ./:從當前位置開始定位
根據屬性進行定位
text = html.xpath("/div[1]//li[@class='item-0']")
根據id進行定位
text = html.xpath("//div[@id='111']")
根據索引號進行定位
text = html.xpath("/div/div[1]/ul/li[2]/a/text()") # 注意xpath索引是從1開始的
取值
獲取文字
- 該節點下的直系文字:
/text()
- 該節點下的所有文字:
//text()
from lxml import etree wb_data = """ <div> <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> </ul> </div> <div id="111"> <div class="item-1"> <a href="www.qq.com">qq.com</a> <p>this is p label</p> <ul> <li class="item-2"> <a href="link1.html">first item1</a> </li> <li class="item-3"> <a href="link2.html">second item2</a> </li> </ul> </div> <a href="www.baidu.com">baidu.com</a> </div> </div> """ html = etree.HTML(wb_data) # 例項化HTML物件 # 獲取/div/div[2]下面的所有文字內容 text = html.xpath("//div[@id='111']//text()") print([i.strip() for i in text]) # 去除換行符,空格等 # 獲取/div/div[1]/ul/li[1]/裡面的文字資訊 print(html.xpath("//li[@class='item-0']/a/text()")[0])
獲取屬性
/@屬性名稱
:獲取該節點下的直系屬性值//@屬性名稱
:獲取該節點下的所有屬性值
from lxml import etree
wb_data = """
<div>
<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>
</ul>
</div>
<div id="111">
<div class="item-1">
<a href="www.qq.com">qq.com</a>
<p>this is p label</p>
<ul>
<li class="item-2">
<a href="link1.html">first item1</a>
</li>
<li class="item-3">
<a href="link2.html">second item2</a>
</li>
</ul>
</div>
<a href="www.baidu.com">baidu.com</a>
</div>
</div>
"""
html = etree.HTML(wb_data) # 例項化HTML物件
# 獲取/div/div[2]/div/下所有的href值
print(html.xpath("//div[@class='item-1']//@href"))
# 獲取/div/div[2]/a下的href值
print(html.xpath("//div[@id='111']/a/@href")[0])
例項
首先自制了一個多執行緒爬蟲模組用於傳送請求,模組名稱為MyModule
import threading, queue
"""爬蟲多執行緒"""
class SpiderThread(threading.Thread):
def __init__(self) -> "裡面包含了請求頭和代理IP 代理ip自己設定":
super().__init__(daemon=True) # daemon執行緒等待,target是執行的函式
# 開啟佇列物件
self.queue = queue.Queue()
# 執行緒
self.start() # 例項化的時候自動執行run函式
try:
# 構建ip池,此ip地址僅支援http請求
file = open("./ip.txt", "r") # 得到大量ip地址,與檔案同一目錄下,儲存http型別的ip池
ipList = file.readlines()
file.close()
import random
self.ip = random.choice(ipList).strip()
except Exception as e:
print(f"沒有批量ip地址,使用本機ip地址{e}")
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
import random
self.ip = s.getsockname()[0] + f":{random.randint(1, 8080)}" # 獲取本電腦的ip地址,同時隨機使用埠訪問網址
s.close()
# 傳入requests所需要的引數
self.headers = {
'User-Agent': "Mozilla / 5.0(Windows NT 10.0;Win64;x64) AppleWebKit / 537.36(KHTML, likeGecko) Chrome / 96.0.4664 .93 Safari / 537.36"
}
self.proxy = {
"http": f"https://{self.ip}",
# 注意:如果請求的ip是https型別的,但代理的ip是隻支援http的,那麼還是使用本機的ip,如果請求的ip是http型別的,那麼代理的ip一定要是http的,前面不能寫成https,否則使用本機IP地址
}
def run(self) -> None: # run方法執行緒自帶的方法,內建方法,線上程執行時會自動呼叫
while True: # 不斷處理任務
func, args, kwargs = self.queue.get()
func(*args, **kwargs) # 呼叫函式執行任務 元組不定長記得一定要拆包
self.queue.task_done() # 解決一個任務就讓計數器減一,避免阻塞
# 生產者模型
def submit_task(self, func, args=(), kwargs={}): # func為要執行的任務,加入不定長引數使用(預設使用預設引數)
self.queue.put((func, args, kwargs)) # 提交任務
# 重寫join方法
def join(self):
self.queue.join() # 檢視佇列計時器是否為0 任務為空 為空關閉佇列
def crawl(url, lis, cookies=None, headers=SpiderThread().headers,
proxy=SpiderThread().proxy) -> "lis用來儲存返回的resp響應 其是傳送get請求": # cookies是一個字典
import requests
if not isinstance(cookies, dict):
resp = requests.get(url=url, headers=headers, proxies=proxy)
else:
resp = requests.get(url=url, headers=headers, cookies=cookies)
if resp.status_code == 200:
print("獲取完成,返回的資料在傳入的列表裡面")
lis.append(resp) # 多執行緒沒有返回值
else:
SpiderThread().submit_task(crawl, args=(i, lis))
# 爬取58同城中全國銷售職位的名稱
from lxml import etree
import MyModule
from concurrent.futures import ThreadPoolExecutor
spider = MyModule.SpiderThread() # 例項化爬蟲物件
"""
通過分析url可得到 url = 'https://nc.58.com/yewu/pu1/?key=%E9%94%80%E5%94%AE';
又第二頁的 url = 'https://nc.58.com/yewu/pn2/?key=%E9%94%80%E5%94%AE'
"""
# 得到所有頁面的url
def spider1():
resp = [] # 接收返回的頁面原始碼
url = "https://nc.58.com/yewu/?key=%E9%94%80%E5%94%AE"
spider.submit_task(MyModule.crawl, args=(url, resp))
spider.join() # 等待執行緒完成
page_source = resp[0].text # 得到頁面原始碼
html = etree.HTML(page_source) # 例項化etree物件
num = int(html.xpath("/html/body/div[3]/div[3]/div/div/div/span[2]/text()")[0]) # 通過分析網頁可得該xpath解析
return [f"https://nc.58.com/yewu/pn{i}/?key=%E9%94%80%E5%94%AE" for i in range(1, num)]
def crawl():
respAll = [] # 儲存響應
for i in spider1():
spider.submit_task(MyModule.crawl, args=(i, respAll)) # 執行封裝的模組
spider.join() # 等待全部執行緒完成
return [i.text for i in respAll] # 返回響應原始碼
def save(resp_text):
html = etree.HTML(resp_text)
torr = html.xpath("//*[@id='list_con']/li")
file = open("./a.txt", "a+", encoding="utf-8") # 寫入檔案
for i in torr:
temp = i.xpath("./div[1]//a//text()")
name = "".join(temp) # 將名字組裝
file.write(f"名稱:{name}")
file.close()
def main(respAll):
with ThreadPoolExecutor(50) as pool: # 使用執行緒池,開啟50個執行緒,對檔案進行儲存
pool.map(save, respAll)
if __name__ == '__main__':
main(crawl()) # 注意:由於是高效能爬蟲,電腦的ip地址很大概率會被58同城封了,儘量使用代理ip
本文來自部落格園,作者:A-L-Kun,轉載請註明原文連結:https://www.cnblogs.com/liuzhongkun/p/15803303.html