puppeteer零基础爬虫

535 阅读4分钟

一、知识点

puppeteernodejsVsCode

二、安装环境

安装nodejs(v10.16.0版本)

nodejs for Window下载包nodejs for Mac下载包

安装VsCode代码编辑器

VsCode for Window下载包VsCode for Mac下载包

三、新建项目目录

1. 新建项目文件夹

nodejs和VsCode安装完成后,任意位置新建文件夹puppeteer-demo,然后VsCode打开该文件夹

2. VsCode打开命令行终端

3. npm init 回车到底生成package.json文件

4. 命令行终端安装项目依赖包

// 先安装国内源环境,下载工具包更快
npm install cnpm -g
// 下载需要用到的工具包
cnpm install express puppeteer chai --save

5. 项目文件夹下新建启动文件entry.js

//引入express的模块
const express = require('express');
//引入puppeteer的模块
const puppeteer = require('puppeteer');
//引入fs文件操作模块
const fs = require('fs');
//引入测试模块
var chai = require('chai');
var expect = chai.expect;

//创建实例
var app = express();

//Router 方法
var Router = express.Router();

Router.get('/test'function (req, res) {
    res.end('Router test success!\n');
});

app.use(Router);
app.listen(7878, function afterListen() {
    console.log('express running on http://localhost:7878');
});

6. 在package.json中加上运行脚本

"start": "node entry.js"

输入命令npm run start启动服务

7. 浏览器输入http://localhost:7878/test测试服务是否成功

四、几个栗子

1. 获取网页内容

// 获取掘金前端模块推荐的前10文章列表
Router.get('/demo1', function (req, res) {
    ; (async () => {
        // 初始化环境 并 打开页面
        const browser = await puppeteer.launch()
        const page = await browser.newPage()
        await page.goto('https://juejin.cn', { waitUntil: 'networkidle2' })

        // 定位到前端tab页
        const handles = await page.?('li.nav-item div a')
        const btnHtmls = await page.$$eval('li.nav-item div a', options => options.map(option => option.innerHTML))
        await handles[btnHtmls.indexOf('前端')].click()

        // 等页面加载完毕
        await page.waitForSelector('.content-box .info-box .title-row a')

        //获取文章title和链接
        const articleInfos = await page.$$eval('.content-box .info-box .title-row a', options => options.map(option => {
            return {
                innerHTML: option.innerHTML,
                href: option.href
            }
        }))

        // 把抓取内容写入到top10.txt文件中
        let content = articleInfos.slice(0, 10).map((info => {
            return `${info.innerHTML}\r\n${info.href}`
        }))
        await fs.writeFile('./top10.txt', content.join('\r\n\r\n'), {}, function (err) {
            if (err) {
                throw err;
            }
        });

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

        res.end('Router demo1 success!!\n')
    })();

});

重启服务,浏览器访问http://localhost:7878/demo1 运行成功后,项目目录下会生成top10.txt的文件

2. 模拟表单输入

// demo2: 百度搜索“puppeteer”然后跳转到puppeteer GitHub主页
Router.get('/demo2', function (req, res) {
    ; (async () => {
        try {
            // 初始化环境 并 打开页面
            // headless: false,这个设置在运行时,可以看到浏览器窗口
            const browser = await puppeteer.launch({ headless: false })
            const page = await browser.newPage()
            await page.goto('https://www.baidu.com', { waitUntil: 'networkidle2' })

            await page.waitFor(1000)

            const elementHandle = await page.$('#kw')
            await elementHandle.type('puppeteer');
            await elementHandle.press('Enter');

            await page.waitFor(1000)

            // 等页面加载完毕
            await page.waitForSelector('.result h3.t a')

            // 找到包含GitHub的条目
            const handles = await page.?('.result h3.t a')
            const items = await page.$$eval('.result h3.t a', options => options.map(option => option.innerHTML))
            let targetItemIdx = 0;
            for (let i = 0; i < items.length; i++) {
                if (items[i].indexOf('GitHub') > -1) {
                    targetItemIdx = i
                    break
                }
            }

            // 点击条目
            await handles[targetItemIdx].click()

            res.end('Router demo2 success!!\n')
        } catch (error) {
            res.end('Router demo2 fail!!\n')
        }
    })();

});

