Comment construire un Web Crawler à partir de zéro avec Python ?

Mis à jours 2 mars 2020

La plupart des tutoriels de Web crawling et scraping en Python utilisent une sorte de bibliothèque de crawling. C’est parfait si vous voulez faire les choses rapidement. Mais si vous ne comprenez pas comment le scraping fonctionne vraiment, alors en cas de problème, il vous sera difficile de savoir comment le résoudre.

Dans ce tutoriel, je vais expliquer en détail comment écrire un Web crawler ( aussi connu sous le nom de robot, spider ou araignée web) à partir de zéro en Python.

Pour cela, nous allons utiliser uniquement la bibliothèque standard Python et le module de requêtes (https://pypi.org/project/requests/2.7.0/). Je vais aussi vous expliquer comment utiliser une API proxy (https://proxyorbit.com) pour éviter que votre crawler sera mis en liste noire.

La création de ce robot est pour des fins éducatives. Toutefois, avec un peu d’attention et de soin, ce spider peut devenir aussi robuste et utile que n’importe quel scraper écrit à l’aide d’une bibliothèque. De plus, il sera sans doute plus léger et plus portable.

Les Prérequis

Je suppose que vous avez déjà une connaissance de base de Python et de la programmation en général.

Comprendre comment fonctionne les requêtes HTTP et les expressions régulières sera important pour bien comprendre le code. Je ne vais pas entrer dans les détails de la mise en œuvre de chaque fonction individuelle.

Par contre, je vais vous donner des aperçus généraux sur comment fonctionnent quelques exemples de code et pourquoi certaines choses fonctionnent comme elles le font.

Le robot d’exploration ou crawler que nous allons créer dans ce tutoriel aura pour objectif « d’indexer Internet ».

Il va fonctionner de la même manière que les robots d’exploration de Google. Bien sûr, nous ne pourrons pas indexer Internet. En fait, l’idée est que ce crawler suivra les liens partout sur Internet et enregistrera ces liens quelque part ainsi que certaines informations sur la page.

Commencer de manière progressive

construire un crawler progressivement

La première tâche consiste à faire le travail préparatoire pour notre scraper. Nous allons utiliser une classe pour héberger toutes nos fonctions.

Nous aurons également besoin des modules re et requests donc nous allons les importer.

 import requests    
 import re    
  
 class PyCrawler(object):    
     def __init__(self, starting_url):    
         self.starting_url = starting_url                       
         self.visited = set()    
  
     def start(self):    
         pass                 
  
 if __name__ == "__main__":    
     crawler = PyCrawler()    
     crawler.start()

Vous pouvez voir que c’est très facile pour commencer. Il est important de construire ce genre de choses progressivement. Codez un peu, testez un peu, etc.

Nous avons deux variables d’instance qui nous aideront dans la construction du robot plus tard.

starting_url

Est l’URL initiale de notre robot d’exploration ou crawler

 visited

Cela nous permettra de garder une trace des URLs que nous avons actuellement visitées pour éviter de visiter deux fois la même URL.

Utiliser un set() permet de conserver  la recherche d’URL visitée en temps O(1), ce qui le rend très rapide.

Crawler les sites web

Les sites crawl

Maintenant, nous allons commencer à écrire le crawler.

Le code ci-dessous fera une requête à starting_url et extraira tous les liens sur la page. Ensuite, il va itérer sur tous les nouveaux liens et les rassembler à partir des nouvelles pages.

Il va continuer ce processus récursif jusqu’à ce que tous les liens aient été grattés qui sont possibles à partir du point de départ. Certains sites Web n’ont pas des liens externes, donc ils s’arrêteront plus tôt que les sites pointant vers d’autres sites.

import requests    
import re    
from urllib.parse import urlparse    
 
class PyCrawler(object):    
    def __init__(self, starting_url):    
        self.starting_url = starting_url    
        self.visited = set()    
 
    def get_html(self, url):    
        try:    
            html = requests.get(url)    
        except Exception as e:    
            print(e)    
            return ""    
        return html.content.decode('latin-1')    
 
    def get_links(self, url):    
        html = self.get_html(url)    
        parsed = urlparse(url)    
        base = f"{parsed.scheme}://{parsed.netloc}"    
        links = re.findall('''<a\s+(?:[^>]*?\s+)?href="([^"]*)"''', html)    
        for i, link in enumerate(links):    
            if not urlparse(link).netloc:    
                link_with_base = base + link    
                links[i] = link_with_base       
 
        return set(filter(lambda x: 'mailto' not in x, links))    
 
    def extract_info(self, url):                                
        html = self.get_html(url)                               
        return None                  
 
    def crawl(self, url):                   
        for link in self.get_links(url):    
            if link in self.visited:        
                continue                    
            print(link)                 
            self.visited.add(link)            
            info = self.extract_info(link)    
            self.crawl(link)                  
 
    def start(self):                     
        self.crawl(self.starting_url)    
 
if __name__ == "__main__":                           
    crawler = PyCrawler("https://google.com")        
    crawler.start()

Comme nous pouvons le voir, on a ajouté un peu de nouveau code.

Pour commencer, les méthodes get_html, get_links, crawl et extract_info ont été ajoutées.

get_html()

Est utilisé pour obtenir le code HTML sur le lien actuel

get_links()

Extrait les liens de la page actuelle

 extract_info()

Ce code sera utilisé pour extraire des informations spécifiques sur la page.

La fonction crawl() a également été ajoutée. C’est sans doute l’élément le plus important et le plus compliqué de ce code. « crawl » fonctionne de manière récursive. Il commence à start_url, extrait les liens de cette page, itère sur ces liens, puis réintroduit les liens en eux-mêmes de manière récursive.

Si le le Web était une série de portes et de pièces, alors ce code recherche en principe ces portes et les traverse jusqu’à ce qu’il arrive dans une pièce sans portes.

Lorsque cela se produit, il retourne dans une pièce aux portes inexplorées et y pénètre. Il le fait de manière indéfinie jusqu’à ce que toutes les portes accessibles depuis l’emplacement de départ aient été accédées. Ce type de processus se prête très bien au code récursif.

Si vous exécutez ce script tel quel, il explorera et imprimera toutes les nouvelles URLs qu’il trouve en commençant par google.com.

Extraire le contenu

extraire les données de pages

Maintenant, nous allons extraire les données de pages. Cette méthode (extract_info) est largement basée sur ce que vous essayez de faire avec votre grattoir ou scraper.

Pour les besoins de ce tutoriel, tout ce que nous allons faire est d’extraire les informations de la balise meta si nous pouvons les trouver sur la page.

import requests    
import re    
from urllib.parse import urlparse    
 
class PyCrawler(object):    
    def __init__(self, starting_url):    
        self.starting_url = starting_url    
        self.visited = set()    
 
    def get_html(self, url):    
        try:    
            html = requests.get(url)    
        except Exception as e:    
            print(e)    
            return ""    
        return html.content.decode('latin-1')    
 
    def get_links(self, url):    
        html = self.get_html(url)    
        parsed = urlparse(url)    
        base = f"{parsed.scheme}://{parsed.netloc}"    
        links = re.findall('''<a\s+(?:[^>]*?\s+)?href="([^"]*)"''', html)    
        for i, link in enumerate(links):    
            if not urlparse(link).netloc:    
                link_with_base = base + link    
                links[i] = link_with_base    
 
        return set(filter(lambda x: 'mailto' not in x, links))    
 
    def extract_info(self, url):    
        html = self.get_html(url)    
        meta = re.findall("<meta .*?name=[\"'](.*?)['\"].*?content=[\"'](.*?)['\"].*?>", html)    
        return dict(meta)    

    def crawl(self, url):    
        for link in self.get_links(url):    
            if link in self.visited:    
                continue    
            self.visited.add(link)    
            info = self.extract_info(link)    
 
            print(f"""Link: {link}    
Description: {info.get('description')}    
Keywords: {info.get('keywords')}    
            """)    
 
            self.crawl(link)    
 
    def start(self):    
        self.crawl(self.starting_url)    
 
if __name__ == "__main__":    
    crawler = PyCrawler("https://google.com")     
    crawler.start()

Peu de choses ont changé là, à part le nouveau formatage de l’impression et la méthode extract_info.

La magie réside dans l’expression régulière de la méthode extract_info. Elle recherche dans le HTML toutes les balises meta qui suivent le format <meta name=X content=Y> et renvoie un dictionnaire Python au format {X:Y}

Ces informations sont ensuite imprimées à l’écran pour chaque URL pour chaque requête.

Intégrer l’API Proxy Rotative

API proxy rotative

L’un des principaux problèmes du Web crawling et Scraping est que les sites vous bloqueront si vous faites trop de requêtes, n’utilisez pas d’agent utilisateur acceptable, etc.

L’une des solutions pour limiter ces problèmes est d’utiliser des proxys.

Puis, il faut définir un agent utilisateur différent pour le spider.

Normalement, l’approche proxy vous oblige à acheter une liste de proxys. Autrement, vous pouvez en procurer gratuitement en cherchant sur le le net. La plupart du temps, ces proxys ne fonctionnent même pas ou sont incroyablement lentes. Ce qui rend le Web crawling beaucoup plus difficile.

Pour éviter ce problème, nous allons utiliser ce qu’on appelle une « API proxy rotative ».

C’est une API qui prend en charge la gestion des proxys pour nous. Tout ce que nous avons à faire est d’envoyer une requête à leur point de terminaison API. Ainsi, nous aurons un nouveau proxy qui fonctionne pour notre crawler. L’intégration du service dans la plateforme ne demandera pas plus de quelques lignes supplémentaires de Python.

Le service que nous allons utiliser est Proxy Orbit (https://proxyorbit.com). Je possède et exécute Proxy Orbit.

Ce service est spécialisé dans la création de solutions proxy pour les applications Web crawling. Les proxys sont vérifiés en permanence pour vous assurer que seuls les meilleurs proxys qui fonctionnent sont dans le pool.

import requests    
import re    
from urllib.parse import urlparse    
import os    
 
class PyCrawler(object):    
    def __init__(self, starting_url):    
        self.starting_url = starting_url    
        self.visited = set()    
        self.proxy_orbit_key = os.getenv("PROXY_ORBIT_TOKEN")    
        self.user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"    
        self.proxy_orbit_url = f"https://api.proxyorbit.com/v1/?token={self.proxy_orbit_key}&amp;ssl=true&amp;rtt=0.3&amp;protocols=http&amp;lastChecked=30"    
 
    def get_html(self, url):                                                                                                               
        try:                                                                                                                               
            proxy_info = requests.get(self.proxy_orbit_url).json()                                                                         
            proxy = proxy_info['curl']                                                                                                
            html = requests.get(url, headers={"User-Agent":self.user_agent}, proxies={"http":proxy, "https":proxy}, timeout=5)        
        except Exception as e:                                                                                                        
            print(e)                                                                                                                  
            return ""                                                                                                                 
        return html.content.decode('latin-1')                                                                                         
 
    def get_links(self, url):    
        html = self.get_html(url)    
        parsed = urlparse(url)    
        base = f"{parsed.scheme}://{parsed.netloc}"    
        links = re.findall('''<a\s+(?:[^>]*?\s+)?href="([^"]*)"''', html)    
        for i, link in enumerate(links):    
            if not urlparse(link).netloc:    
                link_with_base = base + link    
                links[i] = link_with_base    
 
        return set(filter(lambda x: 'mailto' not in x, links))    
 
    def extract_info(self, url):    
        html = self.get_html(url)    
        meta = re.findall("<meta .*?name=[\"'](.*?)['\"].*?content=[\"'](.*?)['\"].*?>", html)    
        return dict(meta)    
 
    def crawl(self, url):    
        for link in self.get_links(url):    
            if link in self.visited:    
                continue    
            self.visited.add(link)    
            info = self.extract_info(link)    
 
            print(f"""Link: {link}    
Description: {info.get('description')}    
Keywords: {info.get('keywords')}    
            """)    
 
            self.crawl(link)    
 
    def start(self):    
        self.crawl(self.starting_url)    
 
if __name__ == "__main__":    
    crawler = PyCrawler("https://google.com")    
    crawler.start() 

Comme vous pouvez le voir, peu de choses ont vraiment changé là. On a créé trois nouvelles variables de classe : proxy_orbit_key, user_agent et proxy_orbit_url

proxy_orbit_key obtient le jeton d’API Proxy Orbit à partir d’une variable d’environnement nommée PROXY_ORBIT_TOKEN

La variable user_agent définit l’agent utilisateur du crawler sur Firefox pour donner l’impression que les requêtes proviennent d’un navigateur

proxy_orbit_url est le point de terminaison de l’API Proxy Orbit que nous allons atteindre. Nous allons filtrer nos résultats en ne demandant que des proxys HTTP prenant en charge SSL et vérifiés au cours des 30 dernières minutes.

dans get_html, une nouvelle requête HTTP est en cours vers l’URL de l’API Proxy Orbit pour obtenir le proxy aléatoire et l’insérer dans le module des requêtes. Cela permet de récupérer l’URL que nous essayons de crawler derrière un proxy.

Si tout se passe bien alors c’est tout !

Maintenant, nous devrions avoir un vrai Web crawler qui fonctionne. Ce dernier extrait les données des pages Web et prend en charge les proxys rotatifs.

Aina Strauss
Les derniers articles par Aina Strauss (tout voir)
Share via
Copy link
Powered by Social Snap