Il nome stesso, PhantomJS, fa pensare a qualcosa che non c'è. Ed in effetti questo tool consente di compiere operazioni che normalmente vengono fatte con un browser, senza però mostrare il browser stesso.
Si tratta infatti di un cosiddetto headless browser, cioè di un tool che consente la manipolazione via JavaScript del DOM, di CSS, JSON, Ajax altre tecnologie Web client-side da riga di comando, senza alcun rendering a video.
Basato su WebKit, PhantomJS è uno strumento multipiattaforma e può essere utilizzato in tutti quei contesti in cui si ha bisogno di automatizzare le tipiche attività di un Web browser, ma non solo.
Per fare qualche esempio, esso può essere utilizzato per attività di Web scraping, per l'automazione di test su siti ed applicazioni Web, per il monitoraggio di rete, ma anche per il rendering in SVG, l'interfacciamento con servizi Web ed addirittura per la creazione di un semplice Web server.
Installare PhantomJS
PhantomJS è un tool multipiattaforma: i pacchetti per Linux, Windows e Mac sono scaricabili insieme ai sorgenti rilasciati sotto licenza BSD.
Per installarlo è sufficiente copiare il contenuto del pacchetto in una cartella ed invocare l'eseguibile phantomjs da riga di comando con la seguente sintassi:
phantomjs [opzioni] nomeScript [argomentiScript]
Gli script che PhantomJS esegue possono essere scritti in JavaScript o in CoffeeScript.
Il primo script con PhantomJS
Come è tradizione, proviamo a creare il classico Hello world! in JavaScript da far eseguire a PhantomJS:
console.log("Hello world!");
phantom.exit();
Salvando il codice in un file helloWorld.js
ed eseguendo il seguente comando da riga di comando:
phantomjs helloWorlds.js
otterremo a video il classico saluto iniziale.
Se vogliamo scrivere a video una stringa generica passata come argomento allo script possiamo fare come mostrato di seguito:
if (phantom.args.length != 0) {
console.log(phantom.args[0]);
} else {
console.log("Inserisci una stringa come argomento");
}
phantom.exit();
L'oggetto globale phantom
mette a disposizione una serie di funzionalità dell'ambiente di esecuzione tra le quali l'array args
è un esempio autoesplicativo.
Come altro esempio abbiamo utilizzato il metodo exit(returnValue)
, che consente di uscire dal programma passando eventualmente un codice di ritorno. Negli script è necessario invocare esplicitamente phantom.exit()
per terminare l'esecuzione, altrimenti PhantomJS rimane attivo.
Gestire pagine Web
Proviamo a realizzare uno script con una valenza un po' più concreta accedendo all'home page di HTML.it e riportando il titolo e la sua descrizione.
Per questo obiettivo utilizzeremo l'oggetto WebPage che consente di effettuare tutte le operazioni di gestione di una pagina Web che faremmo con un browser. Ad esempio, il seguente codice carica l'home page di HTML.it:
var page = new WebPage();
page.open("http://www.html.it");
Ovviamente con queste istruzioni non facciamo altro che caricare il DOM della pagina Web, ma non abbiamo nessun effetto visivo.
Possiamo intercettare gli eventi di inizio e fine caricamento della pagina associando delle funzioni di callback come di seguito mostrato:
var url = 'http://www.html.it';
var page = new WebPage();
page.onLoadStarted = function () {
console.log("Inizio caricamento...");
};
page.onLoadFinished = function (status) {
console.log("Fine caricamento...");
phantom.exit();
};
page.open(url);
In questo modo visualizzeremo un messaggio che ci avvisa quando sta iniziando il caricamento ed uno che comunica il suo completamento. Da notare come l'invocazione di phantom.exit()
avvenga all'interno della funzione di fine caricamento.
Se la invochiamo nel flusso di esecuzione principale, ad esempio subito dopo page.open(url), non vedremo alcun messaggio a video in quanto il caricamento avviene in forma asincrona e quindi l'uscita da PhantomJS avverrebbe verosimilmente prima del caricamento della pagina remota.
Una volta caricata una pagina Web, abbiamo accesso al DOM risultante sfruttando il metodo evaluate()
dell'oggetto WebPage
. Questo metodo consente di eseguire una funzione all'interno del contesto della pagina caricata. Nel nostro caso dunque, per catturare il titolo e la descrizione della pagina possiamo scrivere una funzione come la seguente:
function getInfo() {
var info = {};
info.titolo = document.title;
info.descrizione = document.getElementsByName("description")[0].content
return info;
}
e richiamarla come mostrato di seguito:
page.onLoadFinished = function (status) {
var info;
console.log("Fine caricamento...");
info = page.evaluate(getInfo)
console.log("Titolo della pagina: " + info.titolo);
console.log("Descrizione della pagina: " + info.descrizione);
phantom.exit();
};
Il metodo evaluate()
esegue la funzione getInfo()
nel contesto della pagina caricata e restituisce il relativo risultato.
Da notare come la chiamata a console.log()
per visualizzare le informazioni relative al DOM debba avvenire fuori dal contesto della pagina, cioè non all'interno della funzione getInfo()
. Ciò dipende dal fatto che il codice JavaScript di getInfo()
viene eseguito in una sandbox che impedisce l'accesso a qualsiasi oggetto esterno al contesto della pagina.
Includere script esterni
Nella gestione del DOM di una pagina Web, PhantomJS prevede la possibilità di caricare script esterni. Una prima possibilità è data dall'inclusione di uno script remoto tramite il metodo includeJs() dell'oggetto WebPage. Ad esempio, è possibile includere jQuery dai server di Google in questo modo:
page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js",
function() {
//Sfrutta jQuery per manipolare il DOM della pagina caricata
});
Il primo parametro della funzione indica l'URL che punta allo script mentre il secondo parametro specifica la funziona da eseguire in seguito all'avvenuto caricamento dello script.
Per includere invece uno script locale occorre utilizzare il metodo injectJs()
:
page.injectJs("jquery.min.js");
In questo caso si suppone che il file jquery.min.js si trova nella stessa cartella dello script in esecuzione.
Moduli
Alcune delle funzionalità di PhantomJS sono organizzate in moduli, cioè in unità di codice che possono essere caricate dinamicamente. Il caricamento di un modulo viene effettuato tramite la funzione require(), come mostra il seguente esempio:
var mioModulo = require("nomeModulo");
Allo stato attuale sono previsti tre moduli: webpage, fs e webserver.
Il modulo webpage consente di accedere alle funzioni di caricamento e manipolazione del DOM, tramite l'oggetto WebPage
.
In realtà negli esempi che abbiamo visto finora abbiamo utilizzato l'oggetto WebPage
senza aver caricato esplicitamente il relativo modulo. Si tratta di una semplificazione presente per compatibilità con le versioni precedenti di PhantomJS. La procedura regolare per accedere all'oggetto WebPage sarebbe quella di seguito mostrata:
var moduloWebPage = require("webpage");
var page = moduloWebPage.create();
Il modulo fs consente l'accesso al file system con la possibilità di gestire file e cartelle, mentre il modulo webserver consente di realizzare con poche istruzioni un semplice server web, come mostrato dal seguente codice:
var server, service;
server = require('webserver').create();
service = server.listen(8080, function (request, response) {
response.statusCode = 200;
response.write('Benvenuto!');
});
Il pacchetto di PhantomJS
Il pacchetto con cui scarichiamo il tool contiene un buon numero di esempi avanzati di utilizzo: dall'esecuzione automatizzata di unit test con QUnit e Jasmine all'interfacciamento con le Weather e Direction API di Google, dal rendering di pagine Web in SVG e PDF alla ricezione degli aggiornamenti Twitter di un dato account.
Alla nostra fantasia il compito di sfruttare a pieno le potenzialità di PhantomJS.
Link utili