1. 程式人生 > 其它 >Python中xpath解析

Python中xpath解析

目錄

簡介

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表示式

定位

根據層級定位

  1. / :表示從根節點開始定位
  2. // :表示多個層級,可以從任意位置開始定位
  3. ./:從當前位置開始定位

根據屬性進行定位

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開始的

取值

獲取文字

  1. 該節點下的直系文字:/text()
  2. 該節點下的所有文字://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])

獲取屬性

  1. /@屬性名稱:獲取該節點下的直系屬性值
  2. //@屬性名稱:獲取該節點下的所有屬性值
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