重启服务,浏览器访问http://localhost:7878/demo2

3. 自动化UI测试

// demo3: 自动化UI测试, 输入不同的手机号测试返回结果
Router.get('/demo3', function (req, res) {
    res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' })

        ; (async () => {
            try {
                // 初始化环境 并 打开页面
                const browser = await puppeteer.launch({ headless: false })
                const page = await browser.newPage()
                await page.goto('https://www.mytijian.com/m/mt', { waitUntil: 'networkidle2' })

                // 跳转到登录页
                await page.waitForSelector('.login-link')
                await page.click('.login-link')

                // 输入手机号
                await page.waitForSelector('input[placeholder="请填写手机号码"]')
                const elementHandle = await page.$('input[placeholder="请填写手机号码"]')
                let { mobile } = req.query
                await elementHandle.type(mobile || '12333333333')

                // 点击发送验证码
                await page.waitFor(500)
                const btnHandle = await page.$('.weui-cell__ft button.weui-vcode-btn')
                await btnHandle.click()

                // 检测返回结果是否正确
                await page.waitFor(500)
                expect(await page.$eval('.weui-toast__content-warning', node => node.innerText.trim())).to.equal('验证码不能为空')

                await browser.close()

                res.end('Router demo3 success!!\n')
            } catch (error) {
                console.log(error)
                res.end(error.toString())
            }
        })();

});

重启服务,浏览器访问http://localhost:7878/demo3?mobile=12333333333,可以看到测试结果

4. 生成页面的屏幕截图和pdf文件

// demo4: 生成页面的截图和PDF文件
Router.get('/demo4', function (req, res) {
    res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' })

        ; (async () => {
            try {
                // 初始化环境 并 打开页面
                const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] })
                const page = await browser.newPage()
                await page.goto('http://www.dili360.com')

                // 等待页面加载完毕
                await page.waitForSelector('.tags .right')
                // 隐藏项部导航
                await page.addStyleTag({content: '.part-two { display: none; }'})
                // 指定截屏板块
                const elementHandle = await page.$('.tags .right')
                await elementHandle.screenshot({path: 'screenshot.png'})

                // -------------------------------
                // 打开央视网,生成PDF
                await page.goto('http://news.cctv.com')
                await page.pdf({
                    path: 'news.pdf',
                    format: 'A4',
                    printBackground: true,
                    margin: {
                      top: '15mm',
                      bottom: '15mm'
                    },
                    displayHeaderFooter: true,
                    footerTemplate: '<div style="font-size: 14px;"><span class="pageNumber"></span>/<span class="totalPages"></span></div>',
                    headerTemplate: '<div style="font-size: 14px;">I"m header!</div>',
                  });

                await browser.close()

                res.end('Router demo4 success!!\n')
            } catch (error) {
                res.end(error.toString())
            }
        })();

});

重启服务,浏览器访问http://localhost:7878/demo4,执行结束后,可以看到新生成的screenshot.pngnews.pdf

完整代码 github

//引入express的模块
const express = require('express');
//引入puppeteer的模块
const puppeteer = require('puppeteer');
//引入fs文件操作模块
const fs = require('fs');
//引入测试模块
var chai = require('chai');
var expect = chai.expect;

//创建实例
var app = express();

//Router 方法
var Router = express.Router();

Router.get('/test'function (req, res) {
    res.end('Router test success!\n');
});

Router.get('/demo1', function (req, res) {
    ; (async () => {
        // 初始化环境 并 打开页面
        const browser = await puppeteer.launch()
        const page = await browser.newPage()
        await page.goto('https://juejin.cn', { waitUntil: 'networkidle2' })

        // 定位到前端tab页
        const handles = await page.?('li.nav-item div a')
        const btnHtmls = await page.$$eval('li.nav-item div a', options => options.map(option => option.innerHTML))
        await handles[btnHtmls.indexOf('前端')].click()

        // 等页面加载完毕
        await page.waitForSelector('.content-box .info-box .title-row a')

        //获取文章title和链接
        const articleInfos = await page.$$eval('.content-box .info-box .title-row a', options => options.map(option => {
            return {
                innerHTML: option.innerHTML,
                href: option.href
            }
        }))

        // 把抓取内容写入到top10.txt文件中
        let content = articleInfos.slice(0, 10).map((info => {
            return `${info.innerHTML}\r\n${info.href}`
        }))
        await fs.writeFile('./top10.txt', content.join('\r\n\r\n'), {}, function (err) {
            if (err) {
                throw err;
            }
        });

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

        res.end('Router demo1 success!!\n')
    })();

});


