Puppeteer无头浏览器API调用时弹窗账号密码填充不稳定问题
Puppeteer 操作magicul.io登录偶现填充失败问题
问题详情
- 使用Puppeteer无头模式启动浏览器,操作magicul.io网站完成登录流程:点击登录按钮弹出新窗口,在弹窗内填充账号密码,登录完成后弹窗关闭
- 异常表现:单独运行脚本无问题,通过API调用时偶现失败,有时执行到打印“Find Inputs”后停止,无法填入账号密码
- 测试说明:可使用任意账号密码进行测试
原代码
const express = require('express'); const dotenv = require('dotenv'); const cors = require('cors'); const puppeteer = require('puppeteer'); dotenv.config({ path: '.env' }); global.__appRootDir = __dirname; const app = express(); app.use(cors()); app.use(express.json({ limit: '50mb' })); const PORT = process.env.PORT ?? '3059' const HOST = process.env.HOST ?? 'localhost'; app.get('/', (req, res) => { res.send('server is responding') }) app.post('/conversion', conversion) const conversion = async (req, res) => { try { const browser = await puppeteer.launch({ headless: true }); const page = await browser.newPage(); await page.goto(process.env.SITE_URL, { waitUntil: 'domcontentloaded' }); await page.waitForSelector('button[id=\"login\"]'); await page.click('button[id=\"login\"]'); console.log("Popup Opened"); const newPagePromise = new Promise(x => browser.once('targetcreated', target => x(target.page()))); const popup = await newPagePromise; console.log("Find Inputs"); const usernameInput = await popup.waitForSelector('input[id=\"username\"]', { timeout: 30000 }); await usernameInput.type(process.env.USERNAME) await popup.keyboard.press('Enter') const passwordInput = await popup.waitForSelector('input[id=\"password\"]', { timeout: 30000 }); await passwordInput.type(process.env.PASSWORD) console.log("Form Filled"); await popup.keyboard.press('Enter') // Create a promise that resolves when the popup page is closed const popupClosedPromise = new Promise((resolve) => { popup.once('close', () => { resolve() }) }) // Wait for the popup page to close await popupClosedPromise console.log("popup closed"); console.log('login done') // next process - works perfectly. } catch (error) { console.error(error) return res.status(500).json({ message: error.message }) } } // listen for requests app.listen(PORT, HOST, () => { console.log(`Server is listening on port ${HOST}:${PORT}`); });
解决方案
1. 修复弹窗捕获的竞态问题
原代码使用browser.once('targetcreated')可能错过弹窗创建时机,改为先检查已存在的目标,再监听新目标:
// 替换原弹窗捕获逻辑 await page.click('button[id="login"]'); console.log("Popup Opened"); let popup; // 先检查是否已存在弹窗目标 const existingTargets = await browser.targets(); const popupTarget = existingTargets.find(target => target.type() === 'page' && target.url().includes('magicul.io/login') ); if (popupTarget) { popup = await popupTarget.page(); } else { // 监听新目标创建,确保不会错过 popup = await new Promise(resolve => { const handleTargetCreated = target => { if (target.type() === 'page' && target.url().includes('magicul.io/login')) { browser.removeListener('targetcreated', handleTargetCreated); resolve(target.page()); } }; browser.on('targetcreated', handleTargetCreated); }); }
2. 优化页面等待策略
无头模式下网络加载不稳定,调整页面加载等待条件,并确保弹窗页面完全加载:
// 修改主页面跳转的等待条件 await page.goto(process.env.SITE_URL, { waitUntil: 'networkidle2' }); // 获取弹窗后,等待弹窗页面加载完成 await popup.waitForNavigation({ waitUntil: 'networkidle2' });
3. 确保输入框可交互后再填充
使用waitForFunction验证输入框处于可见且可交互状态,避免元素未就绪导致填充失败:
// 替换原账号输入框等待代码 await popup.waitForFunction( () => { const input = document.getElementById('username'); return input && input.offsetParent !== null && !input.disabled; }, { timeout: 30000 } ); const usernameInput = await popup.$('input[id="username"]'); await usernameInput.type(process.env.USERNAME); // 替换原密码输入框等待代码 await popup.waitForFunction( () => { const input = document.getElementById('password'); return input && input.offsetParent !== null && !input.disabled; }, { timeout: 30000 } ); const passwordInput = await popup.$('input[id="password"]'); await passwordInput.type(process.env.PASSWORD);
4. 移除不可靠的键盘切换逻辑
原代码用Enter键切换到密码框,改为直接定位密码框,避免页面逻辑变化导致失效:
// 移除这一行:await popup.keyboard.press('Enter') // 直接使用上面的密码框等待和填充逻辑即可
5. 优化无头浏览器配置
添加必要参数模拟真实浏览器环境,避免被网站识别或布局异常:
const browser = await puppeteer.launch({ headless: 'new', // 使用新版无头模式,兼容性更好 args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--window-size=1920,1080', // 设置标准窗口大小,避免响应式布局影响元素定位 '--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36' ] });
内容的提问来源于stack exchange,提问作者Saloni Saraiya
相关产品推荐
相关产品推荐

