Il parsing di URL (dall’inglese parse, «analizzare») è la scomposizione dell’indirizzo di una pagina web nelle sue parti costitutive: protocollo, dominio, porta, percorso, parametri di query e frammento. Da una stringa «grezza» come https://example.com/catalog?page=2 otteniamo un oggetto strutturato, in cui ogni campo può essere letto singolarmente e, se serve, modificato.
A prima vista il compito sembra banale: «spezzare la stringa sui caratteri giusti». In pratica gli URL hanno una specifica formale (RFC 3986) piena di sfumature: codifica dei caratteri speciali, link relativi, parti opzionali dell’indirizzo, normalizzazione. Per questo quasi ogni linguaggio include uno strumento standard per il parsing degli URL, e scrivere un parser tutto proprio con le espressioni regolari non è praticamente mai necessario.
Da cosa è composto un URL
Analizziamo un indirizzo il più «completo» possibile per vedere tutti i componenti:
https://user:pass@example.com:8080/catalog/items?page=2&sort=price#reviews
└─┬─┘ └──┬───┘ └────┬────┘└─┬─┘└─────┬──────┘ └──────┬──────┘ └──┬──┘
scheme userinfo host port path query fragment| Componente | Valore nell’esempio | Funzione |
|---|---|---|
| scheme (schema) | https |
Protocollo di accesso: http, https, ftp, mailto ecc. |
| userinfo | user:pass |
Credenziali (si usa di rado, soprattutto per FTP/basic auth) |
| host | example.com |
Nome di dominio o indirizzo IP |
| port (porta) | 8080 |
Porta; se non indicata, si usa quella standard dello schema (80 per http, 443 per https) |
| path (percorso) | /catalog/items |
Percorso della risorsa sul server |
| query | page=2&sort=price |
Parametri di query nel formato «chiave=valore» |
| fragment (frammento) | reviews |
Rimando a una sezione specifica della pagina; non viene inviato al server |
Sono esattamente queste le parti che il parser estrae. Con esse si può poi lavorare: leggere il valore di un parametro GET specifico, sostituire il dominio, scartare il frammento, costruire un nuovo URL dai componenti modificati.
A cosa serve il parsing di URL
Il parsing di URL compare praticamente in qualsiasi applicazione che lavori con indirizzi web. Gli scenari più frequenti:
- Estrazione dei parametri di query. Serve capire in quale pagina del catalogo si trova l’utente (
?page=5), quale filtro ha scelto o quale tag UTM porta con sé. Il parser trasforma la query string in un comodo dizionario. - Validazione e sanitizzazione dei link. Prima di salvare nel database un URL fornito dall’utente o di usarlo in un redirect, è importante verificare che lo schema sia consentito (per esempio che non sia
javascript:) e che il dominio rientri nella whitelist. - Routing. I framework web analizzano percorso e parametri per stabilire quale handler invocare.
- Analisi e logging. Per raggruppare gli accessi per dominio o per percorso, gli indirizzi vanno prima scomposti in parti.
- Web scraping e crawling. Qui il parsing di URL è particolarmente importante: ne parliamo in dettaglio più avanti.
Il parsing di URL nella costruzione dei crawler
Un crawler (robot di scansione) percorre un sito passando di pagina in pagina attraverso i link. Per capire in che cosa il crawling si distingue dallo scraping e dal parsing vero e proprio, ti consigliamo di leggere prima il nostro articolo «Scraping, parsing e crawling: quali sono le differenze?». In breve: il crawler si occupa della scansione e della scoperta delle pagine, lo scraper dell’estrazione dei dati e il parsing dell’analisi del contenuto ottenuto, indirizzi compresi.
La principale «trappola» nella scansione di un sito è che i link nell’HTML sono quasi sempre relativi, non assoluti. Nel markup troverai:
<a href="/about">Chi siamo</a>
<a href="../catalog/phones">Telefoni</a>
<a href="page2.html">Successiva</a>
<a href="#reviews">Recensioni</a>Il robot non può seguire il link /about o page2.html così com’è: non sono indirizzi completi. Per proseguire la scansione, il link relativo va convertito in forma assoluta prendendo come base l’indirizzo della pagina corrente (l’URL base). È proprio questo il compito del parsing di URL:
| URL base della pagina | Link relativo | Risultato (URL assoluto) |
|---|---|---|
https://example.com/catalog/items/ |
/about |
https://example.com/about |
https://example.com/catalog/items/ |
../phones |
https://example.com/catalog/phones |
https://example.com/catalog/items/ |
page2.html |
https://example.com/catalog/items/page2.html |
https://example.com/catalog/items/ |
https://cdn.example.com/x |
https://cdn.example.com/x (già assoluto) |
Questa operazione si chiama risoluzione del link (URL resolution) e quasi tutti i linguaggi offrono una funzione pronta: urljoin in Python, il costruttore new URL(href, base) in JavaScript, URI.resolve() in Java, ResolveReference() in Go.
Normalizzazione ed eliminazione dei duplicati
Oltre a convertire i link in forma assoluta, per il crawler è importante normalizzare gli indirizzi, per non visitare più volte la stessa pagina. Le tecniche tipiche:
- scartare il frammento
#fragment: punta a una porzione interna della pagina, non a una risorsa distinta (/page#tope/pagesono la stessa pagina); - portare schema e dominio in minuscolo;
- rimuovere la porta standard (
:443per https); - ordinare o filtrare i parametri di query (per esempio eliminare i tag UTM).
Dopo la normalizzazione, gli URL assoluti si salvano nell’insieme degli indirizzi già visitati: così il robot non entra in loop e non fa richieste superflue.
Esempi di implementazione di parser di URL
Di seguito, come scomporre un URL nelle sue parti e come risolvere un link relativo con gli strumenti standard dei linguaggi più diffusi.
Python
Nella libreria standard se ne occupa il modulo urllib.parse.
from urllib.parse import urlparse, urljoin, urldefrag, parse_qs
url = "https://example.com:8080/catalog/items?page=2&sort=price#reviews"
parsed = urlparse(url)
print(parsed.scheme) # https
print(parsed.hostname) # example.com
print(parsed.port) # 8080
print(parsed.path) # /catalog/items
print(parsed.query) # page=2&sort=price
print(parsed.fragment) # reviews
# Parametri di query -> dizionario
print(parse_qs(parsed.query)) # {'page': ['2'], 'sort': ['price']}
# Risoluzione di un link relativo (per il crawler)
base = "https://example.com/catalog/items/"
print(urljoin(base, "../about")) # https://example.com/catalog/about
print(urljoin(base, "/contacts")) # https://example.com/contacts
print(urljoin(base, "page2.html")) # https://example.com/catalog/items/page2.html
# Scartare il frammento durante la normalizzazione
print(urldefrag("https://example.com/page#reviews")[0]) # https://example.com/pageJavaScript (browser e Node.js)
La classe integrata URL funziona allo stesso modo nel browser e in Node.js. Sa anche risolvere i link relativi: basta passare l’indirizzo base come secondo argomento.
const url = new URL("https://example.com:8080/catalog/items?page=2&sort=price#reviews");
console.log(url.protocol); // "https:"
console.log(url.hostname); // "example.com"
console.log(url.port); // "8080"
console.log(url.pathname); // "/catalog/items"
console.log(url.search); // "?page=2&sort=price"
console.log(url.hash); // "#reviews"
// Parametri di query
console.log(url.searchParams.get("page")); // "2"
// Risoluzione di un link relativo
const base = "https://example.com/catalog/items/";
console.log(new URL("../about", base).href); // "https://example.com/catalog/about"
console.log(new URL("/contacts", base).href); // "https://example.com/contacts"PHP
Il parsing di base lo esegue parse_url(), mentre parse_str() trasforma la query string in un array.
$url = "https://example.com:8080/catalog/items?page=2&sort=price#reviews";
$parts = parse_url($url);
echo $parts['scheme']; // https
echo $parts['host']; // example.com
echo $parts['port']; // 8080
echo $parts['path']; // /catalog/items
echo $parts['fragment']; // reviews
parse_str($parts['query'], $query);
print_r($query); // ['page' => '2', 'sort' => 'price']In PHP non esiste un equivalente integrato di urljoin, quindi per risolvere i link relativi nei crawler si aggiunge di solito una libreria — per esempio league/uri o gli helper di Guzzle (GuzzleHttp\Psr7\UriResolver).
Java
La libreria standard include la classe java.net.URI per lavorare con gli indirizzi. Il metodo resolve() restituisce direttamente la risoluzione del link relativo.
import java.net.URI;
URI uri = URI.create("https://example.com:8080/catalog/items?page=2&sort=price#reviews");
System.out.println(uri.getScheme()); // https
System.out.println(uri.getHost()); // example.com
System.out.println(uri.getPort()); // 8080
System.out.println(uri.getPath()); // /catalog/items
System.out.println(uri.getQuery()); // page=2&sort=price
System.out.println(uri.getFragment()); // reviews
// Risoluzione di un link relativo
URI base = URI.create("https://example.com/catalog/items/");
System.out.println(base.resolve("../about")); // https://example.com/catalog/about
System.out.println(base.resolve("/contacts")); // https://example.com/contactsGo
Il pacchetto net/url della libreria standard copre sia il parsing sia la risoluzione dei link (ResolveReference).
package main
import (
"fmt"
"net/url"
)
func main() {
u, _ := url.Parse("https://example.com:8080/catalog/items?page=2&sort=price#reviews")
fmt.Println(u.Scheme) // https
fmt.Println(u.Hostname()) // example.com
fmt.Println(u.Port()) // 8080
fmt.Println(u.Path) // /catalog/items
fmt.Println(u.RawQuery) // page=2&sort=price
fmt.Println(u.Query().Get("page")) // 2
// Risoluzione di un link relativo
base, _ := url.Parse("https://example.com/catalog/items/")
ref, _ := url.Parse("../about")
fmt.Println(base.ResolveReference(ref)) // https://example.com/catalog/about
}Esempio pratico: raccogliamo tutti i link di una pagina
Mettiamo tutto insieme nel passaggio tipico di un crawler: fare il parsing dell’HTML, estrarre tutti i link e convertirli in forma assoluta. Un esempio in Python con requests e BeautifulSoup:
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urldefrag
def extract_links(page_url: str) -> set[str]:
response = requests.get(page_url, timeout=10)
soup = BeautifulSoup(response.text, "html.parser")
links = set()
for a in soup.find_all("a", href=True):
# 1. convertiamo il link relativo in assoluto
absolute = urljoin(page_url, a["href"])
# 2. scartiamo il frammento per non generare duplicati
absolute, _ = urldefrag(absolute)
# 3. teniamo solo i link http(s)
if absolute.startswith(("http://", "https://")):
links.add(absolute)
return links
for link in extract_links("https://example.com/catalog/"):
print(link)Qui il parsing di URL svolge due funzioni chiave: urljoin trasforma gli href relativi in indirizzi completi che il robot potrà seguire, e urldefrag evita di considerare /page e /page#top pagine diverse.
Errori frequenti
- Fare il parsing degli URL con le espressioni regolari. Per l’abbondanza di casi limite nella specifica, un parser scritto a mano risulta quasi sempre incompleto. Usa le librerie standard.
- Dimenticare i link relativi. Senza la conversione in forma assoluta, il crawler «inciampa» sul primo link interno.
- Non normalizzare gli indirizzi. Senza scartare il frammento, ordinare i parametri e uniformare maiuscole e minuscole, il robot continua a girare sulle stesse pagine.
- Fidarsi dello schema senza verificarlo. Prima di un redirect o dell’inserimento di un link, assicurati che lo schema sia sicuro (
http/https), altrimenti si apre la strada a un XSS viajavascript:.
Conclusione
Il parsing di URL è un’operazione di base ma importante: trasforma una stringa-indirizzo in una struttura con cui è comodo lavorare. Nelle applicazioni ordinarie serve per estrarre parametri, validare e fare routing; nei crawler diventa critico: senza la conversione dei link relativi in assoluti e senza la normalizzazione, la scansione di un sito è semplicemente impossibile. La buona notizia è che quasi ogni linguaggio offre per questo uno strumento standard e affidabile, e non serve reinventare la ruota.