2023-01-04 16:45:13 +08:00

831 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

本节介绍如何获取元素对象。可以从页面中查找元素,也可以在元素中查找子元素。
无论是数据采集还是页面自动化,定位元素都是重中之重的的技能,浏览器开发者工具虽然可以直接复制绝对 xpath 或 css 路径,但这样做一来代码繁琐,可读性低,二来难以应付动态变化的页面。
本库提供一套简洁易用的语法,用于快速定位元素,并且内置等待功能、支持链式查找,减少了代码的复杂性。
定位元素大致分为三种方法:
- 在页面或元素内查找子元素
- 根据 DOM 结构相对定位
- 根据页面布局位置相对定位
d 模式的元素还有专门用于处理 shadow dom 的`shadow_root`属性。获取到的元素可继续用这些方法获取后代元素,使用方法和普通元素一致。
# ✔️ 示例
先看一些示例,感性认识一下,后面在详细讲解用法。
## 📍 简单示例
```html
<html>
<body>
<div id="one">
<p class="p_cls" name="row1">第一行</p>
<p class="p_cls" name="row2">第二行</p>
<p class="p_cls">第三行</p>
</div>
<div id="two">
第二个div
</div>
</body>
</html>
```
假设 page 为以上页面的`WebPage`对象,我们可以用这些方法获取其中的元素:
```python
# 获取 id 为 one 的元素
div1 = page.ele('#one')
# 获取 div1 元素内所有 p 元素组成的列表
p_eles = div1.eles('tag:p')
# 获取 name 属性为 row1 的元素
p1 = page.ele('@name=row1')
# 获取 name 属性为 row2 且包含“第二”文本的 p 元素
p_ele = page.ele('tag:p@@text():第二@@name=row2')
# 获取包含“第二个div”文本的元素
div2 = page.ele('第二个div')
# 用 xpath 查找
div2 = page.ele('xpath://div[@id="tow"]')
# 从第一行元素用相对定位获取第三行元素
p3 = p1.next(2)
# 获取 p1 元素的父元素
parent = p1.parent()
# 获取 p1 后面的第一个 div 元素
div2 = p1.after(1, 'tag:div')
```
## 📍 实际示例
复制此代码可直接运行查看结果。
```python
from DrissionPage import WebPage
page = WebPage('s')
page.get('https://gitee.com/explore')
# 获取包含“全部推荐项目”文本的 ul 元素
ul_ele = page.ele('tag:ul@@text():全部推荐项目')
# 获取该 ul 元素下所有 a 元素
titles = ul_ele.eles('tag:a')
# 遍历列表,打印每个 a 元素的文本
for i in titles:
print(i.text)
"""
输出:
全部推荐项目
前沿技术
智能硬件
IOT/物联网/边缘计算
车载应用
以下省略……
"""
```
# ✔️ 查找元素方法
## 📍 查找单个元素
查找单个元素使用`ele()`方法。
页面对象和元素对象都拥有此方法,用于查找第一个匹配条件的元素。
d 模式下返回`ChromiumElement`对象s 模式下返回`SessionElement`对象,没有找到返回`None`
在元素下查找子元素时,还可以用 xpath 获取元素属性,直接返回属性文本。
页面对象和元素对象的`ele()`方法参数名称稍有不同,但用法一样。
**参数:**
- `loc_or_str`(元素对象):元素的定位信息,可以是 loc 元组,或查询字符串
- `loc_or_ele`页面对象元素的定位信息可以是元素对象loc 元组,或查询字符串
- `timeout`查找元素超时时间默认与元素所在页面等待时间一致s 模式下无效
**返回:** s 模式下返回`SessionElement`d 模式下返回`ChromiumElement`;或用 xpath 获取到的属性值
```python
from DrissionPage import WebPage
page = WebPage()
# 在页面内查找元素
ele1 = page.ele('search text')
# 在元素内查找后代元素
ele2 = ele1.ele('search text')
# 使用 xpath 获取后代中第一个 div 元素的 class 属性(元素内查找可用)
ele_class = ele1.ele('xpath://div/@class')
```
## 📍 查找多个元素
查找多个元素使用`eles()`方法。
此方法与`ele()`相似,但返回的是匹配到的所有元素组成的列表,用 xpath 获取元素属性时,返回属性文本组成的列表。
**参数:**
- `loc_or_str`:元素的定位信息,可以是 loc 元组,或查询字符串
- `timeout`查找元素超时时间默认与元素所在页面等待时间一致s 模式下无效
**返回:** s 模式下返回`SessionElement`组成的列表d 模式下返回`ChromiumElement`组成的列表;或用 xpath 获取到的属性值组成的列表
```python
# 获取 ele 元素内的所有 p 元素
p_eles = ele.eles('tag:p')
# 打印第一个 p 元素
print(p_eles[0])
```
## 📍 查找单个静态元素
静态元素即 s 模式的`SessionElement`元素对象,是纯文本构造的,因此用它处理速度非常快速。对于复杂的页面,要在成百上千个元素中采集数据时,转换为静态元素可把速度提升几个数量级。作者曾在采集的时候,用同一套逻辑,仅仅把元素转换为静态,就把一个要 30 秒才采集 完成的页面,加速到零点几秒完成。
我们甚至可以把整个页面转换为静态元素,再在其中提取信息。
当然,这种元素不能进行点击等交互。
s 模式下所有元素本身就是静态元素d 模式下,用`s_ele()`可在把查找到的动态元素转换为静态元素输出,或者获取元素或页面本身的静态元素副本。
`s_ele()`方法
页面对象和元素对象都拥有此方法,用于查找第一个匹配条件的元素,获取其静态版本。
页面对象和元素对象的`s_ele()`方法参数名称稍有不同,但用法一样。
此方法用于在一个元素下查找后代元素,以`SessionElement`形式返回结果xpath 获取属性值时依然是返回`str`),也可以直接将一个元素或页面转换为`SessionElement`版本。
这是为了 d 模式处理速度的提升,当爬取复杂页面信息而且不须要和元素进行交互时,生成整个页面或者主要容器元素的`SessionElement`,再在其中获取信息,可以将速度提升几个数量级。
s 模式下这个方法和`ele()`是一样的。
**参数:**
- `loc_or_str`(元素对象):元素的定位信息,可以是 loc 元组,或查询字符串。为`None`时直接返回当前元素的`SessionElemnet`版本
- `loc_or_ele`(页面对象):元素的定位信息,可以是 loc 元组,或查询字符串。为`None`时直接返回当前页面的 `SessionElemnet`版本
**返回:**`SessionElement`,或用 xpath 获取到的属性值
!>**注意:**<br>页面对象和元素对象的`s_ele()`方法不能搜索到在 frame 里的元素,页面对象的静态版本也不能搜索 frame 里的元素。要使用 frame 里元素的静态版本,可先获取该元素,再转换。而使用`ChromiumFrame`对象,则可以直接用`s_ele()`查找元素,这在后面章节再讲述。
```python
from DrissionPage import WebPage
p = WebPage()
# 在页面中查找元素,获取其静态版本
ele1 = page.s_ele('search text')
# 在动态元素中查找元素,获取其静态版本
ele = page.ele('search text')
ele2 = ele.s_ele()
# 获取页面元素的静态副本(不传入参数)
s_page = page.s_ele()
# 获取动态元素的静态副本
s_ele = ele.s_ele()
# 在静态副本中查询下级元素因为已经是静态元素用ele()查找结果也是静态)
ele3 = s_page.ele('search text')
ele4 = s_ele.ele('search text')
```
## 📍 查找多个静态元素
查找多个静态元素使用`s_eles()`方法。
此方法与`s_ele()`相似,但返回的是匹配到的所有元素组成的列表,或属性值组成的列表。
**参数:**
- `loc_or_str`:元素的定位信息,可以是 loc 元组,或查询字符串(必填)
**返回:**`SessionElement`组成的列表,或用 xpath 获取到的属性值组成的列表
```python
from DrissionPage import WebPage
p = WebPage()
for ele in p.s_eles('search text'):
print(ele.text)
```
## 📍 获取当前焦点元素
使用方法`active_ele()`获取页面上焦点所在元素,为 d 模式独有。
```python
ele = page.active_ele
```
## 📍 获取 shadow_root
d 模式元素如果包含 shadow_root可使用`shadow_root`属性获取。
该属性返回的是一个`ChromiumShadowRootElement`对象,用法与`ChromiumElement`相似。也能使用各种元素查找方式,返回内部元素或相对位置元素,返回 `ChromiumElement`元素。返回的`ChromiumElement`和普通的没有区别。
```python
# 获取一个元素下是 shadow root
shadow_ele = ele.shadow_root
# 获取该 shadow root 下的一个元素
ele1 = shadow_ele.ele('search text')
# 点击获取到的元素
ele1.click()
```
# ✔️ 查找语法
我们使用一套简洁高效的语法去定位元素,大大简化了定位元素的代码量,增强了功能,也兼容 css selector、xpath、selenium 原生的 loc 元组s 模式也能用。d 模式和 s
模式定位元素的语法是完全一样的,便于模式切换时平滑过渡。
**匹配模式** 指字符串是否完全匹配,有以下两种:
## 📍 精确匹配符 `=`
表示精确匹配,匹配完全符合的文本或属性。
## 📍 模糊匹配符 `:`
表示模糊匹配,匹配含有某个字符串的文本或属性。
**关键字** 是出现在定位语句最左边,用于指明该语句以哪种方式去查找元素,有以下这些:
## 📍 id 匹配符 `#`
表示`id`属性,只在语句最前面且单独使用时生效,可配合`=``:`
```python
# 在页面中查找 id 属性为 ele_id 的元素
ele1 = page.ele('#ele_id')
# 在 ele1 元素内查找 id 属性包含 ele_id 文本的元素
ele2 = ele1.ele('#:ele_id')
```
## 📍 class 匹配符 `.`
表示`class`属性,只在语句最前面且单独使用时生效,可配合`=``:`
```python
# 查找 class 属性为 ele_class 的元素
ele2 = ele1.ele('.ele_class')
# 查找 class 属性包含 ele_class 文本的元素
ele2 = ele1.ele('.:ele_class')
```
## 📍 单属性匹配符 `@`
表示某个属性,只匹配一个属性。
`@`关键字只有一个简单功能,就是匹配`@`后面的内容,不再对后面的字符串进行解析。因此即使后面的字符串也存在`@``@@`,也作为要匹配的内容对待。
!> **注意:**
如果属性中包含特殊字符,如包含`@`,用这个方式不能正确匹配到,须使用 css selector 方式查找。且特殊字符要用`\`转义。
```python
# 查找 name 属性为 ele_name 的元素
ele2 = ele1.ele('@name=ele_name')
# 查找 name 属性包含 ele_name 文本的元素
ele2 = ele1.ele('@name:ele_name')
# 查找有 name 属性的元素
ele2 = ele1.ele('@name')
# 查找没有任何属性的元素
ele2 = ele1.ele('@')
# 查找 emaile 属性为 abc@def.com 的元素,有多个 @ 也不会重复处理
ele2 = ele1.ele('@email=abc@def.com')
# 属性中有特殊字符的情形匹配abc@def属性等于v的元素
ele2 = ele1.ele('css:div[abc\@def="v"]')
```
## 📍 多属性匹配符 `@@`
表示某个属性,多属性匹配时使用,个数不限。还能匹配要忽略的元素,匹配文本时也和`@`不一样。
`@@`后跟 - 时,表示 not。如
- `@@-name`表示匹配没有`name`属性的元素
- `@@-name=ele_name`表示匹配`name`属性不为`ele_name`的元素
如有以下情况,不能使用此方式,须改用 xpath 的方式:
- 匹配文本或属性中出现`@@`
- 属性名本身以`-`开头
!> **注意:**<br>如果属性中包含特殊字符,如包含`@`,用这个方式不能正确匹配到,须使用 css selector 方式查找。且特殊字符要用`\`转义。
```python
# 查找 name 属性为 name 且 class 属性包含 cls 文本的元素
ele2 = ele1.ele('@@name=name@@class:cls')
# 查找没有 class 属性的元素
ele2 = ele1.ele('@@-class')
# 查找 name 属性不包含 ele_name 的元素
ele2 = ele1.ele('@@-name:ele_name')
```
## 📍 文本匹配符 `text`
要匹配的文本,查询字符串如开头没有任何关键字,也表示根据传入的文本作模糊查找。
如果元素内有多个直接的文本节点,精确查找时可匹配所有文本节点拼成的字符串,模糊查找时可匹配每个文本节点。
```python
# 查找文本为 some text 的元素
ele2 = ele1.ele('text=some text')
# 查找文本包含 some text 的元素
ele2 = ele1.ele('text:some text')
# 与上一行一致
ele2 = ele1.ele('some text')
```
?> **Tips** <br>若要查找的文本包含`text:` ,可下面这样写,即第一个`text:` 为关键字,第二个是要查找的内容:
```python
ele2 = page.ele('text:text:')
```
## 📍 文本匹配符 `text()`
作为查找属性时使用的文本关键字,必须与`@``@@`配合使用。
```python
# 查找文本为 some text 的元素
ele2 = ele1.ele('@text()=some text')
# 查找文本包含 some text 的元素
ele2 = ele1.ele('@text():some text')
# 查找文本为 some text 且 class 属性为 cls 的元素
ele2 = ele1.ele('@@text()=some text@@class=cls')
# 查找文本为 some text 且没有任何属性的元素(因第一个 @@ 后为空)
ele2 = ele1.ele('@@@@text():some text')
# 查找直接子文本包含 some text 字符串的元素,和 text 关键字没有区别
ele = page.ele('@text():some text')
ele = page.ele('text:some text')
# 查找元素内部包含 some text 字符串的元素
ele = page.ele('@@text():some text')
```
须要注意的是,`'text=xxxx'``'@text()=xxxx'`使用上是有细微差别的。
`text=`表示在元素的直接子文本节点中匹配,`@text()=`会忽略一些文本标签,在整个元素的内容里匹配。
## 📍 类型匹配符 `tag`
表示元素的标签,只在语句最前面且单独使用时生效,可与`@``@@`配合使用。`tag:``tag=`效果一致。
```python
# 定位 div 元素
ele2 = ele1.ele('tag:div')
# 定位 class 属性为 cls 的 div 元素
ele2 = ele1.ele('tag:div@class=cls')
# 定位文本为 text 的 div 元素
ele2 = ele1.ele('tag:div@text()=text')
# 定位 class 属性为 cls 且文本为 text 的 div 元素
ele2 = ele1.ele('tag:div@@class=cls@@text()=text')
# 查找直接文本节点包含 text 字符串的 div 元素
ele2 = ele1.ele('tag:div@text():text')
# 查找内部文本节点包含 text 字符串的 div 元素
ele2 = ele1.ele('tag:div@@text():text')
```
?> **Tips:** <br>注意, `tag:div@text():text``tag:div@@text():text` 是有区别的,前者只在`div`的直接文本节点搜索,后者搜索`div`的整个内部。
## 📍 css selector 匹配符 `css`
表示用 css selector 方式查找元素。`css:``css=`效果一致。
```python
# 查找 div 元素
ele2 = ele1.ele('css:.div')
# 查找 div 子元素元素,这个写法是本库特有,原生不支持
ele2 = ele1.ele('css:>div')
```
## 📍 xpath 匹配符 `xpath`
表示用 xpath 方式查找元素。`xpath:``xpath=`效果一致。
该方法支持完整的 xpath 语法,能使用 xpath 直接获取元素属性selenium 不支持这种用法。
```python
# 查找 div 元素
ele2 = ele1.ele('xpath:.//div')
# 和上面一行一样,查找元素的后代时,// 前面的 . 可以省略
ele2 = ele1.ele('xpath://div')
# 获取 div 元素的 class 属性,返回字符串
txt = ele1.ele('xpath://div/@class')
```
?> **Tips:** <br>查找元素的后代时selenium 原生代码要求 xpath 前面必须加`.`,否则会变成在全个页面中查找。作者觉得这个设计是画蛇添足,既然已经通过元素查找了,自然应该只查找这个元素内部的元素。所以,用 xpath 在元素下查找时,最前面`//``/`前面的`.`可以省略。
## 📍 selenium 的 loc 元组
查找方法能直接接收 selenium 原生定位元组进行查找s 模式下也支持这种写法。
```python
from selenium.webdriver.common.by import By
# 查找 id 为 ele_id 的元素
loc1 = (By.ID, 'ele_id')
ele = page.ele(loc1)
# 按 xpath 查找
loc2 = (By.XPATH, '//div[@class="ele_class"]')
ele = page.ele(loc2)
```
# ✔️ 等待
## 📍 等待元素加载
由于网络的不稳定性、js 运行时间的不确定性等因素,经常须要等待元素加载到 DOM 中才能使用。
d 模式下所有查找元素操作都自带等待,时间默认跟随元素所在页面`timeout`属性(默认 10 秒),也可以在每次查找时单独设置,单独设置的等待时间不会改变页面原来设置。
```python
# 页面初始化时设置查找元素超时时间为 15 秒
page = WebPage(timeout=15)
# 设置查找元素超时时间为 5 秒
page.timeout = 5
# 使用页面超时时间来查找元素5 秒)
ele1 = page.ele('search text')
# 为这次查找页面独立设置等待时间1 秒)
ele1 = page.ele('search text', timeout=1)
# 查找后代元素使用页面超时时间5 秒)
ele2 = ele1.ele('search text')
# 查找后代元素使用单独设置的超时时间1 秒)
ele2 = ele1.ele('some text', timeout=1)
```
## 📍 等待元素状态改变
有时候我们须要等待元素到达某种状态,如显示、隐藏、删除。页面对象和元素对象都内置了`wait_ele()`方法,用于等待元素状态变化。
该方法可接收现成的`ChromiumElement`对象,或定位符,默认等待时间为页面对象的`timeout`值,也可以单独设定。
`wait_ele()`方法
**参数:**
- `loc_or_ele`:要等待的元素,可以是元素或定位符
- `timeout`:等待超时时间,默认使用页面超时时间
**方法:**
| 方法 | 参数 | 功能 |
| ----------- | --- | ------------ |
| `display()` | 无 | 等待元素从 DOM 显示 |
| `hidden()` | 无 | 等待元素从 DOM 隐藏 |
| `delete()` | 无 | 等待元素从 DOM 删除 |
**返回:** 这些方法返回布尔值,代表是否等待成功。
```python
# 等待 id 为 div1 的元素显示,超时使用页面设置
page.wait_ele('#div1').display()
# 等待 id 为 div1 的元素被删除(使用 loc 元组设置超时3秒
ele.wait_ele('#div1', timeout=3).delete()
# 等待已获取到的元素被隐藏
ele2 = ele1.ele('#div1')
ele1.wait_ele(ele2).hidden()
```
# ✔️ 相对定位
以下方法可以以某元素为基准,在 DOM 中按照条件获取其兄弟元素、祖先元素、文档前后元素。
除获取元素外,还能通过 xpath 获取任意节点内容,如文本节点、注释节点。这在处理元素和文本节点混排的时候非常有用。
## 📍 获取父级元素
`parent()`方法获取当前元素某一级父元素,可指定筛选条件或层数。
**参数:**
- `level_or_loc`:第几级父元素,或定位符
**返回:** 某层父级元素
```python
# 获取 ele1 的第二层父元素
ele2 = ele1.parent(2)
# 获取 ele1 父元素中 id 为 id1 的元素
ele2 = ele1.parent('#id1')
```
## 📍 获取后面单个兄弟元素
`next()`方法返回当前元素后面的某一个同级元素,可指定筛选条件和第几个。
**参数:**
- `filter_loc`:用于筛选元素的查询语法
- `index`:查询结果中的第几个
- `timeout`:查找元素的超时时间
**返回:** 本元素后面某个兄弟元素或节点文本
```python
# 获取 ele1 后面第一个兄弟元素
ele2 = ele1.next()
# 获取 ele1 后面第 3 个兄弟元素
ele2 = ele1.next(3)
# 获取 ele1 后面第 3 个 div 兄弟元素
ele2 = ele1.next('tag:div', 3)
# 获取 ele1 后面第一个文本节点的文本
txt = ele1.next('xpath:text()', 1)
```
## 📍 获取后面多个兄弟元素
`nexts()`方法返回当前元素后面全部符合条件的同级元素或节点组成的列表,可用查询语法筛选。
**参数:**
- `filter_loc`:用于筛选元素的查询语法
- `timeout`:查找元素的超时时间
**返回:** 本元素前面符合条件的兄弟元素或节点文本组成的列表
```python
# 获取 ele1 后面所有兄弟元素
eles = ele1.nexts()
# 获取 ele1 后面所有 div 兄弟元素
divs = ele1.nexts('tag:div')
# 获取 ele1 后面的所有文本节点
txts = ele1.nexts('xpath:text()')
```
## 📍 获取前面单个兄弟元素
`prev()`方法返回当前元素前面的某一个同级元素,可指定筛选条件和第几个。
**参数:**
- `filter_loc`:用于筛选元素的查询语法
- `index`:查询结果中的第几个
- `timeout`:查找元素的超时时间
**返回:** 本元素前面某个兄弟元素或节点文本
```python
# 获取 ele1 前面第一个兄弟元素
ele2 = ele1.prev()
# 获取 ele1 前面第 3 个兄弟元素
ele2 = ele1.prev(3)
# 获取 ele1 前面第 3 个 div 兄弟元素
ele2 = ele1.prev(3, 'tag:div')
# 获取 ele1 前面第一个文本节点的文本
txt = ele1.prev(1, 'xpath:text()')
```
## 📍 获取前面多个兄弟元素
`prevs()`方法返回当前元素前面全部符合条件的同级元素或节点组成的列表,可用查询语法筛选。
**参数:**
- `filter_loc`:用于筛选元素的查询语法
- `timeout`:查找元素的超时时间
**返回:** 本元素前面符合条件的兄弟元素或节点文本组成的列表
```python
# 获取 ele1 前面所有兄弟元素
eles = ele1.prevs()
# 获取 ele1 前面所有 div 兄弟元素
divs = ele1.prevs('tag:div')
```
## 📍 在后面文档中查找单个元素
`after()`方法返回当前元素后面的某一个元素,可指定筛选条件和第几个。这个方法查找范围不局限在兄弟元素间,而是整个 DOM 文档。
**参数:**
- `filter_loc`:用于筛选元素的查询语法
- `index`:查询结果中的第几个
- `timeout`:查找元素的超时时间
**返回:** 本元素后面某个元素或节点
```python
# 获取 ele1 后面第 3 个元素
ele2 = ele1.after(3)
# 获取 ele1 后面第 3 个 div 元素
ele2 = ele1.after('tag:div', 3)
# 获取 ele1 后面第一个文本节点的文本
txt = ele1.after('xpath:text()', 1)
```
## 📍 在后面文档中查找多个元素
`afters()`方法返回当前元素后面符合条件的全部元素或节点组成的列表,可用查询语法筛选。这个方法查找范围不局限在兄弟元素间,而是整个 DOM 文档。
**参数:**
- `filter_loc`:用于筛选元素的查询语法
- `timeout`:查找元素的超时时间
**返回:** 本元素后面符合条件的元素或节点组成的列表
```python
# 获取 ele1 后所有元素
eles = ele1.afters()
# 获取 ele1 前面所有 div 元素
divs = ele1.afters('tag:div')
```
## 📍 在前面文档中查找单个元素
`before()`方法返回当前元素前面的某一个元素,可指定筛选条件和第几个。这个方法查找范围不局限在兄弟元素间,而是整个 DOM 文档。
**参数:**
- `filter_loc`:用于筛选元素的查询语法
- `index`:查询结果中的第几个
- `timeout`:查找元素的超时时间
**返回:** 本元素前面某个元素或节点
```python
# 获取 ele1 前面第 3 个元素
ele2 = ele1.before(3)
# 获取 ele1 前面第 3 个 div 元素
ele2 = ele1.before('tag:div', 3)
# 获取 ele1 前面第一个文本节点的文本
txt = ele1.before('xpath:text()', 1)
```
## 📍 在前面文档中查找多个元素
`befores()`方法返回当前元素前面全部符合条件的元素或节点组成的列表,可用查询语法筛选。这个方法查找范围不局限在兄弟元素间,而是整个 DOM 文档。
**参数:**
- `filter_loc`:用于筛选元素的查询语法
- `timeout`:查找元素的超时时间
**返回:** 本元素前面符合条件的元素或节点组成的列表
```python
# 获取 ele1 前面所有元素
eles = ele1.befores()
# 获取 ele1 前面所有 div 元素
divs = ele1.befores('tag:div')
```
# ✔️ 查找 frame 里的元素
## 📍 在页面下跨级查找
与 selenium 不同,本库可以直接查找 frame 里面的元素,而且无视层级,可以直接获取到多层 iframe 里的元素。无需切入切出,大大简化了程序逻辑,使用更便捷。
假设在页面中有个两级 iframe其中有个元素`<div id='abc'></div>`,可以这样获取:
```python
page = WebPage()
ele = page('#abc')
```
获取前后无须切入切出,也不影响获取页面上其它元素。如果用 selenium要这样
```python
driver = webdriver.Chrome()
driver.switch_to.frame(0)
driver.switch_to.frame(0)
ele = driver.find_element(By.ID, 'abc')
driver.switch_to.default_content()
```
显然比较繁琐,而且切入到 iframe 后无法对 iframe 外的元素进行操作。
!>**注意:**<br>跨级查找只是页面对象支持,元素对象不能直接查找内部 iframe 里的元素。
## 📍 在 iframe 元素下查找
本库把 iframe 看作一个特殊元素/页面对象看待,逻辑更清晰,还可以实现同时操作多个 iframe而无须来回切换。查找元素外的更多功能详见介绍 iframe 的章节。
对于跨域名的 iframe我们无法通过页面直接查找里面的元素可以先获取到 iframe 元素,再在其下查找。当然,非跨域 iframe 也可以这样操作。
假设一个 iframe 的 id 为 `'iframe1'`,要在其中查找一个 id 为`'abc'`的元素:
```python
page = WebPage()
iframe = page('#iframe1')
ele = iframe('#abc')
```
这个 iframe 元素是一个页面对象,因此可以继续在其下进行跨 iframe 查找(相对这个 iframe 不跨域的)。
# ✔️ `ShadowRootElement`相关查找
本库把 shadow-root 也作为元素对象看待,是为`ChromiumShadowRootElement`对象。该对象可与普通元素一样查找下级元素和 DOM 内相对定位。
`ChromiumShadowRootElement`对象进行相对定位时,把它看作其父对象内部的第一个对象,其余定位逻辑与普通对象一致。
!> **注意:** <br>如果`ChromiumShadowRootElement`元素的下级元素中有其它`ChromiumShadowRootElement`元素,那这些下级`ChromiumShadowRootElement`
元素内部是无法直接通过定位语句查找到的,只能先定位到其父元素,再用`shadow-root`属性获取。
```python
# 获取一个 shadow-root 元素
sr_ele = page.ele('#app').shadow_root
# 在该元素下查找下级元素
ele1 = sr_ele.ele('tag:div')
# 用相对定位获取其它元素
ele1 = sr_ele.parent(2)
ele1 = sr_ele.next('tag:div', 1)
ele1 = sr_ele.after('tag:div', 1)
eles = sr_ele.nexts('tag:div')
# 定位下级元素中的 shadow+-root 元素
sr_ele2 = sr_ele.ele('tag:div').shadow_root
```
# ✔️ 简化写法
为进一步精简代码,对语法进行了精简
- 定位语法都有其简化形式。
- 页面和元素对象都实现了`__call__()`方法,可直接调用。
- 所有查找方法都支持链式操作
示例:
```python
# 定位到页面中 id 为 table_id 的元素,然后获取它的所有 tr 元素
eles = page('#table_id').eles('t:tr')
# 定位到 class 为 cls 的元素,然后在它里面查找文本为 text 的元素
ele2 = ele1('.cls1')('tx=text')
# 获取 ele1 的 shadow_root 元素,再在里面查找 class 属性为 cls 的元素
ele2 = ele1.sr('.cls')
# 按xpath 查找元素
ele2 = ele1('x://div[@class="ele_class"]')
```
简化写法对应列表
| 原写法 | 简化写法 |
|:-------------:|:------:|
| `text` | `tx` |
| `text()` | `tx()` |
| `tag` | `t` |
| `xpath` | `x` |
| `css` | `c` |
| `shadow_root` | `sr` |
# ✔️ Tips
- 从一个`ChromiumElement`元素获取到的`SessionElement`版本,依然能够使用相对定位方法定位祖先或兄弟元素。但如果元素在 frame 中,相对定位不能超越 frame 文档。
- `SessionElement``SessionPage``ele()``eles()`方法也有`timeout`参数,但它是不生效的,仅用于保持与 d 模式元素书写一致,便于无差别的调用。
- 定位语句内容与关键字重复时,请使用 xpath 或 css selector 代替。