Formatos y fuentes de datos 9 min de lectura

Parseo de URL: qué es, para qué sirve y cómo se implementa en distintos lenguajes

Parseo de URL: de qué se compone una dirección, cómo descomponer y construir enlaces y cómo trabajar con parámetros de consulta y codificación en varios lenguajes.

EW
Equipo Web-Scraping.es
Recopilación de datos para las necesidades del negocio
Publicado: 17 abril 2025

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:

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

html
<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#top y /page son la misma página);
  • pasar el esquema y el dominio a minúsculas;
  • eliminar el puerto estándar (:443 en 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.

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

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

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

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"

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

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']

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.

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

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

Go

El paquete net/url de la biblioteca estándar cubre tanto el análisis como la resolución de enlaces (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

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

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

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.