使用Puppeteer登录Twitter时持续加载转圈问题求助
Puppeteer自动化登录Twitter时持续加载转圈的解决方案
问题概述
使用Puppeteer自动化登录Twitter时,输入用户名/邮箱点击"Next"后常出现持续加载的转圈图标,无法进入密码输入界面。本地Windows机器成功率远高于云端Linux机器,推测反检测代码存在环境适配缺陷,现有旧方案已不适配Twitter当前的两步登录流程。
核心问题分析
- 硬编码WebGL参数跨环境不兼容:原代码中WebGL硬件信息固定为Windows Intel显卡值,Linux环境下真实浏览器参数与该值不符,触发反自动化检测。
- 固定等待导致时序错误:大量使用
waitForTimeout固定等待,未根据页面元素实际加载状态调整操作时机,网络波动时易出现操作过早/过晚问题。 - 自定义反检测代码冗余冲突:手动添加的
window.chrome字段结构不符合真实浏览器,且与puppeteer-extra-plugin-stealth默认配置冲突,增加被检测概率。 - 密码输入逻辑不严谨:通过循环按Backspace清除内容的方式不可靠,未直接定位密码输入框操作。
解决方案与代码优化
1. 适配跨环境的WebGL反检测配置
替换硬编码的WebGL参数为通用值,避免环境差异触发检测:
// WebGL Setup 修改为通用实现 const getParameter = WebGLRenderingContext.getParameter; WebGLRenderingContext.prototype.getParameter = function (parameter) { // UNMASKED_VENDOR_WEBGL if (parameter === 37445) { return 'Google Inc.'; } // UNMASKED_RENDERER_WEBGL if (parameter === 37446) { return 'ANGLE (AMD, AMD Radeon(TM) Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)'; } return getParameter(parameter); };
2. 替换固定等待为元素等待
使用waitForXPath等待元素可见/可交互,确保操作时机准确:
// 点击登录按钮 console.log('Clicking on login'); await page.waitForXPath('//a[@href="/login"]', { visible: true, timeout: 15000 }); const [loginButton] = await page.$x('//a[@href="/login"]'); await loginButton.click(); // 输入用户名/邮箱 await page.waitForXPath('//input[@autocomplete="username"]', { visible: true, timeout: 15000 }); const [usernameInput] = await page.$x('//input[@autocomplete="username"]'); await usernameInput.click(); // 模拟人类输入速度,避免过快触发检测 await usernameInput.type(email, { delay: 80 + Math.random() * 120 }); // 点击Next按钮 await page.waitForXPath("//div[@role='button']/descendant::span[text()='Next']", { visible: true, timeout: 15000 }); const [nextButton] = await page.$x("//div[@role='button']/descendant::span[text()='Next']"); await nextButton.click(); // 处理可能的用户名验证步骤 try { await page.waitForXPath('//input[@autocomplete="username"]', { visible: true, timeout: 8000 }); const [verifyUsernameInput] = await page.$x('//input[@autocomplete="username"]'); await verifyUsernameInput.type(username, { delay: 80 + Math.random() * 120 }); await page.keyboard.press('Enter'); } catch (e) { // 无需验证用户名,继续流程 }
3. 优化浏览器启动参数
调整启动参数,提升环境真实性并适配跨平台:
const userDataDir = process.platform === 'win32' ? "C:/Users/User/AppData/Local/Google/Chrome/User Data/selenium3" : "/path/to/linux/chrome/profile"; // Linux环境设置对应用户目录 browser = await puppeteer.launch({ headless: 'new', // 新版无头模式更接近真实浏览器 args: [ '--no-sandbox', '--disable-setuid-sandbox', '--window-size=1920,1080', // 标准分辨率,避免过大尺寸 '--lang=en-US,en;q=0.9', '--disable-dev-shm-usage', // 解决Linux内存限制问题 '--no-first-run', '--no-default-browser-check', '--start-maximized' ], userDataDir: userDataDir });
4. 修复密码输入逻辑
直接定位密码输入框,避免Backspace清除内容的不可靠操作:
// 等待密码输入框并输入 await page.waitForXPath('//input[@autocomplete="current-password"]', { visible: true, timeout: 15000 }); const [passwordInput] = await page.$x('//input[@autocomplete="current-password"]'); await passwordInput.click(); await passwordInput.type(password, { delay: 80 + Math.random() * 120 }); await page.keyboard.press('Enter');
完整优化后的代码
const puppeteer = require('puppeteer-extra'); const pluginStealth = require('puppeteer-extra-plugin-stealth'); const fs = require('fs'); puppeteer.use(pluginStealth()); async function run() { const userDataDir = process.platform === 'win32' ? "C:/Users/User/AppData/Local/Google/Chrome/User Data/selenium3" : "/path/to/linux/chrome/profile"; const browser = await puppeteer.launch({ headless: 'new', args: [ '--no-sandbox', '--disable-setuid-sandbox', '--window-size=1920,1080', '--lang=en-US,en;q=0.9', '--disable-dev-shm-usage', '--no-first-run', '--no-default-browser-check', '--start-maximized' ], userDataDir: userDataDir }); const page = await browser.newPage(); const user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36"; await page.setUserAgent(user_agent); const stealthFile = `// Pass the Webdriver Test const newProto = navigator.__proto__; delete newProto.webdriver; navigator.__proto__ = newProto; // Set up Permissions const originalQuery = window.navigator.permissions.query; window.navigator.permissions.query = (parameters) => parameters.name === 'notifications' ? Promise.resolve({ state: Notification.permission }) : originalQuery(parameters); // WebGL Setup 修改为通用实现 const getParameter = WebGLRenderingContext.getParameter; WebGLRenderingContext.prototype.getParameter = function (parameter) { // UNMASKED_VENDOR_WEBGL if (parameter === 37445) { return 'Google Inc.'; } // UNMASKED_RENDERER_WEBGL if (parameter === 37446) { return 'ANGLE (AMD, AMD Radeon(TM) Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)'; } return getParameter(parameter); }; // Pass navigator.connection.rtt Test Object.defineProperty(navigator.connection, 'rtt', { get: () => 50 }); // Bypass hairline feature const elementDescriptor = Object.getOwnPropertyDescriptor( HTMLElement.prototype, "offsetHeight" ); Object.defineProperty(HTMLDivElement.prototype, "offsetHeight", { ...elementDescriptor, get: function() { if (this.id === "modernizr") { return 1; } return elementDescriptor.get.apply(this); }, });`; await page.evaluateOnNewDocument(stealthFile); console.log('Going to Twitter'); await page.goto('https://twitter.com', { waitUntil: 'networkidle2' }); // 点击登录按钮 console.log('Clicking on login'); await page.waitForXPath('//a[@href="/login"]', { visible: true, timeout: 15000 }); const [loginButton] = await page.$x('//a[@href="/login"]'); await loginButton.click(); // 输入用户名/邮箱 await page.waitForXPath('//input[@autocomplete="username"]', { visible: true, timeout: 15000 }); const [usernameInput] = await page.$x('//input[@autocomplete="username"]'); await usernameInput.click(); const email = await fs.readFileSync('twitter.email','utf8').trim(); await usernameInput.type(email, { delay: 80 + Math.random() * 120 }); // 点击Next按钮 console.log('Clicking on next button'); await page.waitForXPath("//div[@role='button']/descendant::span[text()='Next']", { visible: true, timeout: 15000 }); const [nextButton] = await page.$x("//div[@role='button']/descendant::span[text()='Next']"); await nextButton.click(); // 处理用户名验证 const username = await fs.readFileSync('twitter.username','utf8').trim(); try { await page.waitForXPath('//input[@autocomplete="username"]', { visible: true, timeout: 8000 }); const [verifyUsernameInput] = await page.$x('//input[@autocomplete="username"]'); await verifyUsernameInput.type(username, { delay: 80 + Math.random() * 120 }); await page.keyboard.press('Enter'); } catch (e) { console.log('No username verification required'); } console.log('Saving screenshot for Stack Overflow'); await page.screenshot({path: 'twitter.output.png'}); // 输入密码 const password = await fs.readFileSync('twitter.password.attached.to.email','utf8').trim(); await page.waitForXPath('//input[@autocomplete="current-password"]', { visible: true, timeout: 15000 }); const [passwordInput] = await page.$x('//input[@autocomplete="current-password"]'); await passwordInput.click(); await passwordInput.type(password, { delay: 80 + Math.random() * 120 }); await page.keyboard.press('Enter'); await page.waitForTimeout(3000+Math.random() * 3000); await page.screenshot({path: 'twitter.good.output.png'}); await browser.close(); } run();
内容的提问来源于stack exchange,提问作者Eric Klein
相关产品推荐
相关产品推荐

