结构 、方法 、属性

Puppeteer 安装

  • 安装第三方库:puppeteer

    1. npm i puppeteer
  • 安装谷歌 Chromium:若 npm 安装失败,需要手动下载 chromium 并解压至相应文件夹

    • 注意:默认安装当前最高版本 (最新版本可能导致某些函数被弃用)


      API 分层结构

  • Browser: 对应一个浏览器实例,一个 Browser 可以包含多个 BrowserContext
  • BrowserContext: 对应浏览器一个上下文会话,就像我们打开一个普通的 Chrome 之后又打开一个隐身模式的浏览器一样,BrowserContext 具有独立的 Session(cookie 和 cache 独立不共享),一个 BrowserContext 可以包含多个 Page
  • Page:表示一个 Tab 页面,通过 browserContext.newPage()/browser.newPage() 创建,browser.newPage() 创建页面时会使用默认的 BrowserContext,一个 Page 可以包含多个 Frame
  • Frame: 一个框架,每个页面有一个主框架(page.MainFrame()),也可以多个子框架,主要由 iframe 标签创建产生的
  • ExecutionContext: 是 javascript 的执行环境,每一个 Frame 都一个默认的 javascript 执行环境
  • ElementHandle: 对应 DOM 的一个元素节点,通过该该实例可以实现对元素的点击,填写表单等行为,我们可以通过选择器,xPath 等来获取对应的元素
  • JsHandle:对应 DOM 中的 javascript 对象,ElementHandle 继承于 JsHandle,由于我们无法直接操作 DOM 中对象,所以封装成 JsHandle 来实现相关功能
  • CDPSession:可以直接与原生的 CDP 进行通信,通过 session.send 函数直接发消息,通过 session.on 接收消息,可以实现 Puppeteer API 中没有涉及的功能
  • Coverage:获取 JavaScript 和 CSS 代码覆盖率
  • Tracing:抓取性能数据进行分析
  • Response: 页面收到的响应
  • Request: 页面发出的请求

    高频api

    1.Browser

    | 方法名称 | 方法说明 | 个人经验 | | —- | —- | —- | | browser.newPage() | 创建一个 Page 实例 | | | browser.close() | 关闭浏览器 | 注意catch住异常关闭,避免浏览器进程没有关闭掉,耗费系统资源 | | browser.pages() | 获取所有打开的 Page 实例 | 打开多个tab页处理时切换page特别有用 |

2.page

1.page 常用

方法名称 方法说明 个人经验
puppeteer.launch 启动浏览器 调试时推荐slowMo,headless,devtools 这三个参数
page.goto(url) 打开指定网站 注意使用它的waitUntil参数,默认是load,有时会超时
page.screenshot 把当前页面截图 不但可以截图也可以截取元素的图片奥
Page.$(selector) 获取单个元素,底层是调用的是 document.querySelector()
Page.$$(selector) 获取一组元素,底层调用的是 document.querySelectorAll() 注意返回的参数经过Array.form了,可能有些参数不能够使用
Page.$eval() 此方法在页面内执行 document.querySelector,然后把匹配到的元素作为第一个参数传给 pageFunction 是在在浏览器实例上下文中要执行的方法
page.$$eval
(selector, pageFunction[, …args])
此方法在页面内执行 Array.from(document.querySelectorAll(selector)),然后把匹配到的元素数组作为第一个参数传给 pageFunction
page.click() 点击一个元素
Page.emulate 修改模拟器(客户端)运行配置,模拟设备,参数设备对象,比如 iPhone, Mac, Android 等

2.page 进阶

方法名称 方法说明 个人经验
page.evaluate
(pageFunction, …args)
返回一个可序列化的普通对象,pageFunction 表示要在页面执行的函数, args 表示传入给 pageFunction 的参数, 下面的 pageFunction 和 args 表示同样的意思。 注意是在浏览器页面执行
Page.evaluateHandle
(pageFunction, …args)
在 Page 上下文执行一个 pageFunction, 返回 JSHandle 实体 Page 上下文执行
page.evaluateOnNewDocument
(pageFunction, …args)
在文档页面载入前调用 pageFunction, 如果页面中有 iframe 或者 frame, 则函数调用 的上下文环境将变成子页面的,即iframe 或者 frame, 由于是在页面加载前调用,这个函数一般是用来初始化 javascript 环境的,比如重置或者 初始化一些全局变量
Page.exposeFunction 此方法添加一个命名为 name 的方法到页面的 window 对象 当调用 name 方法时,在 node.js 中执行 puppeteerFunction,并且返回 Promise 对象,解析后返回 puppeteerFunction 的返回值 使用时会变成全局的,也可以引用你的类库

