0

0

Selenium自动化中处理动态弹窗滚动与元素定位的策略

心靈之曲

心靈之曲

发布时间:2025-11-06 14:32:15

|

750人浏览过

|

来源于php中文网

原创

Selenium自动化中处理动态弹窗滚动与元素定位的策略

本教程旨在解决selenium自动化过程中,因网站元素动态变化(尤其是弹窗内的元素)导致的`nosuchelementexception`问题。文章将深入探讨如何利用更具鲁棒性的xpath表达式(如`contains()`和`text()`函数)以及css选择器来稳定定位元素,并提供在复杂场景下(如instagram关注者弹窗)进行滚动和交互的实用代码示例与最佳实践,确保自动化脚本的稳定性和可维护性。

问题剖析:动态元素与NoSuchElementException

在进行网页自动化和数据抓取时,开发者常遇到selenium.common.exceptions.NoSuchElementException错误。这通常发生在Selenium尝试定位一个元素,但该元素在页面上不存在或尚未加载完成时。对于像Instagram这类高度动态的网站,其页面的DOM结构,特别是弹窗(Modal)中的元素,往往会频繁变化。这些变化可能包括元素的完整XPath、类名(class name)或ID等。

当使用绝对XPath(例如/html/body/div[6]/div[1]/div/...)来定位元素时,任何微小的DOM结构调整都可能导致XPath失效,从而引发NoSuchElementException。此外,弹窗内容的动态加载和滚动行为也增加了定位和交互的复杂性。原始代码中尝试定位的弹窗和关注按钮都使用了绝对XPath,这是导致定位失败和无法滚动的主要原因。

提升元素定位鲁棒性的核心策略

为了应对动态网页元素带来的挑战,我们需要采用更具鲁棒性的元素定位策略。

策略一:利用XPath的contains()和text()函数

contains()函数允许我们匹配属性值中包含特定子字符串的元素,而text()函数则用于匹配元素可见文本内容。这两种方法在元素的完整属性值或文本可能变化,但部分内容保持不变时非常有效。

示例:定位包含特定文本的按钮

假设一个按钮的文本是“Click Me”,其XPath可能为:

button = driver.find_element(By.XPATH, "//button[contains(text(), 'Click Me')]")

或者,如果文本在子元素中,可以使用.代表当前元素的文本内容:

button = driver.find_element(By.XPATH, "//button[contains(., 'Click Me')]")

示例:定位包含特定属性值的元素

如果一个元素的class属性包含“modal-content”,即使有其他动态生成的类名,也可以定位:

Axiom
Axiom

Axiom是一个浏览器扩展,用于自动化重复任务和web抓取。

下载
modal_content = driver.find_element(By.XPATH, "//div[contains(@class, 'modal-content')]")

策略二:巧用CSS选择器与部分属性匹配

CSS选择器通常比XPath更简洁,且在某些情况下表现出更好的性能和稳定性。与XPath类似,CSS选择器也支持部分属性匹配。

CSS选择器中的部分属性匹配:

  • [attribute^='value']: 匹配属性值以value开头的元素。
  • [attribute$='value']: 匹配属性值以value结尾的元素。
  • [attribute*='value']: 匹配属性值中包含value的元素。

示例:使用CSS选择器定位元素

# 定位class中包含"dialog"的div
modal_dialog = driver.find_element(By.CSS_SELECTOR, "div[class*='dialog']")

# 定位data-testid为"follower-list"的元素
follower_list = driver.find_element(By.CSS_SELECTOR, "[data-testid='follower-list']")

在检查元素时,优先查找那些看起来相对稳定且具有语义的属性,如id、name、data-testid或部分class名称。

实战演练:处理Instagram弹窗滚动与关注操作

针对Instagram关注者弹窗的场景,我们将结合上述策略进行优化。Instagram的弹窗通常具有role="dialog"属性,并且关注按钮的文本是“关注”或“Following”。

1. 定位可滚动弹窗

Instagram的关注者弹窗通常是一个具有特定role属性或可识别aria-label的div元素。 我们可以尝试使用如下XPath来定位:

# 尝试定位具有role="dialog"且包含特定aria-label的弹窗
# 注意:Instagram的aria-label可能因语言环境而异,例如“关注者”或“Followers”
modal_xpath = "//div[@role='dialog' and contains(@aria-label, '关注者')] | //div[@role='dialog' and contains(@aria-label, 'Followers')]"
# 或者,如果modal是某个特定div的子元素,且该div有特定的class
# modal_xpath = "//div[contains(@class, '_aano') and @role='dialog']" # 这是一个更具体的例子,需要根据实际页面检查

在实际操作中,通过浏览器开发者工具检查弹窗的DOM结构,找到最稳定且唯一的属性组合至关重要。

2. 实现弹窗内容滚动

一旦正确地定位了弹窗元素,就可以使用JavaScript来模拟滚动。关键是找到弹窗内部真正可滚动的元素。通常,这个可滚动区域是弹窗内容的一个子元素。

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import time

