XPath 通常用来进行网站、XML (APP )和数据挖掘,通过元素和属性的方式来获取指定的节点,然后抓取需要的信息。

学习 XPath 语法之前,首先了解一下一些概念。

概念介绍

节点之间的关系

以上面的 HTML 节点树为例,节点之间包含了下列的关系:

  1. 父节点 (Parent): HTML 是 DIV 和 P 节点的父节点;
  2. 子节点 (Child):DIV 和 P 是 HTML 的子节点;
  3. 兄弟节点 (Sibling):拥有同样的一个父节点,DIV 和 P 就是兄弟节点。类似的 span、img 和 i 也是兄弟节点。
  4. 祖先节点 (Ancestor):html 是 span 的祖先节点,隔开一级;
  5. 后代节点 (Descendant):span 是 HTML 的后代节点,隔开一级。

除了了解这些概念,parent、sibling 等关键词也非常关键,在匹配复杂的结构时常常用到。

绝对和相对路径

xpath 中绝对路径使用 / 开始,比如:/html/body/div[1]/a/img,绝对路径较长,其中可能包含变化的部分,不建议单独使用绝对路径来选择元素,最好配合其它语法。

比如下面的情况单独使用绝对路径进行定位就会出错:

  1. // 本意是匹配第三个div下的span,但因为第一个div因为是动态显隐的,导致匹配第而个div匹配到之前的第三个了
  2. /html/body/div[2]/span

相对路径以 // 开始,比如 //*[@class],表示只要包含 class 属性的元素均可匹配,无论从哪一个节点开始。

下面是一些常见选择节点示例:

表达式 说明 举例
/ 下一个节点,或者根节点开始 /html/body/div
// 从任意节点开始 //img
. 选取当前节点 //a/.
.. 当前节点的父节点 //a/..
@ 选取包含某属性的元素 //div[@class]或//@class
* 表示任意元素或者任意属性 //*[@class]

除此之外,通过谷歌浏览器-元素上审查元素-复制 xpath,可以直接获取绝对路径和相对路径。

但复制下来的代码,通常还需要进行一些修改,才能具备通用性。

基础语法

定位需要的信息通常通过元素、属性名、属性值以及三者结合等方式进行。

下面来分别看一下,也这段 html 代码为例:

  1. <div id="app">
  2. <p class="title">喜欢的动物</p>
  3. <ul>
  4. <li class="cat"></li>
  5. <li class="dog"></li>
  6. <li id="panda">熊猫</li>
  7. </ul>
  8. <p class="title">喜欢的电影</p>
  9. <ul>
  10. <li>阿甘正传</li>
  11. <li>霸王别姬</li>
  12. <li>阿凡达</li>
  13. </ul>
  14. <p>其它不需要信息</p>
  15. </div>

1. 通过元素名定位

示例:

1.1 //div/p

定位所有 div 下的 p 子元素,可以是任何 div,只要这个 div 的子节点包含 p 就可以匹配

1.2 //ul

会定位从任何节点开始的 ul 元素

1.3 /html/body/div/p

使用绝对路径定位元素,必须从 /html 开始,否则最好使用 // 相对路径开始

2. 通过属性名定位

通过元素是否包含某个属性来进行定位,属性名需要使用 @ 开始,同时放在 []

2.1 //*[@class]

定位包含 class 属性的元素

2.2 //@class

这种语法定位到的是属性里面的具体值 title,而不是元素,所有没有元素没选中

3. 通过属性值定位

示例:

//li[@class="cat"]

定位包含 class 属性,值为 cat 的 li 属性元素

4. 使用逻辑运算符定位

常用逻辑运算符包括:andornot 三种

示例:

4.1 //li[@class and @class="cat"]

选中包含 class 属性,并且属性值为 cat 的 li 元素对象。

4.2 //li[@class or @id]

选中包含 class 或者 id 属性的 li 元素对象。

4.3 //li[not(@class)]

选中不包含 class 属性的 li 元素对象。

5. 使用谓语定位

5.1 //li[1]

定位任意元素下的第一个 li。

注意

xpath 中索引从 1 开始。

