web-dev-qa-db-fra.com

Puppeteer - Erreur de protocole (Page.navigate): cible fermée

Comme vous pouvez le voir avec l'exemple de code ci-dessous, j'utilise Puppeteer avec un cluster de travailleurs dans Node pour exécuter plusieurs demandes de captures d'écran de sites Web par une URL donnée:

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 });
            });
    });
}

Quelques explications:

  1. Chaque fois qu'une demande arrive, un travailleur la traite et se tue à la fin
  2. Chaque travailleur crée une nouvelle instance de navigateur avec une seule page, et si une page a mis plus de 60 secondes à se charger, il réessayera de la recharger (dans la même page car peut-être que certaines ressources ont déjà été chargées) avec un délai d'attente de 120 secondes
  3. Une fois terminé, la page et le navigateur seront fermés

Mon problème est que certains domaines légitimes reçoivent des erreurs que je ne peux pas expliquer:

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

J'ai lu à un problème git (que je ne peux pas trouver maintenant) que cela peut se produire lorsque la page redirige et ajoute 'www' au début, mais j'espère que c'est faux ... Y a-t-il quelque chose qui me manque?

6
LioRz

Que signifie "cible fermée"

Lorsque vous lancez un navigateur via puppeteer.launch il démarrera un navigateur et s'y connectera. À partir de là, n'importe quelle fonction que vous exécutez sur votre navigateur ouvert (comme page.goto) sera envoyé via le Chrome DevTools Protocol au navigateur. Une cible signifie un onglet dans ce contexte.

L'exception Cible fermée est levée lorsque vous essayez d'exécuter une fonction, mais la cible (tabulation) était déjà fermée.

Messages d'erreur similaires

Le message d'erreur a été récemment modifié pour donner des informations plus significatives. Il donne maintenant le message suivant:

Erreur: erreur de protocole (Target.activateTarget): session fermée. La page a probablement été fermée.


Pourquoi ça arrive

Il y a plusieurs raisons à cela.

  • Vous avez utilisé une ressource qui était déjà fermée

    Très probablement, vous voyez ce message parce que vous avez fermé l'onglet/navigateur et essayez toujours d'utiliser la ressource. Pour donner un exemple simple:

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

    Dans ce cas, le navigateur a été fermé et après cela, un page.goto a été appelé, ce qui a généré le message d'erreur. La plupart du temps, ce ne sera pas si évident. Un gestionnaire d'erreurs a peut-être déjà fermé la page lors d'une tâche de nettoyage, alors que votre script est toujours en cours d'exploration.

  • Le navigateur est tombé en panne ou n'a pas pu s'initialiser

    J'en fais également l'expérience toutes les quelques centaines de demandes. Il y a aussi un problème à ce sujet sur le dépôt de marionnettiste. Cela semble être le cas lorsque vous utilisez beaucoup de mémoire ou de puissance CPU. Peut-être que vous générez beaucoup de navigateur? Dans ces cas, le navigateur peut se bloquer ou se déconnecter.

    Je n'ai trouvé aucune solution miracle à ce problème. Mais vous voudrez peut-être consulter la bibliothèque puppeteer-cluster (clause de non-responsabilité: je suis l'auteur) qui gère ce type de cas d'erreur et vous permet de réessayer l'URL lorsque l'erreur se produit. Il peut également gérer un pool d'instances de navigateur et simplifierait également votre code.

7
Thomas Dondorf