Paris JS #10 : Introduction à PhantomJS, un navigateur webkit headless

Mise à jour : la vidéo est disponible sur http://lacantine.ubicast.eu/videos/parisjs-10/

Pour le dixième meetup ParisJS, j’ai présenté le projet PhantomJS. Pour résumer rapidement, PhantomJS est un navigateur Webkit sans interface graphique, qui se pilote en Javascript (ou Coffeescript).

Voilà les diapos de ma présentation (la captation vidéo sera peut être disponible plus tard):

Dans les choses que j’ai oublié de mentionner :

  • PhantomJS fonctionne sous Windows, Mac et Linux
  • il existe une version en Python, qui permet de créer des plugins pour étendre l’API standard

Les exemples de scripts

J’avais prévu des démos, mais je n’ai pas pu toutes les montrer. Je vais donc les publier ici, dans la suite de l’article. Certains exemples sont inspirés de ceux publiés par Nicolas Perriault dans son article Scrape and test any webpage using PhantomJS.

Exemple 0 : Hello world

Commençons par un exemple simple, afficher “Hello, world!” dans la console:

//exemple pour PhantomJS 1.2
console.log("Hello, world!"); phantom.exit();

Exemple 1 : Charger une page Web

On crée un nouvel objet WebPage qui va permettre de charger le site parisjs.org. Lorsque la page est chargée, on affiche le code source de la page.

//exemple pour PhantomJS 1.2
var page = new WebPage();
page.open('http://parisjs.org/', function (status) {
    if ('success' !== status) {
        console.log("Error");
    } else {
        console.log(page.content);
        phantom.exit();
    }
});

Exemple 2 : Faire un screenshot

Quasiment la même chose, sauf qu’on fait un screenshot de la page cette fois, qui sera sauvegardé dans le fichier parisjs.png. À noter qu’on peut définir le viewport. L’extension du fichier détermine le type de fichier sauvegardé (PNG, JPG, ou PDF).

//Exemple pour PhantomJS 1.2
var page = new WebPage();
page.viewportSize = {
    width: 1280,
    height: 800
};
page.open("http://parisjs.org", function (status) {
    if ('success' !== status) {
        console.log("Error");
    } else {
        page.render("parisjs.png");
        phantom.exit();
    }
});

Exemple 3 : Exécuter du code dans le contexte de la page Web

Cet exemple charge le site parisjs.org. Une fois la page chargée, on va exécuter une fonction dans le contexte de cette page. Comme jQuery est dispo sur cette page, on peut directement utiliser $. Ici, on va simplement retourner la liste des présentations passées qui sont listées sur la page d’accueil.

//Exemple pour PhantomJS 1.2
var page = new WebPage();
var result;
page.open('http://parisjs.org/', function (status) {
    if ('success' !== status) {
        console.log("Error");
    } else {
        result = page.evaluate(function () {
            var talks = $('ul[data-eventnumber] li');
            var out = [];
            talks.each(function () {
                out.push($(this).text());
            });
            return out.join("\n");
        });
        console.log(result);
        phantom.exit();
    }
});

Exemples 4 et 5 : “Scraper” des sites Web

Les exemples qui suivent reprennent les concepts des exemples précédents. Le premier extrait la liste des films qui vont sortir sur Allocine.fr. Le second va récupérer la carte météo sur MeteoFrance.com. Potentiellement, ces exemples violent les conditions générales de ces deux sites, donc ils ne sont là qu’à titre éducatif.

Allociné :

//Exemple pour PhantomJS 1.2
var page = new WebPage();
var result;
page.open('http://www.allocine.fr/film/cettesemaine.html', function (status) {
    if ('success' !== status) {
        console.log("Error");
    } else {
        result = page.evaluate(function () {
            var titles = $('.titlebar h2 a');
            var out = [];
            titles.each(function () {
                out.push($(this).text());
            });
            return out.join("\n");
        });
        console.log(result);
        phantom.exit();
    }
});

Météo France :

