当前位置: 首页 > news >正文

浏览器自动化方案

B端后台列表页自动新增素材方案

我设计了一套完整的浏览器自动化方案,使用 Puppeteer 实现B端后台列表页的自动新增素材功能。该方案包含数据组织、浏览器操作、错误处理等完整流程。

一、技术选型

  1. 浏览器自动化工具:Puppeteer (https://pptr.dev)
  2. 任务调度:Bull (https://github.com/OptimalBits/bull)
  3. 配置管理:dotenv (https://github.com/motdotla/dotenv)
  4. 日志记录:Winston (https://github.com/winstonjs/winston)

二、数据模型设计

首先定义素材数据结构,支持不同类型的素材:

// material.js
class Material {constructor(templateType, category, materialType, content) {this.templateType = templateType; // 模版类型:九宫格模版/幸运盲盒模版this.category = category;         // 素材类别:背景图片/抽奖动画/立即下载文案this.materialType = materialType; // 素材类型:图片/文本/代码块this.content = content;           // 素材内容:图片路径/文本内容/代码块内容this.status = 'pending';          // 处理状态this.createTime = new Date();}
}// 工厂方法创建不同类型的素材
class MaterialFactory {static createImageMaterial(templateType, category, filePath) {return new Material(templateType, category, '图片', { filePath });}static createTextMaterial(templateType, category, text) {return new Material(templateType, category, '文本', { text });}static createCodeMaterial(templateType, category, code) {return new Material(templateType, category, '代码块', { code });}
}module.exports = { Material, MaterialFactory };

三、浏览器自动化实现

使用 Puppeteer 实现浏览器自动操作流程:

// browser-automation.js
const puppeteer = require('puppeteer');
const { Material } = require('./material');
const logger = require('./logger');class BrowserAutomation {constructor(config) {this.config = config;this.browser = null;this.page = null;}// 初始化浏览器async init() {this.browser = await puppeteer.launch({headless: this.config.headless,args: ['--window-size=1920,1080'],slowMo: this.config.slowMo || 0});this.page = await this.browser.newPage();await this.page.setDefaultTimeout(this.config.timeout || 30000);}// 登录系统async login() {logger.info('开始登录系统');await this.page.goto(this.config.loginUrl);// 输入用户名和密码await this.page.type(this.config.selectors.username, this.config.username);await this.page.type(this.config.selectors.password, this.config.password);// 点击登录按钮await Promise.all([this.page.click(this.config.selectors.loginButton),this.page.waitForNavigation()]);logger.info('登录成功');}// 导航到素材列表页async navigateToListPage() {logger.info('导航到素材列表页');await this.page.goto(this.config.listPageUrl);await this.page.waitForSelector(this.config.selectors.listPage);}// 点击"新增AI素材"按钮async clickAddAIMaterialButton() {logger.info('点击"新增AI素材"按钮');await this.page.waitForSelector(this.config.selectors.addAIMaterialButton);await this.page.click(this.config.selectors.addAIMaterialButton);// 等待弹窗出现await this.page.waitForSelector(this.config.selectors.modal);}// 选择模版类型async selectTemplateType(templateType) {logger.info(`选择模版类型: ${templateType}`);await this.page.waitForSelector(this.config.selectors.templateTypeSelect);// 根据模版类型选择对应的选项值const templateValueMap = {'九宫格模版': 'nine-grid','幸运盲盒模版': 'lucky-box'};const templateValue = templateValueMap[templateType];if (!templateValue) {throw new Error(`未知的模版类型: ${templateType}`);}await this.page.select(this.config.selectors.templateTypeSelect, templateValue);}// 选择素材类别async selectCategory(category) {logger.info(`选择素材类别: ${category}`);await this.page.waitForSelector(this.config.selectors.categorySelect);// 根据素材类别选择对应的选项值const categoryValueMap = {'背景图片': 'background-image','抽奖动画': 'lottery-animation','立即下载文案': 'download-copywriting'};const categoryValue = categoryValueMap[category];if (!categoryValue) {throw new Error(`未知的素材类别: ${category}`);}await this.page.select(this.config.selectors.categorySelect, categoryValue);}// 选择素材类型async selectMaterialType(materialType) {logger.info(`选择素材类型: ${materialType}`);await this.page.waitForSelector(this.config.selectors.materialTypeSelect);// 根据素材类型选择对应的选项值const materialTypeValueMap = {'图片': 'image','文本': 'text','代码块': 'code'};const materialTypeValue = materialTypeValueMap[materialType];if (!materialTypeValue) {throw new Error(`未知的素材类型: ${materialType}`);}await this.page.select(this.config.selectors.materialTypeSelect, materialTypeValue);}// 处理素材内容async processContent(material) {logger.info(`处理素材内容: ${material.materialType}`);switch (material.materialType) {case '图片':await this.uploadImage(material.content.filePath);break;case '文本':await this.fillTextContent(material.content.text);break;case '代码块':await this.fillCodeContent(material.content.code);break;default:throw new Error(`不支持的素材类型: ${material.materialType}`);}}// 上传图片async uploadImage(filePath) {logger.info(`上传图片: ${filePath}`);await this.page.waitForSelector(this.config.selectors.imageUploadInput);const fileInput = await this.page.$(this.config.selectors.imageUploadInput);await fileInput.uploadFile(filePath);// 等待上传完成await this.page.waitForSelector(this.config.selectors.imageUploadSuccess, {timeout: 60000 // 图片上传可能需要较长时间});}// 填写文本内容async fillTextContent(text) {logger.info('填写文本内容');await this.page.waitForSelector(this.config.selectors.textContentArea);await this.page.type(this.config.selectors.textContentArea, text);}// 填写代码块内容async fillCodeContent(code) {logger.info('填写代码块内容');await this.page.waitForSelector(this.config.selectors.codeContentArea);await this.page.type(this.config.selectors.codeContentArea, code);}// 点击确定按钮完成入库async submitMaterial() {logger.info('点击确定按钮');await this.page.waitForSelector(this.config.selectors.confirmButton);// 点击确定并等待提交完成await Promise.all([this.page.click(this.config.selectors.confirmButton),this.page.waitForSelector(this.config.selectors.submitSuccessMessage, { timeout: 15000 })]);logger.info('素材入库成功');return true;}// 关闭浏览器async close() {if (this.browser) {await this.browser.close();}}// 执行完整的素材入库流程async processMaterial(material) {try {await this.init();await this.login();await this.navigateToListPage();await this.clickAddAIMaterialButton();await this.selectTemplateType(material.templateType);await this.selectCategory(material.category);await this.selectMaterialType(material.materialType);await this.processContent(material);const success = await this.submitMaterial();material.status = success ? 'success' : 'failed';return { success, material };} catch (error) {logger.error(`处理素材失败: ${error.message}`);material.status = 'failed';material.errorMessage = error.message;return { success: false, material, error };} finally {await this.close();}}
}module.exports = BrowserAutomation;

四、配置管理

创建配置文件管理选择器和环境变量:

// config.js
require('dotenv').config();module.exports = {// 环境配置headless: process.env.HEADLESS === 'true',slowMo: parseInt(process.env.SLOW_MO) || 0,timeout: parseInt(process.env.TIMEOUT) || 30000,// 登录配置loginUrl: process.env.LOGIN_URL,username: process.env.USERNAME,password: process.env.PASSWORD,// 页面URLlistPageUrl: process.env.LIST_PAGE_URL,// DOM选择器配置selectors: {// 登录页面username: '#username',password: '#password',loginButton: '#login-button',// 列表页面listPage: '.material-list',addAIMaterialButton: '#add-ai-material-btn',// 新增弹窗modal: '.add-material-modal',templateTypeSelect: '#template-type',categorySelect: '#category',materialTypeSelect: '#material-type',// 内容区域imageUploadInput: 'input[type="file"]',imageUploadSuccess: '.upload-success',textContentArea: '#text-content',codeContentArea: '#code-content',// 提交区域confirmButton: '.confirm-btn',submitSuccessMessage: '.success-message'}
};

五、任务调度与批量处理

使用 Bull 实现任务队列,支持批量处理多个素材:

// queue.js
const Queue = require('bull');
const BrowserAutomation = require('./browser-automation');
const config = require('./config');
const logger = require('./logger');// 创建任务队列
const materialQueue = new Queue('material-upload', {redis: {host: process.env.REDIS_HOST || 'localhost',port: parseInt(process.env.REDIS_PORT) || 6379}
});// 处理队列任务
materialQueue.process(async (job) => {const material = job.data;logger.info(`开始处理素材任务: ${material.id}`);const automation = new BrowserAutomation(config);return await automation.processMaterial(material);
});// 添加素材到队列
async function enqueueMaterial(material) {return await materialQueue.add(material, {attempts: 3, // 失败重试3次backoff: { type: 'exponential', delay: 1000 } // 重试间隔递增});
}// 监听队列事件
materialQueue.on('completed', (job, result) => {if (result.success) {logger.info(`素材任务 ${job.id} 处理成功`);} else {logger.error(`素材任务 ${job.id} 处理失败: ${result.error.message}`);}
});materialQueue.on('failed', (job, error) => {logger.error(`素材任务 ${job.id} 处理失败: ${error.message}`);
});module.exports = { enqueueMaterial };

六、使用示例

下面是如何使用上述代码的示例:

// app.js
const { MaterialFactory } = require('./material');
const { enqueueMaterial } = require('./queue');async function main() {// 创建不同类型的素材const imageMaterial = MaterialFactory.createImageMaterial('九宫格模版', '背景图片', './assets/background.jpg');const textMaterial = MaterialFactory.createTextMaterial('幸运盲盒模版', '立即下载文案', '点击下载,立即参与抽奖活动,赢取丰厚奖品!');const codeMaterial = MaterialFactory.createCodeMaterial('九宫格模版', '抽奖动画', 'function startLottery() { ... }');// 将素材添加到处理队列await enqueueMaterial(imageMaterial);await enqueueMaterial(textMaterial);await enqueueMaterial(codeMaterial);console.log('所有素材已添加到处理队列');
}main().catch(error => {console.error('执行过程中发生错误:', error);
});

七、部署与运行

  1. 安装依赖:

    npm install puppeteer bull dotenv winston
    
  2. 创建 .env 文件配置环境变量:

    HEADLESS=true
    SLOW_MO=0
    TIMEOUT=60000
    LOGIN_URL=https://your-b-system.com/login
    USERNAME=your_username
    PASSWORD=your_password
    LIST_PAGE_URL=https://your-b-system.com/materials
    REDIS_HOST=localhost
    REDIS_PORT=6379
    
  3. 运行程序:

    node app.js
    

八、关键技术点说明

  1. Puppeteer 核心功能

    • 元素选择与操作:page.$page.$$page.clickpage.type
    • 页面导航与等待:page.gotopage.waitForSelectorpage.waitForNavigation
    • 文件上传:uploadFile 方法
    • 无头模式:headless 选项
  2. 错误处理

    • 重试机制:通过 Bull 队列实现自动重试
    • 超时设置:全局超时和特定操作超时
    • 日志记录:使用 Winston 记录详细操作日志
  3. 配置化设计

    • 将选择器和环境变量外部化,便于适配不同系统
    • 支持通过环境变量控制执行模式(无头/有头、执行速度)

通过这个方案,您可以实现B端后台列表页的自动化新增素材功能,支持多种模版类型、素材类别和素材类型,大幅提高工作效率。

http://www.dtcms.com/a/282566.html

相关文章:

  • 创客匠人解析:系统化工具如何重构知识变现效率
  • 在 kubernetes 上安装 jenkins
  • 闲庭信步使用图像验证平台加速FPGA的开发:第十九课——图像拉普拉斯金字塔的FPGA实现
  • Image 和 IMU 时间戳同步
  • 事务~~~
  • JavaScript进阶篇——第五章 对象成员管理与数组遍历优化
  • 密码喷洒复现
  • Thymeleaf 基础语法与标准表达式详解
  • 如何区分Bug是前端问题还是后端问题?
  • LeetCode经典题解:141、判断链表是否有环
  • 【LeetCode】链表相关算法题
  • Node.js Process Events 深入全面讲解
  • 1.3 vue响应式对象
  • FATFS文件系统原理及其移植详解
  • PyTorch 损失函数详解:从理论到实践
  • 嵌入式学习-PyTorch(5)-day22
  • 【深度学习基础】PyTorch中model.eval()与with torch.no_grad()以及detach的区别与联系?
  • Vue 结合 Zabbix API 获取服务器 CPU、内存、GPU 等数据
  • 数据结构自学Day8: 堆的排序以及TopK问题
  • 前端Vue中,如何实现父组件打开子组件窗口等待子组件窗口关闭后继续执行父组件逻辑
  • DeepSeek(18):SpringAI+DeepSeek大模型应用开发之会话日志
  • 单片机(STM32-中断)
  • JS逆向 - YandexSmartCaptcha (worker线程)
  • 基于WebRTC构建应用的可复用模块
  • 下载webrtc M114版本源码只能使用外网googlesource源-命令版
  • i.mx8 RTC问题
  • TEngine学习
  • 【Noah-MP模型】陆面生态水文模拟与多源遥感数据同化的实践技术应用
  • JavaScript进阶篇——第六章 内置构造函数与内置方法
  • alpineLinux修改包管理为国内源