如何让 Puppeteer 在客户端与 ReactJS 应用程序一起工作
How to make Puppeteer work with a ReactJS application on the client-side
我是 React 的新手,我正在开发一个应用程序,它将截取网页的实际屏幕截图,该应用程序可以在截取的屏幕截图上绘制和添加涂鸦。我最初使用 html2canvas 和 domToImage 来获取客户端屏幕截图,但它并没有完全按照网页中显示的方式呈现图像。
Reddit 用户 /pamblam0 建议我查看 Google 的 Puppeteer。它的工作原理是它启动了一个无头的铬浏览器,该浏览器转到我在本地主机上的反应应用程序,然后轻松获取整个页面的屏幕截图。然而,我的问题是,puppeteer 在 React 应用程序中表现不佳。它给了我一个 ws 错误,正如 google 搜索中所解释的那样,可以通过简单地安装 ws 来修复(顺便说一句,这不起作用)。
现在我的人偶脚本运行我的 React 应用程序会发生什么。据我了解,它不适用于客户端应用程序(我可能错了)。我想要发生的是,每当我从我的 React 应用程序中单击按钮时,puppeteer 应该执行并且 return 一个 base64 字符串,然后将其传递到我的 React 应用程序中的一个组件。
这是我到目前为止所做的。
puppeteerApp.js
const puppeteer = require('puppeteer');
const takeScreenshot = async () => {
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
const options = {
path: 'saved_images/webshot.png',
encoding: 'base64'
}
await page.goto('http://localhost:3000/', { waitUntil: 'networkidle2' });
const elem = await page.$('iframe').then(async (iframe) => {
return await iframe.screenshot(options)
});
await browser.close()
});
}
takeScreenshot();
来自 React 应用程序的代码。
App.js
import React, { Component } from 'react';
import ScreenshotsContainer from './containers/ScreenshotsContainer/ScreenshotsContainer'
import ImageContainer from './containers/ImageContainer/ImageContainer';
import html2canvas from 'html2canvas';
import domtoimage from 'dom-to-image';
import Button from './components/UI/Button/Button'
import classes from './App.module.css';
import { CSSTransition } from 'react-transition-group'
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
imgURIArray: [],
img: null,
showImageContainer: false,
showScreenshotContainer: false,
selectedImageURI: null,
showSaveAnimation: false,
showNotify: false
}
}
storeImageToArrayHandler = (imgURI) => {
if (imgURI !== "") {
this.setState({ imgURIArray: [...this.state.imgURIArray, imgURI] }, () => {
this.setState({ showImageContainer: !this.state.showImageContainer })
})
}
}
getScreenshotHandler = () => {
//use puppeteer here!!!
}
getSelectedImageFromContainerHandler(selectedImageURI) {
this.setState({
selectedImageURI: selectedImageURI,
showImageContainer: !this.state.showImageContainer
})
}
showImageContainerHandler(showImageContainer) {
this.setState({ showImageContainer: showImageContainer })
}
showScreenshotContainerHandler = () => {
this.setState({ showScreenshotContainer: !this.state.showScreenshotContainer })
}
notify = (submitSuccessful, msg) => {
let message = msg ? msg : ""
submitSuccessful ?
toast.success(message, {
autoClose: 3000,
position: toast.POSITION.TOP_CENTER
})
:
toast.error(message, {
position: toast.POSITION.TOP_CENTER
});
}
render() {
let buttonOps = (
<CSSTransition
in={!this.state.showScreenshotContainer}
appear={true}
timeout={300}
classNames="fade"
>
<div className={classes.optionButtons}>
<Button icon={"fas fa-camera"} type={"button-success"} gridClass={""} buttonName={""} style={{ width: "100%", height: "70px" }} onClick={() => this.getScreenshotHandler} />
<Button icon={"fas fa-images"} type={"button-primary "} gridClass={""} buttonName={""} style={{ width: "100%", height: "70px" }} onClick={() => this.showScreenshotContainerHandler} />
</div>
</CSSTransition>
)
return (
<div>
{
this.state.showImageContainer ?
<div>
<ImageContainer
img={this.state.img}
showImageContainer={showImageContainer => this.showImageContainerHandler(showImageContainer)}
storeImageToArrayHandler={imgURI => this.storeImageToArrayHandler(imgURI)}
notify={(submitSuccessful, msg) => this.notify(submitSuccessful, msg)}
/>
</div>
: null
}
<CSSTransition
in={this.state.showScreenshotContainer}
appear={true}
timeout={300}
classNames="slide"
unmountOnExit
onExited={() => {
this.setState({ showScreenshotContainer: false })
}}
>
<ScreenshotsContainer
imgURIArray={this.state.imgURIArray}
getSelectedImageFromContainerHandler={imgURI => this.getSelectedImageFromContainerHandler(imgURI)}
showScreenshotContainerHandler={() => this.showScreenshotContainerHandler}
notify={(submitSuccessful, msg) => this.notify(submitSuccessful, msg)}
/>
</CSSTransition>
{this.state.showImageContainer ? null : buttonOps}
{/* <button onClick={this.notify}>Notify !</button> */}
<ToastContainer />
</div >
);
}
}
export default App;
如有任何帮助,我们将不胜感激。谢谢!
您的 React.js 应用程序 运行 在客户端(在浏览器中)。 Puppeteer 无法在该环境中 运行,因为您无法在浏览器中启动完整的浏览器。
您需要的是一台可以为您完成这些工作的服务器。您可以提供一个 HTTP 端点(选项 1)或公开您的 puppeteer Websocket(选项 2):
选项 1:提供 HTTP 端点
对于此选项,您设置了一个服务器来处理传入的请求并运行为您完成任务(制作屏幕截图):
server.js
const puppeteer = require('puppeteer');
const express = require('express');
const app = express();
app.get('/screenshot', async (req, res) => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(req.query.url); // URL is given by the "user" (your client-side application)
const screenshotBuffer = await page.screenshot();
// Respond with the image
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-Length': screenshotBuffer.length
});
res.end(screenshotBuffer);
await browser.close();
})
app.listen(4000);
使用 node server.js
启动应用程序,您现在可以将 URL 传递到您的服务器并从您的服务器获取屏幕截图:http://localhost:4000/screenshot?url=https://example.com/
来自服务器的响应可以用作您应用程序中图像元素的来源。
选项 2:将 puppeteer Websocket 暴露给客户端
您还可以通过公开 Websocket 从客户端控制浏览器(运行 在服务器上)。
为此,您需要像这样公开服务器的 Websocket:
server.js
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const browserWSEndpoint = browser.wsEndpoint();
browser.disconnect(); // Disconnect from the browser, but don't close it
console.log(browserWSEndpoint); // Communicate the Websocket URL to the client-side
// example output: ws://127.0.0.1:55620/devtools/browser/e62ec4c8-1f05-42a1-86ce-7b8dd3403f91
})();
现在您可以通过 puppeteer bundle for the client. In this scenario you could now connect to the browser via puppeteer.connect 从客户端控制浏览器(运行 在服务器上),并以此方式制作屏幕截图。
我强烈建议使用选项 1,因为在选项 2 中,您将 运行ning 浏览器完全暴露给客户端。即使使用选项 1,您仍然需要处理用户输入验证、超时、导航错误等。
我是 React 的新手,我正在开发一个应用程序,它将截取网页的实际屏幕截图,该应用程序可以在截取的屏幕截图上绘制和添加涂鸦。我最初使用 html2canvas 和 domToImage 来获取客户端屏幕截图,但它并没有完全按照网页中显示的方式呈现图像。
Reddit 用户 /pamblam0 建议我查看 Google 的 Puppeteer。它的工作原理是它启动了一个无头的铬浏览器,该浏览器转到我在本地主机上的反应应用程序,然后轻松获取整个页面的屏幕截图。然而,我的问题是,puppeteer 在 React 应用程序中表现不佳。它给了我一个 ws 错误,正如 google 搜索中所解释的那样,可以通过简单地安装 ws 来修复(顺便说一句,这不起作用)。
现在我的人偶脚本运行我的 React 应用程序会发生什么。据我了解,它不适用于客户端应用程序(我可能错了)。我想要发生的是,每当我从我的 React 应用程序中单击按钮时,puppeteer 应该执行并且 return 一个 base64 字符串,然后将其传递到我的 React 应用程序中的一个组件。
这是我到目前为止所做的。
puppeteerApp.js
const puppeteer = require('puppeteer');
const takeScreenshot = async () => {
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
const options = {
path: 'saved_images/webshot.png',
encoding: 'base64'
}
await page.goto('http://localhost:3000/', { waitUntil: 'networkidle2' });
const elem = await page.$('iframe').then(async (iframe) => {
return await iframe.screenshot(options)
});
await browser.close()
});
}
takeScreenshot();
来自 React 应用程序的代码。 App.js
import React, { Component } from 'react';
import ScreenshotsContainer from './containers/ScreenshotsContainer/ScreenshotsContainer'
import ImageContainer from './containers/ImageContainer/ImageContainer';
import html2canvas from 'html2canvas';
import domtoimage from 'dom-to-image';
import Button from './components/UI/Button/Button'
import classes from './App.module.css';
import { CSSTransition } from 'react-transition-group'
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
imgURIArray: [],
img: null,
showImageContainer: false,
showScreenshotContainer: false,
selectedImageURI: null,
showSaveAnimation: false,
showNotify: false
}
}
storeImageToArrayHandler = (imgURI) => {
if (imgURI !== "") {
this.setState({ imgURIArray: [...this.state.imgURIArray, imgURI] }, () => {
this.setState({ showImageContainer: !this.state.showImageContainer })
})
}
}
getScreenshotHandler = () => {
//use puppeteer here!!!
}
getSelectedImageFromContainerHandler(selectedImageURI) {
this.setState({
selectedImageURI: selectedImageURI,
showImageContainer: !this.state.showImageContainer
})
}
showImageContainerHandler(showImageContainer) {
this.setState({ showImageContainer: showImageContainer })
}
showScreenshotContainerHandler = () => {
this.setState({ showScreenshotContainer: !this.state.showScreenshotContainer })
}
notify = (submitSuccessful, msg) => {
let message = msg ? msg : ""
submitSuccessful ?
toast.success(message, {
autoClose: 3000,
position: toast.POSITION.TOP_CENTER
})
:
toast.error(message, {
position: toast.POSITION.TOP_CENTER
});
}
render() {
let buttonOps = (
<CSSTransition
in={!this.state.showScreenshotContainer}
appear={true}
timeout={300}
classNames="fade"
>
<div className={classes.optionButtons}>
<Button icon={"fas fa-camera"} type={"button-success"} gridClass={""} buttonName={""} style={{ width: "100%", height: "70px" }} onClick={() => this.getScreenshotHandler} />
<Button icon={"fas fa-images"} type={"button-primary "} gridClass={""} buttonName={""} style={{ width: "100%", height: "70px" }} onClick={() => this.showScreenshotContainerHandler} />
</div>
</CSSTransition>
)
return (
<div>
{
this.state.showImageContainer ?
<div>
<ImageContainer
img={this.state.img}
showImageContainer={showImageContainer => this.showImageContainerHandler(showImageContainer)}
storeImageToArrayHandler={imgURI => this.storeImageToArrayHandler(imgURI)}
notify={(submitSuccessful, msg) => this.notify(submitSuccessful, msg)}
/>
</div>
: null
}
<CSSTransition
in={this.state.showScreenshotContainer}
appear={true}
timeout={300}
classNames="slide"
unmountOnExit
onExited={() => {
this.setState({ showScreenshotContainer: false })
}}
>
<ScreenshotsContainer
imgURIArray={this.state.imgURIArray}
getSelectedImageFromContainerHandler={imgURI => this.getSelectedImageFromContainerHandler(imgURI)}
showScreenshotContainerHandler={() => this.showScreenshotContainerHandler}
notify={(submitSuccessful, msg) => this.notify(submitSuccessful, msg)}
/>
</CSSTransition>
{this.state.showImageContainer ? null : buttonOps}
{/* <button onClick={this.notify}>Notify !</button> */}
<ToastContainer />
</div >
);
}
}
export default App;
如有任何帮助,我们将不胜感激。谢谢!
您的 React.js 应用程序 运行 在客户端(在浏览器中)。 Puppeteer 无法在该环境中 运行,因为您无法在浏览器中启动完整的浏览器。
您需要的是一台可以为您完成这些工作的服务器。您可以提供一个 HTTP 端点(选项 1)或公开您的 puppeteer Websocket(选项 2):
选项 1:提供 HTTP 端点
对于此选项,您设置了一个服务器来处理传入的请求并运行为您完成任务(制作屏幕截图):
server.js
const puppeteer = require('puppeteer');
const express = require('express');
const app = express();
app.get('/screenshot', async (req, res) => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(req.query.url); // URL is given by the "user" (your client-side application)
const screenshotBuffer = await page.screenshot();
// Respond with the image
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-Length': screenshotBuffer.length
});
res.end(screenshotBuffer);
await browser.close();
})
app.listen(4000);
使用 node server.js
启动应用程序,您现在可以将 URL 传递到您的服务器并从您的服务器获取屏幕截图:http://localhost:4000/screenshot?url=https://example.com/
来自服务器的响应可以用作您应用程序中图像元素的来源。
选项 2:将 puppeteer Websocket 暴露给客户端
您还可以通过公开 Websocket 从客户端控制浏览器(运行 在服务器上)。
为此,您需要像这样公开服务器的 Websocket:
server.js
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const browserWSEndpoint = browser.wsEndpoint();
browser.disconnect(); // Disconnect from the browser, but don't close it
console.log(browserWSEndpoint); // Communicate the Websocket URL to the client-side
// example output: ws://127.0.0.1:55620/devtools/browser/e62ec4c8-1f05-42a1-86ce-7b8dd3403f91
})();
现在您可以通过 puppeteer bundle for the client. In this scenario you could now connect to the browser via puppeteer.connect 从客户端控制浏览器(运行 在服务器上),并以此方式制作屏幕截图。
我强烈建议使用选项 1,因为在选项 2 中,您将 运行ning 浏览器完全暴露给客户端。即使使用选项 1,您仍然需要处理用户输入验证、超时、导航错误等。