advanced_find

高级元素查找

在基础教程中,我们学习了使用 CSS 选择器、XPath 和文本查找元素的基本方法。本章将深入介绍 DrissionPage 提供的高级元素查找技术,帮助你在复杂的网页结构中准确定位元素。

链式查找

链式查找是一种从外到内,逐层定位元素的方法,它可以有效避免复杂选择器带来的维护困难,同时提高查找的准确性。

基本链式查找

# 传统方式(单一长选择器)
element = page.ele('#container > div.section > ul.list > li.item:nth-child(3) > a')

# 链式查找(逐层查找)
container = page.ele('#container')
section = container.ele('div.section')
list_ele = section.ele('ul.list')
item = list_ele.ele('li.item', index=2)  # 第三个元素,索引从0开始
link = item.ele('a')

链式查找的优势在于:

  • 代码更清晰,易于理解
  • 每一步都可以单独调试
  • 当页面结构变化时,只需修改相应层级的选择器

流式链式查找

DrissionPage 支持流式接口风格的链式查找,使代码更简洁:

# 流式链式查找
link = page.ele('#container').ele('div.section').ele('ul.list').ele('li.item', index=2).ele('a')

# 还可以在链中执行操作
page.ele('#container').ele('.login-form').ele('#username').input('user123')

使用查找范围限定

链式查找实质上是在限定查找范围,这在处理大型页面或具有重复结构的页面时特别有用:

# 在特定区域内查找元素
sidebar = page.ele('#sidebar')
main_content = page.ele('#main-content')

# 在侧边栏中查找链接
sidebar_links = sidebar.eles('a')

# 在主内容区域中查找相同选择器的元素
main_links = main_content.eles('a')

# 处理不同区域的相同结构
for section in page.eles('.content-section'):
    title = section.ele('h2').text
    content = section.ele('p').text
    print(f'标题: {title}, 内容: {content}')

相对定位

相对定位是根据已知元素的位置关系来定位其他元素的方法,适用于处理没有明确标识符的元素。

获取兄弟元素

# 获取前一个兄弟元素
previous_sibling = element.prev()

# 获取后一个兄弟元素
next_sibling = element.next()

# 获取所有前面的兄弟元素
all_previous = element.prevs()

# 获取所有后面的兄弟元素
all_next = element.nexts()

# 获取特定的兄弟元素
second_prev = element.prev(2)  # 前面第二个兄弟
third_next = element.next(3)   # 后面第三个兄弟

获取父元素和祖先元素

# 获取直接父元素
parent = element.parent

# 获取祖先元素
ancestor = element.parents('div.container')

# 获取第n级父元素
third_level_parent = element.parent_ele(level=3)

使用相对关系定位

# 找到标签元素
label = page.ele('label[for="username"]')

# 通过相邻关系找到对应的输入框
input_field = label.next()

# 或者通过父元素找到同级别的输入框
form_group = label.parent
input_field = form_group.ele('input')

# 实际案例:处理表格行
row = page.ele('tr@text*=产品名称')  # 找到包含"产品名称"的行
cell = row.next()                  # 获取下一行
price = cell.ele('span.price').text  # 获取价格

组合查找策略

DrissionPage 支持多种查找条件的组合,使元素定位更加精确。

多条件组合

# 使用多个属性条件
element = page.ele('@id=username@type=text@required')

# 组合CSS和文本条件
element = page.ele('button.primary@text=确认提交')

# 组合XPath和属性条件
element = page.ele('xpath://div[@class="item"]@data-id=123')

# 复杂组合条件
element = page.ele('div.card@title*=产品@text*=限时折扣')

使用索引和筛选

# 使用索引选择特定元素
third_item = page.ele('li.item', index=2)  # 索引从0开始

# 获取所有元素后按条件筛选
buttons = page.eles('button')
submit_buttons = [btn for btn in buttons if 'submit' in btn.attr('class')]

# 使用内置筛选功能
visible_items = page.eles('.item', displayed=True)  # 仅获取可见元素

使用函数筛选元素

# 自定义筛选函数
def is_valid_product(element):
    price_text = element.ele('.price').text
    try:
        price = float(price_text.strip('¥'))
        return 100 <= price <= 500  # 筛选价格在100-500之间的产品
    except:
        return False

# 获取所有产品元素
all_products = page.eles('.product-item')

# 使用筛选函数
valid_products = [product for product in all_products if is_valid_product(product)]
print(f'找到{len(valid_products)}个符合条件的产品')

处理动态内容

现代网页经常包含动态加载的内容,需要特殊的查找策略。

等待元素出现

# 等待元素出现(显式等待)
element = page.wait.ele_appear('#dynamic-content', timeout=10)

