Attention, ce blog est en mode archive. Il n'est plus alimenté et consultable en lecture seulement. Il se peut que certaines informations ne soient plus à jour.
Si vous souhaitez continuer à me suivre, je continuerai à bloguer d'ici quelques semaines sur le blog de SEObserver.

CasperJS ou comment construire un submitter open source

by 512banque on 31 mars 2014

Notes préliminaires : cet article vise essentiellement les développeurs, pas les cliqueurs fous qui vont demander en commentaire « j’ai installer sur windows xp mer sa marche pas ». Tout ce qui est expliqué ici l’est à titre éducatif, vous êtes responsables de ce que vous faîtes. Aucun support de ma part.

Version courte en cliquant ici.

Le succès des submitters tels que Sick Submitter, Senuke, Zenno ou même Imacros tient à trois choses :

  • leur capacité à utiliser un navigateur pour se déplacer dans le DOM et effectuer des actions à la différence de cURL,
  • leur modèle « tout en un », qui permet de centraliser l’intégralité d’une campagne,
  • leur relative facilité de prise en main.

Pour les développeurs, les deux derniers éléments peuvent se révéler d’insupportables limites : le tout-en-un est en effet un frein au branchement d’une campagne à des outils personnalisés, souvent tournant sur des langages PHP/Mysql. Quant à la facilité de prise en main, elle représente souvent de grosses limites pour un développeur qui a des besoins très personnalisés.

Enfin, le plus gros problème de tous ces outils est qu’ils reposent tous sur la nécessité d’un serveur windows, là-dessus pas besoin de faire de commentaire. Vous devrez droguer votre adminsys/dev barbu pour qu’il accepte de bosser avec ça. Et la drogue, ça coûte cher à la longue.

Les navigateurs sans tête

On va passer toute la partie théorique et présentation, googlez les termes suivants : headless browser, phantomJS, selenium, etc.

CasperJS est un framework facilitant l’utilisation de PhantomJS. Il s’agit d’un moyen puissant de dialoguer avec un navigateur.

Les avantages

  • Pas besoin d’apprendre un langage, puisque casperJS c’est du javascript, qu’il est possible de coupler avec des librairies puissantes telles que jQuery.
  • Totale flexibilité et adaptation à la demande, pas de variables stupides telles que VAR1 (imacros) ou $FNAME (sick submitter).
  • Vous pouvez utiliser aussi bien les sélecteurs CSS3 que XPATH.
  • Vous interprétez le javascript, ajax, etc, tout en restant en ligne de commandes, donc pas besoin d’écran
  • Pas besoin de windows, il peut fonctionner sur un serveur linux classique <3
  • Gratuit.

Les inconvénients

  • Pas de prise en main « click and play » pour les débutants.
  • Peu de tutoriels existants pour tout ce qui est soumission SEO.
  • Nécessité d’avoir une certaine maîtrise sur le serveur, notamment lancer des lignes de commande, donc exit les mutualisés.
Pour faire simple, la solution présentée ci-dessous vous permet d’écrire des scripts et de les lancer depuis votre serveur de production/depuis des pages web. Vous pouvez également appeler vos scripts depuis des fichiers php, des classes, bref ce que vous voulez.

Balayons ici les principales questions :

Lancement d’un script

Vous devrez lancer le script seulement en ligne de commande :

Si « casperjs » est enregistré dans votre path, vous pouvez juste taper « casperjs ».

casperjs nomdemonfichier.js

Sinon, vous pouvez appeler le bin et le script à charger en chemin absolu :

/home/moi/scripts/casperjs/bin/casperjs /home/moi/scripts/casperjs/samples/nomdemonfichier.js

Donc pour l’appeler depuis un fichier php :

