Puppeteer写入MySQL报connection.query is not a function错误
报错核心原因
- 你引入的是
mysql2/promise异步Promise版客户端,mysql.createConnection()调用后返回的是Promise对象,没有加await等待连接实例创建完成,拿到的是未resolve的Promise,自然不存在query方法,调用就会触发你看到的报错。 - Promise版mysql2的
query方法不支持回调函数写法,你现在混用了回调版API的调用方式,就算连接正常也会出问题。 - 采集值返回null有两个原因:一是你写的
page.evaluate内部语句多了冗余括号存在语法错误;二是没有做元素存在性判断,选择器匹配不到元素时直接取innerText/src属性就会返回null。 - 你没有合理管理数据库连接生命周期,既没有在爬取前确认连接建立,也没有在任务结束后关闭连接,长期运行会占满数据库连接数。
修复方案
按以下步骤改代码即可正常入库:
- 把数据库连接初始化移到整个爬取逻辑的最外层,加
await等待连接建立,整个爬取任务复用同一个连接即可,不要在循环里重复创建连接。 - 所有
page.evaluate内的DOM取值逻辑先判断元素是否存在,匹配不到就返回空字符串,避免null值入库,修正原来evaluate里多写的冗余括号语法错误。 - 把
connection.query改成Promise版的await调用写法,去掉回调函数,加try/catch捕获插入异常。 - 所有爬取任务结束后,先关闭数据库连接,再退出脚本。
修复后可运行的代码参考
const mysql = require("mysql2/promise") const puppeteer = require('puppeteer-extra'); const StealthPlugin = require('puppeteer-extra-plugin-stealth') puppeteer.use(StealthPlugin()) const AdblockerPlugin = require('puppeteer-extra-plugin-adblocker') puppeteer.use(AdblockerPlugin({ blockTrackers: true })) const UserAgent = require('user-agents'); (async () => { // 初始化数据库连接,等待连接建立完成 const connection = await mysql.createConnection({ host: "localhost", port: 3306, user: "...", password: "...", database: "..." }); console.log("数据库连接成功"); for (let step = 0; step <= 5; step++) { const userAgent = new UserAgent({ deviceCategory: "desktop" }); const cleanUA = userAgent.toString(); const browser = await puppeteer.launch({ headless: false, defaultViewport: {width: 1920, height: 1080}, args: ['--disable-infobars', '--user-agent=' + cleanUA] }); const page = await browser.newPage(); console.log("正在抓取第" + (step+1) + "页"); const url = 'https://example/test?page=' + (step+1); await page.goto(url, {waitUntil: 'domcontentloaded'}); await page.waitForSelector("/* 替换成你实际要等待加载的选择器 */"); // 点击分段的逻辑替换成你自己的选择器 await page.$$eval("/* 替换成你实际的点击目标选择器 */", els => els.forEach(el => el.click())); for (let i = 0; i <= 35; i++){ // 所有取值逻辑加元素存在判断,修正语法错误 const title = await page.evaluate((i) => { const el = document.querySelector("/* 替换成title对应的选择器 */"); return el ? el.innerText.trim() : ''; },i); console.log('title:', title) const subtitle = await page.evaluate((i) => { const el = document.querySelector("/* 替换成subtitle对应的选择器 */"); return el ? el.innerText.trim() : ''; },i); console.log('subtitle:', subtitle) const item_price = await page.evaluate((i) => { const el = document.querySelector("/* 替换成价格对应的选择器 */"); return el ? el.innerText.trim() : ''; },i); console.log('price:', item_price) const ros_info = await page.evaluate((i) => { const el = document.querySelector("/* 替换成ros_info对应的选择器 */"); return el ? el.innerText.trim() : ''; },i); console.log('ros_info:', ros_info) const img = await page.evaluate((i) => { const el = document.querySelector("/* 替换成图片对应的选择器 */"); return el ? el.src : ''; },i); console.log('img:', img) const p = await page.evaluate((i) => { const el = document.querySelector("/* 替换成p字段对应的选择器 */"); return el ? el.innerText.trim() : ''; },i); console.log('p:', p) const info_items = await page.evaluate((i) => { const el = document.querySelector("/* 替换成info_items对应的选择器 */"); return el ? el.innerText.trim() : ''; },i); console.log('info_items:', info_items) const link = await page.evaluate((i) => { const el = document.querySelector("/* 替换成链接对应的选择器 */"); return el ? el.href : ''; },i); console.log('link:', link) const theopeninfo2 = await page.evaluate((i) => { const els = Array.from(document.querySelectorAll("/* 替换成openinfo对应的选择器 */")); return els.map(x => x.innerText.trim()).join("\n"); },i); console.log('theopeninfo2:', theopeninfo2) // 数据库插入用await写法,加异常捕获 try { const [results] = await connection.query( "INSERT INTO `b_info`(`title`, `subtitle`, `item_price`, `ros_info`, `img`, `p`, `info_items`, `link`, `theopeninfo2`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", [title , subtitle , item_price , ros_info , img , p , info_items , link , theopeninfo2 ] ); console.log("数据插入成功,插入ID:", results.insertId); } catch (err) { console.error("数据插入失败:", err); } } await browser.close(); } // 所有任务结束关闭数据库连接 await connection.end(); console.log("全部爬取任务完成"); })();
注意事项
- 代码里所有标注
/* 替换成XX对应的选择器 */的位置,换成你实际业务里的CSS选择器即可。 - 如果爬取量比较大,可以在循环里加适当的延时,避免请求太频繁被站点封IP。
- 提前确认数据库表的字段类型、长度和你采集的内容匹配,避免长文本插入时触发字段长度超限报错。
内容的提问来源于stack exchange,提问作者Gilad Alexander Kaveh
相关产品推荐
相关产品推荐