# 或者在查找时设置超时参数
element = page.ele('#dynamic-content', timeout=10)

# 等待元素消失
page.wait.ele_disappear('.loading-indicator', timeout=5)

# 等待文本出现
page.wait.text_appear('加载完成', timeout=10)

处理动态加载的列表

# 处理无限滚动加载的内容
def load_all_items(page, max_scrolls=10):
    items = set()  # 使用集合去重
    
    for i in range(max_scrolls):
        # 获取当前项目
        current_items = page.eles('.item')
        current_count = len(items)
        
        # 添加新项目到集合
        for item in current_items:
            item_id = item.attr('data-id')
            if item_id:
                items.add(item_id)
        
        # 如果没有新项目,可能已加载完毕
        if len(items) == current_count and i > 0:
            print('没有新项目加载,停止滚动')
            break
        
        # 滚动到底部加载更多
        page.scroll.to_bottom()
        page.wait(1)  # 等待新内容加载
    
    # 根据收集的ID重新获取所有元素
    all_items = []
    for item_id in items:
        item = page.ele(f'.item[data-id="{item_id}"]')
        if item:
            all_items.append(item)
    
    return all_items

# 使用示例
page.get('https://example.com/infinite-scroll')
items = load_all_items(page)
print(f'总共加载了{len(items)}个项目')

查找隐藏元素

# 查找隐藏元素(默认查找可见和隐藏的元素)
hidden_element = page.ele('#hidden-field')

# 仅查找可见元素
visible_element = page.ele('.content', displayed=True)

# 仅查找隐藏元素
invisible_element = page.ele('.hidden-content', displayed=False)

# 显示隐藏元素后操作(仅适用于ChromiumPage或WebPage的d模式)
hidden_input = page.ele('input[type="file"]', show=True)
hidden_input.input(r'C:\path\to\file.jpg')

高级属性查找

DrissionPage 提供了多种方式来通过元素属性进行查找。

属性查找操作符

# 精确匹配属性值
element = page.ele('@id=username')

# 属性值包含某文本
element = page.ele('@class*=btn')

# 属性值以某文本开头
element = page.ele('@data-test^=user')

# 属性值以某文本结尾
element = page.ele('@src$=.jpg')

# 组合多个属性条件
element = page.ele('@type=text@placeholder*=用户名@required')

高级文本匹配

# 精确匹配文本
element = page.ele('@text=确认提交')

# 包含特定文本
element = page.ele('@text*=立即注册')

# 文本以特定字符开头
element = page.ele('@text^=欢迎')

# 文本以特定字符结尾
element = page.ele('@text$=服务')

# 使用正则表达式匹配文本
import re
elements = page.eles('.item')
price_pattern = re.compile(r'¥\d+\.\d{2}')
price_elements = [ele for ele in elements if price_pattern.search(ele.text)]

组合属性和标签查找

# 先用标签类型筛选,再按属性查找
input_elements = page.eles('input')
required_inputs = [ele for ele in input_elements if ele.has_attr('required')]

# 查找带有特定属性的所有元素(不管标签类型)
data_elements = page.eles('[data-test]')

Shadow DOM 元素查找

很多现代网页使用 Shadow DOM 来封装组件,这需要特殊的查找方法。

# 查找Shadow Root宿主元素
host = page.ele('my-component')

# 进入Shadow DOM
shadow_root = host.shadow_root

# 在Shadow DOM中查找元素
shadow_element = shadow_root.ele('.shadow-content')

# 或者使用链式写法
shadow_element = page.ele('my-component').shadow_root.ele('.shadow-content')

# 也可以直接使用deep选择器(Chrome支持,但非标准)
element = page.ele('my-component >>> .shadow-content')

iframe 内元素查找

网页中的 iframe 是独立的文档,需要特殊处理。

# 方法1:使用ChromiumPage的iframe功能
iframe = page.ele('iframe#content-frame')
# 切换到iframe内部
with page.iframe(iframe) as iframe_page:
    # 在iframe中查找元素
    element = iframe_page.ele('.iframe-content')
    element.click()
# 自动返回主文档

# 方法2:直接在iframe元素中查找
iframe = page.ele('iframe#content-frame')
element = iframe.ele('.iframe-content')  # 直接在iframe内查找

处理复杂表格

表格是网页中常见的复杂结构,需要特殊的查找策略。

# 获取表格
table = page.ele('table#data')

# 获取所有行
rows = table.eles('tr')

# 获取表头
headers = [th.text for th in rows[0].eles('th')]

# 提取数据
data = []
for row in rows[1:]:  # 跳过表头行
    cells = row.eles('td')
    row_data = {headers[i]: cells[i].text for i in range(min(len(headers), len(cells)))}
    data.append(row_data)

