能否在Google Cloud Function中运行无头Chrome/Chromium?

16

有没有办法在Google Cloud Function中运行无头Chrome / Chromium?我知道可以在GCF中包含和运行静态编译的二进制文件。我能否获得一个静态编译版本的Chrome来实现这一点?


2
有些人正在这上面工作:https://github.com/adieuadieu/serverless-chrome - Palani
4个回答

16

Google云函数的Node.js 8运行时现在包含所有必要的操作系统软件包来运行Headless Chrome。

这是一个返回屏幕截图的HTTP函数的代码示例:

主要index.js文件:

const puppeteer = require('puppeteer');

exports.screenshot = async (req, res) => {
  const url = req.query.url;

  if (!url) {
    return res.send('Please provide URL as GET parameter, for example: <a href="?url=https://example.com">?url=https://example.com</a>');
  }

  const browser = await puppeteer.launch({
    args: ['--no-sandbox']
  });
  const page = await browser.newPage();
  await page.goto(url);
  const imageBuffer = await page.screenshot();
  await browser.close();

  res.set('Content-Type', 'image/png');
  res.send(imageBuffer);
}

以及 package.json

{
  "name": "screenshot",
  "version": "0.0.1",
  "dependencies": {
    "puppeteer": "^1.6.2"
  }
}

Puppeteer是一个被Google缓存优化的依赖项,还是需要更多资源(例如内存、CPU)才能使用的东西? - jasan
该函数无法在我的 Firebase 帐户上部署。其他(非异步)函数可以正常部署。我已启用 npm 8。 - daniel
有没有可能得到对Java运行时的支持,以便它可以与Selenium配合使用? - kashiB
2
@ebidel,Python环境是否有运行无头Chrome所需的等效包? - FKrauss

6
我刚部署了一个运行无头Chrome的GCF函数。几个要点如下:
  1. 你必须在Debian 8上静态编译Chromium和NSS。
  2. 在启动Chromium之前,你必须修补环境变量以指向NSS。
  3. 性能比在AWS Lambda上获得的要差得多(3秒以上)。
对于第1点,你应该能够在网上找到大量的说明。
对于第2点,我使用的代码如下:
static executablePath() {
  let bin = path.join(__dirname, '..', 'bin', 'chromium');
  let nss = path.join(__dirname, '..', 'bin', 'nss', 'Linux3.16_x86_64_cc_glibc_PTH_64_OPT.OBJ');

  if (process.env.PATH === undefined) {
    process.env.PATH = path.join(nss, 'bin');
  } else if (process.env.PATH.indexOf(nss) === -1) {
    process.env.PATH = [path.join(nss, 'bin'), process.env.PATH].join(':');
  }

  if (process.env.LD_LIBRARY_PATH === undefined) {
    process.env.LD_LIBRARY_PATH = path.join(nss, 'lib');
  } else if (process.env.LD_LIBRARY_PATH.indexOf(nss) === -1) {
    process.env.LD_LIBRARY_PATH = [path.join(nss, 'lib'), process.env.LD_LIBRARY_PATH].join(':');
  }

  if (fs.existsSync('/tmp/chromium') === true) {
    return '/tmp/chromium';
  }

  return new Promise(
    (resolve, reject) => {
      try {
        fs.chmod(bin, '0755', () => {
          fs.symlinkSync(bin, '/tmp/chromium'); return resolve('/tmp/chromium');
        });
      } catch (error) {
        return reject(error);
      }
    }
  );
}

您在启动Chrome时还需要使用一些必需的参数,即:
--disable-dev-shm-usage
--disable-setuid-sandbox
--no-first-run
--no-sandbox
--no-zygote
--single-process

我希望这可以帮到您。

0

正如评论中所提到的,正在努力寻找在云函数中运行无头浏览器的可能解决方案。可以在Google Groups上阅读一个直接适用的讨论:“headless chrome & aws lambda”。


-1

我曾经问过一个问题:在Firebase Cloud Functions中能否运行无头Chrome或Chromium...答案是否定的!因为node.js项目将无法访问任何chrome/chromium可执行文件,因此会失败!(相信我-我已经尝试过了!)

更好的解决方案是使用Phantom npm包,其在幕后使用PhantomJS: https://www.npmjs.com/package/phantom

文档和信息可以在这里找到:

http://amirraminfar.com/phantomjs-node/#/

或者

https://github.com/amir20/phantomjs-node

