如何使Puppeteer与客户端上的ReactJS应用程序一起使用 [英] How to make Puppeteer work with a ReactJS application on the client-side

查看:47
本文介绍了如何使Puppeteer与客户端上的ReactJS应用程序一起使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对React还是很陌生,我正在开发一个应用程序,它将获取网页的实际屏幕截图,并且该应用程序可以在所截取的屏幕截图上方绘制并添加涂鸦.最初,我使用html2canvas和domToImage拍摄客户端屏幕快照,但是它无法完全呈现网页中显示的图像.

I am fairly new to React and I am developing an app which will take actual screenshots of a web page and the app can draw and add doodles on top of the screenshot taken. I initially used html2canvas and domToImage to take client-side screenshots but it doesn't render the image exactly as it is shown in the web page.

Reddit用户/pamblam0建议我调查Google的Puppeteer.它的工作方式是启动无头铬浏览器,该浏览器转到我在本地主机上的react应用,然后轻松获取整个页面的屏幕截图.但是我的问题是,木偶戏在React应用程序中不能很好地发挥作用.它给了我一个ws错误,正如在Google搜索中所述,可以通过简单地安装ws来解决该错误(顺便说一句不起作用).

Reddit user /pamblam0 suggested I look into Google's Puppeteer. How it works is that it launches a headless chromium browser which goes to my react app on localhost then gets a screenshot of that whole page easily. My problem however, is that puppeteer doesn't play nice inside a react app. It gives me a ws error which, as explained on a google search can be fixed by simply installing ws (which doesn't work by the way).

现在会发生什么,我的操纵up脚本可以生成我的react应用程序.据我了解,它不适用于客户端应用程序(我可能错了).我想发生的事情是,每当我从react应用程序中单击按钮时,puppeteer应该执行并返回base64字符串,然后将其传递到我的react应用程序中的组件.

What happens now my puppeteer script works out my react app. From what I understand it doesn't work with client side app (I might be wrong). What I want to happen is that whenever I click the button from my react app, puppeteer should execute and return a base64 string which will then be passed to a component in my react app.

这是我到目前为止所做的.

Here is what I've done so far.

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

Code from react app. 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;

任何帮助将不胜感激.谢谢!

Any help would be appreciated. Thanks!

推荐答案

您的React.js应用程序在客户端(在浏览器中)运行. Puppeteer无法在该环境中运行,因为您无法在浏览器中启动完整的浏览器.

Your React.js application runs on the client-side (in the browser). Puppeteer cannot run inside that environment as you cannot start a full browser inside the browser.

您需要的是一台为您完成此任务的服务器.您可以提供一个HTTP端点(选项1)或公开您的伪造Websocket(选项2):

What you need is a server which does that for you. You could ether offer a HTTP endpoint (option 1) or expose your puppeteer Websocket (option 2):

对于此选项,您设置一个服务器来处理传入的请求并为您运行任务(制作屏幕截图):

For this option, you setup a server which handles the incoming request and runs the task (making a screenshot) for you:

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/

Start the application with node server.js and you can now pass the URL to your server and get a screenshot back from your server: http://localhost:4000/screenshot?url=https://example.com/

然后服务器发出的响应可用作您应用程序中图像元素的来源.

The response from the server could then be used as as the source of an image element in your application.

您还可以通过公开Websocket从客户端控制浏览器(在服务器上运行).

You could also control the browser (which runs on the server) from the client-side by by exposing the Websocket.

为此,您需要像这样公开服务器的Websocket:

For that you need to expose the Websocket of your server like this:

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.connect <连接到浏览器/a>并以这种方式制作屏幕截图.

Now you can control the browser (running on the server) form the client-side with a puppeteer bundle for the client. In this scenario you could now connect to the browser via puppeteer.connect and make a screenshot that way.

我强烈建议您使用选项1,因为在选项2中,您正在将正在运行的浏览器完全暴露给客户端.即使使用选项1,您仍然需要处理用户输入验证,超时,导航错误等.

I would strongly recommend using option 1, as in option 2 you are fully exposing your running browser to the client. Even with option 1, you still need to handle user input validation, timeouts, navigation errors, etc.

这篇关于如何使Puppeteer与客户端上的ReactJS应用程序一起使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