From 1bef5f34709c119c7f6b9d32ae886d5b046b4f2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=97=B6=E7=91=BE?= <74231782+sj817@users.noreply.github.com> Date: Fri, 27 Dec 2024 08:57:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AE=9E=E9=AA=8C?= =?UTF-8?q?=E6=80=A7=E5=8A=9F=E8=83=BD=E4=BB=A5=E6=A8=A1=E6=8B=9F0?= =?UTF-8?q?=E6=AF=AB=E7=A7=92=E7=9A=84waitUntil=E5=B9=B6=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E4=BC=A0=E5=8F=82html=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/puppeteer/core.ts | 115 +++++++++++++++++++++++++++++++++++++---- src/puppeteer/index.ts | 6 ++- src/puppeteer/init.ts | 15 +++++- 3 files changed, 124 insertions(+), 12 deletions(-) diff --git a/src/puppeteer/core.ts b/src/puppeteer/core.ts index 481a32b..cc3bc6e 100644 --- a/src/puppeteer/core.ts +++ b/src/puppeteer/core.ts @@ -3,7 +3,7 @@ import { ChildProcess } from 'child_process' import puppeteer, { Browser, GoToOptions, HTTPRequest, Page, LaunchOptions, ScreenshotOptions } from 'puppeteer-core' export interface screenshot extends ScreenshotOptions { - /** http地址或本地文件路径 */ + /** http地址、本地文件路径、html字符串 */ file: string /** * 选择的元素截图 @@ -98,10 +98,13 @@ export class Render { list: Map /** 浏览器进程 */ process!: ChildProcess | null + /** 页面实例 */ + // pages: Page[] constructor (id: number, config: LaunchOptions) { this.id = id this.config = config this.list = new Map() + // this.pages = [] } /** @@ -216,6 +219,7 @@ export class Render { this.list.delete(echo) if (page) { common.emit('screenshot', this.id) + page.removeAllListeners() await page?.close().catch(() => { }) } } @@ -228,21 +232,58 @@ export class Render { async page (data: screenshot) { /** 创建页面 */ const page = await this.browser.newPage() - - /** 请求拦截处理 */ - if (typeof data.setRequestInterception === 'function') { - await page.setRequestInterception(true) - page.on('request', (req) => data.setRequestInterception!(req, data)) - } + // let page: Page /** 打开页面数+1 */ common.emit('newPage', this.id) + // /** 如果waitUntil传参了 直接加载页面 */ + // if (data?.pageGotoParams?.waitUntil) { + // /** 有监听器需求 new一个 */ + // if (typeof data.setRequestInterception === 'function') { + // page = await this.browser.newPage() + // this.pages.push(page) + + // /** 请求拦截处理 */ + // await page.setRequestInterception(true) + // page.on('request', (req) => data.setRequestInterception!(req, data)) + // } else { + // /** 无监听器需求 从页面中拿一个 */ + // page = this.pages[0] + // } + + // /** 设置HTTP 标头 */ + // if (data.headers) await page.setExtraHTTPHeaders(data.headers) + + // /** 打开、加载页面 */ + // if (data.file.startsWith('http') || data.file.startsWith('file://')) { + // await page.goto(data.file, data.pageGotoParams) + // } else { + // await page.setContent(data.file, data.pageGotoParams) + // } + // } else { + // /** 有监听器需求 new一个 */ + // page = await this.browser.newPage() + // this.pages.push(page) + // /** 设置HTTP 标头 */ + // if (data.headers) await page.setExtraHTTPHeaders(data.headers) + // /** 模拟0毫秒的waitUntil */ + // await this.simulateWaitUntil(page, data) + // } + /** 设置HTTP 标头 */ if (data.headers) await page.setExtraHTTPHeaders(data.headers) + if (typeof data.setRequestInterception === 'function') { + await page.setRequestInterception(true) + page.on('request', (req) => data.setRequestInterception!(req, data)) + } - /** 加载页面 */ - await page.goto(data.file, data.pageGotoParams) + /** 打开页面 */ + if (data.file.startsWith('http') || data.file.startsWith('file://')) { + await page.goto(data.file, data.pageGotoParams) + } else { + await page.setContent(data.file, data.pageGotoParams) + } /** 等待body加载完成 */ await page.waitForSelector('body') @@ -315,4 +356,60 @@ export class Render { } await page.setViewport(setViewport) } + + /** + * 实验性功能 + * @param page 页面实例 + * @param data 截图参数 + * @description 通过捕获请求和响应来模拟0毫秒的waitUntil + */ + async simulateWaitUntil (page: Page, data: screenshot) { + if (!data.pageGotoParams) data.pageGotoParams = {} + data.pageGotoParams.waitUntil = 'load' + + const list: Record = {} + + const delCount = (url: string) => { + if (list[url]) list[url]-- + if (list[url] <= 0) delete list[url] + if (Object.keys(list).length <= 0) common.emit('end', true) + } + + page.on('request', (req) => { + const url = req.url() + if (typeof list[url] !== 'number') { + list[url] = 0 + return + } + + list[url]++ + req.continue() + }) + + page.on('response', request => delCount(request.url())) + page.on('requestfailed', request => delCount(request.url())) + page.on('requestfinished', request => delCount(request.url())) + + /** 加载页面 */ + let result + if (data.file.startsWith('http') || data.file.startsWith('file://')) { + result = page.goto(data.file, data.pageGotoParams) + } else { + result = page.setContent(data.file, data.pageGotoParams) + } + + await new Promise((resolve) => { + const timer = setTimeout(() => { + resolve(true) + common.emit('end', false) + }, 10000) + + common.once('end', (bool) => { + if (bool) clearTimeout(timer) + resolve(true) + }) + }) + + await result + } } diff --git a/src/puppeteer/index.ts b/src/puppeteer/index.ts index 846f913..f90f372 100644 --- a/src/puppeteer/index.ts +++ b/src/puppeteer/index.ts @@ -50,6 +50,8 @@ export class Puppeteer { config: RunConfig /** 启动浏览器的参数 初始化后才产生 */ browserOptions: LaunchOptions + /** 浏览器路径 */ + chromePath!: string constructor (config?: RunConfig) { this.index = 0 this.list = [] @@ -79,8 +81,8 @@ export class Puppeteer { if (!this.config?.executablePath) { const version = PUPPETEER_REVISIONS[this.config.chrome!] const init = new InitChrome(version, this.config.chrome!) - const executablePath = await init.init() - this.browserOptions.executablePath = executablePath + this.chromePath = await init.init() + this.browserOptions.executablePath = this.chromePath } /** 用户数据存储路径 */ diff --git a/src/puppeteer/init.ts b/src/puppeteer/init.ts index 87edaf3..11adf65 100644 --- a/src/puppeteer/init.ts +++ b/src/puppeteer/init.ts @@ -20,6 +20,8 @@ export interface Info { chromeDir: string /** chrome二进制路径 */ chrome: string + /** deb.deps路径 仅在linux下存在 */ + debDeps: string } /** @@ -150,6 +152,10 @@ export default class InitChrome { 'xdg-utils', ] + if (process.getuid?.() !== 0) { + throw new Error('安装系统依赖需要root权限') + } + /** 获取当前的系统 */ const system = await common.exec('cat /etc/os-release').catch(() => '') as string @@ -169,9 +175,13 @@ export default class InitChrome { await install(list) } else if (/debian|ubuntu/i.test(system)) { + let cmd = 'apt install fonts-wqy-microhei fonts-noto-cjk fonts-adobe-source-han-sans-cn' + if (this.info.debDeps && fs.existsSync(this.info.debDeps)) { + cmd = fs.readFileSync(this.info.debDeps, 'utf-8').split('\n').join(',') + } const list = [ { type: '依赖', command: `apt-get install -y ${Debian.join(' ')}` }, - { type: '字体', command: 'apt install fonts-wqy-microhei fonts-noto-cjk fonts-adobe-source-han-sans-cn' }, + { type: '字体', command: cmd } ] await install(list) @@ -234,6 +244,8 @@ export default class InitChrome { const chromeDir = dir /** chrome二进制路径 */ const chrome = path.join(chromeDir, `${this.browser}-${platform}`, `${this.browser}${isWin ? '.exe' : ''}`) + /** deb.deps路径 仅在linux下存在 */ + const debDeps = path.join(chromeDir, `${this.browser}-${platform}`, 'deb.deps') // tips: 压缩包解压后会带一个文件夹: ${this.browser}-${platform} return { @@ -245,6 +257,7 @@ export default class InitChrome { zip, chromeDir, chrome, + debDeps, } } }