Potion Bottle Icon Manuel d'alchimie du code Potion Bottle Icon

Web scraping avec Python — Atelier LINUQ

- 507 mots - Temps de lecture estimé: 3 minutes


Sun Face IconWeb scraping avec PythonSun Face Icon


Atelier LINUQ pratique : extraction de données météo depuis Environnement Canada avec RoboBrowser, et collecte de données économiques via l’API du Bureau of Labor Statistics américain. Deux approches complémentaires du scraping en Python.

Le 9 décembre 2017, j’ai animé un atelier web scraping avec Python au LINUQ. L’objectif : montrer deux approches complémentaires pour extraire des données sur le web avec Python, en utilisant Jupyter Notebooks.

🌘 Deux approches, deux bibliothèques

Le web scraping, c’est l’art d’extraire des données depuis des pages web de façon automatisée. Selon la source, tu peux soit simuler un navigateur avec RoboBrowser, soit interroger une API directement avec requests.

graph TD
    A[Source de données web] --> B{Source offre-t-elle une API?}
    B -->|Oui| C[requests + JSON]
    B -->|Non| D[RoboBrowser<br>requests + BeautifulSoup]
    C --> E[Données structurées fiables]
    D --> F[Interagir avec formulaires<br>Parser le HTML]
    E --> G[Analyse]
    F --> G

On a testé les deux.

🌘 Scraping météo avec RoboBrowser

Premier exemple : récupérer les prévisions météo d’Environnement Canada pour la ville de Québec.

import re
from robobrowser import RoboBrowser

browser = RoboBrowser(history=True, parser="lxml")
browser.open('https://meteo.gc.ca/')

On ouvre la page et on repère le formulaire de sélection de ville :

form = browser.get_form(id="cityjump")
form

Ce qui affiche le formulaire vide :

<RoboForm city=, lang=f, unit=>

On remplit le champ city avec « Québec » :

form['city'].value = 'Québec'
browser.submit_form(form)

Une fois la page de résultats chargée, on extrait les informations avec des sélecteurs CSS. Le site utilise des classes Bootstrap, ce qui facilite le ciblage :

location_xml = browser. \
    select('.col-sm-10')[0]. \
    select('dl.mrgn-bttm-0')[0]. \
    select('dd.mrgn-bttm-0')

Pour extraire les températures jour par jour, on crée une fonction. RoboBrowser ne gère pas le sélecteur nth-child(), mais on peut utiliser les listes Python pour reproduire le même comportement :

def temperature_xml(jour):
    return browser. \
    select('div.div-column')[jour]. \
    select('div')[1]. \
    select('p.mrgn-bttm-0')[0]. \
    select('span')[0]

Finalement, on assemble le tout dans un dictionnaire Python :

{
    'city': location_xml[0].text,
    'date': location_xml[1].text,
    'temperature': [temperature_xml(i).text for i in range(0, 7)]
}

Résultat :

{
    'city': 'Aéroport int. Lesage de Québec',
    'date': '13h00 HNE le samedi 9 décembre 2017',
    'temperature': ['-1°C', '-2°C', '-13°C', '-4°C', '-8°C', '-11°C', '-11°C']
}

Une belle journée d’hiver à Québec !

Voici le flux de l’interaction avec RoboBrowser :

sequenceDiagram
    participant Script
    participant Site as meteo.gc.ca
    Script->>Site: browser.open()
    Script->>Site: get_form(cityjump)
    Script->>Site: form.city = "Québec"
    Script->>Site: submit_form(form)
    Site->>Script: Page de résultats HTML
    Script->>Script: CSS selectors (.col-sm-10, dl, dd)
    Script->>Script: temperature_xml(jour) en boucle
    Script->>Script: Dictionnaire final {city, date, temperature}

🌘 Données économiques avec l’API du BLS

Deuxième exemple : interroger l’API publique du Bureau of Labor Statistics des États-Unis pour récupérer des séries de données sur l’inflation.

import requests
import json
import prettytable

headers = {'Content-type': 'application/json'}
data = json.dumps({
    "seriesid": ['CUUR0000SA0', 'SUUR0000SA0'],
    "startyear": "2011",
    "endyear": "2014"
})
p = requests.post(
    'https://api.bls.gov/publicAPI/v2/timeseries/data/',
    data=data,
    headers=headers
)

La réponse est en JSON. On la parse et on inspecte les en-têtes HTTP :

p.headers
json_data = json.loads(p.text)

On extrait les données et on les formate avec PrettyTable pour produire des fichiers texte lisibles :

for series in json_data['Results']['series']:
    x = prettytable.PrettyTable(["series id", "year", "period", "value", "footnotes"])
    seriesId = series['seriesID']
    for item in series['data']:
        year = item['year']
        period = item['period']
        value = item['value']
        footnotes = ""
        for footnote in item['footnotes']:
            if footnote:
                footnotes = footnotes + footnote['text'] + ','
        if 'M01' <= period <= 'M12':
            x.add_row([seriesId, year, period, value, footnotes[0:-1]])
    output = open(seriesId + '.txt', 'w')
    output.write(x.get_string())
    output.close()

Ce script génère deux fichiers texte contenant les séries chronologiques des indices des prix à la consommation (CPI) et des salaires, prêts à être analysés.

L’appel à l’API du BLS suit un flux plus direct :

sequenceDiagram
    participant Script
    participant API as api.bls.gov
    Script->>API: POST /publicAPI/v2/timeseries/data/
    Note over Script,API: JSON: seriesid, startyear, endyear
    API->>Script: Réponse JSON
    Script->>Script: json.loads() → séries
    Script->>Script: PrettyTable → format tabulaire
    Script->>Script: Écriture dans .txt

🌘 Ce qu’il faut retenir

Les notebooks complets sont dans le dossier de l’atelier.

Tu as déjà fait du web scraping en Python ? Quelle bibliothèque utilises-tu ?

Abonne-toi au fil RSS pour ne rien manquer.

Étiquettes