<?php
echo exec('/home/moi/scripts/casperjs/bin/casperjs /home/moi/scripts/casperjs/samples/nomdemonfichier.js
');
?>

Utilisation d’un proxy

Lancez le script depuis votre shell ainsi :

casperjs --proxy=208.72.118.16:60099 --proxy-auth=username:password proxy.js

Structure-type d’un fichier

// obligatoire, pour charger la librairie casper
var casper = require("casper").create();
// optionnel, appelé pour utiliser xpath
var x = require('casper').selectXPath;

// modification de la résolution d'écran
casper.start(function(){
 this.viewport(1600,1200);
});
casper.start();
casper.userAgent('Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7');
casper.thenOpen("http://www.pagearemplir.com");
casper.then(function() {
console.log("page loaded");
});
// utilisation des sélecteurs CSS3 pour remplir un formulaire de manière "naturelle", en utilisant sendKeys plutôt que fill
casper.then(function() {
 this.sendKeys('.form1 textarea[name="commentaire"]', "Merci pour cet article !");
 this.sendKeys('.form1 input[name="name"]', 'David');
 this.sendKeys('.form1 input[name="email"]', 'david.d@gmail.com');
});
casper.then(function() {
this.click(x("//a[contains(@href, 'commentaire.php')]"));
});
casper.run(function() {
 this.exit();
});

Cliquer sur le lien qui contient « popo » dans le href, attendre que l’élément se charge, puis cliquer sur le lien qui contient toto dans le texte

casper.then(function() {
 this.waitUntilVisible(x("//a[contains(@href, 'popo')]"),
   function() { this.click(x("//a[contains(text(), 'toto')]")); }
);
});

Scraper les google suggests

A lancer comme ceci :

casperjs suggests.js pourquoi

code suggest.js :

/*global casper:true*/
var casper = require('casper').create({
    pageSettings: {
        userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:31.0) Gecko/20100101 Firefox/31.0"
    }
});
var suggestions = [];
var word = casper.cli.get(0);

if (!word) {
    casper.echo('please provide a word').exit(1);
}

casper.start('http://www.google.com/', function() {
    this.sendKeys('input[name=q]', word);
});

casper.waitFor(function() {
  return this.fetchText('.gsq_a table span').indexOf(word) === 0
}, function() {
  suggestions = this.evaluate(function() {
      var nodes = document.querySelectorAll('.gsq_a table span');
      return [].map.call(nodes, function(node){
          return node.textContent;
      });
  });
});

casper.run(function() {
  this.echo(suggestions.join('\n')).exit();
});

Scraper Google

/*jshint strict:false*/
/*global CasperError, console, phantom, require*/

var links = [];
var casper = require("casper").create();

function getLinks() {
    var links = document.querySelectorAll("h3.r a");
    return Array.prototype.map.call(links, function(e) {
        try {
            // google handles redirects hrefs to some script of theirs
            return (/url\?q=(.*)&sa=U/).exec(e.getAttribute("href"))[1];
        } catch (err) {
            return e.getAttribute("href");
        }
    });
}

casper.start("http://google.fr/", function() {
    // search for 'casperjs' from google form
    this.fill('form[action="/search"]', { q: "casperjs" }, true);
});

casper.then(function() {
    // aggregate results for the 'casperjs' search
    links = this.evaluate(getLinks);
    // now search for 'phantomjs' by fillin the form again
    this.fill('form[action="/search"]', { q: "phantomjs" }, true);
});

casper.then(function() {
    // aggregate results for the 'phantomjs' search
    links = links.concat(this.evaluate(getLinks));
});

casper.run(function() {
    // echo results in some pretty fashion
    this.echo(links.length + " links found:");
    this.echo(" - " + links.join("\n - "));
    this.exit();
});

Passer un argument lors de l’appel du script

Je lance le script en séparant mes arguments par des espaces et je n’oublie pas d’encapsuler par des guillemets quand il y a des & ou des espaces dans l’argument.

casperjs monscript.js argument1 "argument 2" "http://www.argument3.com/index.php?po=p&z=za"

Et je récupère le contenu de ces arguments dans le script ainsi :

var arg1 = casper.cli.get(0);
var arg2 = casper.cli.get(1);
var arg3 = casper.cli.get(2);

Charger le contenu d’une page distante dans une variable

var casper = require('casper').create();
var url = 'https://www.youtube.com/robots.txt';
var contents;
casper.start(url, function() {
    contents = atob(this.base64encode(url));
    console.log(contents);
});

