Technologies et protections 10 min de lecture

Scraper un site avec authentification : guide complet avec exemples de code

Parser des sites nécessitant une authentification : cookies et sessions, jetons, CSRF, captcha à la connexion et exemples de connexion par programmation.

ÉW
Équipe Web-Scraping.fr
Collecte de données pour votre activité
Publié le: 19 avril 2025

La plupart des tâches de web scraping se règlent par un simple GET vers une page publique. Mais dès que les données visées se trouvent « derrière le login » — dans un espace client, une section privée ou sous abonnement payant — la requête ordinaire renvoie le formulaire de connexion ou une erreur 401/403. Pour atteindre le contenu, le parseur doit d'abord passer l'authentification exactement comme le fait le navigateur de l'utilisateur.

Où cela s'applique le plus souvent

Le scénario le plus répandu de parsing avec authentification, c'est la veille tarifaire des boutiques en ligne. Situation typique : vous avez un fournisseur dont les prix et stocks à jour ne sont accessibles que dans l'espace client de son site. Pas d'accès complet aux données par API, pas d'exports au format voulu, et les prix changent souvent — les reporter à la main n'a aucun sens.

Point important à souligner : cet accès est convenu avec le fournisseur et ne viole pas les règles d'utilisation du site. Autrement dit, vous automatisez l'obtention de données que vous êtes de toute façon autorisé à voir sous votre compte — simplement par programme plutôt qu'à coups de clics. C'est sous cette forme que le parsing avec authentification est un outil de travail légitime, et non un moyen de contourner des restrictions.

Avant d'écrire le code, assurez-vous toujours que :

  • l'accès aux données est autorisé par le propriétaire de la ressource (contrat, accord écrit, conditions du programme partenaire) ;
  • la collecte automatique n'est pas interdite par les conditions d'utilisation (ToS) ni le fichier robots.txt ;
  • la charge sur le site reste raisonnable et ne gêne pas son fonctionnement ;
  • les données personnelles de tiers ne sont ni collectées ni traitées sans fondement.

Comment fonctionne l'authentification : quatre mécanismes principaux

Pour choisir l'approche côté code, il faut comprendre par quel moyen le site authentifie l'utilisateur. En pratique, on rencontre quatre variantes principales.

1. Formulaire de connexion et cookie de session. Le cas le plus fréquent. Vous envoyez identifiant et mot de passe en POST vers l'endpoint d'authentification, le serveur pose en réponse un cookie de session (par exemple sessionid ou PHPSESSID), et ce cookie s'attache ensuite à toutes les requêtes. La session vit tant que le cookie est valide.

2. Jeton CSRF. Beaucoup de formulaires sont protégés par un jeton caché dans le HTML de la page de connexion (champ masqué ou méta-tag) ou fourni par un cookie séparé. Avant d'envoyer le formulaire, il faut d'abord charger la page de login, en extraire le jeton et le transmettre avec les identifiants. Sans cela, le serveur rejette la requête.

3. Jetons (Bearer / JWT). Les sites modernes et les SPA authentifient souvent via une API qui renvoie un jeton en JSON. Le jeton se transmet ensuite dans l'en-tête Authorization: Bearer <token>. Le cookie peut ne pas être utilisé du tout.

4. HTTP Basic Auth. La variante la plus simple : identifiant et mot de passe encodés en base64 et transmis dans l'en-tête Authorization. On la rencontre sur des systèmes internes et certaines API.

Difficulté à part : les sites qui construisent leur contenu via JavaScript. Un client HTTP ordinaire n'y suffit pas ; il faut un navigateur « headless » (Playwright, Puppeteer, Selenium), qui exécute les scripts et livre le DOM déjà prêt.

Python : requests avec session

requests.Session() conserve automatiquement les cookies entre les requêtes — la base idéale pour le parsing avec authentification. Exemple avec récupération préalable du jeton CSRF :

python
import requests
from bs4 import BeautifulSoup

LOGIN_URL = "https://supplier.example.com/login"
PRICES_URL = "https://supplier.example.com/account/prices"

session = requests.Session()
session.headers.update({
    "User-Agent": "Mozilla/5.0 (compatible; PriceMonitor/1.0)"
})

# 1. On charge la page de connexion et on extrait le jeton CSRF
login_page = session.get(LOGIN_URL, timeout=30)
soup = BeautifulSoup(login_page.text, "html.parser")
csrf_token = soup.select_one('input[name="csrf_token"]')["value"]

# 2. On envoie le formulaire de connexion
payload = {
    "username": "your_login",
    "password": "your_password",
    "csrf_token": csrf_token,
}
resp = session.post(LOGIN_URL, data=payload, timeout=30)
resp.raise_for_status()

if "Espace client" not in resp.text:
    raise RuntimeError("Échec de l'authentification — vérifiez les identifiants")

# 3. La session est établie — on demande la page privée des prix
prices_page = session.get(PRICES_URL, timeout=30)
soup = BeautifulSoup(prices_page.text, "html.parser")

for row in soup.select("table.prices tr"):
    cells = row.select("td")
    if len(cells) >= 2:
        name = cells[0].get_text(strip=True)
        price = cells[1].get_text(strip=True)
        print(f"{name}: {price}")

Bonne pratique : garder identifiant et mot de passe hors du code, dans des variables d'environnement (os.environ) ou un fichier .env, pour ne pas les committer par accident dans le dépôt.

Python : authentification par jeton (API)

Si le site authentifie via une API JSON et renvoie un jeton, le code est plus simple :

python
import requests

auth = requests.post(
    "https://supplier.example.com/api/auth/login",
    json={"login": "your_login", "password": "your_password"},
    timeout=30,
)
auth.raise_for_status()
token = auth.json()["access_token"]

headers = {"Authorization": f"Bearer {token}"}
data = requests.get(
    "https://supplier.example.com/api/prices",
    headers=headers,
    timeout=30,
).json()

for item in data["items"]:
    print(item["sku"], item["price"])

Python : Playwright pour les sites en JavaScript

Quand l'espace client est une SPA et que les prix arrivent par scripts, le navigateur headless vient à la rescousse. Playwright sait se connecter comme un utilisateur vivant et même sauvegarder l'état de la session dans un fichier, pour ne pas se reconnecter à chaque lancement.

python
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    context = browser.new_context()
    page = context.new_page()

    # Authentification
    page.goto("https://supplier.example.com/login")
    page.fill("input[name='username']", "your_login")
    page.fill("input[name='password']", "your_password")
    page.click("button[type='submit']")
    page.wait_for_url("**/account/**")

    # On sauvegarde la session pour la réutiliser plus tard
    context.storage_state(path="auth_state.json")

    # On passe aux prix et on attend le chargement des données
    page.goto("https://supplier.example.com/account/prices")
    page.wait_for_selector("table.prices")

    rows = page.query_selector_all("table.prices tr")
    for row in rows:
        cells = row.query_selector_all("td")
        if len(cells) >= 2:
            print(cells[0].inner_text(), "—", cells[1].inner_text())

    browser.close()

Le fichier auth_state.json sauvegardé se branche ensuite via browser.new_context(storage_state="auth_state.json") — et l'étape de connexion peut être sautée tant que la session n'a pas expiré.

Sous Node, pour garder les cookies entre les requêtes, on assemble axios + tough-cookie + axios-cookiejar-support.

javascript
const axios = require("axios");
const { wrapper } = require("axios-cookiejar-support");
const { CookieJar } = require("tough-cookie");
const cheerio = require("cheerio");

const jar = new CookieJar();
const client = wrapper(axios.create({ jar, withCredentials: true }));

async function run() {
  // 1. On récupère le jeton CSRF sur la page de connexion
  const loginPage = await client.get("https://supplier.example.com/login");
  const $ = cheerio.load(loginPage.data);
  const csrf = $('input[name="csrf_token"]').val();

  // 2. On se connecte
  await client.post(
    "https://supplier.example.com/login",
    new URLSearchParams({
      username: "your_login",
      password: "your_password",
      csrf_token: csrf,
    }),
  );

  // 3. On demande les prix
  const pricesPage = await client.get(
    "https://supplier.example.com/account/prices",
  );
  const $$ = cheerio.load(pricesPage.data);

  $$("table.prices tr").each((_, el) => {
    const cells = $$(el).find("td");
    if (cells.length >= 2) {
      const name = $$(cells[0]).text().trim();
      const price = $$(cells[1]).text().trim();
      console.log(`${name}: ${price}`);
    }
  });
}

run().catch(console.error);

Node.js : Puppeteer pour les pages dynamiques

javascript
const puppeteer = require("puppeteer");

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();

  await page.goto("https://supplier.example.com/login");
  await page.type("input[name='username']", "your_login");
  await page.type("input[name='password']", "your_password");
  await Promise.all([
    page.click("button[type='submit']"),
    page.waitForNavigation(),
  ]);

  await page.goto("https://supplier.example.com/account/prices");
  await page.waitForSelector("table.prices");

  const prices = await page.evaluate(() =>
    Array.from(document.querySelectorAll("table.prices tr"))
      .map((row) => {
        const td = row.querySelectorAll("td");
        return td.length >= 2
          ? { name: td[0].innerText.trim(), price: td[1].innerText.trim() }
          : null;
      })
      .filter(Boolean),
  );

  console.log(prices);
  await browser.close();
})();

En PHP, les cookies se conservent entre les requêtes dans un fichier, via les options COOKIEJAR et COOKIEFILE.

php
<?php
$cookieFile = __DIR__ . "/cookies.txt";

function curlInit(string $cookieFile) {
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_COOKIEJAR      => $cookieFile,
        CURLOPT_COOKIEFILE     => $cookieFile,
        CURLOPT_USERAGENT      => "Mozilla/5.0 (compatible; PriceMonitor/1.0)",
    ]);
    return $ch;
}

