本节介绍如何获取元素对象。 无论是数据采集还是页面自动化,定位元素都是重中之重的的技能,浏览器开发者工具虽然可以直接复制绝对 xpath 或 css 路径,但这样做一来代码繁琐,可读性低,二来难以应付动态变化的页面。 本库提供一套简洁易用的语法,用于快速定位元素,并且内置等待功能、支持链式查找,减少了代码的复杂性。 定位元素大致分为三种方法: - 在页面或元素内查找子元素 - 根据 DOM 结构相对定位 - 根据页面布局位置相对定位 d 模式的元素还有专门用于处理 shadow dom 的`shadow_root`属性。获取到的元素可继续用这些方法获取后代元素,使用方法和普通元素一致。 # ✔️ 示例 ## 📍 简单示例 ```html

第一行

第二行

第三行

第二个div
``` 假设 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`,用 xpath 获取元素属性时,直接返回属性文本。查找不到结果则返回`None`。 参数: - loc_or_str(元素对象拥有):元素的定位信息,可以是 loc 元组,或查询字符串 - loc_or_ele(页面对象拥有):元素的定位信息,可以是元素对象,loc 元组,或查询字符串 - timeout:查找元素超时时间,默认与元素所在页面等待时间一致,s 模式下无效 返回:s 模式下返回`SessionElement`,d 模式下返回`DriverElement`,或用 xpath 获取到的属性值 ```python # 在页面内查找元素 ele1 = page.ele('search text') # 在元素内查找后代元素 ele2 = ele1.ele('search text') # 使用 xpath 获取后代中第一个 div 元素的 class 属性 class = ele1.ele('xpath://div/@class') ``` ## eles() 此方法与`ele()`相似,但返回的是匹配到的所有元素组成的列表,用 xpath 获取元素属性时,返回属性文本组成的列表。 参数: - loc_or_str:元素的定位信息,可以是 loc 元组,或查询字符串 - timeout:查找元素超时时间,默认与元素所在页面等待时间一致,s 模式下无效 返回:s 模式下返回`SessionElement`组成的列表,d 模式下返回`DriverElement`组成的列表,或用 xpath 获取到的属性值组成的列表 ```python # 获取 ele 元素内的所有 p 元素 p_eles = ele.eles('tag:p') # 打印第一个 p 元素 print(p_eles[0]) ``` ## s_ele() 此方法用于在一个元素下查找后代元素,以`SessionElement`形式返回结果(xpath 获取属性值时依然是返回`str`),也可以直接将一个元素或页面转换为`SessionElement`版本。 这是为了 d 模式处理速度的提升,当爬取复杂页面信息而且不须要和元素进行交互时,生成整个页面或者主要容器元素的`SessionElement`,再在其中获取信息,可以将速度提升几个数量级。 s 模式下这个方法和`ele()`是一样的。 参数: - loc_or_str(元素对象拥有):元素的定位信息,可以是 loc 元组,或查询字符串。为`None`时直接返回当前元素的`SessionElemnet`版本 - loc_or_ele(页面对象拥有):元素的定位信息,可以是 loc 元组,或查询字符串。为`None`时直接返回当前页面的 `SessionElemnet`版本 返回:`SessionElement`,或用 xpath 获取到的属性值 ```python # 获取元素或页面的的 SessionElement 版本 ele2 = ele1.s_ele() ele2 = page.s_ele() # 在 ele1 元素下查找元素,并以 SessionElemnet 返回 ele2 = ele1.s_ele('search text') # 在页面下查找元素,并以 SessionElemnet 返回 ele = page.s_ele('search text') ``` ## s_eles() 此方法与`s_ele()`相似,但返回的是匹配到的所有元素组成的列表,或属性值组成的列表。 参数: - loc_or_str:元素的定位信息,可以是 loc 元组,或查询字符串(必填) 返回:`SessionElement`组成的列表,或用 xpath 获取到的属性值组成的列表 ## active_ele 该属性返回当前页面焦点所在元素。d 模式独有。 ```python ele = page.active_ele ``` ## shadow_root `DriverElement`元素除了以上方法和属性外,还有`shadow_root`属性,用于获取其内部的 shadow_root 元素。 该属性返回的是一个`ShadowRootElement`,类似于`DriverElement`,功能比`DriverElement`少。但也有`ele()`和`eles()`方法,可直接搜索其下的元素,返回 `DriverElement` 元素。返回的`DriverElement`和普通的没有区别。 ```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 的方式: - 匹配文本或属性中出现`@@` - 属性名本身以`-`开头 !> **注意:** 如果属性中包含特殊字符,如包含`@`,用这个方式不能正确匹配到,须使用 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:**
若要查找的文本包含`text:` ,可下面这样写,即第一个`text:` 为关键字,第二个是要查找的内容: ```python ele2 = page.ele('text:text:') ``` ## 📍 文本匹配符`text()` 作为查找属性时使用的文本关键字,必须与`@`或`@@`配合使用。 与`@`配合和与`@@`配合使用时,`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') ``` ## 📍 类型匹配符`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:**
注意, `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:**
查找元素的后代时,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) ``` # ✔️ 等待 d 模式下所有查找元素操作都自带等待,默认为跟随元素所在页面`timeout`属性(默认 10 秒),也可以在每次查找时单独设置,单独设置的等待时间不会改变页面原来设置。 ```python # 页面初始化时设置查找元素超时时间为 15 秒 page = MixPage(timeout=15) # 设置查找元素超时时间为 5 秒 page.timeout = 5 # 使用页面超时时间来查找元素(5 秒) ele1 = page.ele('some text') # 为这次查找页面独立设置等待时间(1 秒) ele1 = page.ele('some text', timeout=1) # 查找后代元素,使用页面超时时间(5 秒) ele2 = ele1.ele('some text') # 查找后代元素,使用单独设置的超时时间(1 秒) ele2 = ele1.ele('some text', timeout=1) ``` # ✔️ 相对定位 以下方法可以以某元素为基准,在 DOM 中按照条件获取其兄弟元素、祖先元素、文档前后元素。 除获取元素外,还能通过 xpath 获取任意节点内容,如文本节点、注释节点。这在处理元素和文本节点混排的时候非常有用。 ## parent() 此方法获取当前元素某一级父元素,可指定筛选条件或层数。 参数: - level_or_loc:第几级父元素,或定位符 返回: ```python # 获取 ele1 的第二层父元素 ele2 = ele1.parent(2) # 获取 ele1 父元素中 id 为 id1 的元素 ele2 = ele1.parent('#id1') ``` ## next() 此方法返回当前元素后面的某一个兄弟元素,可指定筛选条件和第几个。 参数: - index:查询结果中的第几个 - filter_loc:用于筛选元素的查询语法 - timeout:查找元素的超时时间 返回:本元素后面某个兄弟元素或节点文本 ```python # 获取 ele1 后面第一个兄弟元素 ele2 = ele1.next() # 获取 ele1 后面第 3 个兄弟元素 ele2 = ele1.next(3) # 获取 ele1 后面第 3 个 div 兄弟元素 ele2 = ele1.next(3, 'tag:div') # 获取 ele1 后面第一个文本节点的文本 txt = ele1.next(1, 'xpath:text()') ``` ## nexts() 此方法返回后面全部符合条件的兄弟元素或节点组成的列表,可用查询语法筛选。 参数: - filter_loc:用于筛选元素的查询语法 - timeout:查找元素的超时时间 返回:本元素前面符合条件的兄弟元素或节点文本组成的列表 ```python # 获取 ele1 后面所有兄弟元素 eles = ele1.nexts() # 获取 ele1 后面所有 div 兄弟元素 divs = ele1.nexts('tag:div') # 获取 ele1 后面的所有文本节点 txts = ele1.nexts('xpath:text()') ``` ## prev() 此方法返回当前元素前面的某一个兄弟元素,可指定筛选条件和第几个。 参数: - index:查询结果中的第几个 - filter_loc:用于筛选元素的查询语法 - 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 文档。 参数: - index:查询结果中的第几个 - filter_loc:用于筛选元素的查询语法 - timeout:查找元素的超时时间 返回:本元素后面某个元素或节点 ```python # 获取 ele1 后面第 3 个元素 ele2 = ele1.after(3) # 获取 ele1 后面第 3 个 div 元素 ele2 = ele1.after(3, 'tag:div') # 获取 ele1 后面第一个文本节点的文本 txt = ele1.after(1, 'xpath:text()') ``` ## afters() 此方法返回后面符合条件的全部元素或节点组成的列表,可用查询语法筛选。这个方法查找范围不局限在兄弟元素间,而是整个 DOM 文档。 参数: - filter_loc:用于筛选元素的查询语法 - timeout:查找元素的超时时间 返回:本元素后面符合条件的元素或节点组成的列表 ```python # 获取 ele1 后所有元素 eles = ele1.afters() # 获取 ele1 前面所有 div 元素 divs = ele1.afters('tag:div') ``` ## before() 此方法返回当前元素前面的某一个元素,可指定筛选条件和第几个。这个方法查找范围不局限在兄弟元素间,而是整个 DOM 文档。 参数: - index:查询结果中的第几个 - filter_loc:用于筛选元素的查询语法 - timeout:查找元素的超时时间 返回:本元素前面某个元素或节点 ```python # 获取 ele1 前面第 3 个元素 ele2 = ele1.before(3) # 获取 ele1 前面第 3 个 div 元素 ele2 = ele1.before(3, 'tag:div') # 获取 ele1 前面第一个文本节点的文本 txt = ele1.before(1, 'xpath:text()') ``` ## befores() 此方法返回前面全部符合条件的元素或节点组成的列表,可用查询语法筛选。这个方法查找范围不局限在兄弟元素间,而是整个 DOM 文档。 参数: - filter_loc:用于筛选元素的查询语法 - timeout:查找元素的超时时间 返回:本元素前面符合条件的元素或节点组成的列表 ```python # 获取 ele1 前面所有元素 eles = ele1.befores() # 获取 ele1 前面所有 div 元素 divs = ele1.befores('tag:div') ``` # ✔️ `ShadowRootElement`相关查找 本库把 shadow-root 也作为元素对象看待,是为`ShadowRootElement`对象。对`ShadowRootElement`对象可与普通元素一样查找下级元素和 DOM 内相对定位,但不能用页面布局相对定位。 对`ShadowRootElement`对象进行相对定位时,把它看作其父对象内部的第一个对象,其余定位逻辑与普通对象一致。 !> **注意:**
如果`ShadowRootElement`元素的下级元素中有其它`ShadowRootElement`元素,那这些下级`ShadowRootElement` 元素内部是无法直接通过定位语句查找到的,只能先定位到其父元素,再用`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(1, 'tag:div') ele1 = sr_ele.after(1, 'tag:div') 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 - 从一个`DriverElement`元素获取到的`SessionElement`版本,依然能够使用相对定位方法定位祖先或兄弟元素。 - `SessionElement`和`SessionPage`的`ele()`和`eles()`方法也有`timeout`参数,但它是不生效的,仅用于保持与 d 模式元素书写一致,便于无差别的调用。 - 定位语句内容与关键字重复时,请使用 xpath 或 css selector 代替。