Cómo codificar URLs en Python (guía completa de urllib.parse)
La codificación de URLs en Python utiliza urllib.parse.quote() para el percent-encoding de cadenas y urllib.parse.urlencode() para codificar diccionarios en query strings. Esta guía cubre quote(), unquote(), urlencode() y parse_qs() con ejemplos prácticos.
Codificación de URLs con quote()
La función urllib.parse.quote() es la herramienta principal de Python para el percent-encoding de cadenas. Convierte los caracteres que no son seguros para usar en URLs en sus equivalentes codificados con porcentaje. De forma predeterminada, considera las barras diagonales (/) como caracteres seguros, pero puedes personalizar este comportamiento.
from urllib.parse import quote
# Codificación básica
print(quote('hello world'))
# Salida: hello%20world
# Codificación de caracteres especiales
print(quote('price=10&qty=2'))
# Salida: price%3D10%26qty%3D2
# De forma predeterminada, / no se codifica
print(quote('path/to/file'))
# Salida: path/to/file
# Para codificar también las barras, establece safe=''
print(quote('path/to/file', safe=''))
# Salida: path%2Fto%2Ffile
# Codificación de caracteres Unicode
print(quote('cafe'))
# Salida: caf%C3%A9
# Especificación de caracteres seguros adicionales
print(quote('key=value&foo=bar', safe='=&'))
# Salida: key=value&foo=bar
El parámetro safe es la clave para controlar qué se codifica. De forma predeterminada, safe='/'. Si quieres codificar todo excepto los caracteres alfanuméricos y _.-~, establece safe=''. Esto es equivalente a encodeURIComponent() de JavaScript.
También existe quote_plus(), que funciona como quote() pero codifica los espacios como + en lugar de %20. Este es el formato utilizado en los datos de formularios HTML (application/x-www-form-urlencoded).
from urllib.parse import quote_plus
print(quote_plus('hello world'))
# Salida: hello+world
print(quote_plus('key=value&name=John Doe'))
# Salida: key%3Dvalue%26name%3DJohn+Doe
Decodificación de URLs con unquote()
La función urllib.parse.unquote() revierte el percent-encoding, convirtiendo las secuencias %XX de nuevo a sus caracteres originales. También existe unquote_plus(), que además convierte los signos + en espacios.
from urllib.parse import unquote, unquote_plus
# Decodificación básica
print(unquote('hello%20world'))
# Salida: hello world
print(unquote('caf%C3%A9'))
# Salida: cafe (con acento)
# unquote NO convierte + en espacio
print(unquote('hello+world'))
# Salida: hello+world
# unquote_plus convierte + en espacio
print(unquote_plus('hello+world'))
# Salida: hello world
# Decodificación de una URL completa
url = 'https://example.com/search?q=C%2B%2B%20programming'
print(unquote(url))
# Salida: https://example.com/search?q=C++ programming
Usa siempre unquote_plus() al decodificar datos de formularios, ya que los formularios HTML codifican los espacios como +. Usa unquote() para la decodificación general de URLs, donde los espacios se codifican como %20.
Codificación de query strings con urlencode()
La función urllib.parse.urlencode() toma un diccionario o una lista de tuplas y los convierte en una query string con el formato adecuado. Esta es la forma más cómoda de construir query strings en Python.
from urllib.parse import urlencode
# De diccionario a query string
params = {
'q': 'python programming',
'page': 1,
'lang': 'en'
}
print(urlencode(params))
# Salida: q=python+programming&page=1&lang=en
# Lista de tuplas (conserva el orden, permite claves duplicadas)
params = [
('tag', 'python'),
('tag', 'web'),
('sort', 'date')
]
print(urlencode(params))
# Salida: tag=python&tag=web&sort=date
# Uso de doseq=True para valores de tipo lista
params = {
'tag': ['python', 'web', 'api'],
'sort': 'date'
}
print(urlencode(params, doseq=True))
# Salida: tag=python&tag=web&tag=api&sort=date
# Uso de quote_via para controlar la codificación de espacios
from urllib.parse import quote
params = {'q': 'hello world'}
print(urlencode(params, quote_via=quote))
# Salida: q=hello%20world (usa %20 en lugar de +)
De forma predeterminada, urlencode() usa quote_plus() internamente, lo que significa que los espacios se convierten en +. Si necesitas %20 para los espacios, pasa quote_via=quote como se muestra arriba.
Análisis de query strings con parse_qs()
La función urllib.parse.parse_qs() analiza una query string y la convierte de nuevo en un diccionario. Cada valor del diccionario es una lista, ya que los parámetros de consulta pueden tener múltiples valores. También existe parse_qsl(), que devuelve una lista de tuplas.
from urllib.parse import parse_qs, parse_qsl
# Analizar una query string y convertirla en un diccionario
qs = 'q=python+programming&page=1&lang=en'
result = parse_qs(qs)
print(result)
# Salida: {'q': ['python programming'], 'page': ['1'], 'lang': ['en']}
# Nota: los valores siempre son listas
print(result['q'][0]) # 'python programming'
# Manejo de múltiples valores para la misma clave
qs = 'tag=python&tag=web&tag=api'
result = parse_qs(qs)
print(result)
# Salida: {'tag': ['python', 'web', 'api']}
# parse_qsl devuelve una lista de tuplas
result = parse_qsl(qs)
print(result)
# Salida: [('tag', 'python'), ('tag', 'web'), ('tag', 'api')]
# Conservar los valores en blanco (de forma predeterminada se omiten)
qs = 'name=John&email=&age=30'
print(parse_qs(qs, keep_blank_values=True))
# Salida: {'name': ['John'], 'email': [''], 'age': ['30']}
Codificación de URLs completas con urlparse
Al trabajar con URLs completas, las funciones urlparse() y urlunparse() de Python te permiten descomponer y reconstruir URLs de forma segura. Esto resulta especialmente útil cuando necesitas modificar partes específicas de una URL sin romper su estructura.
from urllib.parse import urlparse, urlunparse, urlencode, quote
# Analizar una URL en componentes
url = 'https://example.com/search?q=hello&page=1#results'
parsed = urlparse(url)
print(parsed.scheme) # 'https'
print(parsed.netloc) # 'example.com'
print(parsed.path) # '/search'
print(parsed.query) # 'q=hello&page=1'
print(parsed.fragment) # 'results'
# Construir una URL a partir de componentes
from urllib.parse import ParseResult
new_url = urlunparse(ParseResult(
scheme='https',
netloc='api.example.com',
path='/v2/search',
params='',
query=urlencode({'q': 'python & java', 'limit': 10}),
fragment=''
))
print(new_url)
# Salida: https://api.example.com/v2/search?q=python+%26+java&limit=10
# Agregar de forma segura un segmento de ruta con caracteres especiales
base = 'https://example.com/files/'
filename = 'my report (final).pdf'
safe_url = base + quote(filename, safe='')
print(safe_url)
# Salida: https://example.com/files/my%20report%20%28final%29.pdf
Para código Python moderno, considera usar la biblioteca requests, que gestiona la codificación de URLs automáticamente cuando pasas los parámetros como un diccionario. La biblioteca httpx también ofrece capacidades similares de codificación automática.
import requests
# requests gestiona la codificación automáticamente
response = requests.get(
'https://api.example.com/search',
params={
'q': 'python & java',
'page': 1,
'sort': 'relevance'
}
)
print(response.url)
# https://api.example.com/search?q=python+%26+java&page=1&sort=relevance