0

0

基于发布-订阅模式实现多浏览器并行独立自动化操作

花韻仙語

花韻仙語

发布时间:2025-12-07 19:20:02

|

758人浏览过

|

来源于php中文网

原创

基于发布-订阅模式实现多浏览器并行独立自动化操作

本文探讨了如何在多个独立浏览器中并行执行自动化任务,并模拟独立的鼠标光标操作。传统方法难以实现,因此我们提出采用基于发布-订阅(pub-sub)模式的领导者-跟随者架构。通过消息队列系统,领导者程序广播指令,而多个跟随者程序各自驱动一个selenium浏览器实例,接收并执行这些指令,从而实现高效且独立的并行自动化。

在自动化测试、数据抓取或多账号操作等场景中,开发者常常面临一个挑战:如何在多个独立的浏览器实例中同时执行任务,并且每个实例都能模拟独立的鼠标光标操作。这与简单的多线程或多进程启动浏览器不同,因为操作系统通常只提供一个物理鼠标光标,像pyautogui这类库的操作会影响到全局光标,无法实现多个独立浏览器中“各自”的鼠标移动和点击。传统的虚拟化方案虽然能隔离环境,但若要实现程序化的独立鼠标控制,通常会带来较高的复杂性和资源开销。

为了有效解决这一问题,核心策略是采用一种分布式通信模式——发布-订阅(Publish-Subscribe, Pub-Sub)模式,并结合领导者-跟随者(Leader-Follower)架构。这种模式将任务的生成与执行解耦,使得多个浏览器实例能够独立地接收和响应指令。

核心架构:领导者-跟随者与发布-订阅模式

该解决方案的核心在于构建一个领导者程序和N个跟随者程序,并通过一个消息队列系统进行通信。

  1. 领导者程序 (Leader Program)

    • 职责: 负责生成自动化任务的指令。这些指令可以是高层级的(如“打开指定URL并登录”),也可以是低层级的(如“在坐标(X, Y)处模拟鼠标点击”)。
    • 操作: 将生成的指令作为消息发布到预定义的消息通道中。领导者无需关心具体哪个跟随者会接收并执行这些指令。
  2. 跟随者程序 (Follower Programs)

    • 职责: 每个跟随者程序独立运行,并控制一个单独的浏览器实例(通常通过Selenium WebDriver)。
    • 操作: 持续监听消息队列中的指令通道。一旦接收到指令,跟随者程序就会在其控制的浏览器实例中执行相应的操作,例如模拟鼠标移动、点击、键盘输入、页面导航等。
    • 独立性: 每个跟随者程序都拥有自己的浏览器进程和状态,彼此之间互不干扰,从而实现了“独立鼠标光标”的逻辑效果。
  3. 消息队列系统 (Message Queue System)

    Live PPT
    Live PPT

    一款AI智能化生成演示内容的在线工具。只需输入一句话、粘贴一段内容、或者导入文件,AI生成高质量PPT。

    下载
    • 作用: 作为领导者和跟随者之间可靠的通信骨干。它负责消息的存储、路由和分发。
    • 选择: 常用的消息队列系统包括Apache Kafka、RabbitMQ、Redis Pub/Sub等。选择哪个系统取决于项目的规模、性能要求和复杂性。
    • 通道: 可以设置一个或多个消息通道。例如,一个通用的指令通道供所有跟随者监听,或者为需要个性化指令的跟随者设置独立的通道。

实现步骤与示例代码(概念性)

以下是使用Python和Selenium结合消息队列系统实现此架构的概念性示例:

1. 消息队列框架选择与配置

首先,选择一个消息队列框架并进行基本配置。以RabbitMQ为例,你需要安装pika库。

pip install pika selenium webdriver_manager

2. 领导者程序示例

领导者程序负责生成并发布指令。

# leader.py (概念性代码,需根据实际消息队列库调整)
import json
import pika # 假设使用RabbitMQ

class Leader:
    def __init__(self, queue_name='browser_commands'):
        self.connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
        self.channel = self.connection.channel()
        self.channel.queue_declare(queue=queue_name)
        self.queue_name = queue_name

    def publish_command(self, browser_id, command_type, payload):
        """
        发布一个指令到消息队列。
        :param browser_id: 目标浏览器实例的ID。
        :param command_type: 指令类型,如 'mouse_move', 'click', 'navigate'。
        :param payload: 指令的具体参数,如坐标、URL等。
        """
        message = {
            "browser_id": browser_id,
            "type": command_type,
            "payload": payload
        }
        self.channel.basic_publish(
            exchange='',
            routing_key=self.queue_name,
            body=json.dumps(message)
        )
        print(f"Leader published: {message}")

    def close(self):
        self.connection.close()

if __name__ == "__main__":
    leader = Leader()
    # 模拟为不同的浏览器实例发布指令
    leader.publish_command(1, 'navigate', {'url': 'https://www.example.com'})
    leader.publish_command(2, 'navigate', {'url': 'https://www.google.com'})
    leader.publish_command(1, 'mouse_move', {'x': 100, 'y': 200})
    leader.publish_command(1, 'click', {'x': 100, 'y': 200})
    leader.publish_command(2, 'mouse_move', {'x': 50, 'y': 50})
    leader.publish_command(2, 'click', {'x': 50, 'y': 50})
    leader.close()

3. 跟随者程序示例

每个跟随者程序启动一个独立的Selenium浏览器实例,并监听消息队列。

# follower.py (概念性代码,需根据实际消息队列库调整)
import json
import pika
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
import time

