El parseo de URL (del inglés parse, «analizar») es la descomposición de la dirección de una página web en sus partes constituyentes: el protocolo, el dominio, el puerto, la ruta, los parámetros de consulta y el fragmento. De una cadena «en bruto» como https://example.com/catalog?page=2 obtenemos un objeto estructurado, en el que cada campo puede consultarse por separado y, si hace falta, modificarse.
A primera vista, la tarea parece trivial: «trocear una cadena por caracteres». En la práctica, las URL tienen una especificación formal (RFC 3986) con multitud de matices: codificación de caracteres especiales, enlaces relativos, partes opcionales de la dirección, normalización. Por eso casi todos los lenguajes incluyen una herramienta estándar para parsear URL, y escribir un análisis propio con expresiones regulares prácticamente nunca es necesario.
De qué se compone una URL
Analicemos una dirección lo más «completa» posible para ver todos los componentes:
https://user:pass@example.com:8080/catalog/items?page=2&sort=price#reviews
└─┬─┘ └──┬───┘ └────┬────┘└─┬─┘└─────┬──────┘ └──────┬──────┘ └──┬──┘
scheme userinfo host port path query fragment| Componente | Valor en el ejemplo | Función |
|---|---|---|
| scheme (esquema) | https |
Protocolo de acceso: http, https, ftp, mailto, etc. |
| userinfo | user:pass |
Credenciales (se usa poco, sobre todo para FTP/basic auth) |
| host | example.com |
Nombre de dominio o dirección IP |
| port (puerto) | 8080 |
Puerto; si no se indica, se toma el estándar del esquema (80 para http, 443 para https) |
| path (ruta) | /catalog/items |
Ruta al recurso en el servidor |
| query (consulta) | page=2&sort=price |
Parámetros de consulta en formato «clave=valor» |
| fragment (ancla) | reviews |
Referencia a una sección concreta dentro de la página; no se envía al servidor |
Estas son exactamente las partes que extrae el parser. Con ellas ya se puede trabajar: obtener el valor de un parámetro GET concreto, sustituir el dominio, descartar el fragmento o construir una nueva URL a partir de los componentes modificados.
Para qué sirve el parseo de URL
El parseo de URL aparece en prácticamente cualquier aplicación que trabaje con direcciones web. Los escenarios más frecuentes:
- Extracción de los parámetros de consulta. Hay que saber en qué página del catálogo se encuentra el usuario (
?page=5), qué filtro ha elegido o qué etiqueta UTM trae. El parser convierte la cadena de consulta en un práctico diccionario. - Validación y saneamiento de enlaces. Antes de guardar en la base una URL aportada por el usuario o de usarla en una redirección, es importante comprobar que el esquema está permitido (que no sea
javascript:, por ejemplo) y que el dominio figura en la lista blanca. - Enrutamiento. Los frameworks web analizan la ruta y los parámetros para determinar qué controlador invocar.
- Analítica y registro de logs. Para agrupar los accesos por dominios o por rutas, primero hay que descomponer las direcciones en partes.
- Web scraping y crawling. Aquí el parseo de URL es especialmente importante; hablamos de él en detalle más abajo.
El parseo de URL al construir crawlers
Un crawler (robot de rastreo) recorre un sitio pasando de página en página a través de los enlaces. Para entender en qué se diferencia el crawling del scraping y del parseo propiamente dicho, le recomendamos leer primero nuestro material «Scraping, parseo y crawling: ¿en qué se diferencian?». En resumen: el crawler se encarga del recorrido y del descubrimiento de las páginas; el scraper, de la extracción de los datos; y el parseo, del análisis del contenido obtenido, incluidas las direcciones.
La principal «trampa» al recorrer un sitio está en que los enlaces del HTML casi siempre son relativos, no absolutos. En el marcado encontrará:
<a href="/about">Quiénes somos</a>
<a href="../catalog/phones">Teléfonos</a>
<a href="page2.html">Siguiente</a>
<a href="#reviews">Reseñas</a>El robot no puede seguir el enlace /about ni page2.html tal cual: no son direcciones completas. Para continuar el rastreo, el enlace relativo debe convertirse en absoluto tomando como referencia la dirección de la página actual (la URL base). De eso se encarga precisamente el parseo de URL:
| URL base de la página | Enlace relativo | Resultado (URL absoluta) |
|---|---|---|
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 (ya es absoluta) |
Esta operación se denomina resolución del enlace (URL resolution), y casi todos los lenguajes ofrecen una función lista para ella: urljoin en Python, el constructor new URL(href, base) en JavaScript, URI.resolve() en Java, ResolveReference() en Go.
Normalización y eliminación de duplicados
Además de convertir los enlaces en absolutos, al crawler le interesa normalizar las direcciones para no recorrer la misma página varias veces. Técnicas habituales:
- descartar el fragmento
#fragment: apunta a una zona dentro de la página, no a un recurso aparte (/page#topy/pageson la misma página); - pasar el esquema y el dominio a minúsculas;
- eliminar el puerto estándar (
:443en https); - ordenar o filtrar los parámetros de consulta (por ejemplo, quitar las etiquetas UTM).
Tras la normalización, las URL absolutas se guardan en el conjunto de direcciones ya visitadas: así el robot no entra en bucles ni hace peticiones de más.
Ejemplos de implementación de parsers de URL
A continuación, cómo descomponer una URL en partes y cómo resolver un enlace relativo con las herramientas estándar de los lenguajes más populares.
Python
En la biblioteca estándar, de todo se encarga el módulo 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
# Parámetros de consulta -> diccionario
print(parse_qs(parsed.query)) # {'page': ['2'], 'sort': ['price']}
# Resolución de un enlace relativo (para el 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
# Descartar el fragmento al normalizar
print(urldefrag("https://example.com/page#reviews")[0]) # https://example.com/pageJavaScript (navegador y Node.js)
La clase integrada URL funciona igual en el navegador y en Node.js. También sabe resolver enlaces relativos: para ello, la dirección base se pasa como segundo argumento.
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"
// Parámetros de consulta
console.log(url.searchParams.get("page")); // "2"
// Resolución de un enlace 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
El análisis básico lo realiza parse_url(), y parse_str() convierte la cadena de consulta en 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']En PHP no existe un equivalente integrado de urljoin, así que para resolver enlaces relativos en los crawlers se suele incorporar una biblioteca — por ejemplo, league/uri o los helpers de Guzzle (GuzzleHttp\Psr7\UriResolver).
Java
La biblioteca estándar incluye la clase java.net.URI para trabajar con direcciones. El método resolve() da directamente la resolución del enlace 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
// Resolución de un enlace 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
El paquete net/url de la biblioteca estándar cubre tanto el análisis como la resolución de enlaces (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
// Resolución de un enlace relativo
base, _ := url.Parse("https://example.com/catalog/items/")
ref, _ := url.Parse("../about")
fmt.Println(base.ResolveReference(ref)) // https://example.com/catalog/about
}Ejemplo práctico: recopilamos todos los enlaces de una página
Juntémoslo todo en el paso típico de un crawler: parsear el HTML, extraer todos los enlaces y convertirlos en absolutos. Un ejemplo en Python con requests y 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. convertimos el enlace relativo en absoluto
absolute = urljoin(page_url, a["href"])
# 2. descartamos el fragmento para no generar duplicados
absolute, _ = urldefrag(absolute)
# 3. dejamos solo los enlaces http(s)
if absolute.startswith(("http://", "https://")):
links.add(absolute)
return links
for link in extract_links("https://example.com/catalog/"):
print(link)Aquí el parseo de URL cumple dos funciones clave: urljoin convierte los href relativos en direcciones completas que el robot podrá seguir después, y urldefrag evita tratar /page y /page#top como páginas distintas.
Errores frecuentes
- Parsear las URL con expresiones regulares. Por la abundancia de casos límite de la especificación, un análisis casero resulta casi siempre incompleto. Use las bibliotecas estándar.
- Olvidarse de los enlaces relativos. Sin la conversión a absoluto, el crawler «tropieza» en el primer enlace interno.
- No normalizar las direcciones. Sin descartar el fragmento, ordenar los parámetros y unificar mayúsculas y minúsculas, el robot recorre las mismas páginas una y otra vez.
- Confiar en el esquema sin comprobarlo. Antes de una redirección o de insertar un enlace, asegúrese de que el esquema es seguro (
http/https); de lo contrario, queda abierta la puerta a un XSS mediantejavascript:.
Conclusión
El parseo de URL es una operación básica pero importante: convierte una dirección en forma de cadena en una estructura con la que resulta cómodo trabajar. En las aplicaciones corrientes sirve para extraer parámetros, validar y enrutar; en los crawlers se vuelve crítico: sin convertir los enlaces relativos en absolutos y sin normalización, recorrer un sitio es sencillamente imposible. La buena noticia es que casi todos los lenguajes incluyen para ello una herramienta estándar y fiable, así que no hay que reinventar la rueda.