Ne scrappez pas sans vos proxies

Dans cet article je vais partager une petite classe PHP que j’ai mis en place pour me facilité la gestion des proxies lorsque je veux scrapper.

En PHP si l’on veut récupérer du contenu sur une adresse distante il y différentes possibilités.

La fonction file_get_contents permet de faire ce travail et elle le fait très bien. Cependant quand vous utilisez cette fonction depuis votre serveur (nommons le A) et que vous récupérez le contenu du site B, alors dans les logs du site B on peut voir que A à visité B. L’avantage de cette fonction réside dans le fait que l’on peut récupérer du contenu distant en une seule ligne de code.

Cette solution n’est donc pas la meilleure si on veut rester discret.

 

La deuxième possibilité se nomme cURL, celle-ci est beaucoup plus adaptable.

Par exemple, si vous voulez rester discret alors il suffit d’utiliser un proxy.

Vous voulez récupérer du contenu qui se cache derrière une page d’authentification ? Alors utilisez les cookies pour maintenir la session.

Vous voulez faire croire que vous êtes un utilisateur lambda qui utilise un navigateur précis (Firefox, Chrome,…) ? Alors ajoutez une option pour modifier l’user-agent.

Et la liste des possibilités est encore bien plus grande.

 

Par contre pour utiliser cURL il va vous falloir plus qu’une ligne de code. C’est pourquoi j’ai mis en place cette classe.

En plus de cela j’ai ajouté des fonctionnalités comme :

  • Choisir un proxy aléatoirement dans une liste
  • Choisir un user-agent aléatoirement

Comme ça vous fournissez un fichier texte contenant une liste de proxy, un autre fichier contenant les différents user-agents que vous voulez utiliser et SimpleCURL s’occupera du reste.

 

SimpleCURL facilite la gestion de curl

Voici le code de la classe en question.
Le fichier SimpleCURL.php

<?php
/**
 * Gérez facilement vos proxies et user-agents lors de vos sessions de scrap avec SimpleCURL
 * Guillaume Desbieys
 * http://www.guillaumedesbieys.com
 */
class SimpleCURL
{
    /**
    * @var (Array)
    * @desc Liste des proxy
    */
    private $proxy = array();

    /**
    * @var (String)
    * @desc Proxy a utiliser
    */
    private $my_proxy;

    /**
    * @var (String)
    * @desc Identifiants du proxy sous la forme user:password
    */
    private $proxy_auth;

    /**
    * @var (Array)
    * @desc Referer utilise, si vide pas de référer utilise
    */
    private $referer = array();

    /**
    * @var (Array)
    * @desc Liste des users agents
    */
    private $user_agent = array();

    /**
    * @var (String)
    * @desc User agent a utiliser
    */
    private $my_user_agent;

    /**
    * @var (Array)
    * @desc Entete headers a envoyer
    */
    private $header = array();

    /**
    * @var (String)
    * @desc Nom du fichier contenant les cookies, si vide pas de gestion des cookies
    */
    private $cookies;

    /**
    * @var (String)
    * @desc Contenu recupere par la requete
    */
    private $page_content;

    /**
    * @var (Array)
    * @desc Donnees POST a envoyer
    */
    private $post = array();

    public function init(){
        $this->my_user_agent = $this->randomUserAgent();
        $this->my_proxy = $this->randomProxy();
    }

    public function initFromFile($file_proxy, $file_UA){

        if (!empty($file_proxy)){
            if (file_exists($file_proxy)){
                $handle = fopen($file_proxy, "rb");

                while (($buffer = fgets($handle, 4096)) !== false){
                    $buffer = trim($buffer);
                    preg_match("#([^:]+:[^:]+)[:]*(.*)#", $buffer, $match);

                    $proxy = $match[1];
                    $auth = $match[2];
                    $this->addProxy($proxy);    

                    if (!empty($auth))
                        $this->setProxyAuth($auth);
                }

                fclose($handle);
            }else{
                echo "Le fichier $file_proxy est introuvable !";
            }
        }

        if (!empty($file_UA)){
            if (file_exists($file_UA)){
                $handle = fopen($file_UA, "rb");

                while (($buffer = fgets($handle, 4096)) !== false){
                    $buffer = trim($buffer);
                    $this->addUserAgent($buffer);    
                }

                fclose($handle);
            }else{
                echo "Le fichier $$file_UA est introuvable !";
            }
        }

        $this->init();
    }

