如何在Electron中使用Puppeteer-core?

11

我从另一个Stackoverflow问题中获取了这段代码:

import electron from "electron";
import puppeteer from "puppeteer-core";

const delay = (ms: number) =>
  new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, ms);
  });

(async () => {
  try {
    const app = await puppeteer.launch({
      executablePath: electron,
      args: ["."],
      headless: false,
    });
    const pages = await app.pages();
    const [page] = pages;

    await page.setViewport({ width: 1200, height: 700 });
    await delay(5000);
    const image = await page.screenshot();
    console.log(image);
    await page.close();
    await delay(2000);
    await app.close();
  } catch (error) {
    console.error(error);
  }
})();

Typescript编译器对launch方法选项中的executablePath属性抱怨,因为它需要是string类型而不是Electron。那么如何将电子Chromium可执行文件路径传递给Puppeteer呢?

4个回答

15
你不能直接使用electron可执行文件与Puppeteer一起使用,需要进行一些变通和标志更改。它们在API方面有很多差异。特别是electron没有所有的chrome.* API,这是chromium浏览器正常工作所需的,许多标志仍然没有适当的替代品,如the headless flag
下面将介绍两种方法。但是您需要确保两个要点:
- 在启动应用程序之前,请确保已连接到puppeteer。 - 确保您获取了正确版本的puppeteer或puppeteer-core,以适用于在Electron中运行的Chrome版本!

使用puppeteer-in-electron

有很多解决方法,但最近有一个puppeteer-in-electron包,允许您使用electron在electron应用程序中运行puppeteer。
首先,安装依赖项,
npm install puppeteer-in-electron puppeteer-core electron

然后运行{{它}}。

import {BrowserWindow, app} from "electron";
import pie from "puppeteer-in-electron";
import puppeteer from "puppeteer-core";

const main = async () => {
  const browser = await pie.connect(app, puppeteer);

  const window = new BrowserWindow();
  const url = "https://example.com/";
  await window.loadURL(url);

  const page = await pie.getPage(browser, window);
  console.log(page.url());
  window.destroy();
};

main();

获取调试端口并连接

另一种方法是获取 electron 应用的远程调试端口并连接。这个解决方案由 electron 论坛上的 trusktr 分享。

import {app, BrowserWindow, ...} from "electron"
import fetch from 'node-fetch'

import * as puppeteer from 'puppeteer'

app.commandLine.appendSwitch('remote-debugging-port', '8315')

async function test() {
    const response = await fetch(`http://localhost:8315/json/versions/list?t=${Math.random()}`)
    const debugEndpoints = await response.json()

    let webSocketDebuggerUrl = debugEndpoints['webSocketDebuggerUrl ']

    const browser = await puppeteer.connect({
        browserWSEndpoint: webSocketDebuggerUrl
    })

    // use puppeteer APIs now!
}

// ... make your window, etc, the usual, and then: ...

  // wait for the window to open/load, then connect Puppeteer to it:
  mainWindow.webContents.on("did-finish-load", () => { 
    test()
  })

以上两种解决方案都使用webSocketDebuggerUrl来解决问题。

额外信息

添加此说明是因为大多数人使用electron来打包应用程序。

如果您想构建puppeteer-core和puppeteer-in-electron,则需要使用hazardouselectron-builder确保get-port-cli正常工作。

在main.js顶部添加hazardous

// main.js
require ('hazardous');

请确保已解压get-port-cli脚本,并在package.json文件中添加以下内容

"build": {
  "asarUnpack": "node_modules/get-port-cli"
}

构建后的结果:


Electron和Node.js的版本是什么?也许在过去的一年中有些变化了吗? - Md. Abu Taher

4

最高赞的答案对我不起作用,我使用的是electron 11和puppeteer-core 8。但在主进程中启动puppeteer而不是在渲染器进程中启动对我有用。您可以使用ipcMain和ipcRenderer相互通信。下面是代码:

main.ts(主进程代码)

