kangkangblog

Menu

初尝puppeteer

初尝Puppeteer

首先肯定是照搬它的项目简介了哈哈哈哈
  1. 利用网页生成截图以及pdf
  2. 爬取SPA生成预渲染页面内容(我们说的ssr)
  3. 可以从网站爬取内容
  4. 自动化表单提交、UI测试、键盘输入等等
  5. 创建一个最新的自动化测试环境(chrome),可以直接在这个上面测试用例运行最新的JavaScript和浏览器功能。
  6. 捕获网站的时间线跟踪,以帮助诊断性能问题。

本文主要是通过一些小例子,把它的介绍基本都跑上一遍。嘻嘻


install

yarn add puppeteer
# or "npm i puppeteer"
安装puppeteer时,它会下载最新版本的Chromium,以保证与api协同工作,如果要跳过下载的话可以参考环境变量的设置

1.利用网页生成截图以及pdf

// PNG
const puppeteer = require('puppeteer');
// 引入puppeteer
(async ()=>{
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto(`https://example.com`);
    await page.screenshot({
        path: 'example.png'
    });
    await browser.close();
})();
puppeteer默认设置一个page大小为800px*600px,而这个大小也将定义了截图的大小。
当然这个页面大小我们可以通过Page.setViewport()来设置。
// PDF
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://news.ycombinator.com', {waitUntil: 'networkidle2'});
  await page.pdf({path: 'hn.pdf', format: 'A4'});

  await browser.close();
})();

// 当然了.pdf方法提供了更多参数可以使用

2、爬取SPA生成预渲染页面

首先当然是简单地写一个spa页面,其实原理很简单,这样做的ssr,也是以前经常做spa页面ssr的原理,但是以前是用sel较多,利用一个可执行js的内核,进行渲染页面后,把整个页面内容返回,也就是解析执行还是在后端进行,后端做多一层转发
现在演示demo 可以直接用parcel+vue,组装速度要快不少~~

服务端的demo

const puppeteer = require('puppeteer');
const Koa = require('koa')
const app = new Koa()

async function getSpaContent(ctx, next) {
    if(/\.html/.test(ctx.request.url)){
        const browser = await puppeteer.launch();
        const page = await browser.newPage();
        await page.goto(`http://localhost:1234${ctx.request.url}`)
        console.log(`visiting http://localhost:1234${ctx.request.url}`)

        await page.content().then((v)=>{ //此处返回的是promise  简单地获取即可
            ctx.body = v;
        })
        await browser.close();

    }else{
        ctx.body = {};
    }
    await next();
}

app.use(getSpaContent);
let len = process.argv.length;
let port = `3000`;
for(let i =0;i<len-1;i++){
    if(process.argv[i]==='--port' && i!==len-1){
        port = process.argv[i+1];
    }
}
app.listen(port,()=>{
    console.log(`listening  in port with ${port}`);
})

3、网站爬取内容

由于它的特性,可以完全模拟浏览器行为,从而进行获取信息。在这种情况下,浏览器对简单爬虫的防护可谓是形同虚设。我们可以很简单得模拟浏览器模拟用户操作。
const puppeteer = require('puppeteer');
const nextLink = `a[rel*="next"]`;
const ARTICLE_ITEM = `.repo-list-item`;
const TITLE_SELECTOR = `div > h3 > a`;
const STAR_SELECTOR = `div > .muted-link`;
const CONTENT_SELECTOR = `div > div > p`;
const mainLink = `https://github.com/search?p=1&q=javascript&type=Repositories&utf8=%E2%9C%93`;
const data = [];

process.setMaxListeners(0);