class Follower:
    def __init__(self, browser_id, queue_name='browser_commands'):
        self.browser_id = browser_id
        # 初始化Selenium WebDriver
        options = webdriver.ChromeOptions()
        # 可以添加headless模式,或者为每个浏览器设置不同的用户数据目录以完全隔离
        # options.add_argument('--headless')
        # options.add_argument(f'--user-data-dir=/tmp/chrome-profile-{browser_id}')
        self.driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()), options=options)
        print(f"Follower {self.browser_id}: Browser initialized.")

        # 初始化消息队列连接
        self.connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
        self.channel = self.connection.channel()
        self.channel.queue_declare(queue=queue_name)
        self.queue_name = queue_name

    def _execute_command(self, command):
        """根据接收到的指令在浏览器中执行操作。"""
        if command['browser_id'] != self.browser_id:
            return # 忽略不属于本实例的指令

        command_type = command['type']
        payload = command['payload']
        print(f"Follower {self.browser_id}: Executing {command_type} with payload {payload}")

        try:
            if command_type == 'navigate':
                self.driver.get(payload['url'])
            elif command_type == 'mouse_move':
                # Selenium的ActionChains模拟鼠标移动到相对页面左上角的坐标
                # 注意:这只是将Selenium的内部光标移动到该位置,不影响物理光标
                body = self.driver.find_element(By.TAG_NAME, 'body')
                ActionChains(self.driver).move_to_element_with_offset(body, payload['x'], payload['y']).perform()
            elif command_type == 'click':
                # 模拟点击,通常需要先移动到目标位置再点击
                body = self.driver.find_element(By.TAG_NAME, 'body')
                ActionChains(self.driver).move_to_element_with_offset(body, payload['x'], payload['y']).click().perform()
            # 可以添加更多指令类型,如 keyboard_input, find_element_and_click 等
            else:
                print(f"Follower {self.browser_id}: Unknown command type {command_type}")
        except Exception as e:
            print(f"Follower {self.browser_id}: Error executing command {command_type}: {e}")

    def start_listening(self):
        """开始监听消息队列并处理指令。"""
        def callback(ch, method, properties, body):
            command = json.loads(body)
            self._execute_command(command)
            ch.basic_ack(delivery_tag=method.delivery_tag) # 确认消息已处理

        self.channel.basic_consume(
            queue=self.queue_name,
            on_message_callback=callback,
            auto_ack=False # 手动确认,确保消息被成功处理
        )
        print(f"Follower {self.browser_id}: Started listening for commands...")
        self.channel.start_consuming()

    def close(self):
        """关闭浏览器和消息队列连接。"""
        self.driver.quit()
        self.connection.close()
        print(f"Follower {self.browser_id}: Closed.")

if __name__ == "__main__":
    # 启动多个跟随者实例,每个实例在独立的终端或进程中运行
    # 例如,在不同的终端分别运行:
    # python follower.py 1
    # python follower.py 2
    import sys
    if len(sys.argv) > 1:
        browser_id = int(sys.argv[1])
        follower = Follower(browser_id)
        try:
            follower.start_listening()
        except KeyboardInterrupt:
            print(f"Follower {browser_id}: Shutting down...")
        finally:
            follower.close()
    else:
        print("Usage: python follower.py ")

注意事项与最佳实践

  1. 资源管理: 每个Selenium浏览器实例都会消耗独立的内存和CPU资源。在设计系统时,需根据可用硬件资源和预期并行数量进行评估。考虑使用无头浏览器(headless mode)以减少资源消耗。
  2. 指令粒度: 指令的粒度可以根据需求调整。过于细碎的指令会增加消息队列的负担和网络延迟;过于粗粒度的指令则可能降低灵活性。
  3. 错误处理与重试: 消息队列通常提供消息持久化、消费者确认(ACK)、死信队列(Dead Letter Queue)等机制,用于处理消息发送失败、消费者崩溃等情况。应充分利用这些机制确保任务的可靠执行。
  4. 反馈机制: 如果领导者需要知道任务的执行结果或跟随者遇到的问题,可以设计一个反向的“报告通道”,让跟随者将执行状态或日志信息发布回领导者。
  5. 可扩展性: 这种架构天然具有良好的可扩展性。只需启动更多的跟随者程序即可增加并行处理能力,而无需修改领导者程序的逻辑。
  6. 同步与异步: 领导者通常以异步方式发布指令,不等待跟随者的执行结果。如果某些任务需要严格的同步(例如,等待一个浏览器完成特定操作后才能启动另一个),则需要引入额外的协调机制,如使用共享状态存储或更复杂的通信协议。
  7. 隔离性: 为确保浏览器实例的完全独立,除了进程隔离外,还可以考虑为每个浏览器实例配置独立的Selenium用户配置文件或临时目录,以避免缓存、Cookie等数据相互影响。

总结

通过采用发布-订阅模式和领导者-跟随者架构,我们能够有效解决在多个独立浏览器中并行执行自动化任务并模拟独立鼠标光标的复杂问题。这种方案将任务生成与执行解耦,提高了系统的灵活性、可扩展性和鲁棒性。开发者可以根据具体需求选择合适的消息队列系统,并结合Selenium WebDriver等工具,构建出高效、可靠的多浏览器并行自动化解决方案。

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

758

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

639

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

761

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

618

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1264

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

548

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

579

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

708

2023.08.11

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

27

2026.01.16

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 2.5万人学习

Django 教程
Django 教程

共28课时 | 3.2万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.1万人学习

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

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