Python 爬虫之 XPath 元素定位

发布于:2025-05-10 ⋅ 阅读:(14) ⋅ 点赞:(0)

XPath 简介

XPath (XML Path Language) 最初是为了在 XML 文档中进行导航而设计的语言,后来被广泛应用于 HTML 文档的解析。与 BeautifulSoup 相比,XPath 有以下特点:

  • 语法强大:可以通过简洁的表达式精确定位元素
  • 跨平台性:几乎所有编程语言都有 XPath 的实现
  • 灵活性高:可以通过各种轴、谓词和函数构建复杂的选择条件

在 Python 中,我们主要通过 lxml 或者 selenium 库来使用 XPath 功能。可以通过 pip 安装下面的依赖包。

pip install lxml
pip install selenium

XPath 测试工具

浏览器

用浏览器测试验证 XPath 最直观,最方便,优先推荐这种方式。比如在百度热搜 百度热搜 测试 XPath。

在浏览器开发者工具的 Elements 中按 Ctrl + F,在搜索框中输入 XPath 。比如查找百度热搜标题,XPath 正确的话,会在页面高亮显示对应的元素。

lxml 

Python 的 lxml 库提供了强大的 XPath 支持。比如以下用来解析提取热搜新闻标题。

from lxml import etree
import requests

# 获取HTML内容
url = "https://top.baidu.com/board?tab=realtime"
response = requests.get(url)
html_text = response.text

# 解析HTML
# html = etree.HTML(html_text)
# 或者从文件加载HTML
html = etree.parse('百度热搜.html', etree.HTMLParser())

# 使用XPath提取数据
titles = html.xpath('//div/a/div[@class="c-single-text-ellipsis"]/text()')  # 获取标题文本
for index, title in enumerate(titles):
    print(f"第 {index + 1} 条新闻;新闻标题:{title}")
print("===热搜标题提取正常===")
第 1 条新闻;新闻标题:去“三好”邻邦家做客
第 2 条新闻;新闻标题:拜登:特朗普太掉价了
第 3 条新闻;新闻标题:央行1万亿元大红包对普通人影响多大
...
第 48 条新闻;新闻标题:世界人形机器人运动会将在北京举办
第 49 条新闻;新闻标题:苹果探索在浏览器中加入AI搜索功能
第 50 条新闻;新闻标题:专家解读:为何央行此时宣布降准降息
第 51 条新闻;新闻标题:骑士G2冤死 裁判报告公布三次漏判
===热搜标题提取正常===

selenium

Python 的 selenium 库提也供了强大的 XPath 支持。比如使用 RPA 流程自动化爬取数据的时候,总避免不了通过 XPath 去定位和查询元素。以下用来解析提取热搜新闻标题。

from selenium import webdriver
from selenium.webdriver.common.by import By


def init_driver():
    option = webdriver.ChromeOptions()
    driver = webdriver.Chrome(r'./driver/chromedriver.exe', options=option)
    driver.maximize_window()
    return driver


def main():
    driver = init_driver()
    url = r'file:///E:\lky_project\tmp_project\百度热搜.html'
    driver.get(url)
    try:
        xpath_titles = '//div/a/div[@class="c-single-text-ellipsis"]'
        titles = driver.find_elements(By.XPATH, xpath_titles)
        for index, title in enumerate(titles):
            print(f"第 {index + 1} 条新闻;新闻标题:{title.text}")

        print("===热搜标题提取正常===")
    except Exception as e:
        print("===热搜标题提取报错===", e)
    return


if __name__ == '__main__':
    main()
第 1 条新闻;新闻标题:去“三好”邻邦家做客
第 2 条新闻;新闻标题:拜登:特朗普太掉价了
第 3 条新闻;新闻标题:央行1万亿元大红包对普通人影响多大
...
第 48 条新闻;新闻标题:世界人形机器人运动会将在北京举办
第 49 条新闻;新闻标题:苹果探索在浏览器中加入AI搜索功能
第 50 条新闻;新闻标题:专家解读:为何央行此时宣布降准降息
第 51 条新闻;新闻标题:骑士G2冤死 裁判报告公布三次漏判
===热搜标题提取正常===

XPath 常用函数 

contains()

contains() 函数用于判断某个属性的取值中是否包含指定的字符串。其语法格式如下:

contains(@attribute, "substring")
    • @attribute:表示要过滤的属性名称,属性名称前需加上@符号。
    • substring:表示要判断是否包含的字符串。

    在自动化测试中,contains 函数特别适用于定位那些属性值不固定的元素。例如,某个元素的id 属性值在每次页面刷新时都会发生变化,但其中某些字符是固定的。通过 contains 函数,可以基于这些固定的字符进行定位。

    比如页面中查找 name 属性中包含 username 的 input 元素:

    //input[contains(@name, 'username')]

    starts-with()

    与 contains() 类似,只不过是限定指定开头的属性或者文本。

    //div/a/div[starts-with(text(), "  国防部")]

    ends-with()

    限定指定结尾的属性或者文本(部分浏览器不一定支持,要想兼容性更强的话建议最好少用)

    //div/a/div[ends-with(text(), "热烈欢迎 ")]

    position()

    position() 函数用于选取指定位置的元素。其语法格式如下。

    //element[position() < number]
    //element[position() <= number]
    //element[position() = number]
    //element[position() > number]
    //element[position() >= number]
    • position():表示元素的序号,从1开始计数。
    • number:表示设定的阈值。

    比如:

    //element[1]

    等价于:

    //element[position() = 1]

     比如页面中有多个 input 元素,我们希望选取前两个 input 元素。

    //input[position()<3]

    last()

    last() 函数用于选取从后往前数的元素。其语法格式如下:

    //element[last() - number]
    
      • last():表示匹配元素的总数。
      • number:表示从后往前数的元素位置。

      假设页面中有多个 input 元素,我们希望选取最后一个 input 元素:

      //input[last()]
      

        如果希望选取倒数第二个 input 元素:

        //input[last() - 1]

        count() 

        count() 函数通过对子元素指定类型节点进行统计来限定父元素的选取。语法格式如下:

        //element[count(sub_element) < number]
        //element[count(sub_element) <= number]
        //element[count(sub_element) = number]
        //element[count(sub_element) > number]
        //element[count(sub_element) >= number]
        • count(sub_element):表示 element 元素 下 sub_element 的数目。
        • number:表示设定的阈值。

         比如我们选取子元素中 div 的个数为 2 的 a 元素:

        //a[count(div)=2] 

        text()

        text() 函数用于获取元素的直接文本内容。一般与 contains() 结合使用,来选取文本内容包含指定字符串的元素。

        比如查找文本中包含 "热烈欢迎" 的 div 元素。

        //div[contains(text(), "热烈欢迎")]

        也可以用文本的精确匹配。

        //div[text()="热烈欢迎"]

        node()

        node() 函数用于选取所有节点。其语法格式如下:

        //node()
        

          node()函数的功能与 * 通配符号类似,用于选取所有节点。例如:

          //* 
          或 
          //node()
          

            上述两种表达式的效果相同,均用于选取页面中的所有节点(但通配符 * 不包括文本,注释,指令等节点,如果也要包含这些节点请用 node() 函数)。

            XPath 基础语法

            路径操作符

            操作符 描述 示例
            / 从根节点选取元素 /html/body
            // 递归步进下降选择文档中符合条件的所有元素 //a
            . 选取当前节点 ./div
            .. 选取当前节点的父节点 ../
            @ 选取属性 //div[@id]
            * 通配符,选择任意元素 //*

            条件谓词

            XPath 允许我们使用方括号 [] 来添加条件谓词进行筛选限定 。

            //div[1]                                # 第一个div元素
            //div[last()]                           # 最后一个div元素
            //div[position()<3]                     # 前两个div元素
            //div[@class]                           # 所有有class属性的div元素
            //div[@class='main']                    # class属性值为'main'的div元素
            //div[contains(@class, 'content')]      # class属性包含'content'的div元素
            //a[text()='click']                     # 文本内容为'click'的a元素
            //a[count(div)=2]                       # 包含2个div元素的a元素

            轴操作

            以 百度热搜 首页为例。

            ancestor

            ancestor 用以获取元素的祖先节点。比如 热搜 对应  xpath 为:

            //div[2]/a/span[text()="热搜"]

            则下面的 xpath 用以获取 热搜 元素对应的所有祖先节点:

            //div[2]/a/span[text()="热搜"]/ancestor::*

            包含当前节点自身则需要用 ancestor-or-self: 

            //div[2]/a/span[text()="热搜"]/ancestor-or-self::*

            则下面的 xpath 用以获取 热搜 元素对应的所有 div 祖先节点:

            //div[2]/a/span[text()="热搜"]/ancestor::div

            descendant

            descendant 用以获取元素的后代节点。

            比如获取 //div[@id="sanRoot"] 的所有后代 div 节点(不限定节点类型则使用 *)

            //div[@id="sanRoot"]/descendant::div

            包含当前节点自身则需要用 decendant-or-self: 

            //div[@id="sanRoot"]/descendant-or-self::div

            following

            following 用以选取文档中当前节点结束标签之后的所有节点。

            //div/div[@class="bg-wrapper"]/following::*

            如果只选取当前节点之后的所有兄弟节点,则使用 following-sibling。

            //div/div[@class="bg-wrapper"]/following-sibling::*

            preceding

            preceding 用于选取文档中当前节点开始标签之前的所有节点(不包含当前节点的父辈节点)。

            //div[last()]/div[@class="content_1YWBm"]/preceding::*

            如果选取当前节点之前的所有同级节点,则使用 preceding-sibling。

            //div[last()]/div[@class="content_1YWBm"]/preceding-sibling::*

            parent

            parent 用以获取元素的父节点。

            //div/div[@class="bg-wrapper"]/parent::*

            child

            child 用以获取元素的子节点。

            //div/div[@id="sanRoot"]/child::*

            self 

            self 用以获取当前元素节点自己本身。

            //div/div[@id="sanRoot"]/self::*

            attribute 

            通过属性来获取元素。

            比如获取所有包含 class 属性的节点。

            //attribute::class
            或
            //@class

            获取所有包含 class 属性的 div 节点。

             //div[@class]

            逻辑运算符

            and

            逻辑与,比如查询热搜的前三个热搜。

            //div/div[@class="category-wrap_iQLoo horizontal_1eKyQ" and position()<4]

            or

            逻辑或

            //div/div[@class="category-wrap_iQLoo horizontal_1eKyQ" or position()=last()]

            not

            //div/a/div[not(@class="c-single-text-ellipsis")]

            XPath 优化

            一般可以在浏览器中直接复制 XPath,但是浏览器复制的 XPath 一般是用绝对路径写的能唯一定位这个元素的 XPath。 如果页面结构稍微发生变动,可能导致 XPath 不可用。

            比如复制的 XPath 如下:

            //*[@id="sanRoot"]/main/div[2]/div/div[2]/div[1]/div[2]/a/div[1]

            优化后可以为: 

            //div[1]/div/a/div[@class="c-single-text-ellipsis"]

             XPath 常用的优化思路包括:

            • 找到元素附近的独特标识(如 id、特定的 class 等)
            • 尽量使用相对路径而非绝对路径
            • 使用 contains() 等函数处理动态变化的属性值

            网站公告

            今日签到

            点亮在社区的每一天
            去签到