You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Puppeteer登录Twitter时持续加载转圈问题求助

Puppeteer自动化登录Twitter时持续加载转圈的解决方案

问题概述

使用Puppeteer自动化登录Twitter时,输入用户名/邮箱点击"Next"后常出现持续加载的转圈图标,无法进入密码输入界面。本地Windows机器成功率远高于云端Linux机器,推测反检测代码存在环境适配缺陷,现有旧方案已不适配Twitter当前的两步登录流程。

核心问题分析

  1. 硬编码WebGL参数跨环境不兼容:原代码中WebGL硬件信息固定为Windows Intel显卡值,Linux环境下真实浏览器参数与该值不符,触发反自动化检测。
  2. 固定等待导致时序错误:大量使用waitForTimeout固定等待,未根据页面元素实际加载状态调整操作时机,网络波动时易出现操作过早/过晚问题。
  3. 自定义反检测代码冗余冲突:手动添加的window.chrome字段结构不符合真实浏览器,且与puppeteer-extra-plugin-stealth默认配置冲突,增加被检测概率。
  4. 密码输入逻辑不严谨:通过循环按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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 14:14:53