我正在尝试使用 puppeteer 从多个页面抓取价格。我遇到的问题是用所有抓取的数据编写一个 JSON 文件。问题是,如果我尝试使用
async function
内部的变量写入文件,我会收到一条错误消息,指出该变量尚未声明。
async function scrapeVMZ(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url);
const [vmzel1] = await page.$x('//*[@id="__layout"]/div/div[1]/section/div/div/div[2]/div[2]/div[1]/div/div/div[2]/div/div[1]/span[2]');
const vmztxt1 = await vmzel1.getProperty('textContent');
const vmzRawTxt1 = await vmztxt1.jsonValue();
const [vmzel2] = await page.$x('//*[@id="__layout"]/div/div[1]/section/div/div/div[2]/div[2]/div[1]/div/div/div[2]/div/div[1]/span[4]/b');
const vmztxt2 = await vmzel2.getProperty('textContent');
const vmzRawTxt2 = await vmztxt2.jsonValue();
console.log({vmzRawTxt1, vmzRawTxt2});
const vmz01 = JSON.stringify(vmzRawTxt1);
const vmz02 = JSON.stringify(vmzRawTxt2);
console.log(vmz01, vmz02);
browser.close();
}
scrapeVMZ('https://www.vmzviagens.com.br/ingressos/orlando/walt-disney-orlando');
async function scrapeMB(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(url);
const [mbel1] = await page.$x('/html/body/section[3]/div/div/div[2]/div[1]/div/div[2]/a[1]/span[2]/span/div/div[2]/span');
const mbtxt1 = await mbel1.getProperty('textContent');
const mbRawTxt1 = await mbtxt1.jsonValue();
const [mbel2] = await page.$x('/html/body/section[3]/div/div/div[2]/div[1]/div/div[2]/a[1]/span[2]/span/div/div[4]/span');
const mbtxt2 = await mbel2.getProperty('textContent');
const mbRawTxt2 = await mbtxt2.jsonValue();
console.log({mbRawTxt1, mbRawTxt2});
const mb01 = JSON.stringify(mbRawTxt1);
const mb02 = JSON.stringify(mbRawTxt2);
console.log(mb01, mb02);
browser.close();
}
scrapeMB('https://www.ingressosmagicblue.com.br/produtos/?mpage=2');
如何使用上面的代码编写一个文件,以在我的 JSON 文件中存储变量
vmz01, vmz02
和 mb01, mb02
,如下面的示例所示?
let abc = {
"MB": {
preco: mb01,
preco2: mb02
},
"VMZ": {
preco: vmz01,
preco2: vmz02
}
};
当
console.log
出现在函数中而不是返回结果时,那就是死胡同。如果您想稍后使用它们,请返回结果。由于您要返回承诺,因此您可以在调用者中await
它们,串行或并行。
你的函数中也有很多重复的代码,你可能不需要2个浏览器。这是在单个浏览器中并行运行的快速重构(
preco
键有点尴尬——我建议在这里使用一个数组)。
const fs = require("fs").promises;
const puppeteer = require("puppeteer"); // ^14.3.0
const vmzPaths = [
'//*[@id="__layout"]/div/div[1]/section/div/div/div[2]/div[2]/div[1]/div/div/div[2]/div/div[1]/span[2]',
'//*[@id="__layout"]/div/div[1]/section/div/div/div[2]/div[2]/div[1]/div/div/div[2]/div/div[1]/span[4]/b',
];
const mbPaths = [
"/html/body/section[3]/div/div/div[2]/div[1]/div/div[2]/a[1]/span[2]/span/div/div[2]/span",
"/html/body/section[3]/div/div/div[2]/div[1]/div/div[2]/a[1]/span[2]/span/div/div[4]/span",
];
const scrape = async (browser, url, paths) => {
const page = await browser.newPage();
await page.goto(url);
return Promise.all(paths.map(async p =>
(await page.waitForXPath(p)).evaluate(e => e.textContent)
));
};
let browser;
(async () => {
browser = await puppeteer.launch({headless: true});
const text = await Promise.all([
scrape(browser, "https://www.ingressosmagicblue.com.br/produtos/?mpage=2", mbPaths),
scrape(browser, "https://www.vmzviagens.com.br/ingressos/orlando/walt-disney-orlando", vmzPaths),
]);
const names = ["MB", "VMZ"];
const collected = Object.fromEntries(text.map((e, i) => [
names[i], Object.fromEntries(e.map((e, i) =>
[`preco${i === 0 ? "" : (i + 1)}`, e]
))
]));
await fs.writeFile("out.json", JSON.stringify(collected, null, 2));
})()
.catch(err => console.error(err))
.finally(() => browser?.close())
;
顺便说一句,我不太喜欢超精确的浏览器生成的路径和选择器。这些往往非常脆弱,并且几乎总是有更好的方法来选择选择器。但我没有为了关注承诺问题而查看该页面,因此我将其作为读者的练习。
披露:我是链接博客文章的作者。