// 1. On charge la page de connexion et on extrait le jeton CSRF
$ch = curlInit($cookieFile);
curl_setopt($ch, CURLOPT_URL, "https://supplier.example.com/login");
$html = curl_exec($ch);

preg_match('/name="csrf_token"\s+value="([^"]+)"/', $html, $m);
$csrf = $m[1] ?? "";

// 2. On envoie le formulaire de connexion
curl_setopt($ch, CURLOPT_URL, "https://supplier.example.com/login");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    "username"   => "your_login",
    "password"   => "your_password",
    "csrf_token" => $csrf,
]));
curl_exec($ch);

// 3. On demande les prix
curl_setopt($ch, CURLOPT_URL, "https://supplier.example.com/account/prices");
curl_setopt($ch, CURLOPT_POST, false);
$pricesHtml = curl_exec($ch);
curl_close($ch);

// Analyse du HTML
$dom = new DOMDocument();
@$dom->loadHTML($pricesHtml);
$xpath = new DOMXPath($dom);
foreach ($xpath->query("//table[@class='prices']//tr") as $row) {
    $cells = $row->getElementsByTagName("td");
    if ($cells->length >= 2) {
        echo trim($cells->item(0)->textContent) . ": "
           . trim($cells->item(1)->textContent) . PHP_EOL;
    }
}

Go : net/http avec cookiejar

La bibliothèque standard de Go inclut net/http/cookiejar, qui gère automatiquement les cookies pour vous.

go
package main

import (
    "fmt"
    "net/http"
    "net/http/cookiejar"
    "net/url"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

func main() {
    jar, _ := cookiejar.New(nil)
    client := &http.Client{Jar: jar}

    // 1. On récupère le jeton CSRF
    resp, _ := client.Get("https://supplier.example.com/login")
    doc, _ := goquery.NewDocumentFromReader(resp.Body)
    resp.Body.Close()
    csrf, _ := doc.Find(`input[name="csrf_token"]`).Attr("value")

    // 2. On se connecte
    form := url.Values{
        "username":   {"your_login"},
        "password":   {"your_password"},
        "csrf_token": {csrf},
    }
    client.Post(
        "https://supplier.example.com/login",
        "application/x-www-form-urlencoded",
        strings.NewReader(form.Encode()),
    )

    // 3. On parse les prix
    pricesResp, _ := client.Get("https://supplier.example.com/account/prices")
    pricesDoc, _ := goquery.NewDocumentFromReader(pricesResp.Body)
    pricesResp.Body.Close()

    pricesDoc.Find("table.prices tr").Each(func(_ int, s *goquery.Selection) {
        cells := s.Find("td")
        if cells.Length() >= 2 {
            name := strings.TrimSpace(cells.Eq(0).Text())
            price := strings.TrimSpace(cells.Eq(1).Text())
            fmt.Printf("%s: %s\n", name, price)
        }
    })
}

Recommandations pratiques

Réutilisez la session. Ne vous reconnectez pas à chaque requête — charge inutile et risque de blocage. Sauvegardez le cookie ou le jeton et ne les renouvelez que lorsque la session a expiré.

Gérez l'expiration de session. Cookies et jetons ont une durée de vie. Prévoyez la vérification : si la requête renvoie une redirection vers le formulaire de connexion ou un code 401 — ré-authentifiez-vous et rejouez la requête.

Gardez un rythme raisonnable. Faites des pauses entre les requêtes (par exemple 1–3 secondes), ne lancez pas des dizaines de fils parallèles. C'est à la fois poli envers le serveur du fournisseur et moins risqué face aux protections anti-bots.

Stockez les secrets en sécurité. Identifiant, mot de passe et jetons — dans des variables d'environnement ou un coffre sécurisé, pas dans le code et encore moins dans un dépôt public.

Soyez robuste aux changements de balisage. Les sites évoluent, les sélecteurs cassent. Journalisez les erreurs de parsing et configurez des notifications pour repérer vite un changement de structure de page.

Affichez un User-Agent honnête et, si possible, des coordonnées de contact. Si l'accès est convenu avec le fournisseur, un bot identifiable simplifie le diagnostic de son côté en cas de problème.

Conclusion

Techniquement, le parsing avec authentification revient à reproduire les étapes que fait le navigateur à la connexion : obtenir et envoyer le formulaire (avec le jeton CSRF s'il existe), conserver le cookie de session ou le jeton, et l'attacher aux requêtes suivantes. Pour les pages statiques, un client HTTP avec gestion de session suffit (requests, axios, cURL, net/http) ; pour les dynamiques — un navigateur headless (Playwright, Puppeteer).

Mais l'essentiel n'est pas dans le code : il est dans les fondements de la collecte. Parser l'espace client d'un fournisseur pour la veille tarifaire est un outil de travail légitime exactement lorsque l'accès est convenu avec le propriétaire de la ressource et ne viole pas les règles d'utilisation. La technologie fonctionne aussi bien dans les deux sens, la responsabilité de son usage correct vous revient donc.