# 通过列名查找特定单元格
def find_cell(table, row_idx, col_name):
    # 获取表头
    headers = [th.text for th in table.ele('thead').eles('th')]
    if col_name not in headers:
        return None
    
    col_idx = headers.index(col_name)
    rows = table.ele('tbody').eles('tr')
    if row_idx >= len(rows):
        return None
    
    cells = rows[row_idx].eles('td')
    if col_idx >= len(cells):
        return None
    
    return cells[col_idx]

# 使用示例
price_cell = find_cell(page.ele('table#products'), 2, '价格')
if price_cell:
    print(f'价格: {price_cell.text}')

查找性能优化

对于复杂页面,元素查找可能成为性能瓶颈,以下是一些优化技巧:

缓存常用元素

# 缓存常用的容器元素
main_container = page.ele('#main')
sidebar = page.ele('#sidebar')

# 在缓存的容器中查找,而不是从整个页面查找
for i in range(10):
    # 更高效:在容器中查找
    item = main_container.ele(f'.item-{i}')
    
    # 不太高效:从整个页面查找
    # item = page.ele(f'#main .item-{i}')

优化选择器

# 更高效:使用ID选择器
element = page.ele('#username')

# 中等效率:使用类选择器
element = page.ele('.username-field')

# 较低效率:使用复杂组合选择器
element = page.ele('div.form > div.field > input.username-field')

# 较低效率:使用广泛的属性选择器
element = page.ele('@placeholder=请输入用户名')

限制查找数量

# 当只需要检查元素是否存在时
if page.ele('#notification'):
    print('通知存在')

# 当只需要第一个匹配元素时
first_item = page.ele('.item')  # 优于 page.eles('.item')[0]

# 当需要特定数量的元素时,避免获取全部
top_items = page.eles('.item', limit=5)  # 只获取前5个元素

实战案例:电商网站商品提取

以下是一个综合案例,展示如何在电商网站中使用高级查找技术提取商品信息:

from DrissionPage import ChromiumPage
import csv

def extract_products(url, output_csv):
    page = ChromiumPage()
    try:
        page.get(url)
        
        # 等待商品列表加载
        page.wait.ele_appear('.product-grid', timeout=10)
        
        # 查找所有商品类别标签
        categories = page.eles('.category-tab')
        
        all_products = []
        
        # 遍历每个类别
        for category in categories:
            category_name = category.text
            print(f'正在提取类别: {category_name}')
            
            # 点击类别标签
            category.click()
            page.wait.ele_appear('.product-item', timeout=5)
            
            # 查找当前类别下的所有商品
            product_items = page.eles('.product-item')
            
            for product in product_items:
                # 提取商品信息
                product_data = {
                    'category': category_name,
                    'title': product.ele('.product-title').text,
                    'price': product.ele('.product-price').text,
                    'rating': product.ele('.rating').attr('data-score') if product.ele('.rating') else 'N/A'
                }
                
                # 提取是否有折扣
                discount_tag = product.ele('.discount-tag')
                product_data['discount'] = discount_tag.text if discount_tag else 'No'
                
                # 检查是否有库存
                stock_info = product.ele('.stock-info')
                product_data['in_stock'] = '缺货' not in stock_info.text if stock_info else 'Unknown'
                
                # 提取详情链接
                detail_link = product.ele('a.details')
                if detail_link:
                    product_data['link'] = detail_link.attr('href')
                
                all_products.append(product_data)
        
        # 保存数据到CSV
        with open(output_csv, 'w', newline='', encoding='utf-8') as f:
            writer = csv.DictWriter(f, fieldnames=['category', 'title', 'price', 'rating', 'discount', 'in_stock', 'link'])
            writer.writeheader()
            writer.writerows(all_products)
        
        print(f'成功提取{len(all_products)}个商品信息并保存到{output_csv}')
        
    finally:
        page.close()

# 使用示例
extract_products('https://example.com/products', 'products.csv')

小结

通过本章学习,我们掌握了DrissionPage提供的高级元素查找技术:

  • 链式查找:逐层精确定位元素,提高代码可读性和维护性
  • 相对定位:通过元素间的关系查找相关元素
  • 组合查找:使用多条件组合提高查找精确度
  • 动态内容处理:特殊技巧处理动态加载的内容
  • Shadow DOM和iframe处理:处理特殊页面结构
  • 性能优化:优化查找策略提高脚本执行效率

掌握这些高级查找技术,能够帮助你处理各种复杂的网页结构,准确高效地定位所需元素。在下一章中,我们将学习如何处理等待和超时,确保自动化脚本的稳定性和可靠性。

使用 Hugo 构建
主题 StackJimmy 设计