    public function setProxy($proxy){
        $this->proxy = $proxy;
    }

    public function setProxyAuth($ident){
        $this->proxy_auth = $ident;
    }

    public function addProxy($proxy){
        array_push($this->proxy, $proxy);
    }

    public function setHeader($head){
        $this->header = $head;
    }

    public function addHeader($head){
        array_push($this->header, $head);
    }

    public function setCookies($file){
        if (!is_file(__PATH__ . $file))
            touch(__PATH__ . $file);

        $this->cookies = __PATH__ . $file;
    }

    public function setReferer($ref){
        $this->referer = $ref;
    }

    public function addReferer($ref){
        array_push($this->referer, $ref);
    }

    public function addPost($data){
        if (sizeof($data) == 2 && is_array($data))
            $this->post[$data[0]] = $data[1];
    }

    public function setUserAgent($UA){
        $this->user_agent = $UA;
    }

    public function reloadCookies(){
        if (is_file($this->cookies)){
            $f = fopen($this->cookies,"w");
            ftruncate($f,0);
        }
    }

    public function deleteCookies(){
        if (is_file($this->cookies)){
            @unlink(__PATH__ . $this->cookies);
        }
    }

    public function addUserAgent($UA){
        array_push($this->user_agent, $UA);
    }

    public function getPageContent(){
        return $this->page_content;
    }

    private function randomProxy(){
        if (sizeof($this->proxy)>0)
            return $this->proxy[rand(0, (sizeof($this->proxy)-1))];
        else
            return null;
    }

    private function randomUserAgent(){
        if (sizeof($this->user_agent)>0)
            return $this->user_agent[rand(0, (sizeof($this->user_agent)-1))];
        else 
            return null;
    }

    public function curl($URL){

        $ch = curl_init($URL); 
        curl_setopt($ch, CURLOPT_FRESH_CONNECT, true); 
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        /**
        * Gestion du proxy
        */
        if (!empty($this->my_proxy)){
            curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, true); 
            curl_setopt($ch, CURLOPT_PROXY, $this->my_proxy); 

            if (!empty($this->proxy_auth)){
                curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->proxy_auth);
            }
        } 

        /**
        * Gestion des cookies
        */
        if (!empty($this->cookies)){
            curl_setopt ($ch, CURLOPT_COOKIEJAR, $this->cookies); 
            curl_setopt ($ch, CURLOPT_COOKIEFILE, $this->cookies); 
        }

        /**
        * Gestion des headers
        */
        if (sizeof($this->post) > 0){
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->post);
        }

        if (sizeof($this->header) > 0){
            curl_setopt($ch, CURLOPT_HTTPHEADER, $this->header);
        }

        if (!empty($this->referer)){
            curl_setopt($ch, CURLOPT_REFERER, $this->referer);
        }

        if (!empty($this->my_user_agent)){
            curl_setopt($ch, CURLOPT_USERAGENT, $this->my_user_agent);
        }

        $this->page_content = curl_exec($ch); 

        curl_close($ch);         
    }

}
?>

Vous pouvez par exemple copier ce code et le coller dans un fichier nommé SimpleCURL.php

Comment utiliser SimpleCURL ?

Voici un exemple d’utilisation avec un fichier contenant des proxies et un autre fichier contenant des user-agents.

Le fichier proxy.txt

108.177.194.213:1234
46.105.154.52:1234:login:password

Vous pouvez utiliser des proxies nécessitant des identifiants en utilisant le modèle suivant PROXY:PORT:LOGIN:PASSWORD

 

Le fichier UA.txt

Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_5_8) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.151 Safari/535.19
Mozilla/5.0 (X11; U; Linux x86_64; en; rv:1.9.0.8) Gecko/20080528 Fedora/2.24.3-4.fc10 Epiphany/2.22 Firefox/3.0
Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120716 Firefox/15.0a2
Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:14.0) Gecko/20100101 Firefox/14.0.1
Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0) Gecko/20120427 Firefox/15.0a1
Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00
Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52
Opera/9.80 (X11; Linux i686; U; it) Presto/2.7.62 Version/11.00

 

