La maggior parte delle attività di web scraping si risolve con una semplice richiesta GET a una pagina pubblica. Ma non appena i dati necessari finiscono «dietro il login» — nell’area riservata, in una sezione privata o sotto un abbonamento a pagamento — la richiesta ordinaria restituisce il form di accesso o un errore 401/403. Per arrivare al contenuto, lo scraper deve prima autenticarsi, esattamente come fa il browser dell’utente.
Dove si usa più spesso
Lo scenario più diffuso di scraping con autenticazione è il monitoraggio dei prezzi dei negozi online. La situazione tipica è questa: lavori con un fornitore i cui prezzi e le cui disponibilità aggiornati sono accessibili solo nell’area riservata del suo sito. Non c’è un accesso completo ai dati via API, non ci sono export nel formato che ti serve, e i prezzi cambiano così spesso che ricopiarli a mano non è razionale.
Va sottolineato un punto: questo accesso è concordato con il fornitore e non viola le condizioni d’uso del sito. In altre parole, automatizzi l’ottenimento di dati che hai già il permesso di vedere con il tuo account: semplicemente lo fai via programma anziché a colpi di clic. Solo a queste condizioni lo scraping con autenticazione è uno strumento di lavoro legittimo, e non un modo per aggirare le restrizioni.
Prima di scrivere codice, assicurati sempre che:
- l’accesso ai dati sia autorizzato dal proprietario della risorsa (contratto, consenso scritto, condizioni del programma partner);
- la raccolta automatizzata non sia vietata dai termini di servizio (ToS) né dal file
robots.txt; - il carico sul sito resti ragionevole e non ne ostacoli il funzionamento;
- non vengano raccolti né trattati dati personali di terzi senza una base giuridica.
Come funziona l’autenticazione: i quattro meccanismi principali
Per scegliere l’approccio nel codice bisogna capire in che modo il sito autentica l’utente. In pratica si incontrano quattro varianti principali.
1. Form di login e cookie di sessione. Il caso più frequente. Invii nome utente e password con una richiesta POST all’endpoint di autenticazione; il server risponde impostando un cookie di sessione (per esempio sessionid o PHPSESSID) e da quel momento il cookie accompagna tutte le richieste. La sessione vive finché il cookie resta valido.
2. Token CSRF. Molti form sono protetti da un token nascosto nell’HTML della pagina di accesso (in un campo hidden o in un meta tag) oppure fornito in un cookie a parte. Prima di inviare il form bisogna caricare la pagina di login, estrarre il token e trasmetterlo insieme alle credenziali. Senza, il server respingerà la richiesta.
3. Token (Bearer / JWT). I siti moderni e le SPA spesso autenticano l’utente tramite un’API che restituisce un token in JSON. Il token viene poi trasmesso nell’header Authorization: Bearer <token>. Qui i cookie possono non essere usati affatto.
4. HTTP Basic Auth. La variante più semplice: nome utente e password vengono codificati in base64 e viaggiano nell’header Authorization. Si incontra nei sistemi interni e in alcune API.
Una difficoltà a parte sono i siti che generano il contenuto via JavaScript. Lì un normale client HTTP non basta: serve un browser «headless» (Playwright, Puppeteer, Selenium) che esegua gli script e restituisca il DOM già costruito.
Python: requests con sessione
requests.Session() conserva automaticamente i cookie tra una richiesta e l’altra: la base ideale per lo scraping con autenticazione. Esempio con recupero preliminare del token CSRF:
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. Carichiamo la pagina di login ed estraiamo il token 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. Inviamo il form di login
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 "Area riservata" not in resp.text:
raise RuntimeError("Autenticazione non riuscita: controlla le credenziali")
# 3. Sessione stabilita: richiediamo la pagina riservata dei prezzi
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}")Una buona pratica è tenere nome utente e password fuori dal codice — in variabili d’ambiente (os.environ) o in un file .env — per non committarli per errore nel repository.
Python: autenticazione via token (API)
Se il sito autentica tramite un’API JSON e restituisce un token, il codice è più semplice:
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 per i siti in JavaScript
Quando l’area riservata è una SPA e i prezzi vengono caricati dagli script, la soluzione è un browser headless. Playwright sa effettuare il login come un utente reale e perfino salvare lo stato della sessione in un file, per non ripetere l’accesso a ogni esecuzione.
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()
# Login
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/**")
# Salviamo la sessione per riutilizzarla in seguito
context.storage_state(path="auth_state.json")
# Andiamo alla pagina dei prezzi e attendiamo il caricamento dei dati
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()Il file auth_state.json salvato si riaggancia poi con browser.new_context(storage_state="auth_state.json") — e il passaggio di login si può saltare finché la sessione non scade.
Node.js: axios con salvataggio dei cookie
In Node, per conservare i cookie tra le richieste si usa la combinazione axios + tough-cookie + axios-cookiejar-support.
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. Otteniamo il token CSRF dalla pagina di login
const loginPage = await client.get("https://supplier.example.com/login");
const $ = cheerio.load(loginPage.data);
const csrf = $('input[name="csrf_token"]').val();
// 2. Effettuiamo il login
await client.post(
"https://supplier.example.com/login",
new URLSearchParams({
username: "your_login",
password: "your_password",
csrf_token: csrf,
}),
);
// 3. Richiediamo i prezzi
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 per le pagine dinamiche
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();
})();PHP: cURL con i cookie di sessione
In PHP i cookie si conservano tra le richieste in un file, tramite le opzioni COOKIEJAR e COOKIEFILE.
<?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. Carichiamo la pagina di login ed estraiamo il token 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. Inviamo il form di login
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. Richiediamo i prezzi
curl_setopt($ch, CURLOPT_URL, "https://supplier.example.com/account/prices");
curl_setopt($ch, CURLOPT_POST, false);
$pricesHtml = curl_exec($ch);
curl_close($ch);
// Parsing dell'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 con cookiejar
La libreria standard di Go include net/http/cookiejar, che gestisce i cookie automaticamente al posto tuo.
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. Otteniamo il token 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. Effettuiamo il login
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. Estraiamo i prezzi
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)
}
})
}Raccomandazioni pratiche
Riutilizza la sessione. Non fare il login a ogni richiesta: è carico inutile e rischio di blocco. Salva il cookie o il token e rinnovali solo quando la sessione è scaduta.
Gestisci la scadenza della sessione. Cookie e token hanno una durata limitata. Prevedi un controllo: se la richiesta restituisce un redirect al form di login o un codice 401, autenticati di nuovo e ripeti la richiesta.
Mantieni un ritmo ragionevole. Fai pause tra le richieste (per esempio 1–3 secondi) e non lanciare decine di thread in parallelo. È una cortesia verso il server del fornitore e riduce la probabilità di far scattare la protezione anti-bot.
Custodisci i segreti in modo sicuro. Nome utente, password e token vanno in variabili d’ambiente o in un archivio protetto, non nel codice e tanto meno in un repository pubblico.
Metti in conto i cambiamenti di layout. I siti cambiano e i selettori si rompono. Logga gli errori di parsing e configura gli avvisi, per accorgerti in fretta di quando la struttura della pagina è cambiata.
Usa uno User-Agent onesto e, se possibile, indica dei contatti. Se il fornitore ha approvato l’accesso, un bot identificabile semplifica la diagnostica dal suo lato quando qualcosa va storto.
Conclusione
Tecnicamente, lo scraping con autenticazione consiste nel riprodurre i passaggi che il browser compie al momento dell’accesso: ottenere e inviare il form (con il token CSRF, se c’è), salvare il cookie di sessione o il token e allegarlo alle richieste successive. Per le pagine statiche basta un client HTTP con supporto delle sessioni (requests, axios, cURL, net/http); per quelle dinamiche serve un browser headless (Playwright, Puppeteer).
La cosa più importante, però, non sta nel codice ma nelle basi che legittimano la raccolta dei dati. Lo scraping dell’area riservata di un fornitore per il monitoraggio dei prezzi è uno strumento di lavoro legittimo esattamente quando l’accesso è concordato con il proprietario della risorsa e non viola le condizioni d’uso. La tecnologia funziona altrettanto bene in entrambe le direzioni, quindi la responsabilità di usarla correttamente resta tua.