# ... (InstaFollower类和初始化代码) ...

class InstaFollower:
    # ... (login方法) ...

    def find_followers(self):
        time.sleep(5) # 等待页面加载
        self.driver.get(f"https://www.instagram.com/{SIMILAR_ACCOUNT}/followers")
        time.sleep(5) # 等待关注者页面加载

        # 尝试更鲁棒的弹窗定位
        # 假设弹窗的容器是role="dialog"的div,并且其内部有一个实际可滚动的div
        # 你可能需要检查Instagram的最新DOM结构来确认这个XPath
        modal_container_xpath = "//div[@role='dialog' and contains(@aria-label, '关注者')] | //div[@role='dialog' and contains(@aria-label, 'Followers')]"

        # 等待弹窗出现
        WebDriverWait(self.driver, 20).until(
            EC.presence_of_element_located((By.XPATH, modal_container_xpath))
        )

        # 找到弹窗的实际可滚动区域。通常是弹窗内部的一个div
        # 这个XPath可能需要根据实际页面进行调整,例如:
        # modal_scrollable_area_xpath = f"{modal_container_xpath}//div[contains(@class, 'isgrP')]" # 这是一个旧的Instagram类名示例
        # 更通用的方法是找到弹窗内具有'overflow: auto'或'scroll'样式的元素
        # 这里假设弹窗本身就是可滚动的,或者其直接子元素是可滚动的

        # 尝试定位弹窗内可滚动的div,通常它会有一个特定的class或者它是role="dialog"的直接子元素
        # 这是一个常见的Instagram弹窗内容滚动区域的XPath模式,但需要根据实际情况调整
        scrollable_div_xpath = f"{modal_container_xpath}//div[contains(@class, '_aano') and @tabindex='0']" # 示例,需要实际验证

        modal_scrollable_area = WebDriverWait(self.driver, 10).until(
            EC.presence_of_element_located((By.XPATH, scrollable_div_xpath))
        )

        # 滚动弹窗
        last_height = self.driver.execute_script("return arguments[0].scrollHeight", modal_scrollable_area)
        for i in range(10): # 滚动10次,每次滚动到底部
            self.driver.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight", modal_scrollable_area)
            time.sleep(2) # 等待新内容加载
            new_height = self.driver.execute_script("return arguments[0].scrollHeight", modal_scrollable_area)
            if new_height == last_height: # 如果没有新内容加载,说明已经滚到底部
                print(f"滚动到第 {i+1} 次,已到底部。")
                break
            last_height = new_height
            print(f"滚动到第 {i+1} 次,当前高度:{new_height}")

        # 滚动完成后,等待所有关注者元素加载
        WebDriverWait(self.driver, 10).until(
            EC.presence_of_all_elements_located((By.XPATH, f"{modal_scrollable_area_xpath}//button[contains(., '关注')] | {modal_scrollable_area_xpath}//button[contains(., 'Following')]"))
        )

    def follow(self):
        # 假设我们已经滚动并加载了所有可见的关注者
        # 现在定位所有“关注”按钮
        # 再次强调:这里的XPath需要根据实际页面结构调整
        # 常见的模式是:在弹窗内部,找到包含“关注”或“Following”文本的button
        follow_buttons_xpath = "//div[@role='dialog']//button[contains(., '关注')] | //div[@role='dialog']//button[contains(., 'Following')]"
        all_buttons = self.driver.find_elements(By.XPATH, follow_buttons_xpath)

        print(f"找到 {len(all_buttons)} 个关注按钮。")

        for button in all_buttons:
            try:
                # 确保按钮是可点击的
                WebDriverWait(self.driver, 5).until(EC.element_to_be_clickable(button))
                button.click()
                print("点击关注按钮。")
                time.sleep(1.1)
            except ElementClickInterceptedException:
                print("点击被拦截,尝试点击取消按钮。")
                # 尝试找到取消按钮,这个按钮通常是弹窗的一部分
                cancel_button = WebDriverWait(self.driver, 5).until(
                    EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), '取消')] | //button[contains(text(), 'Cancel')]"))
                )
                cancel_button.click()
                time.sleep(1) # 等待取消操作完成
            except Exception as e:
                print(f"点击按钮时发生其他错误: {e}")

3. 完整示例代码

下面是一个整合了上述策略的InstaFollower类,重点展示了如何使用更鲁棒的定位方式来处理Instagram的弹窗滚动和关注操作。

import time
from selenium.common.exceptions import ElementClickInterceptedException, NoSuchElementException, TimeoutException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium import webdriver

# 替换为你的目标账号、用户名和密码
SIMILAR_ACCOUNT = "honeymoon" 
USERNAME = "your_username" 
PASSWORD = "your_password" 

class InstaFollower:
    def __init__(self):
        chrome_options = webdriver.ChromeOptions()
        chrome_options.add_experimental_option("detach", True) # 保持浏览器打开
        # chrome_options.add_argument("--headless") # 无头模式,不显示浏览器界面
        # chrome_options.add_argument("--disable-gpu") # 禁用GPU硬件加速
        # chrome_options.add_argument("--no-sandbox") # 禁用沙盒模式
        # chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") # 设置User-Agent
        self.driver = webdriver.Chrome(options=chrome_options)
        self.driver.set_window_size(1024, 768) # 设置窗口大小,避免响应式布局问题

    def login(self):
        self.driver.get("https://www.instagram.com")

        try:
            # 等待登录表单加载,使用更鲁棒的选择器
            login_form = WebDriverWait(self.driver, 20).until(
                EC.presence_of_element_located((By.XPATH, "//form[contains(@id, 'loginForm')] | //form[contains(@action, '/accounts/login/ajax/')]"))
            )

            # 定位用户名和密码输入框
            username_input = WebDriverWait(login_form, 10).until(
                EC.presence_of_element_located((By.NAME, 'username'))
            )
            password_input = WebDriverWait(login_form, 10).until(
                EC.presence_of_element_located((By.NAME, 'password'))
            )

            username_input.send_keys(USERNAME)
            password_input.send_keys(PASSWORD)

            # 定位登录按钮,使用更鲁棒的选择器
            login_button = WebDriverWait(login_form, 10).until(
                EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), '登录')] | //button[contains(text(), 'Log in')]"))
            )
            login_button.click()

            # 等待登录成功后的页面元素出现,例如Instagram的Logo或主页feed
            WebDriverWait(self.driver, 20).until(
                EC.presence_of_element_located((By.XPATH, '//*[@aria-label="Instagram"] | //a[contains(@href, "/explore/")]'))
            )
            print("登录成功!")
            time.sleep(3) # 额外等待,确保页面完全加载

            # 处理“保存登录信息”弹窗(如果出现)
            try:
                not_now_button = WebDriverWait(self.driver, 10).until(
                    EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), '以后再说')] | //button[contains(text(), 'Not Now')]"))
                )
                not_now_button.click()
                print("点击 '以后再说' / 'Not Now' 按钮。")
                time.sleep(2)
            except TimeoutException:
                print("未检测到 '保存登录信息' 弹窗。")

            # 处理“开启通知”弹窗(如果出现)
            try:
                not_now_button_notification = WebDriverWait(self.driver, 10).until(
                    EC.element_to_be_clickable((By.XPATH, "//button[contains(text(), '以后再说')] | //button[contains(text(), 'Not Now')]"))
                )
                not_now_button_notification.click()
                print("点击 '以后再说' / 'Not Now' 按钮(通知)。")
                time.sleep(2)
            except TimeoutException:
                print("未检测到 '开启通知' 弹窗。")

        except TimeoutException:
            print("登录超时或元素未找到,请检查XPath或网络连接。")
            self.driver.quit()
        except Exception as e:
            print(f"登录过程中发生错误: {e}")
            self.driver.quit()

    def find_followers(self):
        print(f"导航到 {SIMILAR_ACCOUNT} 的关注者页面...")
        self.driver.get(f"https://www.instagram.com/{SIMILAR_ACCOUNT}/followers")
        time.sleep(5) # 等待页面加载

        try:
            # 鲁棒地定位关注者弹窗
            # 查找具有 role="dialog" 且 aria-label 包含 "关注者" 或 "Followers" 的 div
            modal_container_xpath = "//div[@role='dialog' and (contains(@aria-label, '关注者') or contains(@aria-label, 'Followers'))]"

            modal_container = WebDriverWait(self.driver, 20).until(
                EC.presence_of_element_located((By.XPATH, modal_container_xpath))
            )
            print("成功定位到关注者弹窗容器。")

            # 找到弹窗内部实际可滚动的区域
            # Instagram的滚动区域通常是弹窗内部一个具有特定class或tabindex的div
            # 这里的XPath可能需要根据Instagram的最新DOM结构进行调整
            # 常见模式:modal_container下,找到tabindex="0"的div作为滚动区域
            scrollable_div_xpath = f"{modal_container_xpath}//div[@tabindex='0' and contains(@class, '_aano')]"

            modal_scrollable_area = WebDriverWait(self.driver, 10).until(
                EC.presence_of_element_located((By.XPATH, scrollable_div_xpath))
            )
            print("成功定位到弹窗可滚动区域。")

            last_height = self.driver.execute_script("return arguments[0

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

557

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

395

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

756

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

479

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

494

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

1071

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

659

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

554

2023.09.20

yy漫画官方登录入口地址合集
yy漫画官方登录入口地址合集

本专题整合了yy漫画入口相关合集,阅读专题下面的文章了解更多详细内容。

0

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Sass 教程
Sass 教程

共14课时 | 0.8万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3万人学习

CSS教程
CSS教程

共754课时 | 22.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号