casper.run();

Spammer le wordpress de Bertimus

/*jshint strict:false*/
/*global CasperError console phantom require*/

var casper = require("casper").create();
var x = require('casper').selectXPath;
var scrap = require('casper').create();
var commentaire;

scrap.start().then(function() {
    this.open('http://api.randomuser.me/', {
        method: 'GET',
        headers: {
            'Accept': 'application/json',
        }
    });
});
scrap.then(function() {
    contents = JSON.parse(scrap.getPageContent())
    require('utils').dump(contents.results[0].user.name);
})
scrap.thenOpen('http://www.rouflaquette.com/easytrolling/');

casper.start(function(){
    this.viewport(1600,1200);
});

casper.start();
casper.userAgent('Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7');
casper.thenOpen("http://boost.bookmarks.fr");

// on va au dernier article, on utilise le sélecteur xpath pour la démo
casper.then(function() {
        this.click(x("//a[contains(@href, '#comments')]"));
});

// depuis la première instance, on lance le 2eme casper qui va aller scraper un commentaire stupide
casper.then(function() {
    commentaire = scrap.getHTML("div#container textarea");
    this.echo(commentaire);
});

casper.then(function() {
	console.log("page loaded");
});

// on remplit le formulaire en tapant touche après touche, on utilise le sélecteur css3 pour la démo
casper.then(function() {
    this.sendKeys('#commentform textarea[name="comment"]', commentaire);
    this.sendKeys('#commentform input[name="author"]', contents.results[0].user.name.first+' '+contents.results[0].user.name.last);
    this.sendKeys('#commentform input[name="email"]', contents.results[0].user.email);
    this.sendKeys('#commentform input[name="url"]', 'http://www.spammeur-bourrin.com/');
});

// on va cliquer avec la souris sur le bouton submit
casper.then(function() {
    this.click('#commentform input[type="submit"]');
});

casper.then(function() {
	this.capture('tarace.png');
});

casper.run(function() {});
scrap.run(function() {});

setTimeout(function() {
    casper.exit();
}, 5000);

Du coup, comment remplacer sick submitter concrètement ?

Simple.

1) Créez-vous un générateur d’identité qui vous envoie en json des identités fake, comme l’exemple ci-dessus avec randomuser.me.

2) Pour la partie checkmail, vous pouvez aller parcourir votre boîte email grâce à un script php qui génère un feed (vous savez, le script de LFE !), ou bien vous optez pour une solution plus globale de catchall personnalisé sur un serveur, qui ouvre chaque email entrant et qui applique des règles en fonction du destinataire/émetteur de l’email (par exemple cliquer sur tous les liens de l’email, ou enregistrer dans un fichier si ce sont des credentials).

3) Vous créez votre fichier casperjs et vous lui passez comme argument les éléments qui changent pour votre campagne (url cible, etc).

4) Au moment d’appeler votre script, vous redirigez la sortie vers un fichier log qui vous affichera la dernière étape à laquelle vous vous êtes arrêté (voire le détail, ou un screenshot de l’étape en question).

Pour toute la partie multithread, cela se gère en amont de casperjs, en lançant plusieurs scripts différents via un script shell, et en enlevant d’une pile centrale les jobs à effectuer (redis, mysql, simple fichier texte) : il faut bien que vos scripts séparés puissent communiquer entre eux et qu’ils ne fassent pas 2x le même job !

Essayez de spammer le moins possible, ce n’est pas ce pour quoi cet outil a été conçu.

Si vous avez besoin d’aide, vous pouvez demander à Didier ou Benoît qui fournissent un support gratuit et illimité (attention, appelez-les de préférence entre 3 et 7h du matin par téléphone exclusivement, n’hésitez pas à insister).

{ 1 comment }

Si vous avez trouvé ce blog via une liste de blogs dofollow, ne perdez pas votre temps, je refuse systématiquement tous les commentaires sauf ceux en valent réellement la peine ;)

Comments on this entry are closed.

Previous post: