发布于 2026-01-06 6 阅读
0

使用 Puppeteer 抓取 Stackoverflow DEV 的全球 Show and Tell Challenge 中的答案,该挑战由 Mux 呈现:Pitch Your Projects!

使用 Puppeteer 抓取 Stack Overflow 上的答案

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

什么是木偶师

Puppeteer 是一个 Node 库,它允许我们通过命令控制 Chrome 浏览器,它是网络爬虫最常用的工具之一,因为它使我们能够轻松地自动化操作。


我们在做什么?

今天我们将学习如何设置 Puppeteer,以便在 Stack Overflow 上搜索问题时抓取 Google 搜索结果的前几条,让我们看看它是如何工作的:

  • 首先,我们运行包含以下问题的脚本
node index "how to exit vim"
Enter fullscreen mode Exit fullscreen mode
  • 现在我们用谷歌搜索 Stack Overflow 上的热门结果。
    vim 问题

  • 收集所有与我们问题中一半或以上词语匹配的链接。

[
  {
    keywordMatch: 4,
    url: 'https://stackoverflow.com/questions/31595411/how-to-clear-the-screen-after-exit-vim/51330580'
  }
]
Enter fullscreen mode Exit fullscreen mode
  • 为提出的问题创建一个文件夹。

  • 访问每个网址并查找答案。

  • 如果有答案,请截屏保存。
    Stack Overflow 的回答

  • 将其保存到我们之前创建的文件夹中。
    文件夹


存储库

我不会在这篇博客文章中介绍所有的代码细节,诸如如何使用 node.js 创建文件夹、如何遍历 URL 数组以及如何在脚本中允许参数等内容都在我的 GitHub 存储库中。

您可以在这里找到完整代码。


解释代码

了解了上一节中我们需要采取的步骤之后,现在是时候自己动手搭建了。

我们先在一个异步函数中初始化 puppeteer。

无头浏览器是指没有用户界面的网络浏览器。

建议使用 try catch 代码块,因为很难控制浏览器运行时发生的错误。


(async () => {
  try {
    const browser = await puppeteer.launch({
      headless: false,
    });

    const page = await browser.newPage();

  } catch (error) {
    console.log("Error " + error.toString());
  }
})();

Enter fullscreen mode Exit fullscreen mode

要获取特定网站的所有结果,我们需要用以下方式构建 URL +site:stackoverflow.com

page.goto接受两个参数,一个字符串作为 url,一个对象作为选项,在本例中,我们指定等待页面完全加载后再继续。

const googleUrl = `https://www.google.com/search?q=how%20to%20exit%20vim+site%3Astackoverflow.com`;

await page.goto(googleUrl, ["load", "domcontentloaded", "networkidle0"]);

Enter fullscreen mode Exit fullscreen mode

获取网址

进入谷歌搜索页面后,接下来需要收集属于该部分的所有 href 链接https://stackoverflow.com/questions

在page.evaluate方法中,我们可以使用 document 对象访问DOM,这意味着我们可以使用选择器轻松地找到所需的信息document.querySelectordocument.querySelectorAll

请记住,document.querySelectorAll 返回的不是数组,而是节点列表,这就是为什么我们在筛选之前将其转换为数组的原因。

然后,我们遍历所有元素并返回它们的 URL。


const queryUrl = "how%20to%20exit%20vim"

const validUrls = await page.evaluate((queryUrl) => {
 const hrefElementsList = Array.from(
      document.querySelectorAll(
          `div[data-async-context='query:${queryUrl}%20site%3Astackoverflow.com'] a[href]`
        )
      );

      const filterElementsList = hrefElementsList.filter((elem) =>
        elem
          .getAttribute("href")
          .startsWith("https://stackoverflow.com/questions")
      );

      const stackOverflowLinks = filterElementsList.map((elem) =>
        elem.getAttribute("href")
      );

      return stackOverflowLinks;
    }, queryUrl);
Enter fullscreen mode Exit fullscreen mode

匹配网址

我们已经将已验证的 URL 存储在一个名为 `urls` 的变量中,validUrls现在是时候检查其中是否有一些大致符合我们正在寻找的内容了。

我们将问题拆分成一个数组,并遍历每个单词,如果该单词在 Stack Overflow URL 中,则将其添加到我们的变量中wordCounter,完成此过程后,我们检查是否有一半的单词与 URL 匹配。


const queryWordArray = [ 'how', 'to', 'exit', 'vim' ]
const keywordLikeability = [];

validUrls.forEach((url) => {
  let wordCounter = 0;

  queryWordArray.forEach((word) => {
     if (url.indexOf(word) > -1) {
       wordCounter = wordCounter + 1;
     }
  });

  if (queryWordArray.length / 2 < wordCounter) {
    keywordLikeability.push({
      keywordMatch: wordCounter,
      url: url,
    });
  }
});

Enter fullscreen mode Exit fullscreen mode

捕捉答案

最后,我们需要一个函数来访问 stackoverflow 网站并检查是否有答案,如果有,则截取元素的屏幕截图并保存。

我们首先访问 Stack Overflow 的网址,然后关闭弹出窗口,否则它会出现在我们的屏幕截图中,而我们不希望这样。

为了找到弹出窗口的关闭按钮,我们使用xpath选择器,它就像我们喜爱的 CSS 选择器的一个奇怪的表亲,但它是用于 xml/html 的。

弹出窗口

弹窗消失后,是时候看看我们是否能找到答案了,如果找到了,我们就截保存。

await acceptedAnswer.screenshot({
 path: `.howtoexitvim.png`,
 clip: { x: 0, y: 0, width: 1024, height: 800 },
});

Enter fullscreen mode Exit fullscreen mode

使用截图方法时要小心,因为它不稳定,为了获得更流畅的体验,请尝试获取 DOM 元素的大小和位置,如上图所示。


const getAnswerFromQuestion = async (website, page) => {
  console.log("Website", website);
  await page.goto(website,["load","domcontentloaded","networkidle0"]);
  const popUp = (await page.$x("//button[@title='Dismiss']"))[0];
  if (popUp) await popUp.click();

  const acceptedAnswer = await page.$(".accepted-answer");

  if (!acceptedAnswer) return;

  await acceptedAnswer.screenshot({
    path: `./howtoexitvim.png`,
  });
};


Enter fullscreen mode Exit fullscreen mode

调用上一节中创建的函数并传入参数,就完成了!


await getAnswerFromQuestion(keywordLikeability[0].url, page);

Enter fullscreen mode Exit fullscreen mode

最终结果出来了,我们终于可以退出VIM了!
Stack Overflow 的回答


结语

希望你今天学到了一些东西,请查看我创建的代码仓库,里面包含了所有代码。感谢阅读,祝你一切顺利❤️

文章来源:https://dev.to/producthackers/using-puppeteer-to-scrape-answers-in-stackoverflow-4608