Chrome 管理 (puppeteer)

A-Parser 允许使用 Chrome (Chromium) 浏览器作为下载和渲染页面的引擎,它利用了流行的 puppeteer 库。
puppeteer 与 A-Parser 结合使用的主要优势:
- 支持为每个浏览器标签页配置独立的代理
- 多线程管理浏览器标签页
- 请求拦截
- 拥有 A-Parser 在队列管理、查询构造和结果处理方面的所有功能
使用 Chrome 浏览器开启了以下可能性:
- 渲染
DOM和JavaScript - 与网站元素进行交互的能力:
- 填充表单
- 点击链接
- drag & drop
- 下载文件
- 模拟鼠标操作
- 以及更多功能,可以自动化任何标准操作
- 更轻松地绕过各种反爬虫保护,因为 Chromium 浏览器与用户使用的浏览器高度一致
- 支持在
Headless模式下运行,即无浏览器图形界面,这可以节省资源,并允许在没有图形环境的服务器上运行浏览器
需要注意的是,浏览器的运行对资源(CPU、内存)的消耗远高于普通的 A-Parser 线程
根据网站的复杂程度,建议每个可用的处理器核心运行的线程数(浏览器标签页)不超过 1-2 个,例如对于 8 核处理器,建议运行 8 到 16 个标签页
使用示例
以 Chrome::ScreenshotMaker2 爬虫工具为例:
- 该爬虫工具可以按照指定尺寸对网站进行截图,并可以缩小(缩放)图片
- 可以选择性地使用代理
- 为每个 A-Parser 线程创建一个独立的浏览器标签页
import { BaseParser, PuppeteerTypes } from 'a-parser-types';
let browser: PuppeteerTypes.Browser;
let jimp;
class JS_Chrome_ScreenshotsMaker2 extends BaseParser {
static defaultConf: typeof BaseParser.defaultConf = {
version: '0.2.1',
results: {
flat: [
['screenshot', 'PNG screenshot'],
]
},
results_format: '$screenshot',
load_timeout: 30,
width: 1024,
height: 768,
log_screenshots: 0,
headless: 1,
};
static editableConf: typeof BaseParser.editableConf = [
['log_screenshots', ['checkbox', 'Log Screenshots']],
['width', ['textfield', 'Viewport Width']],
['height', ['textfield', 'Viewport Height']],
['resize_width', ['textfield', 'Resize Width']],
['resize_height', ['textfield', 'Resize Height']],
['headless', ['checkbox', 'Chrome Headless']],
];
async init() {
// 初始化浏览器
browser = await this.puppeteer.launch({
headless: this.conf.headless,
logConnections: false,
defaultViewport: {
width: parseInt(this.conf.width),
height: parseInt(this.conf.height),
}
});
if (this.conf.resize_width) {
// 如果需要调整截图尺寸,则连接 jimp 模块
jimp = require('jimp');
};
};
async destroy() {
// 任务完成后关闭浏览器
if (browser)
await browser.close();
}
page: PuppeteerTypes.Page;
async threadInit() {
// 初始化线程时创建浏览器页面
this.page = await browser.newPage();
// puppeteer 标准方法
await this.page.setCacheEnabled(true);
await this.page.setDefaultNavigationTimeout(this.conf.timeout * 1000);
// 指示 A-Parser 为该页面使用代理
await this.puppeteer.setPageUseProxy(this.page);
this.logger.put(`New page created for thread #${this.threadId}`);
}
async parse(set, results) {
const self = this;
const { conf, page } = self;
for (let attempt = 1; attempt <= conf.proxyretries; attempt++) {
try {
self.logger.put(`Attempt #${attempt}`);
// 跳转到请求中指定的页面
await page.goto(set.query);
// 为截图隐藏滚动条
await page.evaluate(() => { document.querySelector('html').style.overflow = 'hidden'; });
// 获取截图
results.screenshot = await page.screenshot();
if (parseInt(conf.resize_width)) {
// 必要时调整图像尺寸
let image = await jimp.read(results.screenshot);
image.resize(parseInt(conf.resize_width), parseInt(conf.resize_height));
results.screenshot = await image.getBufferAsync('image/png');
}
self.logger.put(`Screenshot(${attempt}): OK, size: ${parseInt("" + (results.screenshot.length / 1024))}KB`);
if (conf.log_screenshots)
self.logger.putHTML("<img src='data:image/png;base64," + results.screenshot.toString('base64') + "'>");
results.success = 1;
// 关闭当前连接,因为浏览器使用 keep-alive
await self.puppeteer.closeActiveConnections();
break;
}
catch (error) {
self.logger.put(`Fetch page error: ${error}`);
// 关闭当前连接,因为浏览器使用 keep-alive
await self.puppeteer.closeActiveConnections();
// 为浏览器标签页更换代理
await self.proxy.next();
}
}
return results;
}
}
该示例展示了为每个标签页使用不同代理的简便性,以及多线程工作模式(1 个线程 = 1 个浏览器标签页)
方法说明
await this.puppeteer.launch(opts?)
该方法类似于 puppeteer 库的 .launch 方法,它使用必要的选项 opts 启动 Chromium 浏览器。主要区别在于与 A-Parser 的集成、支持为每个标签页设置代理,以及包含以下额外选项:
logConnections?: boolean
启用所有连接的日志记录(无论是否使用代理),日志按线程分开输出
stealth?: boolean
使用 puppeteer-extra 插件将 Chromium 伪装成真实的 Chrome
stealthOpts?: any
puppeteer-extra 插件的额外选项
extraPlugins?: array
使用额外的插件,例如 puppeteer-extra/packages

其他选项
所有其他启动选项可以在 puppeteer 的原始文档中查看
await this.puppeteer.setPageUseProxy(page)
该方法将浏览器页面与 A-Parser 线程关联,以确保代理正常工作,必须在创建页面后立即调用:
const page = await browser.newPage();
await this.puppeteer.setPageUseProxy(page);
await this.puppeteer.closeActiveConnections(page?)
该方法应在处理完请求后或在更换代理以进行下一次尝试之前调用
Chrome 浏览器默认会保持与所连接网站的开放连接,该方法允许控制资源使用量,并降低代理的负载
page 参数是可选的,如果在调用时不带参数,A-Parser 将关闭与当前线程关联的标签页的连接
await this.puppeteer.logScreenshot()
该方法记录当前页面的截图