// demo2: 百度搜索“puppeteer”然后跳转到puppeteer GitHub主页
Router.get('/demo2', function (req, res) {
    ; (async () => {
        try {
            // 初始化环境 并 打开页面
            const browser = await puppeteer.launch({ headless: false })
            const page = await browser.newPage()
            await page.goto('https://www.baidu.com', { waitUntil: 'networkidle2' })

            await page.waitFor(1000)

            const elementHandle = await page.$('#kw')
            await elementHandle.type('puppeteer');
            await elementHandle.press('Enter');

            await page.waitFor(1000)

            // 等页面加载完毕
            await page.waitForSelector('.result h3.t a')

            // 找到包含GitHub的条目
            const handles = await page.?('.result h3.t a')
            const items = await page.$$eval('.result h3.t a', options => options.map(option => option.innerHTML))
            let targetItemIdx = 0;
            for (let i = 0; i < items.length; i++) {
                if (items[i].indexOf('GitHub') > -1) {
                    targetItemIdx = i
                    break
                }
            }

            // 点击条目
            await handles[targetItemIdx].click()

            res.end('Router demo2 success!!\n')
        } catch (error) {
            res.end('Router demo2 fail!!\n')
        }
    })();

});

// demo3: 自动化UI测试, 输入不同的手机号测试返回结果
Router.get('/demo3', function (req, res) {
    res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' })

        ; (async () => {
            try {
                // 初始化环境 并 打开页面
                const browser = await puppeteer.launch({ headless: false })
                const page = await browser.newPage()
                await page.goto('https://www.mytijian.com/m/mt', { waitUntil: 'networkidle2' })

                // 跳转到登录页
                await page.waitForSelector('.login-link')
                await page.click('.login-link')

                // 输入手机号
                await page.waitForSelector('input[placeholder="请填写手机号码"]')
                const elementHandle = await page.$('input[placeholder="请填写手机号码"]')
                let { mobile } = req.query
                await elementHandle.type(mobile || '12333333333')

                // 点击发送验证码
                await page.waitFor(500)
                const btnHandle = await page.$('.weui-cell__ft button.weui-vcode-btn')
                await btnHandle.click()

                // 检测返回结果是否正确
                await page.waitFor(500)
                expect(await page.$eval('.weui-toast__content-warning', node => node.innerText.trim())).to.equal('验证码不能为空')

                await browser.close()

                res.end('Router demo3 success!!\n')
            } catch (error) {
                console.log(error)
                res.end(error.toString())
            }
        })();

});

// demo4: 生成页面的截图和PDF文件
Router.get('/demo4', function (req, res) {
    res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' })

        ; (async () => {
            try {
                // 初始化环境 并 打开页面
                const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] })
                const page = await browser.newPage()
                await page.goto('http://www.dili360.com')

                // 等待页面加载完毕
                await page.waitForSelector('.tags .right')
                // 隐藏项部导航
                await page.addStyleTag({content: '.part-two { display: none; }'})
                // 指定截屏板块
                const elementHandle = await page.$('.tags .right')
                await elementHandle.screenshot({path: 'screenshot.png'})

                // -------------------------------
                // 打开央视网,生成PDF
                await page.goto('http://news.cctv.com')
                await page.pdf({
                    path: 'news.pdf',
                    format: 'A4',
                    printBackground: true,
                    margin: {
                      top: '15mm',
                      bottom: '15mm'
                    },
                    displayHeaderFooter: true,
                    footerTemplate: '<div style="font-size: 14px;"><span class="pageNumber"></span>/<span class="totalPages"></span></div>',
                    headerTemplate: '<div style="font-size: 14px;">I"m header!</div>',
                  });

                await browser.close()

                res.end('Router demo4 success!!\n')
            } catch (error) {
                res.end(error.toString())
            }
        })();

});

app.use(Router);
app.listen(7878, function afterListen() {
    console.log('express running on http://localhost:7878');
});