# Cours 5

Introductions au techniques de crawl en Python

Principes du crawl

Le crawl est une technique qui consiste à naviguer sur ou plusieurs site pour en récupérer les information C'est la technique qu'emploient les moteurs de recherche pour créer une base de données qui contient les références au site et les mots clés qui correspondent à la recherche. La base du crawl est de stocker les liens présents dans une page, de les télécharger au fur et à mesure et de stocker les informations intéressantes. Le crawl permet donc de reconstituer une base de données en collectant les informations html et en les formattant pour les insérer dans une base

Les différents types de crawl

Il existe autant de crawler que de besoins spécifiques parfois et même souvent une même extraction peut procéder de différentes manières et donc avoir différents algorithmes.

Pour la collecte de données, on développe des crawlers spécifiques:

  • qui suit le parcours identifié sur le site
  • a une profondeur fixée
  • se concentre uniquement sur le site en question
  • extrait les informations préalablement identifiées
  • représente souvent les données sous forme tabulaire

Par opposition le crawler web que développent les moteurs de recherche et qu'on appelera ici Spider

  • suit toujours le même parcours
  • ne se concentre sur aucun site en particuler
  • n'extrait que les informations qui sont communes à tous les sites web
  • représente souvent les données sous forme de réseau ou de graphes

Le Spider web

Algorithme et implémentation

L'algorithme d'un spider simplifié fonctionne de la manière suivante:

A partir d'une url de départ:

    * télécharger la page
    * parser le contenu
    * extraire toutes les urls

Pour chaque url de la liste de départ:

* refaire le travail
* et ainsi de suite jusqu'à ce qu'il n'y ait plus d'url à traiter

En pseudo code python cela donnerait


In [ ]:
tocrawl = []
def crawl(url):
    html = download(url)
    page = parse(html)
    urls = extract_links(page)
    tocrawl.append(urls)
    return tocrawl

starter_url = "www.example.com"
tocrawl = crawl(starter_url)
while len(tocrawl) != 0:
    for url in tocrawl:
        crawl(url)

Evidemment c'est un tout petit peu plus compliqué que ça... Quelques exemples...

  • Fonctionnement du Spider de Google
  • Fonctionnement de Hyphe
  • Fonctionnement de Crawtext
  • Reprendre le fonctionnement des crawlers en ligne: détailer leur algorithme
Représentation des données

Les données du spider permettent de constituer des réseaux et des graphes en fonctions des informations collectées. Les réseaux constitués les plus simple constistent en graphes de co-citations une url source etant reliée a plusieurs urls par le fait quelle les mentionnent sur la page on peut cartographier les sites qui se citent les uns les autres.

Le crawler de site

Algorithme et implémentation

L'algorithme d'un crawler de site simplifié fonctionne de la manière suivante: Une fois le parcours sur le site et les informations identifiées, le robot aggrège les données cibles qu'il stocke dans une base de données ou un fichier final à plat. La parcours varie en fonction de l'information auquel on souhaite accéder et la manière dont on veut interroger les données.

L'objectif étant de récupérer l'ensemble des arguments pour chaque article de loi

  • Lister les urls de tous les participants Pour chaque participants:
    • extraire les arguments qui lui appartiennent
Représentation des données

Le crawl produit une base de données d'arguments reliés à un article de loi Dans le cas d'un crawl sur un site particulier, il est capital de définir avant le crawl les données à extraire et les différentes étapes pour appliquer l'extraction à plusieurs pages

TP: construction d'un crawler de site web

Extracteur d'une page web

Reprenons les étapes d'extraction pour la page d'un site. à travers plusieurs exercices

Exercice 0: Extraire les liens d'une page web

Vous pouvez télécharger le [script0] complet d'exemple ou le copier coller dans votre éditeur


In [ ]:
#!/usr/bin/python

Définissons ensuite l'encodage pour prendre en compte les accents


