我一直致力于制作一个简单的Web抓取工具,以更深入地了解使用异步/等待和承诺/解决功能。我在使用async / await函数时遇到问题,无法正确返回解析。我可以在返回解析之前,在函数内console.log完整的数据集,但是我无法分配常量并在函数本身范围之外调用函数,而console.log该数据(它以未定义的形式返回)。
这是应该返回要抓取的数据集的函数。 saleHeaders已成功提取,因为我能够抓取内容并将其分配给它们各自的常量。使用下面注释掉的console.log时,我将获得完整的数据集输出。
async function getInnerPosts(saleHeaders) {
await Promise.all(saleHeaders.map(async (job) => {
const dataSet = [];
return new Promise(resolve => {
request(job.fullURL, (err, res, html) => {
const $ = cheerio.load(html);
$('.result-info').each((index, element) => {
const postTitle = $(element).children(".result-title").text();
const postDate = $(element).children(".result-date").attr('title');
const postLink = $(element).children("a").attr('href');
const postPrice = $(element).children(".result-meta").children(".result-price").text();
const postLocation = $(element).children(".result-meta").children(".result-hood").text().replace(/[{()}]/g, '');
// gather data to one const
const fetchedData = { postTitle, postDate, postLink, postPrice, postLocation };
dataSet.push(fetchedData);
// console.log(dataSet);
});
return resolve(dataSet);
});
});
}));
}
但是当我尝试运行该函数,将其分配给一个常量,然后尝试记录该常量时,我变得未定义,没有引发任何警告/错误。我试图重新构造数据集的返回结构,并从头开始重写完整的函数,以确保我不会错过一些小错误,但是没有运气。
async function scrapeData() {
const saleHeaders = await getForSaleHeader();
// Loop through the categories and pull the inner-data from the posts page
const innerPosts = await getInnerPosts(saleHeaders);
console.log(innerPosts);
}
这是时间,运行上述代码示例时我正在接收输出:
undefined
real 0m31.272s
user 0m33.594s
sys 0m0.322s
编辑 我正在调用整个脚本来运行:
// run the script
scrapeData();
答案 0 :(得分:0)
async function getInnerPosts(saleHeaders) {
// here
return await Promise.all(saleHeaders.map(async (job) => {
const dataSet = [];
return new Promise(resolve => {
您必须返回它,因为您想进一步使用函数外的值。
const innerPosts = await getInnerPosts(saleHeaders);
如果不这样做,您认为innerPosts
的值从哪里来?它是从getInnerPosts
返回的!
答案 1 :(得分:0)
问题在于您的return语句仅在map函数内部返回(map具有return语句,但函数本身没有!
答案 2 :(得分:0)
问题的根源是您将Promises与async
/ await
混合在一起。请记住,如果函数返回Promise(例如return new Promise
或return Promise.all
),则不应为async
。
一个较小但同样重要的问题是getInnerPosts
不返回任何内容。它以await Promise.all(...)
开头。它应以return Promise.all(...)
开头。在没有return
的情况下,getInnerPosts
返回undefined
,因此调用它的函数实际上只是在执行await undefined
,它立即解析为undefined
。
让我们逐步解决这个问题。首先,通过将cheerio选择器代码拖入其自己的函数中,以消除大部分代码:
function getData(element) {
const $el = $(element);
return {
postTitle: $el.children('.result-title').text(),
postDate: $el.children('.result-date').attr('title'),
postLink: $el.children('a').attr('href'),
postPrice: $el.children('.result-meta > .result-price').text(),
postLocation: $el.children('.result-meta > .result-hood').text().replace(/[{()}]/g, ''),
};
}
接下来,摆脱async
/ await
并在return
之前添加Promise.all
:
function getInnerPosts(saleHeaders) {
return Promise.all(saleHeaders.map(job => {
const dataSet = [];
return new Promise(resolve => {
request(job.fullURL, (err, res, html) => {
const $ = cheerio.load(html);
$('.result-info').each((index, element) => dataSet.push(getData(element)));
resolve(dataSet);
});
});
}));
}
这应该足以使您的代码按预期工作。但是,这种金字塔结构不是很容易阅读。让我们看看是否可以改善它。
首先,您可以将对request
的调用包装在一个返回Promise的函数中(或者,您可以使用request-promise-native)。完成此操作后,您可以使传递给saleHeaders.map
的回调函数成为async
函数,并在其中使用await pRequest(...)
。最后,您可以通过使用cheerio的dataSet
而不是在map
循环内调用push
来完全消除each
。最终结果如下:
function pRequest(url) {
return new Promise((resolve, reject) =>
request(url, (err, res, html) => err ? reject(err) : resolve(html)));
}
function getInnerPosts(saleHeaders) {
return Promise.all(saleHeaders.map(async job => {
const html = await pRequest(job.fullURL);
const $ = cheerio.load(html);
return $('.result-info').map((index, element) => getData(element)));
}));
}
回复您的评论:
仍然试图绕过async / await和promise之间的差异。
了解async
/ await
与promise之间的差异(或更重要的是关系)并非微不足道。它需要三到四层的理解。您需要了解JS中的异步回调(例如,传递给request
或setTimeout
的函数)如何工作,然后您需要了解Promise与回调的关系,最后是async
/ await
与诺言有关。这是一个很难解决的难题,我认为大多数人只是通过经验来掌握它。
如果我有任何建议,请放慢速度。考虑一下代码的每个部分以及它的实际作用。当您编写此代码时:
request(url, (err, res, body) => { /* … */ });
…request
函数中的第二个参数(回调)会发生什么?
当您写这篇文章时:
fetch(url)
.then(res => { /* … */ })
.catch (err => { /* … */ });
…fetch(url)
返回什么,then
是什么?为什么要改写这个(在async
函数中)?
try {
const res = await fetch(url);
// …
} catch (err) {
// …
}
它的行为是否相同,还是有区别?
放慢脚步,想想你在写什么。除非您知道为什么这么做,否则不要添加await
。除非您知道自己为什么这么做,否则不要添加new Promise
。
最后,将您的代码分解为较小的函数。当函数具有return
,await
和三个嵌套的回调函数时,很容易忘记需要Promise.all
的位置或想要new Promise
的位置。如果函数只有2–3行,那么判断一个函数是否可以为async
就容易得多。
使用一个相对于另一个有什么优势吗?
IMO async
/ await
的主要优点是可读性。 Promise构造函数和嵌套的then
/ catch
回调 work ,但是它们往往使您的代码难以理解。 async
/ await
使您的代码阅读起来更像普通的同步代码(更不用说“ await”是一个已经很熟悉的英语单词),这使您更容易理解意图。当然,您仍然需要Promises。 await
使功能有效地暂停,但是有时在执行其余功能时只想延迟一件事,因此您需要then
。 Promise.all