web-dev-qa-db-ja.com

操り人形師-プロトコルエラー(Page.navigate):ターゲットが閉じられました

以下のサンプルコードを見るとわかるように、Node=のワーカーのクラスターでPuppeteerを使用して、特定のURLでWebサイトのスクリーンショットの複数のリクエストを実行しています。

const cluster = require('cluster');
const express = require('express');
const bodyParser = require('body-parser');
const puppeteer = require('puppeteer');

async function getScreenshot(domain) {
    let screenshot;
    const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'] });
    const page = await browser.newPage();

    try {
        await page.goto('http://' + domain + '/', { timeout: 60000, waitUntil: 'networkidle2' });
    } catch (error) {
        try {
            await page.goto('http://' + domain + '/', { timeout: 120000, waitUntil: 'networkidle2' });
            screenshot = await page.screenshot({ type: 'png', encoding: 'base64' });
        } catch (error) {
            console.error('Connecting to: ' + domain + ' failed due to: ' + error);
        }

    await page.close();
    await browser.close();

    return screenshot;
}

if (cluster.isMaster) {
    const numOfWorkers = require('os').cpus().length;
    for (let worker = 0; worker < numOfWorkers; worker++) {
        cluster.fork();
    }

    cluster.on('exit', function (worker, code, signal) {
        console.debug('Worker ' + worker.process.pid + ' died with code: ' + code + ', and signal: ' + signal);
        Cluster.fork();
    });

    cluster.on('message', function (handler, msg) {
        console.debug('Worker: ' + handler.process.pid + ' has finished working on ' + msg.domain + '. Exiting...');
        if (Cluster.workers[handler.id]) {
            Cluster.workers[handler.id].kill('SIGTERM');
        }
    });
} else {
    const app = express();
    app.use(bodyParser.json());
    app.listen(80, function() {
        console.debug('Worker ' + process.pid + ' is listening to incoming messages');
    });

    app.post('/screenshot', (req, res) => {
        const domain = req.body.domain;

        getScreenshot(domain)
            .then((screenshot) =>
                try {
                    process.send({ domain: domain });
                } catch (error) {
                    console.error('Error while exiting worker ' + process.pid + ' due to: ' + error);
                }

                res.status(200).json({ screenshot: screenshot });
            })
            .catch((error) => {
                try {
                    process.send({ domain: domain });
                } catch (error) {
                    console.error('Error while exiting worker ' + process.pid + ' due to: ' + error);
                }

                res.status(500).json({ error: error });
            });
    });
}

いくつかの説明:

  1. リクエストが到着するたびに、ワーカーはそれを処理し、最後に自分自身を殺します
  2. 各ワーカーは、単一のページで新しいブラウザインスタンスを作成し、ページの読み込みに60秒以上かかった場合、120秒のタイムアウトで(おそらくいくつかのリソースが既に読み込まれているため、同じページで)再読み込みを再試行します
  3. 終了すると、ページとブラウザの両方が閉じられます

私の問題は、いくつかの正当なドメインが説明できないエラーを受け取ることです。

Error: Protocol error (Page.navigate): Target closed.
Error: Protocol error (Runtime.callFunctionOn): Session closed. Most likely the page has been closed.

いくつかのgitの問題(今は見つけることができません)で、ページがリダイレクトして最初に「www」を追加すると発生する可能性があることを読みましたが、間違っていることを願っています...何か足りないものはありますか?

6
LioRz

「ターゲットが閉じられた」とは

puppeteer.launchを介してブラウザを起動すると、ブラウザが起動して接続されます。そこから、開いたブラウザで実行する関数(page.gotoなど)が Chrome DevTools Protocol を介してブラウザに送信されます。ターゲットは、このコンテキストのタブを意味します。

Target closed例外は、関数を実行しようとしたときにスローされますが、ターゲット(タブ)は既に閉じられています。

同様のエラーメッセージ

エラーメッセージは最近、より意味のある情報を提供するために変更されました。現在、次のメッセージが表示されます。

エラー:プロトコルエラー(Target.activateTarget):セッションが閉じられました。ほとんどの場合、ページは閉じられています。


なぜそれが起こるのか

これが起こる理由はいくつかあります。

  • すでに閉じられているリソースを使用しました

    最も可能性が高いのは、タブ/ブラウザを閉じたがまだリソースを使用しようとしているためです。簡単な例を挙げます:

    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    
    await browser.close();
    await page.goto('http://www.google.com');
    

    この場合、ブラウザは閉じられ、その後、page.gotoが呼び出され、エラーメッセージが表示されました。ほとんどの場合、それはそれほど明白ではありません。スクリプトのクロール中に、エラーハンドラーがクリーンアップタスク中にページを既に閉じている可能性があります。

  • ブラウザがクラッシュしたか、初期化できませんでした

    また、これは数百のリクエストごとに発生します。 puppeteerリポジトリにも これに関する問題 があります。大量のメモリまたはCPUパワーを使用している場合に当てはまるようです。たぶん、あなたは多くのブラウザを生み出していますか?これらの場合、ブラウザがクラッシュまたは切断する可能性があります。

    この問題に対する「特効薬」ソリューションは見つかりませんでした。しかし、ライブラリ puppeteer-cluster (免責事項:私は著者です)をチェックアウトして、この種のエラーのケースを処理し、エラーが発生したときにURLを再試行してみましょう。また、ブラウザインスタンスのプールを管理し、コードを簡素化することもできます。

7
Thomas Dondorf