In [ ]:
# coding: utf-8
  • un module pour ecrire des csv

In [1]:
import csv

On importe au debut du script tous les modules complémentaire dont on va avoir besoin et qui sont importer grace à l'instruction import

  • un module de téléchargement

In [1]:
import requests
  • un module qui permet de parcourir le html

In [ ]:
from bs4 import BeautifulSoup

Pour BeautifulSoup, on spécifie le type parser que nous allons utiliser


In [ ]:
BeautifulSoup("html.parser")

On va ensuite écrire les fonctions dont on a besoin:

* une fonction pour télécharger les urls ``download``

* une fonction pour écrire dans un fichier ``write_csv``

* une fonction pour extraire les liens ``extract_links``

Pour les utiliser ensuite (les appeler) dans notre fichier à la fin comme on va voir par la suite


In [1]:
def download(url):
    '''fonction qui télécharge une page a partir d'une url et retourne le html sous forme de texte'''
    #on appelle le module requests qui permet de télécharger la page
    #et on stocke le résultat du téléchargement
    #dans une variable qu'on appelle ici reponse
    reponse = requests.get(url)
    #response est produite par le module requests
    #elle a donc des valeurs spécifiques qui sont documentés sur la page du module
    # requests 
    #tel que le code de status de la page cf[[Les codes d'erreur HTTP
    # le texte ou encore un json
    if response.status_code == 200:
        return response.text

L'avantage d'écrire une fonction est qu'elle peut etre utilisée autant de fois
qu'on veut avec n'importe quelle variable c'est à dire pour cette fonction download avec n'importe quelle url


In [ ]:
#ici la fonction pour écrire une ligne dans un fichier CSV
def write_csv(filename, line):
    #on ouvre le fichier appelé filename comme un fichier csv
    # a pour ajouter à la suite (append)
    #r pour lire (read)
    #w pour ecrire (write)
    with open(filename, 'a') as csvfile: 
        #on met le fichier dans le writer de csv
        #en specifiant la délimitation ici ";"
        spamwriter = csv.writer(csvfile, delimiter=';')
        #line doit etre une liste d'éléments représentée en python par []
        # chaque element contenu dans line va s'inscrire dans une colonne
        spamwriter.writerow(data)
    return

In [ ]:
#ici la fonction pour extraire des liens a partir d'une url
def extract_links(url):
    #on télécharge le contenu de la page
    #avec une fonction qu'on a déjà écrite plus haut
    html = download(page)
    #on va stocker tous les liens contenu dans la page dans une liste
    #avec le nom
    links = []
    soup = BeautifulSoup(html)
    
    #la fonction find_all renvoie une **liste**
    #de plusieurs elements BeautifulSoup
    # qui sont manipulable en utilisant les fonctions de Beautifulsoup
    #on a besoin ici de récupérer le lein contenu de la balise a href="{{lien}}"
    #qu'on va stocker dans notre liste comme dans un tableau
    
    #ici on veut récupérer toutes les balises de type a sans filtre particulier
    tag_link_list = soup.find_all("a")
    #name est donc une liste d'element parsés de tags
    #on va donc derouler les élements contenu
    for element in tag_link_list:
        #l'url de la page est stocké
        #dans le corps de la balise
        #exemple <a href="http://lemonde.fr">Site du Monde </a>
        #pour le récupérer on utilise la fonction de Beautifulsoup get
        lien = element.get("href")
        #on ajoute le lien à la liste
        liens.append(lien)
    return liens

