Puppeteer 爬虫使用总结

1,269 阅读3分钟

基本使用

使用

const browser = await puppeteer.launch({headless: false}); // default is true

绑定已安装的 chrome

const browser = await puppeteer.launch({
  executablePath: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
});

参数参考:pptr.dev/api/puppete… 常用 headless、slowMo、args。

打开新的标签页

const page = await browser.newPage();

点击

await page.click(".btn");

输入框输入

await page.type("#user-mobile", telephone);

页面跳转

page.goto(url)

页面返回

page.goBack()

元素的点击

const element = await page.waitForSelector("text/销量");
await element.click();

等待导航

await page.waitForNavigation();

等待选择器

await page.waitForSelector("#main"),

获取元素,page.$$ 类似

const el = await page.$('.title');

获取元素内容,page.$$eval 类似

const title = await page.$eval('.title > span', el => el.textContent.trim());

打开新的 tab 页之后,需要获取当前 page 对象

let page = await browser.newPage();
await page.goto(url);
let btn = await page.waitForSelector('#btn');
//在点击按钮之前,事先定义一个 Promise,用于返回新 tab 的 Page 对象
const newPagePromise = new Promise(res => 
  browser.once('targetcreated', 
    target => res(target.page())
  )
);
await btn.click();
//点击按钮后,等待新tab对象
let newPage = await newPagePromise;

页面滚动

const { scrollPageToBottom } = require("puppeteer-autoscroll-down");

数据保存文件使用 xlsx

// 写入 Excel
async function writeToExcel(data, filename) {
  const workbook = xlsx.utils.book_new();
  const worksheet = xlsx.utils.json_to_sheet(data);
  xlsx.utils.book_append_sheet(workbook, worksheet, 'sheet1');
  await xlsx.writeFile(workbook, `./data/${filename}.xlsx`);
}

连接现有浏览器

修改chrome的,打开 chrome 所在文件夹位置,chrome 右键,属性,修改目标类似:

"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --user-data-dir="D:\work"

--user-data-dir 必须,修改成自己的路径。

访问 http://127.0.0.1:9222/json/version 得到:

{
   "Browser": "Chrome/108.0.5359.99",
   "Protocol-Version": "1.3",
   "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
   "V8-Version": "10.8.168.21",
   "WebKit-Version": "537.36 (@aa99c50ebc03bd334a4a8f11e99ab6fb5e0adf78)",
   "webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/ce51e0b5-4598-4bcd-8c9a-9bccdfefed3b"
}

通过 puppeteer.connect 获取 browser,browserWSEndpoint 为上面获取的值:

// 连接现有的浏览器
  const browser = await puppeteer.connect({
    browserWSEndpoint: config.webSocketDebuggerUrl
  });
  const pages = await browser.pages();
  const page = pages[0];

注意 webSocketDebuggerUrl 从 http://127.0.0.1:9222/json/version 中获取而不是 http://127.0.0.1:9222/json

报错:

(node:26404) UnhandledPromiseRejectionWarning: Error: connect ECONNREFUSED 127.0.0.1:9222

首先要打开浏览器。

报错:

(node:31528) UnhandledPromiseRejectionWarning: #<ErrorEvent>
(Use `node --trace-warnings ...` to show where the warning was created)
(node:31528) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:31528) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

检查 webSocketDebuggerUrl 是否正确,每次关闭浏览器,webSocketDebuggerUrl 都会改变,最好通过 Ajax 请求获取:

async function getDebugUrl() {
  const res = await axios.get('http://127.0.0.1:9222/json/version');
  return res.data.webSocketDebuggerUrl;
}

常见报错

  • 等待选择器超时,没有找到选择

  • 等待导航超时报错:

    Error: Navigation Timeout Exceeded: 30000ms exceeded
    

    解决:

    await Promise.all([
      page.waitForNavigation(),
      page.click('a')
    ]);
    

    也可能为不在当前的连接浏览器桌面,导致断开

  • 导航错误:

    Puppeteer Execution context was destroyed, most likely because of a navigation
    

    导航时出现了问题,执行上下文被销毁

    • 可能 page 对象改变

    • page 返回,比如循环中,需要重新获取元素

要点总结

  • 选择器我只使用过简单的,类似 nth-child 用法报错,还未解决;
  • 如果页面要前往新页面,元素没有链接,只能使用元素点击跳转,注意返回当前页面;
  • 反爬虫:打开页面以及操作时增加等待时间;
  • 反爬虫:增加 proxy, userAgent, device 的使用;
  • page.evaluate() 是在 page 的上下文中执行代码并返回结果,所以其中的代码无法获取 page 等外部变量,即 page 是 Node 的环境变量,可以不用 page(例如获取元素使用 document.querySelector)或者使用其他方法获取外部变量;
  • 与其通过选择器获取数据,不如分析页面内容,看是否能从 window 中获取;
  • 如果是通过字符串解析,使用正则 match 模式匹配解析获取数据;
  • 如何获取 window 对象?page.evaluate 无法获取整个 window 对象,但是可以获取到其中具体的值,参考:github.com/puppeteer/p…

参考链接