Et notre fichier test.php

<?php
include "SimpleCURL.php";

$curl = new SimpleCURL();
$curl->initFromFile("proxy.txt","UA.txt");

$curl->curl("http://www.monip.org/");

echo $curl->getPageContent();
?>

Et voilà grâce au fichier test.php vous pouvez récupérer du contenu distant en quelques lignes seulement tout en utilisant un proxy et un user-agent modifié.

 

Les limites de SimpleCURL

Par contre le fait d’utiliser une fonction aléatoire pour choisir son proxy peut être un point négatif. Car si vous faites du scrap de façon intense il se peut qu’une certaine IP soit plus utilisé qu’une autre parmi votre liste de proxy.
Pour éviter ce problème je pense faire évoluer cette classe pour que l’utilisation des proxies soit répartie de façon équitable dans le temps.

16 thoughts on “Ne scrappez pas sans vos proxies

  1. pierre Reply

    Merci pour cette classe !
    L’article est de bonne qualité, bon courage et bonne continuation pour ton blog…

  2. Débutant SEO Reply

    Merci, ça sert à quoi de collecter le contenu par exemple ? #debutant_vraiment

    • Guillaume

      Alors la liste des objectifs du scrap est vraiment longue.
      Voici un petit exemple :
      Admettons que sur ton site tu veux afficher le programme télé de la soirée. Mais il y à un problème, tu n’as pas envie d’enregistrer toutes les données de chaque jour car ça prend un peu de temps.
      Admettons aussi qu’il n’existe aucun flux permettant de te fournir ces données, cependant il existe un très bon site qui affiche déjà ces données.
      Tu peux te faire un petit script qui récupère ces données à ta place et de façon automatique.
      Par contre il faut faire attention avec tout ce qui est contenu protégé (vol de contenu,…), enfin après c’est toi qui décide 😉

  3. Débutant SEO Reply

    Ha, donc ça sert à créer ce qui existe déjà ailleurs. Non merci, le net est sufissament dupliqué comme ça. Je préfère créer du contenu unique, je suis pas trop “mouton” ,)
    Merci pour les conseils

    • Guillaume

      Non pas forcément, c’était juste un exemple parmi tant d’autre. Comme il est souvent dit : le scrap n’a pour limite que ton imagination.
      Tu peux te faire des outils d’analyse avec comme pour vérifier tes positions dans Google…

  4. Jérôme Reply

    Si je ne me trompe pas, ta solution oblige l’utilisation de proxy et d’UA ?

    Le problème avec l’utilisation de proxy est que :
    – si tu utilises des proxies publics, ils peuvent tomber d’un jour à l’autre
    – si tu utilises des proxies privés, ils peuvent nécessiter de rentrer un captcha
    – tout le monde n’en a pas forcément

    Pour les privés c’est plus rare mais si tu as une utilisation assez vaste de tes proxies : blast, scrap, etc. ça peut vite arriver. Donc dans ces différents cas, il faudrait faire un fallback soit pour nous alerter du problème soit pour utiliser l’ip du serveur en dernier recours si les autres ne fonctionnent pas. De toute façon, il faut suivre le travail de scrap même si c’est une tâche automatisée et quotidienne.

    Bon travail sur cette petite classe sinon :p

    PS : l’histoire des cookies se fait automatiquement ? (détection, ajout dans un fichier txt)

    • Guillaume

      Non tu n’es pas obligé d’utiliser de proxy ou d’user-agent pour cela il te suffit d’enlever la ligne “$curl->initFromFile(“proxy.txt”,”UA.txt”);”
      La méthode pour être alerté en cas de captcha dépend de ton utilisation, et il faudrait adapter le système de détection de captcha au site que tu scrappe.
      Les messages qui t’indiquent de saisir une captcha seront différents suivant les sites. Bon après à part Google je ne connais pas d’autre sites qui te bloquent si tu fait trop de requètes.

      La gestion des cookies ne se fait pas automatiquement il suffit de rajouter un $curl->setCookies(“cookies.txt”); et là les sessions seront enregistrés.
      Mais je ferais surement un petit article pour montrer une utilisation de la classe SimpleCurl avec les cookies 😉

  5. mikiweb - référenceur freelance Reply

    Bonne exemple d’utilise de php pour scrapper un site de façon pratiquement invisible 😉
    Bravo ça permet surtout de voir comment marche certaine class et d’en apprendre un peu plus sur Curl.

    Sinon pour ceux qui veulent un scrapper plus automatisé et un plus puissant que du fait maison => http://scraper.rddz-tools.com/ est le must.
    Pour ceux qui maitrise un peu le code on peut aller paramétrer les requêtes google que l’on veux pour scrapper les urls que l’on veux. A l’intérieur des rquête ont peu même aller rajouter des paramètres custom afin de multiplier les possiblités de scrap en un clic.
    Et se rajoute à cela la possiblité de paramétrer un second niveau de scrap en utilisant xpath qui va permettre de récupérer ce que l’on souhaite à l’intérieur des urls précédements scrappées.

    Avec ça les possibilités sont infini !!

    • Guillaume

      Oui le RDDZ Scraper est un très bon outils, mais pour le moment je n’ai pas eu l’occasion de pousser son utilisation à l’extrême.
      Après il nécessite un investissement, certes très vite rentabilisé si on l’utilise bien. Cette petite classe permet juste de faire du scrap disons “léger” :p
      Toutes façon à partir du moment où on veut faire du scrap très poussé je pense que le PHP n’est plus adapté, il faut alors se tourner vers des langages plus adaptés.

  6. Guillaume Reply

    Salut,

    Super intéressant comme script ! Merci beaucoup, je risque très fortement de l’utiliser dans les prochains jours ! 🙂

    J’ai cependant une question en ce qui concerne ta dernière remarque, en quoi le fait qu’une IP soit plus utilisée peut poser problème puisque l’on passe par un proxy ? Est-ce qu’une solution “facile” ne serait pas d’augmenter considérablement le nombre d’UA et de proxy ?

    Autre question, j’ai déjà vu qu’il était possible de bloquer un accès externe à la fonction file_get_content(), est-ce que c’est possible de faire pareil avec cURL ?

    • Guillaume

      Bonjour,
      Oui en effet le fait d’augmenter considérablement le nombre de proxy permettrais de résoudre ce problème.
      Mais admettons que l’on ne possède que 5 proxy, on veut effectuer 100 requêtes vers le même site. Donc à ce moment là on pourrais avoir une amélioration dans la classe qui permettrais de répartir l’utilisation des proxy, c’est à dire qu’on utilise 20 fois chaque proxy. Et encore mieux on pourrais définir une sorte de cycle. Requête 1 : proxy 1, requête 2 : proxy 2, […], requête 5 : proxy 5, requête 6 : proxy 1,… Comme çà cela permettrais d’espacer les temps d’utilisation, et cette fonctionnalité pourrais être utile si on scrappe beaucoup sur Google.

      Oui en effet on peut bloquer les URL distantes avec file_get_contents via le php.ini, par contre pour curl je ne sais absolument pas. Mais de toute façon je n’en vois pas l’utilité. Parce que utiliser cURL que en local on devient très limité et dans ce cas autant utiliser file_get_contents.

  7. jb Reply

    Bien utile cette fonction. Seul petit bémol c’est que tu enlève la possibilité de faire du cUrl multithread ce qui est à mon sens une des grandes forces de cUrl.

    • Guillaume

      Ah oui je n’y avais pas pensé à cette fonction, en même temps pour le moment je ne m’en suis jamais vraiment servit. Je vais me pencher un peu sur cette fonctionnalité et essayer de l’intégrer à cette classe 😉

  8. evenstood Reply

    Bon article bien complet et bien technique….Pour ma part j’ai arrêter de m’emmerder, j’utilise la source de proxy Scrapebox que je fais tourner chaque matin pour en tirer 50bons.

    Ca me permet gentiment de scrapper ce que je souhaite pour la journée, mais du coup c’est un peu chronophage au début.

Leave a Reply

Your email address will not be published. Required fields are marked *