3.page waitFor 系列 API

很重要,所以拿出来单独列一下

方法名称 方法说明 个人经验
page.waitFor 下面四个个的综合 API,不写就类似于java的Thread.sleep 即将废除了,可以用
page.waitForTimeout( )替代
page.waitForFunction
(pageFunction[, options[, …args]])
等待 pageFunction 执行完成之后
page.waitForNavigation(options) 等待页面基本元素加载完之后,比如同步的 HTML, CSS, JS 等代码
page.waitForSelector
(selector[, options])
css语法,等待某个选择器的元素加载之后,这个元素可以是异步加载的
page.waitForXPath
(xpath[, options])
xpath语法,等待某个选择器的元素加载之后,这个元素可以是异步加载的

语法

puppeteer实例

  1. const puppeteer = require('puppeteer');
  2. (async () => {
  3. const browser = await puppeteer.launch();
  4. const page = await browser.newPage();
  5. await page.goto('https://www.baidu.com');
  6. await page.close();
  7. await browser.close();
  8. })();

puppeteer 提供了两种方法用于创建一个 Browser 实例:

  • puppeteer.connect :连接一个已经存在的 Chrome 实例
  • puppeteer.launch : 每次都启动一个 Chrome 实例

puppeteer.launch() 的参数解释:其使用字典进行配置输入(常用参数如下)

  • headless: 是否使用浏览器界面启动,turn 和 false

默认为 turn 无头浏览器,不展示浏览器页面
设置 false 参数,展示浏览器页面

  • executablePath:指定浏览器执行地址
  • timeout: 等待浏览器实例启动的最长时间(以毫秒为单位)。默认为30000(30秒)。设置为 0 禁用超时
  • slowMo: 放慢浏览器执行速度,方便测试观察
  • ignoreHTTPSErrors:false 表示忽略 https 报错
  • args: 传递给浏览器实例的其余参数 (参见官方文档

例如:窗口最大化:[‘—start-maximized’]

加载导航页面

  • page.goto( ): 打开新页面
  • page.goBack( ) : 回退到上一个页面
  • page.goForward( ) : 前进到下一个页面
  • page.reload( ) : 重新加载页面
  • page.waitForNavigation( ) : 等待页面跳转

 Pupeeteer 中的基本上所有的操作都是异步的,以上几个 API 都涉及到关于打开一个页面,什么情况下才能判断这个函数执行完毕呢,这些函数都提供了两个参数 waitUtil 和 timeout , waitUtil 表示直到什么出现就算执行完毕,timeout 表示如果超过这个时间还没有结束就抛出异常。

等待元素、请求、响应

  • page.waitForXPath: 等待 XPath 对应的元素出现,返回对应的 ElementHandle 实例
  • page.waitForSelector : 等待选择器对应的元素出现,返回对应的 ElementHandle 实例
  • page.waitForResponse : 等待某个响应结束,返回 Response 实例
  • page.waitForRequest: 等待某个请求出现,返回 Request 实例

    自定义等待

  • page.waitForFunction : 等待在页面中自定义函数的执行结果,返回 JsHandle 实例

  • page.waitFor : 设置等待时间,实在没办法的做法(官方即将弃用)
  • page.waitForTimeout : 设置等待时间 几乎等同于waitFor 的作用(ms)

    元素定位(CSS选择器 原则)

    拓展:

    1. const searchInputs = await page.$("#text3");
    2. if (searchInputs) console.log('对象存在')
    3. else console.log('对象不存在')
  • page.$(‘#uniqueId’): 获取某个选择器对应的第一个元素

  • page.$$(‘div’): 获取某个选择器对应的所有元素
  • page.$x(‘//img’): 获取某个 XPath 对应的所有元素
  • page.waitForXPath(‘//img’): 等待某个 XPath 对应的元素出现
  • page.waitForSelector(‘#uniqueId’): 等待某个选择器对应的元素出现

    用户模拟操作(鼠标事件)

  • elementHandle.click( ): 点击某个元素

  • elementHandle.tap( ): 模拟手指触摸点击
  • elementHandle.focus( ): 聚焦到某个元素
  • elementHandle.hover( ): 鼠标 hover 到某个元素上
  • elementHandle.type(‘hello’): 在输入框输入文本
  • page.mouse.click( x , y ):点击鼠标左键
  • page.mouse.down( ):按压鼠标左键
  • page.mouse.move( 600,600,{steps:10} )分10步 移动鼠标前往(600,600)点位
  • page.mouse.up( ):松开鼠标 ```javascript let element = await page.$(“#TANGRAMPSP_37closeBtn”); let box = await element.boundingBox(); const x = box.x + (box.width/2); const y = box.y + (box.height/2); await page.mouse.move(x,y);

//上方代码等价于 await page.hover(“#js_cover_area”);

  1. ```javascript
  2. // 模拟鼠标拖动事件
  3. 1.获取页面元素,page.$('#TANGRAM__PSP_37__closeBtn')
  4. 2.通过element.boundingBox()拿到坐标参数
  5. 3.移动鼠标page.mouse.move(x,y)
  6. (async () => {
  7. let browser = await puppeteer.launch({headless: false});
  8. let page = await browser.newPage();
  9. let response = await page.goto("https://www.baidu.com/");
  10. await page.waitFor(1000);
  11. let element = await page.$("#u1 > a.bri");
  12. let box = await element.boundingBox();
  13. const x = box.x + (box.width/2);
  14. const y = box.y + (box.height/2);
  15. await page.mouse.move(x,y);
  16. await page.waitFor(10000);
  17. await page.close();
  18. await browser.close();
  19. }
  20. )();
  21. //实战演示
  22. (async () => {
  23. let browser = await puppeteer.launch({headless: false});
  24. let page = await browser.newPage();
  25. let response = await page.goto("https://map.baidu.com/");
  26. //因为访问百度地图会有弹出层登陆提示,这里我们等待这个弹出层元素3s。并关闭它
  27. page.waitForSelector("#TANGRAM__PSP_37__foreground",{timeout:3000});
  28. const searchPopout = await page.$("#TANGRAM__PSP_37__foreground");
  29. await page.waitForTimeout(1000);
  30. if (searchPopout){
  31. await page.click('#TANGRAM__PSP_37__closeBtn');
  32. await page.waitForTimeout(2000);
  33. }
  34. // 模拟鼠标拖动事件,(分10步 前往 600,600 点位 的拖动)
  35. page.mouse.move(960,540);//初始点位
  36. await page.mouse.down();
  37. await page.mouse.move(600,600,{steps:10});
  38. await page.mouse.up();
  39. await page.close();
  40. await browser.close();
  41. }
  42. )();

我们查找的时候 page. 或者 page.$ 是返回jshandle对象,
而模拟用户操作 事件是 elementHandle 对象 才可以执行,所以需要 asElement( ) 转换对象类型。

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

键盘按键表

用户模拟操作(键盘事件)

  1. // 模拟键盘“回车”键
  2. await page.keyboard.press('Enter');
  3. // 模拟键盘 文本输入
  4. let txt ='hello';
  5. await page.keyboard.type('#key', txt, { delay: 400 });
  6. // 模拟键盘 实现复制/粘贴 Ctrl+c 、Ctrl+v
  7. //1) 获取元素-实现复制
  8. document.body.querySelector('input[fao="fao"]').select()
  9. document.execCommand("copy", false, null);
  10. //2) focus 聚焦光标
  11. await page.focus('input.nav-search-input')
  12. //3) 实现粘贴
  13. await page.keyboard.down('Control');
  14. await page.keyboard.press('KeyV', { delay: 100 });
  15. await page.keyboard.up('Control');

请求拦截

我们可以在监听 Page 的 request 事件,并进行请求拦截,前提是要开启请求拦截 page.setRequestInterception(true)

  1. (async () => {
  2. const browser = await puppeteer.launch();
  3. const page = await browser.newPage();
  4. const blockTypes = new Set(['image', 'media', 'font']);
  5. await page.setRequestInterception(true); //开启请求拦截
  6. page.on('request', request => {
  7. const type = request.resourceType();
  8. const shouldBlock = blockTypes.has(type);
  9. if(shouldBlock){
  10. //直接阻止请求
  11. return request.abort();
  12. }else{
  13. //对请求重写
  14. return request.continue({
  15. //可以对 url,method,postData,headers 进行覆盖
  16. headers: Object.assign({}, request.headers(), {
  17. 'puppeteer-test': 'true'
  18. })
  19. });
  20. }
  21. });
  22. await page.goto('https://demo.youdata.com');
  23. await page.close();
  24. await browser.close();
  25. })();

page 页面提供的 触发事件

  • page.on(‘close’) 页面关闭
  • page.on(‘console’) console API 被调用
  • page.on(‘error’) 页面出错
  • page.on(‘load’) 页面加载完
  • page.on(‘request’) 收到请求
  • page.on(‘requestfailed’) 请求失败
  • page.on(‘requestfinished’) 请求成功
  • page.on(‘response’) 收到响应
  • page.on(‘workercreated’) 创建 webWorker
  • page.on(‘workerdestroyed’) 销毁 webWorker

    获取 WebSocket 响应

    Puppeteer 目前没有提供原生的用于处理 WebSocket 的 API 接口,但是我们可以通过更底层的 Chrome DevTool Protocol (CDP) 协议获得

    1. (async () => {
    2. const browser = await puppeteer.launch();
    3. const page = await browser.newPage();
    4. //创建 CDP 会话
    5. let cdpSession = await page.target().createCDPSession();
    6. //开启网络调试,监听 Chrome DevTools Protocol 中 Network 相关事件
    7. await cdpSession.send('Network.enable');
    8. //监听 webSocketFrameReceived 事件,获取对应的数据
    9. cdpSession.on('Network.webSocketFrameReceived', frame => {
    10. let payloadData = frame.response.payloadData;
    11. if(payloadData.includes('push:query')){
    12. //解析payloadData,拿到服务端推送的数据
    13. let res = JSON.parse(payloadData.match(/\{.*\}/)[0]);
    14. if(res.code !== 200){
    15. console.log(`调用websocket接口出错:code=${res.code},message=${res.message}`);
    16. }else{
    17. console.log('获取到websocket接口数据:', res.result);
    18. }
    19. }
    20. });
    21. await page.goto('https://netease.youdata.163.com/dash/142161/reportExport?pid=700209493');
    22. await page.waitForFunction('window.renderdone', {polling: 20});
    23. await page.close();
    24. await browser.close();
    25. })();

    植入 JavaScript 代码

    —————————————————————————————-
    await page.evaluate(() => { 逻辑实现 })
    —————————————————————————————-

    1. await page.evaluate(() => {
    2. // document.getElementById("#menus").classList.add("menus-show");
    3. // let menus = document.querySelector('#menus') as HTMLDivElement
    4. // menus.classList.add('menus-show')
    5. // console.log(menus)
    6. let button = document.querySelector('#all_menus_item') as HTMLDivElement
    7. button.addEventListener('click', function () {
    8. console.log("点击【全部产品】")
    9. })
    10. })

    在浏览器环境中执行代码的函数

  • page.evaluate(pageFunction[, …args]): 在浏览器环境中执行函数

  • page.evaluateHandle(pageFunction[, …args]): 在浏览器环境中执行函数,返回 JsHandle 对象
  • page.$$eval(selector, pageFunction[, …args]):

把 selector 对应的所有元素传入到函数并在浏览器环境执行

创建一个新的 Document 时在浏览器环境中执行,会在页面所有脚本执行之前执行

  • page.exposeFunction(name, puppeteerFunction):

在 window 对象上注册一个函数,这个函数在 Node 环境中执行,有机会在浏览器环境中调用 Node.js 相关函数库

页面性能分析

Puppeteer 提供了对页面性能分析的工具,目前功能还是比较弱的,只能获取到一个页面性能执行的数据
—————————————————————————————-
await page.tracing.start({path: ‘./files/trace.json’});
await page.goto(‘https://www.google.com‘);
await page.tracing.stop( );
await page.close( );
—————————————————————————————-

  1. (async () => {
  2. const browser = await puppeteer.launch();
  3. const page = await browser.newPage();
  4. await page.tracing.start({path: './files/trace.json'});
  5. await page.goto('https://www.google.com');
  6. await page.tracing.stop();
  7. /*
  8. continue analysis from 'trace.json'
  9. */
  10. browser.close();
  11. })();

文件的上传和下载

—————————————————————————————————————————
//通过 CDP 会话设置下载路径
const cdp = await page.target().createCDPSession();
await cdp.send(‘Page.setDownloadBehavior’, {
behavior: ‘allow’, //允许所有下载请求
downloadPath: ‘path/to/download’ //设置下载路径
});
//点击按钮触发下载
await (await page.waitForSelector(‘#someButton’)).click();
//等待文件出现,轮训判断文件是否出现
await waitForFile(‘path/to/download/filename’);
—————————————————————————————————————————
//上传时对应的 inputElement 必须是元素
let inputElement = await page.waitForXPath(‘//input[@type=”file”]’);
await inputElement.uploadFile(‘/path/to/file’);
—————————————————————————————————————————

  1. (async () => {
  2. const browser = await puppeteer.launch();
  3. const page = await browser.newPage();
  4. //通过 CDP 会话设置下载路径
  5. const cdp = await page.target().createCDPSession();
  6. await cdp.send('Page.setDownloadBehavior', {
  7. behavior: 'allow', //允许所有下载请求
  8. downloadPath: 'path/to/download' //设置下载路径
  9. });
  10. //点击按钮触发下载
  11. await (await page.waitForSelector('#someButton')).click();
  12. //等待文件出现,轮训判断文件是否出现
  13. await waitForFile('path/to/download/filename');
  14. //上传时对应的 inputElement 必须是<input>元素
  15. let inputElement = await page.waitForXPath('//input[@type="file"]');
  16. await inputElement.uploadFile('/path/to/file');
  17. browser.close();
  18. })();

跳转新 tab 页处理

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

模拟不同的设备

Puppeteer 提供了模拟不同设备的功能,其中 puppeteer.devices 对象上定义很多设备的配置信息,
这些配置信息主要包含 viewport userAgent,然后通过函数 page.emulate 实现不同设备的模拟

  1. const puppeteer = require('puppeteer');
  2. const iPhone = puppeteer.devices['iPhone 6'];
  3. puppeteer.launch().then(async browser => {
  4. const page = await browser.newPage();
  5. await page.emulate(iPhone);
  6. await page.goto('https://www.google.com');
  7. await browser.close();
  8. });

分页监听

  1. await mpage.setRequestInterception(true);
  2. page.on("request", async (request) => {
  3. request.continue();
  4. });
  5. page.on("response", async (response) => {
  6. const req = response.request();
  7. if (req.url().indexOf("分页请求的地址") > 0) {
  8. //操作
  9. }
  10. })

错误捕获,处理

如果 Puppeteer 方法无法执行一个请求,就会抛出一个错误。例如,page.waitForSelector(selector[, options]) 选择器如果在给定的时间范围内无法匹配节点,就会失败。
对于某些类型的错误,Puppeteer 使用特定的错误类处理。这些类可以通过 require(‘puppeteer/Errors’) 获得。
支持的类列表:

  • TimeoutError ```javascript const {TimeoutError} = require(‘puppeteer/Errors’);

// …

try { await page.waitForSelector(‘.foo’); } catch (e) { if (e instanceof TimeoutError) { // 如果超时,做一些处理。 } }

  1. <a name="eCpte"></a>
  2. # 实战+代码演示+实操问题
  3. <a name="jiJxK"></a>
  4. ## 实例模板
  5. 直接套用的实例代码<br />都是一些最基本的配置信息,满足日常的爬虫功能所需
  6. ```javascript
  7. (async () => {
  8. const args = [
  9. '--no-sandbox', // 沙盒模式
  10. '--disable-setuid-sandbox', // uid沙盒
  11. '--disable-infobars',
  12. '--window-position=0,0',
  13. '--ignore-certifcate-errors',
  14. '--ignore-certifcate-errors-spki-list',
  15. '--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"',
  16. '--disable-gpu', // GPU硬件加速
  17. '--disable-dev-shm-usage', // 创建临时文件共享内存
  18. '--no-first-run', // 没有设置首页。在启动的时候,就会打开一个空白页面。
  19. '--no-zygote',
  20. '--single-process' // 单进程运行
  21. ];
  22. const options = {
  23. args,
  24. headless: false,
  25. ignoreHTTPSErrors: true,
  26. slowMo: 250,
  27. defaultViewport: { width: 1920, height: 1080 }
  28. };
  29. const browser = await puppeteer.launch(options);
  30. const browserWSEndpoint = browser.wsEndpoint();
  31. const page = await browser.newPage();
  32. await page.goto('http://页面路径', { timeout: 0 });
  33. page.close();
  34. browser.close();
  35. })();
  1. startProfile();
  2. async function startProfile(){
  3. // let profileId = data[2];
  4. let mlaPort = 35000;
  5. // let fun_type= data[3]?data[3]:1;
  6. /*通过profileId发送GET请求来启动浏览器配置文件。
  7. 返回web套接字作为响应,该响应应该传递给puppeteer.connect*/
  8. http.get(`http://127.0.0.1:${mlaPort}/api/v1/profile/start?automation=true&puppeteer=true&profileId=${profileId}`, (resp) => {
  9. let data = '';
  10. let ws = '';
  11. //按块接收响应数据
  12. resp.on('data', (chunk) => {
  13. data += chunk;
  14. });
  15. /*已收到全部响应数据。 解析错误,
  16. 检验ws是否为一个对象并包含'value'参数*/
  17. resp.on('end', () => {
  18. let ws;
  19. try {
  20. ws = JSON.parse(data);
  21. } catch(err) {
  22. console.log(err);
  23. }
  24. console.log(ws.hasOwnProperty('value'));
  25. if (typeof ws === 'object' && ws.hasOwnProperty('value')) {
  26. console.log(`Browser websocket endpoint: ${ws.value}`);
  27. switch (fun_type) {
  28. case '1':
  29. connect(ws.value,URLPath);
  30. break;
  31. default:
  32. break;
  33. }
  34. }
  35. });
  36. }).on("error", (err) => {
  37. console.log(err.message);
  38. });
  39. }
  40. function connect(ws,URLPath){
  41. (async () => {
  42. const args = [
  43. '--no-sandbox', // 沙盒模式
  44. '--disable-setuid-sandbox', // uid沙盒
  45. '--disable-infobars',
  46. '--window-position=0,0',
  47. '--ignore-certifcate-errors',
  48. '--ignore-certifcate-errors-spki-list',
  49. '--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"',
  50. '--disable-gpu', // GPU硬件加速
  51. '--disable-dev-shm-usage', // 创建临时文件共享内存
  52. '--no-first-run', // 没有设置首页。在启动的时候,就会打开一个空白页面。
  53. '--no-zygote',
  54. '--single-process' // 单进程运行
  55. ];
  56. const options = {
  57. args,
  58. headless: false,
  59. ignoreHTTPSErrors: true,
  60. slowMo: 250,
  61. defaultViewport: { width: 1920, height: 1080 },
  62. browserWSEndpoint: ws
  63. };
  64. const browser = await puppeteer.connect(options);
  65. const browserWSEndpoint = browser.wsEndpoint();
  66. const page = await browser.newPage();
  67. await page.goto(URLPath, { timeout: 0 });
  68. page.close();
  69. browser.close();
  70. })();
  71. }

指定主题图片的批量采集

主要的图片采集方式当前有3种!
1:滚动截屏=》适合长篇内容,例如文章截图、商品详情截图等
2:元素截图=》适合随机刷新内容,例如识图元素,图片验证码等
3:数据返回=》适合纯粹的文件,通过修改encoding参数为base64便可得到”字符串形式的截图数据”。

因为Puppeteer 从入门第一章官方就已经教授了 页面截图采集图片 所以此处将不在废话将这一部分当做重点
而是以 获取图片URL -》 获取本地缓存 -》 写入本地文件保存 的方式。实现图片的下载采集
截图采集 可以参考大佬文章

废话少说!干货如下(大家复制过去可以直接使用)!如果还有任何环节不理解,欢迎私信沟通

  1. //实现搜狗批量采集 指定主题图片(非截图)
  2. const puppeteer = require('puppeteer');
  3. const assert = require('assert');
  4. const fs = require('fs');
  5. const { writeFile, mkdir } = require('fs');
  6. // sogou_downLoadImg();
  7. function sogou_downLoadImg(URLPath = 'http://pic.sogou.com/', textType = '文艺美女治愈系唯美写真高清桌面壁纸', Max = 500, DirPath = 'images') {
  8. (async () => {
  9. const args = [
  10. '--no-sandbox',
  11. '--disable-setuid-sandbox',
  12. '--disable-infobars',
  13. '--window-position=0,0',
  14. '--ignore-certifcate-errors',
  15. '--ignore-certifcate-errors-spki-list',
  16. '--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"'
  17. ];
  18. const options = {
  19. args,
  20. headless: false,
  21. ignoreHTTPSErrors: true,
  22. slowMo: 250,
  23. defaultViewport: { width: 1920, height: 1080 }
  24. };
  25. const browser = await puppeteer.launch(options);
  26. const browserWSEndpoint = browser.wsEndpoint();
  27. const page = await browser.newPage();
  28. await page.goto(URLPath, { timeout: 0 });
  29. await page.waitForSelector('#formid > input')
  30. await page.focus("input[name^='query']")
  31. // 模拟用户输入图片主题并按下Enter回车键 跳转
  32. await page.keyboard.type(textType);
  33. await page.keyboard.down('Enter');
  34. //等待元素加载之后,否则获取不异步加载的元素
  35. await page.waitForSelector('.figure-result-list');
  36. // 获取页面数据
  37. let links = await page.$$eval('ul.figure-result-list > li > div.img-layout > a.img-height > img', links => {
  38. return links.map(img => {
  39. return {
  40. href: img.src.trim()
  41. }
  42. });
  43. });
  44. //注入代码,慢慢把滚动条滑到最底部,保证所有的元素被全部加载
  45. let scrollEnable = true;
  46. let scrollStep = 300; //每次滚动的步长
  47. let len = links.length
  48. // 判断用户想要获取的图片数量是否大于初次加载数量
  49. if (Max > len) {
  50. loop = Math.floor(Max / len) * 3;
  51. loop_fun(loop);
  52. }
  53. await page.waitForTimeout(1000 * loop);
  54. // 定义函数模拟用户下拉滚动条
  55. async function loop_fun(loop = 1) {
  56. while (scrollEnable) {
  57. scrollEnable = await page.evaluate((scrollStep, loop) => {
  58. let scrollTop = document.scrollingElement.scrollTop;
  59. document.scrollingElement.scrollTop = scrollTop + scrollStep;
  60. loop--
  61. return loop
  62. }, scrollStep, loop);
  63. loop = scrollEnable;
  64. console.log(loop);
  65. await page.waitForTimeout(100);
  66. }
  67. }
  68. //判断图片存放路径是否存在,不存在则尝试创建
  69. fs.stat(DirPath, function (err) {
  70. if (err != null) {
  71. console.log('目标目录不存在');
  72. mkdir(DirPath, { recursive: true }, (mkDir_err) => {
  73. console.log('正在创建目录');
  74. if (mkDir_err == null) {
  75. console.log('创建成功');
  76. } else {
  77. fs.unlink(DirPath, (unDir_err) => { })
  78. }
  79. })
  80. }
  81. });
  82. // 获取最新的页面数据
  83. links = await page.$$eval('ul.figure-result-list > li > div.img-layout > a.img-height > img', links => {
  84. return links.map(img => {
  85. return {
  86. href: img.src.trim()
  87. }
  88. });
  89. });
  90. let img_box = links.length
  91. // console.log(links.length);
  92. const aTags = links.splice(0, img_box);//全取弥补部分图片路径undefined状况
  93. let max = img_box > Max ? Max : img_box;
  94. for (var j = 1; j < max; j++) {
  95. // let k = Math.floor(Math.random() * img_box + 1)
  96. let a = aTags[j];
  97. if (a.href == undefined) {
  98. max += 1;
  99. continue;
  100. }
  101. console.log(a.href);
  102. let filename = DirPath + "/items-" + j + ".png";
  103. // 获取线上文件流信息,写入本地文件保存
  104. const content = await getResourceContent(page, a.href);
  105. const contentBuffer = Buffer.from(content, 'base64');
  106. fs.writeFileSync(filename, contentBuffer, 'base64');
  107. }
  108. await page.waitForTimeout(2000)
  109. page.close();
  110. await page.waitForTimeout(1000)
  111. browser.close();
  112. })();
  113. // puppeteer 从浏览器缓存中拿文件,js,压缩文件等
  114. async function getResourceTree(page) {
  115. var resource = await page._client.send('Page.getResourceTree');
  116. return resource.frameTree;
  117. }
  118. async function getResourceContent(page, url) {
  119. const { content, base64Encoded } = await page._client.send(
  120. 'Page.getResourceContent',
  121. { frameId: String(page.mainFrame()._id), url },
  122. );
  123. assert.equal(base64Encoded, true);
  124. return content;
  125. };
  126. }
const puppeteer = require('puppeteer');
const fs = require('fs');
const { writeFile, mkdir } = require('fs');
// 截取指定网址商品页面
shop_screenshot();
function shop_screenshot(ShopUrl='https://www.jd.com/',keyType='华为手机',DirPath='images15') {
    puppeteer.launch({
        ignoreHTTPSErrors: true,
        headless: false,
        slowMo: 250,
        timeout: 0
    }).then(async browser => {

        let page = await browser.newPage();
        await page.setJavaScriptEnabled(true);
        await page.goto(ShopUrl);
        await page.setViewport({ width: 1920, height: 1080 });
        const searchInput = await page.$("#key");
        await searchInput.focus(); //定位到搜索框
        await page.keyboard.type(keyType);
        const searchBtn = await page.$(".button");
        await searchBtn.click();
        await page.waitForSelector('.gl-item'); //等待元素加载之后,否则获取不异步加载的元素
        const links = await page.$$eval('.gl-item > .gl-i-wrap > .p-img > a', links => {
            return links.map(a => {
                return {
                    href: a.href.trim(),
                    title: a.title
                }
            });
        });
        page.close();

        //判断图片存放路径是否存在,不存在则尝试创建
        fs.stat(DirPath, function (err) {
            if (err != null) {
                console.log('目标目录不存在');
                mkdir(DirPath, { recursive: true }, (mkDir_err) => {
                    console.log('正在创建目录');
                    if (mkDir_err==null) {
                        console.log('创建成功');
                    }else{
                        fs.unlink(DirPath,(unDir_err)=>{})
                    }
                })
            }
        });
        const aTags = links.splice(0, 5);
        let max=aTags.length;
        for (var i = 1; i <= max; i++) {
            //判断图片是否已经存在同名文件,存在则尝试移除
            let filename = DirPath+"/items-" + i + ".png";
            fs.stat(filename, function (err) {
                if (err == null) {
                    fs.unlink(filename,(unFile_err)=>{
                        if(unFile_err==null)
                            console.log('同名文件已移除');
                    })
                }
            })

            page = await browser.newPage()
            page.setJavaScriptEnabled(true);
            await page.setViewport({ width: 1920, height: 1080 });
            // 获取窗口的宽高大小便于裁剪
            const documentSize = await page.evaluate(() => {
                return {
                    width: document.documentElement.clientWidth,
                    height: document.body.clientHeight,
                }
            })

            var a = aTags[i];
            await page.goto(a.href,{waitUtil: 'networkidle2'}, { timeout: 0 }); //防止页面太长,加载超时

            //注入代码,慢慢把滚动条滑到最底部,保证所有的元素被全部加载
            let scrollEnable = true;
            let scrollStep = 500; //每次滚动的步长
            while (scrollEnable) {
                scrollEnable = await page.evaluate((scrollStep) => {
                    let scrollTop = document.scrollingElement.scrollTop;
                    document.scrollingElement.scrollTop = scrollTop + scrollStep;
                    return document.body.clientHeight > scrollTop + 1080 ? true : false
                }, scrollStep);
                await page.waitForTimeout(100);
            }
            await page.waitForTimeout(3000);
            await page.waitForSelector("#footer-2017", { timeout: 0 }); //判断是否到达底部了
            await page.screenshot({ path: filename, fullPage: true });
            page.close();
        }
        browser.close();
    });
}

截图相关问题

解决截图不精确问题

Puppeteer操作本地文件

● 读文件(同步/异步)
● 获取文件 后缀名/拓展名
● 判断文件是不是图片
● 获取文件名
● 读取文件夹下每个文件内容

文件下载相关问题

page._client.send is not a function

image.png

更多文献

Puppeteer 从入门到入狱的捷径大全 - 图2
官方文档
http://www.puppeteerjs.com/
————————————————————————————————————-

Puppeteer 从入门到入狱的捷径大全 - 图3
W3Cschool
https://www.w3cschool.cn/puppeteer/puppeteer-2a9i37sl.html
————————————————————————————————————-

编程之家
https://www.jb51.cc/js/32426.html
————————————————————————————————————-
键盘事件 键位表
https://github.com/puppeteer/puppeteer/blob/v1.7.0/lib/USKeyboardLayout.js
————————————————————————————————————-