Formati e fonti di dati 9 min di lettura

Parsing di URL: che cos’è, a cosa serve e come si implementa nei vari linguaggi

Parsing di URL: da cosa è composto un indirizzo, come scomporre e costruire i link e come gestire parametri di query e codifica nei vari linguaggi.

TW
Team Web-Scraping.it
Raccolta dati per le esigenze del business
Pubblicato il 17 aprile 2025

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:

code
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:

html
<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#top e /page sono la stessa pagina);
  • portare schema e dominio in minuscolo;
  • rimuovere la porta standard (:443 per 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.

python
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/page

JavaScript (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.

javascript
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.

php
$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.

java
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/contacts

Go

Il pacchetto net/url della libreria standard copre sia il parsing sia la risoluzione dei link (ResolveReference).

go
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:

python
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 via javascript:.

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.