puppeteer经验之谈从入门到进阶

用puppeteer有一段时间,有一些心得,特在此记录一下!
puppeteer的使用,我们首先是可以看这个网站。

https://zhaoqize.github.io/puppeteer-api-zh_CN/#

虽然文档有些api没有代码示例,但是基本的demo是可以的。下面我准备按照实战代码,和一些使用上的常见问题进行介绍。

代码部分

  1. 浏览器的创建
const puppeteer = require('puppeteer');

(async () => {
    const args = [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-infobars',
        '--window-position=0,0',
        '--ignore-certifcate-errors',
        '--ignore-certifcate-errors-spki-list',
        '--user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3312.0 Safari/537.36"'
    ];

    const options = {
        args,
        headless: false,
        ignoreHTTPSErrors: true,
        userDataDir: './tmp2',
        defaultViewport:{width:1280,height:800}
    };
    // 后面都可以基于这些代码套一个壳
      const browser = await puppeteer.launch(options);
      const browserWSEndpoint = browser.wsEndpoint();
	  console.log(browserWSEndpoint,'site')
	  const page = await browser.newPage();
	  await page.goto('https://www.bilibili.com/v/music/original/?spm_id_from=333.5.b_6d757369635f6f726967696e616c.59#/all/click/0/1/?open=hot');
	
	})()
  1. 浏览器节点的存储和获取
    使用过程中很多并不是运行一个,而是调用多个已经打开的浏览器进行操作,这就要做好节点信息的存储
 //存贮节点
 const browserWSEndpoint = browser.wsEndpoint();
// 使用节点来重新建立连接
  const browser2 = await puppeteer.connect({browserWSEndpoint});
  1. 等待节点的出现
 await page.waitForSelector('#videolist_box > div.vd-list-cnt > ul > li > div > div.r > a');

4.page 的$$ 和$
其实就是对应于document.querySelectorAll
和document.querySelector

let btn = await mpage.$('div[aria-label="Create a post"]')
let btn = await mpage.$$('div[aria-label="Create a post"]')

$$eval其实没什么区别就是可以有个第二个参数传回调函数,回调你这个元素的数组,如果没找到是 []

 let title = await page.$$eval('#videolist_box > div.vd-list-cnt > ul > li > div > div.r > a',
      link=>link.map(v=>{ return { title:v.innerText, href:v.href} })
    )

5.等待操作

await mpage.waitForTimeout(400);

6.关于多层查找后再点击

let btn = await mpage.$('div[aria-label="Create a post"]')
              await mpage.waitForTimeout(400);
              let btn2= await btn.asElement().$$('div[data-visualcompletion="ignore"]')
              await mpage.waitForTimeout(400);
              await btn2[1].asElement().click()

我们查找的时候page. 或者 p a g e . 或者page. 或者page.$是返回jshandle对象,而点击事件是elementHandle对象才可以执行,所以需要asElement转换对象类型。
7.关于如何上传文件(图片/视频)

 const uploadFile = await mpage.$$('input[type="file"]');
            if(filePath){
              await uploadFile[uploadFile.length-1].uploadFile(filePath);
            }

filePath是你的要上传的文件目录,这个是可以提前知道的。

8.键盘操作

8.1 打一段字

let txt ='hello'
await mpage.keyboard.type( txt, { delay: 400 });

8.2 如何在脚本内部自实现control+c control+v

const puppeteer = require('puppeteer');

(async () => {
    const args = [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-infobars',
        '--window-position=0,0',
        '--ignore-certifcate-errors',
        '--ignore-certifcate-errors-spki-list',
        '--user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3312.0 Safari/537.36"'
    ];

    const options = {
        args,
        headless: false,
        ignoreHTTPSErrors: true,
        userDataDir: './tmp2',
        defaultViewport:{width:1280,height:800}
    };

  const browser = await puppeteer.launch(options);
  const browserWSEndpoint = browser.wsEndpoint();
  console.log(browserWSEndpoint,'site')
  const page = await browser.newPage();
  page.on('dialog', async dialog => { 
    console.log(dialog.message()); 
    await dialog.dismiss();
  }); 
  await page.goto('https://www.bilibili.com/');

    await page.waitForSelector('input.nav-search-keyword')
    await page.focus('input.nav-search-keyword')

    await page.evaluate(async () => {
      // try {
        let data = '12311ff'
        //创建一个input框
          let input = document.createElement('input');
          //设置id
          input.id = "temp_copy";
          //设置只读
          input.setAttribute('readonly', 'readonly');
          input.setAttribute('fao', 'fao');
          //赋值
          input.setAttribute('value', data);
          //把input添加到body中
          document.body.appendChild(input);
          //选中input框,取值复制
          console.log(input)
          document.body.querySelector('input[fao="fao"]').select()

          document.execCommand("copy",false,null);
          //删除input框
          document.body.removeChild(input); 
      // } catch (error) {}
    });
    await page.waitFor(1000)
    await page.focus('input.nav-search-keyword')
    // 直接粘贴
    await page.keyboard.down('Control');
    await page.keyboard.press('KeyV',{delay:100});
    await page.keyboard.up('Control');
})();

这里的话down是代表键盘按下,up才会释放,press相当于down + up ,delay是按下和释放的间隔时间,具体可以看代码,可以直接运行。

键盘操作对应表受限于篇幅,可以自己百度

