含async函数仍报await只能在async内使用错误的JS爬虫代码排查
解决"await can only be declared inside async function"报错问题
嘿,我看到你遇到的问题了!虽然你把最外层的函数声明成了async,但问题出在**client.query的回调函数**上——这个回调函数是普通函数,不是async的,而你在里面用了await,所以才会触发这个报错。
问题根源拆解
你的代码结构大概是这样的:
(async () => { // 这层是async函数没问题 client.query('SQL语句', (err, res) => { // 👉 这个回调函数是普通函数!不是async的! // 所以里面的await都会报错 await puppeteer.launch(); }); })();
await必须直接在async函数内部使用,而回调函数是一个独立的作用域,和外层的async IIFE没关系。
两种解决方案
方案1:把回调函数改成async函数
最简单的修改是给client.query的回调加上async关键字,这样回调内部就可以正常用await了:
(async () => { client.query("SELECT DISTINCT url,article_id FROM public.content_paraarticle", async (err, res, fields) => { if (err) throw err; for(var i=0;i<res.rows.length;i++) { // 现在这里的await就合法了 const browser = await puppeteer.launch({headless: false}); const page = await browser.newPage(); page.setViewport({ width: 1280, height: 926 }); var str = res.rows[i].article_id + '.png'; var url = res.rows[i].url; console.log(str, 'taken'); await Promise.race([ page.goto(url, {waitUntil: 'load'}), // 注意:原代码这里写死了一个url,要改成循环里的url! new Promise(x => setTimeout(x, 60000)), ]); const image = await page.$('body > div.container-main > div.jscroll > div > div > div > section > div > div > div > div:nth-child(2) > div.lead-img-cont > div > picture > img'); console.log('screenshot started to get taken'); if (image) { // 加个判断,避免元素找不到报错 await image.screenshot({ path: str, omitBackground: true, }); console.log('screenshot taken'); } else { console.log(`No image found for article ${res.rows[i].article_id}`); } await browser.close(); } client.end(); }); })();
方案2:用Promise化写法(更推荐,适合新手学习现代JS)
回调嵌套容易陷入"回调地狱",更优雅的方式是把client.query转成Promise风格的函数,用await替代回调。Node.js自带的util.promisify可以帮我们做到这一点:
const util = require('util'); const puppeteer = require('puppeteer'); (async () => { try { // 把client.query转成返回Promise的函数 const queryAsync = util.promisify(client.query).bind(client); // 用await执行SQL查询,不用写回调 const res = await queryAsync("SELECT DISTINCT url,article_id FROM public.content_paraarticle"); // 用for...of循环更简洁,避免var的作用域问题 for(const row of res.rows) { // 解构赋值,让代码更清晰 const { article_id, url } = row; const str = `${article_id}.png`; // 模板字符串比字符串拼接更方便 console.log(str); console.log('taken'); const browser = await puppeteer.launch({headless: false}); const page = await browser.newPage(); page.setViewport({ width: 1280, height: 926 }); await Promise.race([ page.goto(url, {waitUntil: 'load'}), // 这里用循环里的url,不是硬编码的固定值 new Promise(x => setTimeout(x, 60000)), ]); const image = await page.$('body > div.container-main > div.jscroll > div > div > div > section > div > div > div > div:nth-child(2) > div.lead-img-cont > div > picture > img'); console.log('screenshot started to get taken'); if (image) { await image.screenshot({ path: str, omitBackground: true, }); console.log('screenshot taken'); } else { console.log(`No image found for article ${article_id}`); } await browser.close(); } } catch (err) { // 统一处理所有错误,避免程序崩溃 console.error('程序出错了:', err); } finally { // 不管成功还是失败,都关闭数据库连接 client.end(); } })();
额外的新手友好提示
- 避免硬编码URL:原代码里
page.goto写死了一个固定URL,这应该是个失误,要改成循环里的row.url才能爬取对应文章的页面。 - 用
const/let替代var:var有作用域提升的问题,容易引发bug,现代JS推荐用const(值不变时)或let(值需要改变时)。 - 错误处理很重要:用
try/catch包裹异步逻辑,能捕获所有步骤的错误,方便排查问题;finally块确保数据库连接一定会关闭,避免资源泄漏。 - 元素存在性判断:如果页面找不到你指定的选择器,
page.$会返回null,直接调用screenshot会报错,所以加个if (image)的判断很有必要。
内容的提问来源于stack exchange,提问作者Sachin
相关产品推荐
相关产品推荐