5.2 (//li)[1]

两者区别如下:

//li[1] 任意元素下第一个li,也就是说这个 li 在任意的 ul 下是第一个就会被选中

(//li)[1] 将所有的 li 选出来的结果数组中取第一个,这两者是完全不同的含义

6. 使用文本定位

使用元素中文本的内容进行定位。

示例:

6.1 //li[text()="猫"]

选中文本内容为 的 li 元素对象。

6.2 //*[contains(text(),"喜欢")]

选中任意元素文本中包含 喜欢 两个字的元素,其中 * 表示所有元素是通配符,contains() 表示包含函数。

类似的有:starts-withends-with 函数,表示以什么字符开始和字符结尾的文本。

节点选择器

除了相对和绝对选择之外,下面这些选择器在处理较复杂的匹配场景可以发挥关键作用。

  • parent:::选中父级节点,/.. 也是选中父级,但是通常 parent:: 用于写在 [] 里面作为条件来判断
  • child:::选中子级节点,/ 也是选中子级,通常也是作为条件来使用
  • preceding-sibling:::选中同一层级的前面所有兄弟节点
  • following-sibling:::选中同一层级的后面所有兄弟节点
  • ancestor:::选中祖先节点,包括父级以及更上层的节点
  • descendant:::选中当前节点下面的所有节点,包括子级

举例:

//*[ancestor::div]

选中所有元素中,上级是 div 的元素,其实也就是选中了所有元素,来看看这个

//ancestor::div

只选中了一个元素。

两者的区别如下:

//*[ancestor::div] 选中的 * 表示所有元素,这些元素条件是 [ancestor::div] 父级及以上有 div。

//ancestor::div 选中的是作为别人父级及以上的 div,也就是选中的是 div,这个 div 的是别人的父级或者爷级等

两者是完全不同的概念

美团 APP 匹配示例

看了半天 HTML,我们来了解一下 APP 中的 XML,通常匹配 APP 比网页复杂太多,基本就那几个元素,而且属性名基本都一样,所以常用的手段还是使用各种条件来进行限制匹配,下面来看一个例子。

  1. <android.view.View index="5" class="android.view.View" text="" checked="false" clickable="true">
  2. <android.widget.TextView index="1" class="android.widget.TextView" text="象山酥院(湛江印象汇店)" checked="false"/>
  3. <android.widget.TextView index="2" class="android.widget.TextView" text="" checked="false" clickable="true"/>
  4. <android.view.View index="3" class="android.view.View" text="" checked="false">
  5. <android.widget.TextView index="0" class="android.widget.TextView" text="5.0" checked="false" />
  6. </android.view.View>
  7. <android.widget.TextView index="4" class="android.widget.TextView" text="周销量 872" checked="false" />
  8. </android.view.View>
  9. <android.view.View index="5" class="android.view.View" text="" checked="false" clickable="true">
  10. <android.widget.TextView index="1" class="android.widget.TextView" text="蜜雪冰城" checked="false"/>
  11. <android.widget.TextView index="2" class="android.widget.TextView" text="" checked="false" clickable="true"/>
  12. <android.view.View index="3" class="android.view.View" text="" checked="false">
  13. <android.widget.TextView index="0" class="android.widget.TextView" text="5.0" checked="false"/>
  14. </android.view.View>
  15. <android.widget.TextView index="4" class="android.widget.TextView" text="周销量 2322" checked="false"/>
  16. </android.view.View>

上面代码为美团的城市列表页面的 UI XML 代码,其中每个元素都包含大量相同的属性和属性值,关键在于整个页面,任何地方基本就是 android.view.Viewandroid.widget.TextView ,像匹配 HTML 那样元素显然行不通。

示例:获取两个商品的评分

//*[@text and ancestor::*/following-sibling::*[contains(@text, '周销量')]]

规则解释:获取任何包含 text 属性的元素,它的父级的的兄弟元素必须是一个 text 值中包含 "周销量"的元素。

我这里没有使用 [1][2][3] 来定位,是因为不同商品的属性很多时候不一样。

通常还是根据想要的元素的位置,以及相邻元素的特征来定位,首先找到独特的文本,比如上面的周销量是固定会出现的,还有 ¥ 符号也可以,这些都是位置和文本值固定的,找到这个的位置,再去定位需要的元素的位置。

工具推荐

  1. 谷歌浏览器-审查元素- ctrl + f,可以直接输入 xpath 语句
  2. 谷歌浏览器-selectorshub 插件,文中使用的是这个插件

XPath从入门到精通:基础和高级用法完整指南,附美团APP匹配示例的更多相关文章

  1. Java入门到精通——基础篇之多线程实现简单的PV操作的进程同步

    Java入门到精通——基础篇之多线程实现简单的PV操作的进程同步 一.概述     PV操作是对信号量进行的操作.     进程同步是指在并发进程之间存在一种制约关系,一个进程的执行依赖另一个进程的消 ...

  2. Python爬虫入门之Urllib库的高级用法

    1.设置Headers 有些网站不会同意程序直接用上面的方式进行访问,如果识别有问题,那么站点根本不会响应,所以为了完全模拟浏览器的工作,我们需要设置一些Headers 的属性. 首先,打开我们的浏览 ...

  3. shell中echo基础及高级用法详解-渐入佳境

    --作者:飞翔的小胖猪 --创建时间:2021年2月19日 1.1 基础用法 echo命令用来输出文本,在shell脚本中用来输出提示信息用的比较多. 单引号:原样输出所有的内容,不用转义就能输出特殊 ...

  4. Java入门到精通——基础篇之面向对象

    一.概述. Java属于面向对象的一种语言,因为Java是面向对象的语言所以这个语言的诞生需要有五个基本特性: 1)万物皆为对象. 2)程序是对象的集合. 3)每个对象都有自己的由其他对象所构成的存储 ...

  5. Java从入门到精通——基础篇之JSTL标签

    一.语言基础 EL(Expression Language)表达式,目的:为了使JSP写起来更加简单.提供了在 JSP 中简化表达式的方法. 二.分类 核心标签库:提供条件判断.属性访问.URL处理及 ...

  6. Java入门到精通——基础篇之static关键字

    一.概述        static 关键字是声明静态变量,静态方法用的.static的含义是属于类且不属于类对象的变量和函数. 二.static的产生.         在创建对象的时候除非用new ...

  7. Java从入门到精通——基础篇之Servlet与JSP的区别

    一.基本概念 1.1 Servlet Servlet是一种服务器端的Java应用程序,具有独立于平台和协议的特性,可以生成动态的Web页面.它担当客户请求(Web浏览器或其他HTTP客户程序)与服务器 ...

  8. Scala入门到精通——第二十二节 高级类型 (一)

    作者:摇摆少年梦 视频地址:http://www.xuetuwuyou.com/course/12 本节主要内容 this.type使用 类型投影 结构类型 复合类型 1. this.type使用 c ...

  9. 从入门到精通,Java学习路线导航(附学习资源)

    原文链接:https://blog.csdn.net/qq_42453117/article/details/100655512 引言 最近也有很多人来向我"请教",他们大都是一些 ...

  10. 【Vue】详解组件的基础与高级用法

    Vue.js 最核心的功能就是组件(Component),从组件的构建.注册到组件间通信,Vue 2.x 提供了更多方式,让我们更灵活地使用组件来实现不同需求. 一.构建组件 1.1 组件基础 一个组 ...

