跳转到主要内容

Chrome 管理 (puppeteer)

A-Parser + Puppeteer

A-Parser 允许使用 Chrome (Chromium) 浏览器作为下载和渲染页面的引擎,它利用了流行的 puppeteer 库。

puppeteer 与 A-Parser 结合使用的主要优势:

  • 支持为每个浏览器标签页配置独立的代理
  • 多线程管理浏览器标签页
  • 请求拦截
  • 拥有 A-Parser 在队列管理、查询构造和结果处理方面的所有功能

使用 Chrome 浏览器开启了以下可能性:

  • 渲染 DOMJavaScript
  • 与网站元素进行交互的能力:
    • 填充表单
    • 点击链接
    • 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

A-Parser + Puppeteer

其他选项

所有其他启动选项可以在 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()

该方法记录当前页面的截图