我试图爬取的网站已经实现了屏幕抓取软件,诀窍是通过搜索预期的字符串或正则表达式匹配来等待页面加载,例如,我对一个进行正则表达式匹配,如果您需要任何复杂度的正则表达式 - 请联系https://AppLogics.uk/ - 起价为5英镑(GBP)。

这里是一个TypeScript片段,用于进行http或https调用:

        const phantom = require('phantom');
        const instance: any = await phantom.create(['--ignore-ssl-errors=yes', '--load-images=no']);
        const page: any = await instance.createPage();
        const status = await page.open('https://somewebsite.co.uk/');
        const content = await page.property('content');

同样的JavaScript代码:

        const phantom = require('phantom');
        const instance = yield phantom.create(['--ignore-ssl-errors=yes', '--load-images=no']);
        const page = yield instance.createPage();
        const status = yield page.open('https://somewebsite.co.uk/');
        const content = yield page.property('content');

这是最简单的部分!如果它是一个静态页面,你几乎完成了,你可以将HTML解析成像cheerio npm包https://github.com/cheeriojs/cheerio这样的东西 - 一个为服务器设计的核心JQuery实现!

然而,如果它是一个动态加载页面,即懒加载,或者甚至是反爬虫方法,你需要等待页面更新,通过循环调用page.property('content')方法并运行文本搜索或正则表达式来查看页面是否已经加载完成。

我创建了一个通用的异步函数,在成功时返回页面内容(作为字符串),在失败或超时时抛出异常。它以页面、文本(指示成功的字符串)、错误(指示失败的字符串或null以不检查错误)和超时(数字 - 不言自明)作为参数:

TypeScript:

    async function waitForPageToLoadStr(page: any, text: string, error: string, timeout: number): Promise<string> {
        const maxTime = timeout ? (new Date()).getTime() + timeout : null;
        let html: string = '';
        html = await page.property('content');
        async function loop(): Promise<string>{
            async function checkSuccess(): Promise <boolean> {
                html = await page.property('content');
                if (!isNullOrUndefined(error) && html.includes(error)) {
                    throw new Error(`Error string found: ${ error }`);
                }
                if (maxTime && (new Date()).getTime() >= maxTime) {
                    throw new Error(`Timed out waiting for string: ${ text }`);
                }
                return html.includes(text)
            }
            if (await checkSuccess()){
                return html;
            } else {
                return loop();
            }                
        }
        return await loop();
    }

JavaScript:

    function waitForPageToLoadStr(page, text, error, timeout) {
            return __awaiter(this, void 0, void 0, function* () {
                const maxTime = timeout ? (new Date()).getTime() + timeout : null;
                let html = '';
                html = yield page.property('content');
                function loop() {
                    return __awaiter(this, void 0, void 0, function* () {
                        function checkSuccess() {
                            return __awaiter(this, void 0, void 0, function* () {
                                html = yield page.property('content');
                                if (!isNullOrUndefined(error) && html.includes(error)) {
                                    throw new Error(`Error string found: ${error}`);
                                }
                                if (maxTime && (new Date()).getTime() >= maxTime) {
                                    throw new Error(`Timed out waiting for string: ${text}`);
                                }
                                return html.includes(text);
                            });
                        }
                        if (yield checkSuccess()) {
                            return html;
                        }
                        else {
                            return loop();
                        }
                    });
                }
                return yield loop();
            });
        }

我个人是这样使用这个函数的:
TypeScript:
    try {
        const phantom = require('phantom');
        const instance: any = await phantom.create(['--ignore-ssl-errors=yes', '--load-images=no']);
        const page: any = await instance.createPage();
        const status = await page.open('https://somewebsite.co.uk/');
        await waitForPageToLoadStr(page, '<div>Welcome to somewebsite</div>', '<h1>Website under maintenance, try again later</h1>', 1000);
    } catch (error) {
        console.error(error);
    }

JavaScript:

    try {
        const phantom = require('phantom');
        const instance = yield phantom.create(['--ignore-ssl-errors=yes', '--load-images=no']);
        const page = yield instance.createPage();
        yield page.open('https://vehicleenquiry.service.gov.uk/');
        yield waitForPageToLoadStr(page, '<div>Welcome to somewebsite</div>', '<h1>Website under maintenance, try again later</h1>', 1000);
    } catch (error) {
        console.error(error);
    }

祝你爬取愉快!


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