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

Puppeteer点击Facebook投票3个相同按钮仅成功2个,第二个始终失效

问题:Puppeteer操作Facebook群组投票时第二个按钮无法触发点击

使用Puppeteer自动化操作Facebook群组投票页面,尝试点击3个相同的投票选项按钮,但每次仅能成功点击2个,第二个按钮始终无法被触发点击。


功能代码片段

以下是实现按钮点击功能的核心代码:

async function getPollInfo(pollUrl) {
    await page.goto(pollUrl, { waitUntil: "networkidle2" });

    const groupName = await waitAndExtract(
        "a.x1i10hfl.xjbqb8w.x1ejq31n.xd10rxx.x1sy0etr.x17r0tee.x972fbf.xcfux6l.x1qhh985.xm0m39n.x9f619.x1ypdohk.xt0psk2.xe8uvvx.xdj266r.x11i5rnm.xat24cr.x1mh8g0r.xexx8yu.x4uap5.x18d9i69.xkhd6sd.x16tdsg8.x1hl2dhg.xggy1nq.x1a2a7pz.x1heor9g.x1sur9pj.xkrqix3.x1xlr1w8"
    );

    const groupIdAndPostId = extractGroupAndPostId(pollUrl);
    const groupId = groupIdAndPostId["groupid"];
    const owner = await waitAndExtract(
        "span.x193iq5w.xeuugli.x13faqbe.x1vvkbs.x1xmvt09.x1lliihq.x1s928wv.xhkezso.x1gmr53x.x1cpjm7i.x1fgarty.x1943h6x.xudqn12.x3x7a5m.x6prxxf.xvq8zen.xo1l8bm.xi81zsa.x1yc453h h3 span span a strong span"
    );
    const text = await waitAndExtract("div.xdj266r.x11i5rnm.xat24cr.x1mh8g0r.x1vvkbs.x126k92a div");
    const postId = groupIdAndPostId["postid"];

    const buttonClassString = "x1iyjqo2.xwib8y2 div div div";

    await page.waitForSelector(`div.${buttonClassString}[role="button"]`);

    const optionElements = await page.$$(`div.${buttonClassString}[role="button"]`);

    const ops = [];

    const responseHandler = async (response) => {
        const url = response.url();
        if (url.includes("api")) {
            console.log(url);
            const status = response.status();
            try {
                const responseBody = await response.json();
                console.log(responseBody);
                const optionText = responseBody["data"]["question_option"]["text"];
                console.log(responseBody["data"]["question_option"]);
                const voteCount = responseBody["data"]["question_option"]["voters"]["count"];
                const op = new Option(optionText, voteCount);
                ops.push(op);
            } catch (error) {
                console.error(`Error parsing response body: ${error}`);
            }
        }
    };

    let cou = 0;

    page.on("response", responseHandler);

    for (let i = 0; i < 1; i++) {
        console.log(optionElements[0]);
        console.log(optionElements[1]);
        await optionElements[1].scrollIntoView();
        await optionElements[1].click();
        cou++;
    }

    await new Promise((resolve) => {
        setTimeout(() => {
            page.off("response", responseHandler);
            resolve();
        }, 5000);
    });

    const question = new Question(
        owner,
        text,
        "",
        postId,
        ops,
        groupId,
        groupName
    );

    console.log(cou);
    return question;
}

最小复现代码

const puppeteer = require('puppeteer');

class Option {
  constructor(text, voteCount) {
    this.text = text;
    this.voteCount = voteCount;
  }
}

class Question {
  constructor(owner, text, placeholder, postId, options, groupId, groupName) {
    this.owner = owner;
    this.text = text;
    this.placeholder = placeholder;
    this.postId = postId;
    this.options = options;
    this.groupId = groupId;
    this.groupName = groupName;
  }
}

async function waitAndExtract(selector, page) {
  await page.waitForSelector(selector);
  return page.$eval(selector, element => element.innerText);
}

function extractGroupAndPostId(url) {
  const urlParts = url.split('/');
  return {
    groupid: urlParts[4],
    postid: urlParts[6]
  };
}

async function getPollInfo(pollUrl) {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();
  await page.goto(pollUrl, { waitUntil: "networkidle2" });

  const groupName = await waitAndExtract(
    "a.x1i10hfl.xjbqb8w.x1ejq31n.xd10rxx.x1sy0etr.x17r0tee.x972fbf.xcfux6l.x1qhh985.xm0m39n.x9f619.x1ypdohk.xt0psk2.xe8uvvx.xdj266r.x11i5rnm.xat24cr.x1mh8g0r.xexx8yu.x4uap5.x18d9i69.xkhd6sd.x16tdsg8.x1hl2dhg.xggy1nq.x1a2a7pz.x1heor9g.x1sur9pj.xkrqix3.x1xlr1w8",
    page
  );

  const groupIdAndPostId = extractGroupAndPostId(pollUrl);
  const groupId = groupIdAndPostId["groupid"];
  const owner = await waitAndExtract(
    "span.x193iq5w.xeuugli.x13faqbe.x1vvkbs.x1xmvt09.x1lliihq.x1s928wv.xhkezso.x1gmr53x.x1cpjm7i.x1fgarty.x1943h6x.xudqn12.x3x7a5m.x6prxxf.xvq8zen.xo1l8bm.xi81zsa.x1yc453h h3 span span a strong span",
    page
  );
  const text = await waitAndExtract("div.xdj266r.x11i5rnm.xat24cr.x1mh8g0r.x1vvkbs.x126k92a div", page);
  const postId = groupIdAndPostId["postid"];

  const buttonClassString =
    "x1iyjqo2.xwib8y2 div div div";
  
  await page.waitForSelector(`div.${buttonClassString}[role="button"]`);

  const optionElements = await page.$$(`div.${buttonClassString}[role="button"]`);

  const ops = [];

  const responseHandler = async (response) => {
    const url = response.url();
    if (url.includes("api")) {
      console.log(url)
      const status = response.status();
      // console.log(status)
      try {
        const responseBody = await response.json();
        console.log(responseBody)
        const optionText = responseBody["data"]["question_option"]["text"];
        console.log(responseBody["data"]["question_option"])
        const voteCount = responseBody["data"]["question_option"]["voters"]["count"];
        const op = new Option(optionText, voteCount);
        ops.push(op);
        // console.log(op);
      } catch (error) {
        console.error(`Error parsing response body: ${error}`);
      }
    }
  };

  let cou = 0

  page.on("response", responseHandler);
  // console.log(JSON.stringify(optionElements))
  for (let i = 0; i < 1; i++) {
    // console.log("<<<<<<<<<<<<<<<<<<<<<<")
    // console.log(optionElement.type)
    console.log(optionElements[0])
    console.log(optionElements[1])
    await optionElements[1].scrollIntoView();
    await optionElements[1].click();
    cou++;
  }

  // Wait for all responses to be processed
  await new Promise((resolve) => {
    setTimeout(() => {
      page.off("response", responseHandler);
      resolve();
    }, 5000); // Wait 5 seconds for responses to complete
  });

  const question = new Question(
    owner,
    text,
    "",
    postId,
    ops,
    groupId,
    groupName
  );

  // console.log(question);
  console.log(cou)
  await browser.close();
  return question;
}

// Example usage
const pollUrl = 'YOUR_POLL_URL_HERE'; // Replace with the actual poll URL
getPollInfo(pollUrl).then(question => {
  console.log(question);
}).catch(error => {
  console.error(error);
});

问题分析与解决方案

可能原因

  1. DOM动态更新导致元素引用失效:点击第一个按钮后,Facebook页面会重新渲染投票区域,之前获取的optionElements数组中的元素已脱离当前DOM树,操作的是无效引用。
  2. 元素未完全可交互:scrollIntoView后直接点击,元素可能仍处于加载或被临时遮挡状态,未达到可交互条件。
  3. 动态类名不稳定:依赖x1iyjqo2.xwib8y2这类Facebook动态生成的类名,DOM更新后类名可能变化,导致选择器匹配错误。

解决步骤

  1. 每次点击前重新获取元素:避免复用旧的元素引用,每次循环中重新查询按钮,确保操作的是当前页面的真实DOM元素。
  2. 等待元素完全可交互:使用waitForSelector的visible参数确保元素可见,再通过waitForFunction验证元素未被遮挡。
  3. 优化选择器:改用更稳定的选择逻辑,比如结合投票选项的文本内容或父容器的固定属性,减少对动态类名的依赖。
  4. 添加操作间隔:点击后等待网络请求完成,避免连续操作导致页面状态混乱。

修改后的核心点击逻辑

// 替换原有的点击循环部分
page.on("response", responseHandler);

// 循环点击3个按钮
for (let i = 0; i < 3; i++) {
    // 每次重新获取当前页面的按钮元素
    await page.waitForSelector(`div.${buttonClassString}[role="button"]`, { visible: true, timeout: 5000 });
    const currentOptions = await page.$$(`div.${buttonClassString}[role="button"]`);
    
    if (currentOptions[i]) {
        // 滚动到元素中心位置
        await currentOptions[i].scrollIntoView({ behavior: 'smooth', block: 'center' });
        // 验证元素未被遮挡且处于DOM树中
        await page.waitForFunction(
            el => el.isConnected && document.elementFromPoint(
                el.getBoundingClientRect().x + 10, 
                el.getBoundingClientRect().y + 10
            ) === el, 
            {}, 
            currentOptions[i]
        );
        await currentOptions[i].click();
        cou++;
        // 等待网络空闲,确保投票请求完成
        await page.waitForNetworkIdle({ idleTime: 1000 });
    }
}

额外建议

  • 临时关闭无头模式(headless: false),直观观察点击过程,确认第二个按钮是否存在遮挡或未被正确触发。
  • 增加元素状态日志,输出元素的isConnected、offsetHeight等属性,帮助定位问题。
  • 添加随机延迟(await page.waitForTimeout(Math.random() * 1000 + 500))模拟人类操作,规避Facebook的反检测机制。

内容的提问来源于stack exchange,提问作者Atharva udavant

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 07:22:02