使用 Puppeteer 进行爬虫

1,293 阅读3分钟

一. 使用Puppeteer进行爬虫的流程

1.安装Puppeteer

在终端中运行npm install puppeteer --save命令安装Puppeteer库。

2.引入Puppeteer

在代码中使用const puppeteer = require('puppeteer')引入Puppeteer库。

3.创建浏览器实例

使用const browser = await puppeteer.launch()创建一个headless Chrome浏览器实例。如果需要打开有界面的浏览器进行调试,可以通过传参选项对象来配置启动参数,例如:{ headless : false }

4.打开目标网站页面

使用const page = await browser.newPage()创建一个新的页面对象,并使用await page.goto(url)打开目标网站的页面。在打开页面之前,还可以使用page.setUserAgent(userAgent)方法设置用户代理,以此来模拟不同的浏览器环境。

5.提取数据

使用page.evaluate()方法执行Javascript脚本,在浏览器环境中运行脚本,并将结果返回给爬虫程序 。还可以使用选择器或者其他方式提取所需的数据,例如使用document.querySelector(selector)或者document.querySelectorAll(selector)方法选取元素。

6.处理异步操作

由于页面某些操作(页面滚动,拖拽,点击按钮)可能是异步的,因此需要使用await关键字等待这些操作完成。也可以使用page.waitForSelector(selector)方法等待某个元素出现后再进行操作。

7.关闭浏览器实例

使用browser.close()关闭浏览器实例,并释放资源。

8.反爬虫机制

Puppeteer 可以模拟用户对网站的操作,因此可以抵御一些反爬虫机制。但需要注意不要过度使用,以免给目标网站带来过大的负担。还可以使用一些类似于随机延迟、更换 IP 等技巧来模拟人工操作,减少被识别为爬虫的风险。

9.错误处理

在使用Puppeteer进行爬虫时,可能会遇到各种错误,例如页面加载超时,元素不存在等。因此需要进行适当的错误处理,例如使用try···catch捕获异常,并输出错误的信息进行重试操作。

二. Puppeteer的一些常用的API

1. puppeteer.launch([options]):

启动一个 headless Chrome 实例,并返回一个 Browser 对象。options 参数可以包含一些启动选项,例如是否显示浏览器界面等。

2. browser.newPage():

创建一个新的 Page 对象,代表一个浏览器页面。

3. page.goto(url, [options]):

在当前浏览器页面中打开指定的 URL。可以通过 options 参数设置超时时间、referer 等请求选项。

4. page.evaluate(pageFunction, [...args]):

在浏览器上下文中执行 JavaScript 脚本,并将结果返回给 Node.js 程序。pageFunction 可以是一个函数或字符串,args 是传递给函数的参数。

5. page.$(selector):

此方法在页面内执行 document.querySelector。返回第一个匹配指定选择器的元素,如果没有找到则返回 null。

6. page.$$(selector):

此方法在页面内执行 document.querySelectorAll。返回所有匹配指定选择器的元素数组,如果没有找到任何元素则返回空数组。

7. elementHandle.click([options]):

  • selector <string> 要点击的元素的选择器。 如果有多个匹配的元素, 点击第一个。
  • options <Object>
    • button <stringleftright, 或者 middle, 默认是 left
    • clickCount <number> 默认是 1. 查看 UIEvent.detail
    • delay <numbermousedown 和 mouseup 之间停留的时间,单位是毫秒。默认是0
  • 返回: <Promise> Promise对象,匹配的元素被点击。 如果没有元素被点击,Promise对象将被rejected。
// click() 触发了一个跳转,会有一个独立的 page.waitForNavigation() Promise对象需要等待。 
const [response] = await Promise.all([
  page.waitForNavigation(waitOptions),
  page.click(selector, clickOptions),
]);

8. page.waitForSelector(selector, [options]):

等待指定选择器的元素出现在页面中,然后返回该元素的 ElementHandle 对象。

9. page.setCookie(...cookies):

设置浏览器页面的 cookie。

10. page.setUserAgent(userAgent):

设置浏览器页面的用户代理。

三. 下面是用Puppeteer爬取wegame折扣速递模块的代码

Snipaste_2023-05-02_01-04-50.png

Snipaste_2023-05-02_01-07-44.png

//引入Puppeteer
const puppeteer = require('puppeteer');

//引入fs
const fs = require('fs');

//动态获取折扣速递板块的数据
(async() => {
    // 启动浏览器
    const browser = await puppeteer.launch({
        headless: false, // 默认是无头模式,这里为了示范所以使用正常模式
    })

    // 控制浏览器打开新标签页面
    const page = await browser.newPage()

    // 控制浏览器全屏
    await page.setViewport({ width: 1920, height: 1080 });

    // 在新标签中打开要爬取的网页
    await page.goto('https://www.wegame.com.cn/store')

    // 由于折扣速递板块位于最下方,所以需要页面滚动到最底端(封装页面滚动函数)
    await autoScroll(page);

    //返回的data
    let data = [];
    //获取点击换一换的按钮
    const change = await page.$('#discount-express-container > div > div.skin-panel-inner > div.tui-panel-hd > div > a:nth-child(1)');
    //任意模拟点击事件50次
    for (let i = 0; i < 50; i++) {
        //模拟点击事件
        change && await change.click();
        //每次点击之后,留出1s给页面某些元素(图片/视频等)加载
        await page.waitForTimeout(1000);
        data = await page.evaluate((data) => {
            let titles = document.querySelectorAll('#discount-express-container > div > div.skin-panel-inner > div.tui-panel-bd > ul > li > div > div > div.gcard-bd > div > div.gcard-tit');
            let imgs = document.querySelectorAll('#discount-express-container > div > div.skin-panel-inner > div.tui-panel-bd > ul > li > div > div > div.gcard-cover > a > div > img:nth-child(1)');
            let prices = document.querySelectorAll('#discount-express-container > div > div.skin-panel-inner > div.tui-panel-bd > ul > li > div > div > div.gcard-bd > div > div:nth-child(2) > div')
            //遍历每次点击换一换,页面更新的数据组(3个)
            for (let i = 0; i < titles.length; i++) {
                // 拆分字符串,对页面数据进行处理
                const [discount, new_price, old_price] = prices[i].textContent.split('¥');
                data.push({
                    img: {
                        src: imgs[i].getAttribute('src'),
                    },
                    title: titles[i].textContent,
                    price: {
                        discount,
                        newprice: '¥' + new_price,
                        oldprice: '¥' + old_price,
                    }
                })
            }
            return data
        }, data)
    }
    //换一换的数据进行去重处理
    const newdata = Array.from(new Set(data.map(JSON.stringify)), JSON.parse);

    // 关闭浏览器实例
    await browser.close();

    // 将数据作为 JSON 写入json文件
    const jsonData = JSON.stringify(newdata);

    //写入文件路径
    const filePath = './discount.json';
    //调用fs.writeFile()进行写入文件
    fs.writeFile(filePath, jsonData, (err) => {
        if (err) {
            console.error(err);
            return;
        }
        console.log('Data written to file');
    });

})()

//封装页面滚动函数
async function autoScroll(page) {
    await page.evaluate(async() => {
        await new Promise((resolve, reject) => {
            var totalHeight = 0;
            var distance = 100;
            var timer = setInterval(() => {
                var scrollHeight = document.body.scrollHeight;
                window.scrollBy(0, distance);
                totalHeight += distance;

                if (totalHeight >= scrollHeight) {
                    clearInterval(timer);
                    resolve();
                }
            }, 50);
        });
    });
}