参考:
js-banana.github.io/blog/pages/...
介绍
无头浏览器:使用脚本执行的浏览器。
Google 提供的无头浏览器(headless Chrome):
利用Puppeteer可以获取页面DOM节点、网络请求和响应、程序化操作页面行为、进行页面的性能监控和优化、获取页面截图和PDF,可以操作Chrome浏览器等;
一个 Node 库,它提供了高级的 API 并通过 DevTools 协议来控制 Chrome。
- Browser:这是一个浏览器实例,可以拥有浏览器上下文,可通过 puppeteer.launch 或 puppeteer.connect 创建一个 Browser 对象。
- BrowserContext:该实例定义了一个浏览器上下文,可拥有多个页面,创建浏览器实例时默认会创建一个浏览器上下文(不能关闭),此外可以利用 **browser.createIncognitoBrowserContext()**创建一个匿名的浏览器上下文(不会与其它浏览器上下文共享cookie/cache).
- Page:至少包含一个主框架,除了主框架外还有可能存在其它框架,例如iframe。
- Frame:页面中的框架,在每个时间点,页面通过**page.mainFrame()和frame.childFrames()**方法暴露当前框架的细节。对于该框架中至少有一个执行上下文
- ExecutionCOntext:表示一个JavaScript的执行上下文。
- Worker:具有单个执行上下文,便于与 WebWorkers 交互。
可以做什么
- 生成网页截图或者 PDF
- 抓取 SPA(单页应用)并生成预渲染内容(即"SSR"(服务器端渲染))
- 做表单的自动提交、UI的自动化测试、模拟键盘输入等
- 用浏览器自带的一些调试工具和性能分析工具帮助我们分析问题
- 在最新的无头浏览器环境里做测试、使用最新浏览器特性
- 测试浏览器扩展
使用
文档:puppeteer.bootcss.com/#%E4%BD%BF%...
表单提交
javascript
/**
* 在无头浏览器自动填写表单并提交
*/
const puppeteer = require('puppeteer');
const autoSubmitForm = async (url) => {
//启动浏览器实例&非无头模式
const browser = await puppeteer.launch({
headless: false,
});
// 创建了一个新的页面,并设置了视窗大小
const page = await browser.newPage();
page.setViewport({
width: 1468,
height: 768,
});
// 方法导航到指定的 URL,并在页面加载完成时算是完成
await page.goto(url, {
waitUntil: 'networkidle2',
});
// 等待页面中具有 .RNNXgb 类名的元素出现,确保该元素已经加载完毕。(指的是搜索输入框的选择器)
await page.waitForSelector('.RNNXgb');
// 将焦点设置到具有.RNNXgb 类名的输入框。
await page.focus('.RNNXgb');
// 向输入框中输入 '麻辣香锅',并通过设置 { delay: 100 } 控制输入每个字母的间隔。
await page.type('.RNNXgb', '麻辣香锅', { delay: 100 });
// 模拟按下键盘上的 Enter 键
await page.keyboard.press('Enter');
// 不关闭浏览器,看看效果
// await browser.close();
};
module.exports = autoSubmitForm;
// 检查脚本是否被直接执行,而不是通过导入模块的方式调用。
if (require.main === module) {
autoSubmitForm('http://google.com');
}
爬虫
传统的爬虫是基于 HTTP 协议,模拟 UserAgent 发送 http 请求,获取到 html 内容后使用正则解析出需要抓取的内容,这种方式面对服务端渲染直出 html 的网页时非常便捷。
但遇到单页应用(SPA)时,或遇到登录校验时,这种爬虫就显得比较无力。
而使用无头浏览器,抓取网页时完全使用了人机交互时的操作,所以页面的初始化完全能使用宿主浏览器环境渲染完备,不再需要关心这个单页应用在前端初始化时需要涉及哪些 HTTP 请求。
无头浏览器提供的各种点击、输入等指令,完全模拟人的点击、输入等指令,也就再也不用担心正则写不出来了
SPA(单页应用程序)具有动态加载内容和路由的特性。传统爬虫在处理SPA时可能遇到一些挑战:
- 动态加载:
SPA通常使用JavaScript动态加载内容,这意味着页面内容可能不是一次性加载的,而是在用户与页面进行交互时动态生成。
- 路由跳转:
SPA使用客户端路由管理页面状态。在URL更改时,页面内容可能会动态变化,但浏览器不会实际刷新页面。
- 页面状态:
传统爬虫在爬取网页时通常只能获取到初始加载的HTML内容,而无法获取后续由JavaScript生成的内容,导致缺乏页面状态的完整性。
为了解决这些问题,针对SPA的爬虫需要模拟用户与页面交互的过程,并等待内容加载完成后再进行抓取。这意味着需要等待异步加载的内容,并在页面内容改变时及时更新抓取的数据。
对于SPA,可能需要使用无头浏览器(如Puppeteer)来模拟用户操作并抓取动态生成的内容。另外,也可以通过监视路由变化、等待特定元素加载完成等方法来优化爬取过程。
javascript
// 引入process和puppeteer模块
const process = require("process");
const puppeteer = require("puppeteer");
// 定义代理地址、延时时间和延时函数
const PROXY = "http://www.16yun.cn:80"; // 代理服务器(产品 www.16yun.cn)可以修改这个代理地址
const SLEEP = 5000; // 你可以修改这个延时时间(毫秒)
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// 定义一个异步函数view,接受一个URL和一个代理地址作为参数
async function view(url, proxy) {
// 在view函数中,使用puppeteer.launch方法启动一个浏览器实例,并设置一些参数,如headless、ignoreHTTPSErrors、defaultViewport和args
const browser = await puppeteer.launch({
headless: false, // 设置为无头模式(不显示浏览器窗口)
ignoreHTTPSErrors: true, // 忽略HTTPS错误
defaultViewport: { width: 1280, height: 800 }, // 设置默认视口大小
args: [`--proxy-server=${proxy}`], // 设置代理服务器地址
});
// 使用browser.newPage方法创建一个新的页面实例,并设置视口大小
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
// 使用page.on方法监听request事件,如果请求的资源类型是media,并且请求的URL以https://video.twimg.com/开头,则打印出请求的URL
page.on("request", (request) => {
if (
request.resourceType() === "media" &&
request.url().startsWith("https://weibo.com//")
) {
console.log(request.url());
}
});
// 使用page.goto方法访问传入的URL
await page.goto(url);
// 使用page.click方法点击页面上选择器为.r-1ndi9ce > div:nth-child(1) > div:nth-child(1) > span:nth-child(1) > span:nth-child(1) 的元素
await page.click(
".r-1ndi9ce > div:nth-child(1) > div:nth-child(1) > span:nth-child(1) > span:nth-child(1)"
);
// 使用sleep函数等待一段时间(SLEEP)
await sleep(SLEEP);
// 使用page.screenshot方法截取页面图片并保存为debug.png文件
await page.screenshot({ path: "debug.png" });
// 使用browser.close方法关闭浏览器实例
await browser.close();
}
// 最后,在主程序中,获取命令行参数中的第一个参数作为URL,并调用view函数。
const url = process.argv[2]; // 获取命令行参数中的第一个参数作为URL
if (url) {
view(url, PROXY);
} else {
console.log("请输入一个有效的URL");
}
截图
通过接口 传入URL指定元素转发请求后截图DOM返回图片,缺点十分明显,就是耗时太久。
(HT中的截图服务)
截图服务需要首先写好一个页面,Puppeteer原理是打开一个看不见的网页然后去截图。
比如一个活动的页面地址,其中包含一个分享功能,需要分享一个页面海报出去,其中包含了自己的用户数据。
那么,我们可以在当前页面中,写一些参数,这些参数是我们在海报截图中会用到的,如果有这些参数,我们就直接打开海报页面,然后将参数拼接过去在调用截图服务即可,拼接之后的地址和参数如下
然后将这个地址作为截图服务的参数地址传入,这样就实现了海报截图。
🌰
javascript
const express = require('express');
const router = express.Router();
const puppeteer = require('puppeteer');
router.post('/', async (req, res) => {
try {
const { url, width = 375, height = 667 } = req.body;
console.log('url', url);
const browser = await puppeteer.launch({
headless: true,
});
const page = await browser.newPage();
await page.setViewport({ width, height });
await page.goto(url);
await page.waitForTimeout(1000);
const imgBuffer = await page.screenshot({
type: 'png',
});
const base64Str = imgBuffer.toString('base64');
res.json({
code: 200,
data: `data:image/png;base64,${base64Str}`,
msg: '海报生成成功',
});
browser.close();
} catch (error) {
res.status(500).json({
message: error.message,
errors: error.stack,
});
}
});
使用方式
用户端只需要传生成图片的H5链接作为参数。
javascript
axios({
method: 'POST',
url: 'http://localhost:3000/screenshot', // 托管海报的页面
data: {
url: `https://xxx.com/poster`,
}
})
🌰
javascript
const getScreenshot = (domId: string, day: string | number, ipLocation: string) => {
const key = md5(userInfo.getUser.userId + "hellotalktalktome")
.toString()
.toLocaleLowerCase();
return new Promise((resolve, reject) => {
getPosterScreenshot({
screen_url: `${host}hybrid_h5/activity/generate-screenshots?hidebar=1&htdisableselectedword=1&hidebottombar=1&winningDay=${day}&lang=${userInfo.getUser.appLang}&ipLocation=${ipLocation}&w=750&h=1920&id=${domId}`,
user_id: Number(userInfo.getUser.userId),
key
}).then((res: Record<string, any>) => {
if (res.code === 0) {
resolve(res.data.data.url);
} else {
reject();
}
});
}).catch((error) => {
isShare.value = false;
console.log(error);
});
};
问题
- PS:
运行node_modules/puppeteer/install.mjs 文件会尝试下载并设置 Puppeteer 运行所需的特定版本的 Chromium 浏览器。通常情况下,当你安装 Puppeteer 时,它会自动下载所需的 Chromium 版本。但是,有时由于网络问题或其他限制,自动下载可能会失败。在这种情况下,手动运行
install.mjs 可以尝试再次下载并配置 Chromium。
手动安装解决报错:
为了确保新版本的正确安装,检查目录node_modules/puppeteer。如果您发现安装文件的扩展名为.mjs,请相应地使用它。
javascript
node node_modules/puppeteer/install.mjs