Puppeteer 安装
安装第三方库:puppeteer
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实例
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://www.baidu.com');
await page.close();
await browser.close();
})();
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: 传递给浏览器实例的其余参数 (参见官方文档)
加载导航页面
- 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选择器 原则)
拓展:
const searchInputs = await page.$("#text3");
if (searchInputs) console.log('对象存在')
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”);
```javascript
// 模拟鼠标拖动事件
1.获取页面元素,page.$('#TANGRAM__PSP_37__closeBtn')
2.通过element.boundingBox()拿到坐标参数
3.移动鼠标page.mouse.move(x,y)
(async () => {
let browser = await puppeteer.launch({headless: false});
let page = await browser.newPage();
let response = await page.goto("https://www.baidu.com/");
await page.waitFor(1000);
let element = await page.$("#u1 > a.bri");
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.waitFor(10000);
await page.close();
await browser.close();
}
)();
//实战演示
(async () => {
let browser = await puppeteer.launch({headless: false});
let page = await browser.newPage();
let response = await page.goto("https://map.baidu.com/");
//因为访问百度地图会有弹出层登陆提示,这里我们等待这个弹出层元素3s。并关闭它
page.waitForSelector("#TANGRAM__PSP_37__foreground",{timeout:3000});
const searchPopout = await page.$("#TANGRAM__PSP_37__foreground");
await page.waitForTimeout(1000);
if (searchPopout){
await page.click('#TANGRAM__PSP_37__closeBtn');
await page.waitForTimeout(2000);
}
// 模拟鼠标拖动事件,(分10步 前往 600,600 点位 的拖动)
page.mouse.move(960,540);//初始点位
await page.mouse.down();
await page.mouse.move(600,600,{steps:10});
await page.mouse.up();
await page.close();
await browser.close();
}
)();
我们查找的时候 page. 或者 page.$ 是返回jshandle对象,
而模拟用户操作 事件是 elementHandle 对象 才可以执行,所以需要 asElement( ) 转换对象类型。
let btn = await page.$('div[aria-label="Create a post"]')
await page.waitForTimeout(400);
let btn2 = await btn.asElement().$$('div[data-visualcompletion="ignore"]')
await page.waitForTimeout(400);
await btn2[1].asElement().click()
用户模拟操作(键盘事件)
// 模拟键盘“回车”键
await page.keyboard.press('Enter');
// 模拟键盘 文本输入
let txt ='hello';
await page.keyboard.type('#key', txt, { delay: 400 });
// 模拟键盘 实现复制/粘贴 Ctrl+c 、Ctrl+v
//1) 获取元素-实现复制
document.body.querySelector('input[fao="fao"]').select()
document.execCommand("copy", false, null);
//2) focus 聚焦光标
await page.focus('input.nav-search-input')
//3) 实现粘贴
await page.keyboard.down('Control');
await page.keyboard.press('KeyV', { delay: 100 });
await page.keyboard.up('Control');
请求拦截
我们可以在监听 Page 的 request 事件,并进行请求拦截,前提是要开启请求拦截 page.setRequestInterception(true)
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const blockTypes = new Set(['image', 'media', 'font']);
await page.setRequestInterception(true); //开启请求拦截
page.on('request', request => {
const type = request.resourceType();
const shouldBlock = blockTypes.has(type);
if(shouldBlock){
//直接阻止请求
return request.abort();
}else{
//对请求重写
return request.continue({
//可以对 url,method,postData,headers 进行覆盖
headers: Object.assign({}, request.headers(), {
'puppeteer-test': 'true'
})
});
}
});
await page.goto('https://demo.youdata.com');
await page.close();
await browser.close();
})();
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) 协议获得
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
//创建 CDP 会话
let cdpSession = await page.target().createCDPSession();
//开启网络调试,监听 Chrome DevTools Protocol 中 Network 相关事件
await cdpSession.send('Network.enable');
//监听 webSocketFrameReceived 事件,获取对应的数据
cdpSession.on('Network.webSocketFrameReceived', frame => {
let payloadData = frame.response.payloadData;
if(payloadData.includes('push:query')){
//解析payloadData,拿到服务端推送的数据
let res = JSON.parse(payloadData.match(/\{.*\}/)[0]);
if(res.code !== 200){
console.log(`调用websocket接口出错:code=${res.code},message=${res.message}`);
}else{
console.log('获取到websocket接口数据:', res.result);
}
}
});
await page.goto('https://netease.youdata.163.com/dash/142161/reportExport?pid=700209493');
await page.waitForFunction('window.renderdone', {polling: 20});
await page.close();
await browser.close();
})();
植入 JavaScript 代码
—————————————————————————————-
await page.evaluate(() => { 逻辑实现 })
—————————————————————————————-await page.evaluate(() => {
// document.getElementById("#menus").classList.add("menus-show");
// let menus = document.querySelector('#menus') as HTMLDivElement
// menus.classList.add('menus-show')
// console.log(menus)
let button = document.querySelector('#all_menus_item') as HTMLDivElement
button.addEventListener('click', function () {
console.log("点击【全部产品】")
})
})
在浏览器环境中执行代码的函数
page.evaluate(pageFunction[, …args]): 在浏览器环境中执行函数
- page.evaluateHandle(pageFunction[, …args]): 在浏览器环境中执行函数,返回 JsHandle 对象
- page.$$eval(selector, pageFunction[, …args]):
把 selector 对应的所有元素传入到函数并在浏览器环境执行
page.$eval(selector, pageFunction[, …args]):=》详细用法
把 selector 对应的第一个元素传入到函数在浏览器环境执行
page.evaluateOnNewDocument(pageFunction[, …args]):
创建一个新的 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( );
—————————————————————————————-
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.tracing.start({path: './files/trace.json'});
await page.goto('https://www.google.com');
await page.tracing.stop();
/*
continue analysis from 'trace.json'
*/
browser.close();
})();
文件的上传和下载
—————————————————————————————————————————
//通过 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’);
—————————————————————————————————————————
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
//通过 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 必须是<input>元素
let inputElement = await page.waitForXPath('//input[@type="file"]');
await inputElement.uploadFile('/path/to/file');
browser.close();
})();
跳转新 tab 页处理
let page = await browser.newPage();
await page.goto(url);
let btn = await page.waitForSelector('#btn');
//在点击按钮之前,事先定义一个 Promise,用于返回新 tab 的 Page 对象
const newPagePromise = new Promise(res =>
browser.once('targetcreated',
target => res(target.page())
)
);
await btn.click();
//点击按钮后,等待新tab对象
let newPage = await newPagePromise;
模拟不同的设备
Puppeteer 提供了模拟不同设备的功能,其中 puppeteer.devices 对象上定义很多设备的配置信息,
这些配置信息主要包含 viewport 和 userAgent,然后通过函数 page.emulate 实现不同设备的模拟
const puppeteer = require('puppeteer');
const iPhone = puppeteer.devices['iPhone 6'];
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
await page.emulate(iPhone);
await page.goto('https://www.google.com');
await browser.close();
});
分页监听
await mpage.setRequestInterception(true);
page.on("request", async (request) => {
request.continue();
});
page.on("response", async (response) => {
const req = response.request();
if (req.url().indexOf("分页请求的地址") > 0) {
//操作
}
})
错误捕获,处理
如果 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) { // 如果超时,做一些处理。 } }
<a name="eCpte"></a>
# 实战+代码演示+实操问题
<a name="jiJxK"></a>
## 实例模板
直接套用的实例代码<br />都是一些最基本的配置信息,满足日常的爬虫功能所需
```javascript
(async () => {
const args = [
'--no-sandbox', // 沙盒模式
'--disable-setuid-sandbox', // uid沙盒
'--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"',
'--disable-gpu', // GPU硬件加速
'--disable-dev-shm-usage', // 创建临时文件共享内存
'--no-first-run', // 没有设置首页。在启动的时候,就会打开一个空白页面。
'--no-zygote',
'--single-process' // 单进程运行
];
const options = {
args,
headless: false,
ignoreHTTPSErrors: true,
slowMo: 250,
defaultViewport: { width: 1920, height: 1080 }
};
const browser = await puppeteer.launch(options);
const browserWSEndpoint = browser.wsEndpoint();
const page = await browser.newPage();
await page.goto('http://页面路径', { timeout: 0 });
page.close();
browser.close();
})();
startProfile();
async function startProfile(){
// let profileId = data[2];
let mlaPort = 35000;
// let fun_type= data[3]?data[3]:1;
/*通过profileId发送GET请求来启动浏览器配置文件。
返回web套接字作为响应,该响应应该传递给puppeteer.connect*/
http.get(`http://127.0.0.1:${mlaPort}/api/v1/profile/start?automation=true&puppeteer=true&profileId=${profileId}`, (resp) => {
let data = '';
let ws = '';
//按块接收响应数据
resp.on('data', (chunk) => {
data += chunk;
});
/*已收到全部响应数据。 解析错误,
检验ws是否为一个对象并包含'value'参数*/
resp.on('end', () => {
let ws;
try {
ws = JSON.parse(data);
} catch(err) {
console.log(err);
}
console.log(ws.hasOwnProperty('value'));
if (typeof ws === 'object' && ws.hasOwnProperty('value')) {
console.log(`Browser websocket endpoint: ${ws.value}`);
switch (fun_type) {
case '1':
connect(ws.value,URLPath);
break;
default:
break;
}
}
});
}).on("error", (err) => {
console.log(err.message);
});
}
function connect(ws,URLPath){
(async () => {
const args = [
'--no-sandbox', // 沙盒模式
'--disable-setuid-sandbox', // uid沙盒
'--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"',
'--disable-gpu', // GPU硬件加速
'--disable-dev-shm-usage', // 创建临时文件共享内存
'--no-first-run', // 没有设置首页。在启动的时候,就会打开一个空白页面。
'--no-zygote',
'--single-process' // 单进程运行
];
const options = {
args,
headless: false,
ignoreHTTPSErrors: true,
slowMo: 250,
defaultViewport: { width: 1920, height: 1080 },
browserWSEndpoint: ws
};
const browser = await puppeteer.connect(options);
const browserWSEndpoint = browser.wsEndpoint();
const page = await browser.newPage();
await page.goto(URLPath, { timeout: 0 });
page.close();
browser.close();
})();
}
指定主题图片的批量采集
主要的图片采集方式当前有3种!
1:滚动截屏=》适合长篇内容,例如文章截图、商品详情截图等
2:元素截图=》适合随机刷新内容,例如识图元素,图片验证码等
3:数据返回=》适合纯粹的文件,通过修改encoding参数为base64便可得到”字符串形式的截图数据”。
因为Puppeteer 从入门第一章官方就已经教授了 页面截图采集图片 所以此处将不在废话将这一部分当做重点
而是以 获取图片URL -》 获取本地缓存 -》 写入本地文件保存 的方式。实现图片的下载采集
截图采集 可以参考大佬文章
废话少说!干货如下(大家复制过去可以直接使用)!如果还有任何环节不理解,欢迎私信沟通
//实现搜狗批量采集 指定主题图片(非截图)
const puppeteer = require('puppeteer');
const assert = require('assert');
const fs = require('fs');
const { writeFile, mkdir } = require('fs');
// sogou_downLoadImg();
function sogou_downLoadImg(URLPath = 'http://pic.sogou.com/', textType = '文艺美女治愈系唯美写真高清桌面壁纸', Max = 500, DirPath = 'images') {
(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,
slowMo: 250,
defaultViewport: { width: 1920, height: 1080 }
};
const browser = await puppeteer.launch(options);
const browserWSEndpoint = browser.wsEndpoint();
const page = await browser.newPage();
await page.goto(URLPath, { timeout: 0 });
await page.waitForSelector('#formid > input')
await page.focus("input[name^='query']")
// 模拟用户输入图片主题并按下Enter回车键 跳转
await page.keyboard.type(textType);
await page.keyboard.down('Enter');
//等待元素加载之后,否则获取不异步加载的元素
await page.waitForSelector('.figure-result-list');
// 获取页面数据
let links = await page.$$eval('ul.figure-result-list > li > div.img-layout > a.img-height > img', links => {
return links.map(img => {
return {
href: img.src.trim()
}
});
});
//注入代码,慢慢把滚动条滑到最底部,保证所有的元素被全部加载
let scrollEnable = true;
let scrollStep = 300; //每次滚动的步长
let len = links.length
// 判断用户想要获取的图片数量是否大于初次加载数量
if (Max > len) {
loop = Math.floor(Max / len) * 3;
loop_fun(loop);
}
await page.waitForTimeout(1000 * loop);
// 定义函数模拟用户下拉滚动条
async function loop_fun(loop = 1) {
while (scrollEnable) {
scrollEnable = await page.evaluate((scrollStep, loop) => {
let scrollTop = document.scrollingElement.scrollTop;
document.scrollingElement.scrollTop = scrollTop + scrollStep;
loop--
return loop
}, scrollStep, loop);
loop = scrollEnable;
console.log(loop);
await page.waitForTimeout(100);
}
}
//判断图片存放路径是否存在,不存在则尝试创建
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) => { })
}
})
}
});
// 获取最新的页面数据
links = await page.$$eval('ul.figure-result-list > li > div.img-layout > a.img-height > img', links => {
return links.map(img => {
return {
href: img.src.trim()
}
});
});
let img_box = links.length
// console.log(links.length);
const aTags = links.splice(0, img_box);//全取弥补部分图片路径undefined状况
let max = img_box > Max ? Max : img_box;
for (var j = 1; j < max; j++) {
// let k = Math.floor(Math.random() * img_box + 1)
let a = aTags[j];
if (a.href == undefined) {
max += 1;
continue;
}
console.log(a.href);
let filename = DirPath + "/items-" + j + ".png";
// 获取线上文件流信息,写入本地文件保存
const content = await getResourceContent(page, a.href);
const contentBuffer = Buffer.from(content, 'base64');
fs.writeFileSync(filename, contentBuffer, 'base64');
}
await page.waitForTimeout(2000)
page.close();
await page.waitForTimeout(1000)
browser.close();
})();
// puppeteer 从浏览器缓存中拿文件,js,压缩文件等
async function getResourceTree(page) {
var resource = await page._client.send('Page.getResourceTree');
return resource.frameTree;
}
async function getResourceContent(page, url) {
const { content, base64Encoded } = await page._client.send(
'Page.getResourceContent',
{ frameId: String(page.mainFrame()._id), url },
);
assert.equal(base64Encoded, true);
return content;
};
}
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
更多文献
官方文档
http://www.puppeteerjs.com/
————————————————————————————————————-
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
————————————————————————————————————-