import { app, BrowserWindow, ipcMain } from 'electron';
import puppeteer from 'puppeteer-core';
async function newGrabBrowser({ url }) {
  const browser = await puppeteer.launch({
    headless: false,
    executablePath:
      '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
  });
  const page = await browser.newPage();
  page.goto(url);
}
ipcMain.on('grab', (event, props) => {
  newGrabBrowser(JSON.parse(props));
});

home.ts(渲染进程代码)

const { ipcRenderer } = require('electron');
ipcRenderer.send('grab',JSON.stringify({url: 'https://www.google.com'}));

OP的问题不在于如何使用Electron运行Puppeteer,而是如何将Puppeteer浏览器(Chromium)嵌入到主应用程序的子窗口中,而不是作为一个单独的窗口。 - JM217
如果你读了问题的最后一行,那就是他所问的,所以那个评论是不必要的。 - Maxx
它运行得很完美,但我该如何构建它呢?我正在尝试运行electron-builder和electron-packager,但每个可执行文件都没有打开chromium。有什么建议吗? - Patryk Chowratowicz 'Zszywacz'
我正在使用这个,它运行良好。 - klys
我遇到的唯一问题是,即使在打开Chrome的情况下,Puppeteer也无法进行任何交互。 - klys

0

还有另一种选项,适用于 electron 5.x.y 及以上版本(目前最高版本为 7.x.y,我尚未在 8.x.y beta 上测试):

// const assert = require("assert");
const electron = require("electron");
const kill = require("tree-kill");
const puppeteer = require("puppeteer-core");
const { spawn } = require("child_process");

let pid;

const run = async () => {
  const port = 9200; // Debugging port
  const startTime = Date.now();
  const timeout = 20000; // Timeout in miliseconds
  let app;

  // Start Electron with custom debugging port
  pid = spawn(electron, [".", `--remote-debugging-port=${port}`], {
    shell: true
  }).pid;

  // Wait for Puppeteer to connect
  while (!app) {
    try {
      app = await puppeteer.connect({
        browserURL: `http://localhost:${port}`,
        defaultViewport: { width: 1000, height: 600 } // Optional I think
      });
    } catch (error) {
      if (Date.now() > startTime + timeout) {
        throw error;
      }
    }
  }

  // Do something, e.g.:
  // const [page] = await app.pages();
  // await page.waitForSelector("#someid")// 
  // const text = await page.$eval("#someid", element => element.innerText);
  // assert(text === "Your expected text");
  // await page.close();
};

run()
  .then(() => {
    // Do something
  })
  .catch(error => {
    // Do something
    kill(pid, () => {
      process.exit(1);
    });
  });

获取 pid 并使用 kill 是可选的。对于在某些 CI 平台上运行脚本来说并不重要,但对于本地环境来说,在每次失败尝试后您需要手动关闭 electron 应用程序。

请参阅此 示例存储库


0
根据这份文档 puppeteer-in-electron,它可以正常工作。但请注意,在使用 ipcMain 之前,您应该先初始化应用程序再调用主函数。

main.js

// main.js
const {BrowserWindow, app} = require("electron");
const pie = require("puppeteer-in-electron")
const puppeteer = require("puppeteer-core");

app.whenReady().then(() => {
    ipcMain.handle('get-url', getUrl)
    createWindow()

    ///
})

async function initialize(){
    await pie.initialize(app);
}
initialize();

async function getUrl(){
  const browser = await pie.connect(app, puppeteer);
 
  const window = new BrowserWindow();
  const url = "https://example.com/";
  await window.loadURL(url);
 
  const page = await pie.getPage(browser, window);
  console.log(page.url());
  window.destroy();
  return page.url();
};

preload.js

const { contextBridge,ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
    getUrl: () => ipcRenderer.invoke('get-url')
})

渲染器 (Vue3)

<script setup>
import { ref } from 'vue';

const res = ref();

async function get(){
    const result = await window.electronAPI.getUrl()
    res.value = result
    console.log(result);
}
</script>

我的软件包版本

  • "puppeteer-core": "^19.7.1"
  • "puppeteer-in-electron": "^3.0.5"
  • "electron": "^23.1.0"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接