//Exemple pour PhantomJS 1.2
var page = new WebPage();
var result;
page.viewportSize = {
    width: 1280,
    height: 800
};
page.clipRect = {
    top: 381,
    left: 175,
    width: 452,
    height: 381
};
page.open('http://france.meteofrance.com/france/accueil', function (status) {
    if ('success' !== status) {
        console.log("Error");
    } else {
        page.render("meteo.png");
        phantom.exit();
    }
});

Exemple 6 : Récupérer les articles linkés depuis Hacker News, au format PDF pour les sauvegarder dans une Dropbox

Cet exemple est un peu alambiqué, mais montre qu’il peut y avoir des utilisations créatives de PhantomJS. Dans ce dernier exemple, on va scraper la page d’accueil de Hacker News pour récupérer les liens vers les articles les plus intéressants. On va alors récupérer le contenu de ces articles via ViewText.org qui va se charger d’éliminer tout ce qui n’est pas intéressant (header, navigation, footer, etc.). Ce contenu va être sauvegardé en PDF dans le dossier Dropbox.

//Exemple pour PhantomJS 1.2
var page = new WebPage();
var dropboxPath = '/Users/mauricesvay/Dropbox/hackernews';

/**
 * Get all links from Hacker News front page
 */
function getLinks() {
    var json = page.evaluate(function () {
        var links = $('td.title > a');
        var url = [];
        links.each(function () {
            url.push(this.href);
        }); //can only return simple data structures
        return JSON.stringify(url);
    });
    return JSON.parse(json);
}

/**
 * Recursively open content-only articles, save as PDF in Dropbox
 */
function saveToDropbox(links) {
    if (links.length == 0) {
        phantom.exit();
        return;
    }
    var url = links.shift();
    var readableUrl = 'http://viewtext.org/api/text?url=' + url; //filesystem friendly filenames
    var filename = [dropboxPath, '/', url.replace(/[^a-zA-Z0-9]/g, '_'), '.pdf'].join('');
    var page = new WebPage();
    page.open(readableUrl, function (status) {
        if (status === 'success') {
            console.log("Saving " + filename); // Fry says : Not sure if async or buggy
            page.render(filename);
            saveToDropbox(links);
        }
        delete page;
    });
}
//Here we go...
page.open('http://news.ycombinator.com/', function (status) {
    if ('success' !== status) {
        console.log("Error");
    } else {
        //inject jQuery into Hacker News
        page.includeJs("https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js", function () {
            var links = getLinks();
            saveToDropbox(links);
        });
    }
});

J’espère que cela vous inspirera et que vous trouverez plein d’autres utilisations créatives. Pour plus d’exemples, consultez la documentation officielle et le projet Github qui regorge d’exemples. N’hésitez pas à me poser des questions sur twitter ou dans les commentaires.

3 replies on “Paris JS #10 : Introduction à PhantomJS, un navigateur webkit headless”

  1. Ça à l’air super puissant et assez facile à appréhender ce phantomJs. Sauf que là pour le moment je bloque encore sur l’utilisation avec Xvfb (sur xubuntu)… J’ai systématiquement une erreur m’indiquant “cannot connect to X server” même après avoir suivi la procédure indiqué ici: http://code.google.com/p/phantomjs/

    Si quelqu’un à eu le même problème…? :p
    On trouve encore assez peu de documentation sur le sujet.

  2. @Clem, :

    J’ai eu quelques problèmes concernant Xvfb sur debian. En gros je lance Xvfb en fond dans ce style : Xvfb :99 -nolisten tcp &

    Puis ensuite, dans ton programme :
    export DISPLAY :=99; /home/www/bin/phantom1-2 …

    Dis-moi si ça résout ton problème

  3. Bonjour J’ai essayé l’exemple 1 ( récupéré web page) marche parfaitement en mode console mais pas quand on le lance via php
    dans ma page php je fais un echo exec et j’ai ajouté un return page.content dans le script js mais rien ne revient.

    Un conseil ?
    merci

Comments are closed.