随机推荐

  1. 【每日一题】23.Removal (计数DP)

    补题链接:Here 计数DP讲解:Here 这是一个计数类的dp dp[i][j]表示前i个数字中,删除j个元素的方案数 很容易得到转移方程:\(f[i][j] = f[i - 1][j - 1] + ...

  2. 技术文档丨 OpenSCA技术原理之npm依赖解析

    本文主要介绍基于npm包管理器的组件成分解析原理. npm介绍 npm(全称Node Package Manager)是Node.js标准的软件包管理器. npm的依赖管理文件是package.jso ...

  3. SpringCloud学习 系列三、 创建一个没有使用springCloud的服务提供者和消费者

    系列导航 SpringCloud学习 系列一. 前言-为什么要学习微服务 SpringCloud学习 系列二. 简介 SpringCloud学习 系列三. 创建一个没有使用springCloud的服务 ...

  4. SpringBoot AOP 记录操作日志、异常日志

    使用SpringBoot AOP 记录操作日志.异常日志 我们在做项目时经常需要对一些重要功能操作记录日志,方便以后跟踪是谁在操作此功能.在操作某些功能时也有可能会发生异常,但是每次发生异常要定位原因 ...

  5. C语言基础之基础的输入输出

    前言 学一门编程语言,不能编写让用户输入数据然后输出处理后的数据的程序那么就等于没学,而在C语言中可以用printf() 和 scanf() 函数进行输入和输出操作.这两个函数是内置的库函数,定义在 ...

  6. python常见面试题讲解(四)字符串分隔

    题目描述 •连续输入字符串,请按长度为8拆分每个字符串后输出到新的字符串数组:•长度不是8整数倍的字符串请在后面补数字0,空字符串不处理. 输入描述: 连续输入字符串(输入2次,每个字符串长度小于10 ...

  7. TICK 中Kapacitor功能和使用说明

    转载请注明出处: 1.Kapacitor 简介 Kapacitor是InfluxData公司开发的一个实时流数据处理引擎.它可以实时地通过TICK脚本处理InfluxDB中的流数据以及批处理数据. K ...

  8. 【C/C++】 开发必备知识总结

    >from: C/C++ 开发必备知识总结 (qq.com) const 作用 修饰变量,说明该变量不可以被改变: 修饰指针,分为指向常量的指针和指针常量: 常量引用,经常用于形参类型,即避免了 ...

  9. 2023第十四届极客大挑战 — PWN WP

    WP可能有点简陋,因为是直接从docx导入到博客的,实在不想再重新写了!大家凑合着看吧!哈哈哈,问题不大! pwn方向出自:队友 nc_pwntools 只要过了chal1和chal2即可执行任意命令 ...

  10. 最新版TikTok 抖音国际版解锁版 v33.1.4 去广告 免拔卡

    软件简介: 抖音国际版App是全球最受欢迎的短视频应用,抖音国际版TikTok(海外版)横扫全球下载量常居榜首.这是最新抖音国际版解锁版,无视封锁和下载限制,国内免拔卡,去除了广告,下载视频无水印(T ...