9.如何进行分页监听

await mpage.setRequestInterception(true);
mpage.on("request", async (request) => {
	request.continue();
});
mpage.on("response", async (response) => {
	const req = response.request();
	if (req.url().indexOf("分页请求的地址") > 0) {
	//操作
	}
})

10.获取节点必备
当网络差时,会出现页面加载很久。那么你使用waitfor是没有用处的。所以用事件监听触发在取节点。

mpage.on('load',async ()=>{
  console.log('页面加载完毕')
   let re =  doCloseNotification(mpage, { from: id }); // 我的操作
 })

问题分析

一、获取节点

  1. 不使用文字作为该节点的判断依据
    尽量不要使用文字作为节点的获取依据,有些网站允许个人设置自己首页的语言,这会导致基于文字的节点获取方式失效。

  2. 不基于类名,或者xpath
    在我爬取facebook网站的时候,人家网站的类名是动态改变的,更麻烦的是dom节点的层数都是会改变,这导致你直接失效,这两种方式根本行不通。可行的替代方案是基于自定义属性查找,不过这只是通法。我不是反对这两种,只要代码易读,简单就可以使用。

  3. 节点等待
    节点获取到的前提是加载完成,有时候必须进行等待,当出问题的时候,往往是由于节点没有出现,而使用了waitForSelector()导致的

  4. foucs获取不到的问题
    有些网站的某些输入框采用自定义的框架来显示。是使用div模拟的文本输入框,并不是常见的input输入框。然后这个问题可能发生在你使用page.$evalute ,基于document的focus.使用官方的.focus()方法可以很好的进行获取焦点。

  5. 模态框的节点获取失败
    模态框的节点,通常是点击后动态加载来的,我们需要进行必要的等待,如果还没有,就需要逐层递进,先取到整个模态框的dom节点,在基于他在往下找。

  6. 点击节点没有效果
    比较复杂的网站,往往dom节点层数很深,我们点不到,是由于获取的那一个节点不是人家代码绑定点击事件的那一层,而事件冒泡的问题他们程序员都处理的很好,基本不会存在。所以没什么其他方法,一层一层的dom去click()一下,肯定有一个是绑定监听的地方。

  7. 使用中的定时器的问题
    puppeteer中使用定时器的时候,你需要考虑下你是否真的需要定时器去实现,而定时器完成的事情,往往可以使用递归去解决,定时器的关闭很容易造成问题,尤其是你需要频繁开启关闭定时器。

  8. 如何处理单页分页滚动
    有些网站的分页如今日头条,首页向下滚动分页的实现。然后append节点到首页底部。所以不可避免的会出现 滚到底” 的问题。常见就是定时器,然后判断这个分页展示区高度。但是肯定不够。而且你滚动停止的时候,你的滚动是不是由于定时器没有停止持续滚动。需要就是第一点更换定时器而使用递归的方式来进行持续滚动,更换分页完成的判断依据为判断节点数量是否增多。然后就是断点继续读取,这里就是得获取高度了

let getHeight = async (mpage) => {
  return mpage.evaluate(() => {
    height = window.document.body.scrollHeight;
    console.log("页面高度", height);
    return height;
  });
};

当然仍然不是很好,这种有个弊端,你怎么去控制滚动的距离,在滚动距离过多的时候,会导致滚动进度条直接跳到最上面,有时候会卡主页面。所以可以使用监听分页请求来获取想要的数据。

9 关于如何多浏览器多页并行执行

  let gutherWrapper = async () => {
    const browseArr = await getBrowseData();
    let promiseFunc = [];
    if (browseArr.length == 0) {
      console.log("无浏览器打开");
    }
    browseArr.forEach(async (browse) => {
      promiseFunc.push(async () => {
        return new Promise(async (resolve, reject) => {
          try {
            let mpages = await browse.pages();
            for (var key in mpages) {
              var mpage = mpages[key];
              let u = await mpage.url();

                // 你的主逻辑代码。。。
                
            }
            resolve("start guther");
          } catch (error) {
            reject("error gutherWrapper");
          }
        });
      });
    });
    return promiseFunc;
  };

  let start = async () => {
    let func = await gutherWrapper();
    func.forEach((fun) => {
      Promise.all([fun()]).then((res) => {
        console.log("Execute completed:", res.pop());
      }).catch(e=>{
        clearInterval(timer)
      });
    });
  };

  start();

getBrowseData方法是返回我保存的浏览器节点所对应的browse对象。就是你写浏览器重连的代码返回的browse数组。基于promise的封装可以并行运行,你写好一个。可以直接用我这个代码把你的逻辑套一下就可以了。

10.关于iframe内嵌页面的元素
有时候,你明明页面是出现了我们所期待的元素,但是就是获取不到,但是如果你使用检查元素,在检查下那一块的页面内容
在这里插入图片描述

在获取的时候,就可以获取到元素,出现这种情况,需要看看是不是iframe。
获取方式,先获取iframe,在根据iframe继续获取

 let iframe = document.querySelector('iframe[tabindex="-1"]').contentWindow.document;  
 let doms = iframe.querySelectorAll('i[data-visualcompletion="css-img"]')

11.对于2层结构得selector获取

这种获取建议使用page.evaluate, page. e v a l 里面在写一个 p a g e . eval里面在写一个page. eval里面在写一个page.eval会有问题

还有什么问题,想到了在更新!

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值