(async() => {
    const browser = await puppeteer.launch({
        headless:true //要看演示可以使用false
    });
    const page = await browser.newPage();

    let val = {};
    await page.setViewport({
        width:980,
        height:980
    })

    try {
        await page.goto(mainLink);
        val = await page.evaluate((nextLink) => {
            return document.querySelector(nextLink); //主要是审查页面元素  防止进入深渊进入死循环
        }, nextLink);

        while (val !== null && !!val) {
            await page.evaluate((ARTICLE_ITEM, TITLE_SELECTOR, STAR_SELECTOR, CONTENT_SELECTOR) => {
                function searchElement(parent = null) {
                    function getDataWithNull(element, attr, defaultValue) {
                        if ((element !== null) && (element instanceof HTMLElement)) {
                            return element[attr];
                        } else {
                            return defaultValue;
                        }
                    }
                    if (parent === null) {
                        parent = document;
                    }
                    return {
                        title: getDataWithNull(parent.querySelector(TITLE_SELECTOR), 'innerText', ''),
                        star: getDataWithNull(parent.querySelector(STAR_SELECTOR), 'innerText', ''),
                        content: getDataWithNull(parent.querySelector(CONTENT_SELECTOR), 'innerText', '')
                    }
                }
                return Array.from(document.querySelectorAll(ARTICLE_ITEM)).map((val) => {
                    return searchElement(val)
                })
            }, ARTICLE_ITEM, TITLE_SELECTOR, STAR_SELECTOR, CONTENT_SELECTOR).then((v) => {
                data.push(v);
                return v;
            })

            await page.click(nextLink)

            await page.waitForNavigation({timeout:500}).then(()=>{},async (a) => {
                val = await page.evaluate((nextLink) => {
                    return document.querySelector(nextLink);
                }, nextLink);
            })

            await page.screenshot({
                path: 'demo3.png',// 拍个照证明我们确实是因为调入深渊了
                fullPage: true
            })
        }

    } catch (e) {
        // 速度太快会进入深渊。这里只是演示所以直接点。
        console.log(`共爬取  ${data.length*10}`)
    } finally {
        await browser.close();
    }
})();
// 逻辑很简单。大概不用解释。。

4、简单的测试以及搜索自动提交

// 测试
// 使用mocha测试
const assert = require('assert'); //使用assert断言库
const puppeteer = require('puppeteer');
const WEBSITE_TITLE = 'kangkangblog – Mr.kangblog';
const MY_GITHUB_LINK = 'https://github.com/ZWkang';
const FIRST_ITEM_TEXT = '首页';
let browser;
let page;

before(async ()=>{
        browser = await puppeteer.launch({
            headless:true
        })
        page = await browser.newPage()
        await page.goto('https://ls-l.cn')

})

describe('check my website',()=>{

    it('i need a title man!!',async ()=>{
        const titleValue = await page.title().then((title_value)=>{
            return title_value            
        })
        assert.equal(titleValue,WEBSITE_TITLE)
    }).timeout(10000);

    it('menu frist item',async ()=>{
        await page.waitForSelector('#site-navigation')
        const titleItem = await page.evaluate(()=>{
            return document.querySelectorAll('#site-navigation ul > li')[0].innerText;
        })
        assert.equal(titleItem,FIRST_ITEM_TEXT);
    }).timeout(10000);

    it(`the website will have my github link`,async()=>{
        const my_github_link = await page.evaluate(()=>{
            return document.querySelector('.call-to-action-button').href
        })
        assert.equal(my_github_link,MY_GITHUB_LINK)
    }).timeout(10000);
})

after(async ()=>{
    await browser.close()
})
// 表单提交
// 键盘输入
const puppeteer = require('puppeteer');

(async () => {
    const browser = await puppeteer.launch({
        headless: false //要看演示可以使用false
    });
    const page = await browser.newPage();
    await page.setViewport({// 设置viewport尺寸
        width:1280,
        height:980
    })
    await page.goto('https://segmentfault.com/')

    await page.waitForSelector('#searchBox')
    await page.click('#searchBox')
    await page.type('javascript',{delay:100})
    await page.click('.btn-link')
    await page.waitForSelector('.search-result')
    await page.waitFor(8000).then(async ()=>{
        await page.screenshot({
            path: 'keyboardTest.png',// 拍个照
            fullPage: true
        })
    })
   await browser.close()
})();

5、网站的时间线跟踪

就是将proformance的数据获取导出json
要使用生成的json也简单,在浏览器proformance导入即可
学会利用timeline proformance也是很重要的一件事呀!~~
const puppeteer = require('puppeteer');

(async ()=>{
    const browser = await puppeteer.launch({
        headless:true
    })
    const page = await browser.newPage()

    await page.tracing.start({path: 'trace.json'});
    await page.goto('https://ls-l.cn');
    await page.tracing.stop()
    await page.close()
    await browser.close()
})()

示例代码地址

本文示例代码

总结

测试可以给我们更了解自己的代码,在update之后更快地得到反馈。
puppeteer给前端带来了新的意义~一部分携带着统一。
在未来可以看到会有基于puppeteer封装的二次工具的出现~保持学习,迎接未来的挑战吧~
欢迎有不同观点的合理讨论。嘻嘻


参考

— 于 共写了5846个字
— 文内使用到的标签:

发表评论

电子邮件地址不会被公开。 必填项已用*标注