In [ ]:
#ici la fonction qui permet d'extraire les informations interessantes
#pour un site particulier
def extract_data(url):
    #on télécharge le contenu de la page
    #avec une fonction qu'on a déjà écrite plus haut
    html = download(page)
    #on transforme le html en le parcourant (parsing) avec BeautifulSoup
    soup = BeautifulSoup(html)
    #on va stocker tous les futures informations 
    #dans une liste qu'on appelle data
    data = []
    #pour extraire les données on utilise les fonctions de BeautifulSoup
    #find_all renvoie une liste d'elements
    #find renvoie le premier élement
    
    #imaginons que nous voulons extraire toutes les videos de cette page
    #http://www.bbc.com/news/science_and_environment
    #elles sont contenues dans la balise suivante
    #<div id="comp-candy-asset-munger" class="distinct-component-group container-condor wide-only">
    #on peut découper la partie qui nous interesse
    #en utilisant find qui renvoie la première balise
    # et l'id qui permet de trouver cette balise particuliere
    colonne_videos = soup.find("div", {"id": "comp-candy-asset-munger"})
    #on peut ensuite appliquer les méthodes de BeautifulSoup à l'interieur de cette partie
    #recuperer toutes les images en utilisant find_all
    img_tags = colonne_videos.find_all("img", {"class":"responsive-image__inner-for-label"})
    #il s'agit encore d'une liste de tag
    #on va récupérer la source de cette image stockée dans src
    for img in img_tags:
        image_sources = img.get("scr")
        
        data.append([image_sources, )
    return data

Toutes ces fonctions toutes sont universelles et s'applique à n'importe quelle page html sauf la derniere extract_data() qui extrait les données a partir de tag qui n'existent que pour ce site et cette page précise. Vous pouvez télécharger la version propre et complete depuis ce fichier

Maintenant nous allons appeler les différentes fonctions pour faire marcher notre crawler


In [ ]:
#### Exemple d'un crawl simple à profondeur 1
Ici le code de base pour extraire tous les liens d'une page de départ:
on part d'une url de départ qu'on télécharge et dont on extrait les liens qu'on stocke dans une liste

In [ ]:
url_de_depart='http://www.bbc.com/news/science_and_environment'
liens_page0 = extract_links(url_de_depart)
liens1 = []
# on déroule les urls de la page
for page in liens_page0:
    # pour chaque lien 
    for lien in extract_links(page):
        #on ajoute dans la liste
        liens.append(lien)

La liste de toutes les urls de cette page de départ est stockées dans liens1 pour savoir combien d'url on a récupéré on peut appeler une fonction standard dans python len(uneliste) qui a partir d'une liste en parametres donne le nombre d'élements présents dans la liste


In [ ]:
nb_liens1 = len(liens1)
print nb_liens

Crawl simple d'urls

Maintenant imaginons que nous soyons un simple crawler qui collecte toutes les url sans rien stocker de plus que des liens mais à profondeur infinie

On a besoin d'une liste d'url à traiter et d'une liste d'url déjà traitée pour s'assurer que le robot ne tourne pas à l'infini et qu'il ne crawl pas plusieurs fois une même url pour la même raison il est utile aussi de vérifier qu'une url n'est pas en double dans les deux listes


In [ ]:
url_de_depart='http://www.bbc.com'
#liste d'url a traiter
to_do = []
#liste d'url traitées
done = []

starter = extract_links(url_de_depart)
# on déroule les urls de la page
for page in starter:
    # pour chaque lien 
    for lien in extract_links(page):
        #on l'ajoute à faire
        to_do.append(lien)
    #on ajoute la page dont les liens ont déjà ete extrait
    done.append(page)

#while est une instruction spéciale en python de la manière que for c'est une boucle
#while permet d'executer des instructions tant que la condition est remplie
#ici tant que la liste de to_do n'est pas vide
while len(to_do) != 0:
    #on enlève les éventuels doublons avec la fonction de python set() qui enlève les éléments en double
    uniq_to_do = set(to_do)
    for lien in uniq_to_do:
        for new_link in extract_links(lien):
            if new_link not in done:
                to_do.append(new_link)
        done.append(lien)
        to_do.remove(lien)
        
#ici on aura donc au final une liste de url crawlées stockées dans la liste done

In [ ]: