使用 Puppeteer 抓取 Stack Overflow 上的答案
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
什么是木偶师
Puppeteer 是一个 Node 库,它允许我们通过命令控制 Chrome 浏览器,它是网络爬虫最常用的工具之一,因为它使我们能够轻松地自动化操作。
我们在做什么?
今天我们将学习如何设置 Puppeteer,以便在 Stack Overflow 上搜索问题时抓取 Google 搜索结果的前几条,让我们看看它是如何工作的:
- 首先,我们运行包含以下问题的脚本
node index "how to exit vim"
[
{
keywordMatch: 4,
url: 'https://stackoverflow.com/questions/31595411/how-to-clear-the-screen-after-exit-vim/51330580'
}
]
存储库
我不会在这篇博客文章中介绍所有的代码细节,诸如如何使用 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());
}
})();
要获取特定网站的所有结果,我们需要用以下方式构建 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"]);
获取网址
进入谷歌搜索页面后,接下来需要收集属于该部分的所有 href 链接https://stackoverflow.com/questions。
在page.evaluate方法中,我们可以使用 document 对象访问DOM,这意味着我们可以使用选择器轻松地找到所需的信息document.querySelector。document.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);
匹配网址
我们已经将已验证的 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,
});
}
});
捕捉答案
最后,我们需要一个函数来访问 stackoverflow 网站并检查是否有答案,如果有,则截取元素的屏幕截图并保存。
我们首先访问 Stack Overflow 的网址,然后关闭弹出窗口,否则它会出现在我们的屏幕截图中,而我们不希望这样。
为了找到弹出窗口的关闭按钮,我们使用xpath选择器,它就像我们喜爱的 CSS 选择器的一个奇怪的表亲,但它是用于 xml/html 的。
弹窗消失后,是时候看看我们是否能找到答案了,如果找到了,我们就截屏保存。
await acceptedAnswer.screenshot({
path: `.howtoexitvim.png`,
clip: { x: 0, y: 0, width: 1024, height: 800 },
});
使用截图方法时要小心,因为它不稳定,为了获得更流畅的体验,请尝试获取 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`,
});
};
调用上一节中创建的函数并传入参数,就完成了!
await getAnswerFromQuestion(keywordLikeability[0].url, page);
结语
希望你今天学到了一些东西,请查看我创建的代码仓库,里面包含了所有代码。感谢阅读,祝你一切顺利❤️
文章来源:https://dev.to/producthackers/using-puppeteer-to-scrape-answers-in-stackoverflow-4608