Esdocu
python-requests

Analizando la Respuesta

Profundiza en el objeto Response devuelto por requests. Descubre cómo extraer el texto sin formato, acceder a las cabeceras del servidor y entender la codificación de caracteres empleada.

Una petición HTTP solo cobra sentido cuando entendemos lo que el servidor devuelve. Al realizar una solicitud con la biblioteca requests, el flujo de red se transforma en un objeto Response, una potente interfaz que almacena mucho más que el contenido de una página.

En este capítulo aprenderás a auditar con precisión el ciclo de retorno de tus datos. Descubriremos la anatomía interna de este objeto, la diferencia crítica entre procesar texto decodificado o bytes puros, la forma correcta de inspeccionar los metadatos ocultos en las cabeceras sin cometer errores de capitalización y cómo rastrear de forma automatizada el historial de redirecciones en la red.

5.1. El objeto Response al detalle

Cada vez que ejecutas una petición con la biblioteca requests, el servidor devuelve una respuesta HTTP. En el código, esta respuesta se materializa como una instancia de la clase requests.models.Response. Este objeto no es simplemente una cadena de texto con el contenido de la página; es un contenedor sofisticado que encapsula la totalidad de la transacción de vuelta, incluyendo metadatos, configuraciones de red, el estado de la conexión y el cuerpo de los datos.

Para comprender cómo interactúa requests con el servidor, es útil visualizar el ciclo completo de intercambio de objetos:

TEXT
[ Cliente ] --- ( requests.get() ) ---> Objeto PreparedRequest | ( Red / Servidor ) | [ Cliente ] <-- ( Objeto Response ) <------- Respuesta HTTP

Cuando invocas requests.get(), la biblioteca traduce tus argumentos en un objeto interno de petición preparada (PreparedRequest). Tras enviarlo y recibir la contestación del servidor, requests construye el objeto Response. Este objeto actúa como una interfaz unificada para inspeccionar todo lo que el servidor ha devuelto.

Anatomía interna del objeto Response

El objeto Response no almacena la información de forma estática, sino que expone un conjunto de atributos y métodos que expondremos en detalle a continuación. Podemos agrupar sus componentes principales en cuatro áreas funcionales:

TEXT
+-------------------------------------------------------+ | OBJETO RESPONSE | +-------------------------------------------------------+ | 1. Información de Estado | | - .status_code (e.g., 200, 404) | | - .ok (Booleano) | | - .reason (e.g., "OK", "Not Found") | +-------------------------------------------------------+ | 2. Datos del Contenido | | - .text (Cena de texto codificada) | | - .content (Bytes puros de datos binarios) | | - .encoding (Esquema de caracteres, e.g., UTF-8) | +-------------------------------------------------------+ | 3. Metadatos y Contexto | | - .headers (Diccionario de cabeceras HTTP) | | - .cookies (Contenedor RequestsCookieJar) | | - .url (URL final de la respuesta) | | - .elapsed (Objeto timedelta de tiempo de espera) | +-------------------------------------------------------+ | 4. Origen y Rastreo | | - .request (Objeto PreparedRequest original) | | - .history (Lista de redirecciones previas) | +-------------------------------------------------------+

Atributos esenciales y su comportamiento

A continuación se detalla el comportamiento y la utilidad de los atributos fundamentales del objeto Response:

  • status_code: Un entero que representa el código de estado HTTP devuelto por el servidor (por ejemplo, 200 para una solicitud exitosa o 404 para un recurso no encontrado).
  • ok: Una propiedad booleana muy útil para flujos de control rápidos. Devuelve True si el status_code es menor que 400 (lo que significa que la petición se completó con éxito o se redireccionó). Devuelve False si el código está en los rangos de error de cliente (4xx) o de servidor (5xx).
  • reason: Una cadena de texto que contiene la descripción textual del código de estado HTTP provista por el estándar del protocolo (por ejemplo, "OK" para un código 200, o "Not Found" para un 404).
  • url: Contiene la cadena de texto de la URL final. Esto es especialmente importante si la petición original pasó por una o varias redirecciones; .url mostrará el destino definitivo donde se detuvo la transferencia, no necesariamente la dirección que escribiste en el método inicial.
  • elapsed: Un objeto de la clase datetime.timedelta que mide el tiempo transcurrido entre el envío de la petición y la recepción completa de las cabeceras de la respuesta. Es excelente para realizar mediciones básicas de rendimiento y latencia del servicio web.

Inspección básica en código

Para observar cómo interactúan estas propiedades en un entorno real, analiza el siguiente bloque de código enfocado en desglosar la respuesta de una petición estándar:

Python
import requests # Realizamos una petición a un recurso existente respuesta = requests.get("https://api.github.com/events") # Inspección de los metadatos de estado y red print(f"Tipo de objeto: {type(respuesta)}") print(f"Código de estado: {respuesta.status_code}") print(f"¿Petición exitosa? (.ok): {respuesta.ok}") print(f"Frase de estado: {respuesta.reason}") print(f"URL definitiva: {respuesta.url}") print(f"Tiempo de respuesta: {respuesta.elapsed.total_seconds()} segundos") # Validación del enlace original que originó el objeto print(f"Método de la petición asociada: {respuesta.request.method}")

El vínculo con la petición original: el atributo request

Un aspecto avanzado pero crucial del objeto Response es su propiedad .request. Mantener la trazabilidad en aplicaciones de red es vital. Por ello, requests vincula directamente la respuesta con el objeto PreparedRequest que la generó.

A través de respuesta.request, puedes auditar con precisión qué cabeceras exactas envió la biblioteca tras bambalinas, la URL original con sus parámetros ya codificados o los cuerpos de datos adjuntos en operaciones de escritura. Esto convierte al objeto Response en una bitácora bidireccional de toda la transacción HTTP.

5.2. Propiedades de texto y codificación

Cuando el servidor web responde a una petición, envía una secuencia de bytes puros a través de la red junto con metadatos que describen el tipo de contenido. El objeto Response de la biblioteca requests procesa este flujo de datos subyacente y proporciona dos mecanismos principales para acceder al cuerpo de la respuesta: uno optimizado para datos basados en texto (.text) y otro diseñado para datos puros o binarios (.content).

Comprender la diferencia matemática y operativa entre estas dos propiedades, así como la lógica que utiliza la biblioteca para gestionar la codificación de caracteres, es fundamental para evitar la corrupción de datos y los errores de lectura de caracteres especiales.

TEXT
+----------------------------------+ | Flujo de bytes del servidor | +----------------------------------+ | v [ Objeto response.content ] (Bytes puros) | +-------------------------+-------------------------+ | | | Si se lee .content directamente | Si se invoca .text v v Mantiene los bytes Aplica el valor de .encoding (Ej: b'\x48\x65\x6c\x6c\x6f') | v Decodifica a string (str) (Ej: "Hello")

La propiedad .text y la decodificación implícita

La propiedad .text devuelve el cuerpo de la respuesta en forma de cadena de caracteres de Python (str). Cuando invocas esta propiedad, requests realiza automáticamente una decodificación de los bytes crudos que recibió del servidor.

Para lograr esto, la biblioteca necesita saber qué mapa de caracteres (o codificación) se utilizó para transformar el texto original en los bytes transmitidos. Aquí es donde entra en juego el atributo .encoding.

La lógica interna que sigue requests para determinar el valor de .encoding y resolver el texto se basa en el siguiente orden de prioridades:

  1. Cabecera HTTP Content-Type: La biblioteca examina las cabeceras de la respuesta en busca de la directiva charset dentro de Content-Type (por ejemplo, Content-Type: text/html; charset=utf-8). Si está presente, asigna ese valor a .encoding.
  2. Heurística de chardet o charset_normalizer: Si el servidor omitió especificar el charset en las cabeceras, requests no asume una codificación universal. En su lugar, analiza estadísticamente los primeros bytes del cuerpo de la respuesta utilizando una biblioteca interna de detección para adivinar cuál es la codificación más probable (comúnmente detectando variaciones como ISO-8859-1 o utf-8).
  3. Soporte predeterminado para texto: De acuerdo con el estándar RFC 2616 de HTTP, si el tipo de medio es de la familia text/ (como text/html) y no se define un juego de caracteres, la especificación dicta que se debe asumir ISO-8859-1. La biblioteca adopta este comportamiento como último recurso si las cabeceras no ayudan.

Diferencias operativas: .text frente a .content

Es importante distinguir cuándo delegar la lectura en .text y cuándo es obligatorio recurrir a .content:

  • response.text: Retorna un tipo str. Debe utilizarse única y exclusivamente para recursos que representen formatos de texto plano legibles, tales como páginas HTML, documentos XML, estructuras JSON o archivos de configuración CSV.
  • response.content: Retorna un tipo bytes. Ignora por completo cualquier configuración de .encoding y entrega la información exactamente como llegó del socket de red. Debe utilizarse para archivos binarios como imágenes (PNG, JPEG), flujos de audio/video, instaladores ejecutables o archivos comprimidos (ZIP, TAR). Intentar leer un binario a través de .text forzará una decodificación innecesaria que probablemente corromperá el archivo o lanzará excepciones.

Control manual de la codificación

El proceso de detección automática no es infalible. Existen situaciones en las que el servidor declara una codificación incorrecta o simplemente no aporta suficiente información, lo que provoca que caracteres especiales (como eñes, acentos o emojis) se muestren dañados o alterados en .text (un fenómeno conocido como mojibake).

Afortunadamente, el atributo .encoding no es de solo lectura; es completamente mutable. Si conoces de antemano la codificación real del recurso, puedes sobrescribir el atributo antes de acceder a .text. Al cambiar .encoding, requests utilizará inmediatamente el nuevo mapa de caracteres la próxima vez que evalúe la propiedad .text.

Implementación práctica en código

El siguiente script ejemplifica cómo comprobar la codificación asignada automáticamente, cómo difieren los tipos de datos devueltos por ambas propiedades y cómo forzar una codificación específica cuando sea necesario:

Python
import requests # 1. Petición a un recurso de texto respuesta = requests.get("https://www.wikipedia.org") # Comprobamos qué codificación detectó requests desde las cabeceras o la heurística print(f"Codificación inferida: {respuesta.encoding}") # Analizamos los tipos de datos de ambas propiedades tipo_text = type(respuesta.text) tipo_content = type(respuesta.content) print(f"Tipo con .text: {tipo_text} (Texto decodificado)") print(f"Tipo con .content: {tipo_content} (Bytes binarios)") # Mostramos los primeros 50 elementos de cada propiedad print(f"Fragmento .text: {respuesta.text[:50]}") print(f"Fragmento .content: {respuesta.content[:50]}") print("-" * 50) # 2. Corrección manual de codificación # Imaginemos un servidor que devuelve UTF-8 pero omite declararlo, provocando fallos en caracteres respuesta_servidor_antiguo = requests.get("https://httpbin.org/encoding/utf8") print(f"Codificación errónea u omitida: {respuesta_servidor_antiguo.encoding}") # Forzamos manualmente la propiedad a 'utf-8' antes de extraer el texto plano respuesta_servidor_antiguo.encoding = "utf-8" print("Codificación corregida a UTF-8 de forma manual.") # Ahora .text usará obligatoriamente la tabla UTF-8 para construir el string de Python texto_corregido = respuesta_servidor_antiguo.text print(f"Texto decodificado con éxito.")

5.3. Inspección de cabeceras de respuesta

Las cabeceras de respuesta HTTP son parejas de clave-valor que el servidor envía de vuelta junto con el recurso solicitado. Estas cabeceras contienen metadatos cruciales sobre la transacción, como el tipo de servidor, la fecha de emisión, las directivas de almacenamiento en caché, las cookies de sesión y la longitud o tipo de contenido que se va a transferir.

En la biblioteca requests, las cabeceras de la respuesta se exponen a través del atributo .headers. Este atributo no es un diccionario estándar de Python (dict), sino una instancia de una clase interna especializada llamada requests.structures.CaseInsensitiveDict.

TEXT
+-----------------------------------------+ | response.headers | | (CaseInsensitiveDict de requests) | +-----------------------------------------+ / \ / \ v v Clave: "Content-Type" Clave: "content-type" \ / \ / v v Ambas acceden al mismo valor único: "application/json; charset=utf-8"

El diccionario insensible a mayúsculas y minúsculas

La especificación del protocolo HTTP (RFC 7230) establece explícitamente que los nombres de los campos de las cabeceras no deben distinguir entre mayúsculas y minúsculas. Esto significa que Content-Type, content-type y CONTENT-TYPE hacen referencia exactamente a la misma información.

Si requests utilizara un diccionario común de Python, los desarrolladores se verían obligados a escribir condicionales complejos para prever cómo ha estructurado el servidor sus respuestas, ya que un error de capitalización devolvería una excepción KeyError. Al implementar CaseInsensitiveDict, la biblioteca resuelve este problema de raíz: puedes consultar las cabeceras utilizando cualquier combinación de mayúsculas y minúsculas de forma totalmente transparente.

Recuperación segura de datos con .get()

Aunque las cabeceras se pueden consultar utilizando la sintaxis tradicional de corchetes (respuesta.headers['Cache-Control']), no todos los servidores web envían el mismo conjunto de metadatos. Si intentas acceder a una cabecera que el servidor omitió mediante corchetes, Python lanzará un error de ejecución deteniendo tu programa.

Para escribir código robusto y tolerante a fallos, la mejor práctica consiste en utilizar el método .get(). Este método funciona exactamente igual que el de los diccionarios nativos: permite realizar la consulta de manera segura y definir un valor de retorno por defecto (o None si se omite) en caso de que la cabecera no se encuentre en la respuesta.

Cabeceras comunes de respuesta y su utilidad

Al analizar datos remotos, existen ciertos metadatos que querrás inspeccionar con regularidad:

  • Content-Type: Indica el tipo de medio del recurso (MIME type). Te ayuda a validar si el servidor te ha devuelto lo que esperabas (por ejemplo, application/json frente a text/html).
  • Content-Length: Expresa el tamaño del cuerpo de la respuesta en bytes puros. Es sumamente útil para validar la integridad de una descarga antes de procesarla por completo.
  • Server: Identifica el software que gestiona el servidor web de origen (por ejemplo, nginx, Apache, cloudflare).
  • Date: La fecha y hora exactas en las que el servidor generó la respuesta HTTP, útil para sincronizar marcas de tiempo.

Implementación práctica en código

El siguiente ejemplo práctico demuestra la insensibilidad a las mayúsculas, la extracción segura de metadatos de red y la iteración completa sobre la estructura de cabeceras:

Python
import requests # Realizamos una petición a un servicio que devuelve información de prueba respuesta = requests.get("https://httpbin.org/headers") # 1. Demostración de insensibilidad a mayúsculas/minúsculas cabeceras = respuesta.headers print("--- Validación de Capitalización ---") print(f"Usando 'Content-Type': {cabeceras['Content-Type']}") print(f"Usando 'content-type': {cabeceras['content-type']}") print(f"Usando 'CONTENT-TYPE': {cabeceras['CONTENT-TYPE']}") print("\n--- Recuperación Segura ---") # 2. Acceso seguro a cabeceras que podrían no existir politica_seguridad = cabeceras.get("Content-Security-Policy") print(f"Content-Security-Policy encontrada: {politica_seguridad}") # Definición de un valor por defecto si no existe la cabecera servidor_alternativo = cabeceras.get("Server", "Servidor Desconocido") print(f"Software del Servidor: {servidor_alternativo}") print("\n--- Iteración Completa ---") # 3. Listar todas las cabeceras devueltas por el servidor remoto # Limitamos la salida a las primeras 5 para no saturar la pantalla for clave, valor in list(cabeceras.items())[:5]: print(f"-> {clave}: {valor}")

5.4. Historial de redirecciones

En el ecosistema web, es muy común que un servidor no entregue el recurso directamente en la URL solicitada, sino que ordene al cliente dirigirse a una ubicación diferente. Este fenómeno se conoce como redirección HTTP (identificado comúnmente por códigos de estado de la serie 3xx, como 301 para mudanzas permanentes o 302 para desplazamientos temporales).

Por defecto, la biblioteca requests sigue de forma automática casi todas las redirecciones cuando realizas peticiones de lectura (como GET, OPTIONS o HEAD). Sin embargo, para no perder el rastro de la ruta original que recorrió la petición, el objeto Response final almacena la cadena completa de saltos dentro de su atributo .history.

TEXT
[Cliente] ---> GET /antiguo ---> [Servidor (301)] | +------------------------------------+ v [Objeto Response intermediario] ---> Guardado en -> response.history[0] | +---> Nueva petición automática a /nuevo | v [Objeto Response final] <-------- [Servidor (200)]

El atributo .history al detalle

El atributo .history es una lista ordenada de Python que contiene objetos Response. Cada elemento de esta lista representa una de las paradas o saltos intermedios que el cliente tuvo que realizar antes de llegar al destino definitivo.

Si una petición va directo a su objetivo sin desvíos, la lista .history estará completamente vacía. En cambio, si el servidor aplica un enrutamiento en cadena, los objetos dentro de .history aparecerán en el orden cronológico exacto en que ocurrieron, permitiéndote auditar los códigos de estado intermedios y las URLs de origen.

El parámetro allow_redirects

Aunque la automatización de redirecciones es cómoda, hay escenarios avanzados —como la auditoría de seguridad, el web scraping preciso o la depuración de APIs— donde necesitas capturar la primera respuesta del servidor sin que la biblioteca salte automáticamente al siguiente enlace.

Para modificar este comportamiento, todos los métodos de petición de requests exponen el parámetro booleano allow_redirects.

  • allow_redirects=True (Predeterminado en GET, OPTIONS, POST, etc.): Sigue todo el camino hasta el final y rellena el historial.
  • allow_redirects=False: Detiene la ejecución inmediatamente al recibir el primer código 3xx. El objeto devuelto corresponderá a la redirección misma, permitiéndote leer sus cabeceras (como la cabecera Location, que indica hacia dónde pretendía enviarte el servidor) y dejando la lista .history vacía.

Implementación práctica en código

El siguiente ejemplo práctico ilustra cómo inspeccionar un viaje de redirección completo y cómo desactivar el comportamiento automático para analizar la respuesta intermedia:

Python
import requests print("--- Escenario A: Redirección Automática ---") # Buscamos un enlace que sabemos que fuerza una redirección segura (HTTP a HTTPS) respuesta_final = requests.get("http://github.com") print(f"URL de destino final: {respuesta_final.url}") print(f"Código de estado final: {respuesta_final.status_code}") print(f"Número de redirecciones intermedias: {len(respuesta_final.history)}") # Recorremos el historial para auditar el camino for i, salto in enumerate(respuesta_final.history, start=1): print(f" Salto #{i}: {salto.url} [Código: {salto.status_code}]") print("\n--- Escenario B: Bloqueo de Redirecciones ---") # Forzamos a requests a no seguir la orden del servidor respuesta_intermedia = requests.get("http://github.com", allow_redirects=False) print(f"URL capturada: {respuesta_intermedia.url}") print(f"Código de estado capturado: {respuesta_intermedia.status_code}") print(f"¿Existe historial?: {len(respuesta_intermedia.history)} elementos") # Inspeccionamos a dónde nos quería enviar el servidor leyendo la cabecera Location destino_propuesto = respuesta_intermedia.headers.get("Location") print(f"Dirección propuesta por el servidor: {destino_propuesto}")

Resumen del capítulo

En este Capítulo 5: Analizando la Respuesta, hemos explorado a fondo la anatomía de la respuesta que nos devuelve el servidor web al interactuar con la biblioteca requests.

  • Aprendimos que el objeto Response centraliza toda la información de la transacción, permitiendo comprobar el éxito de la petición mediante propiedades directas como .status_code u .ok.
  • Distinguimos de forma operativa el acceso a datos textuales mediante .text (que depende de la propiedad mutable .encoding para la interpretación de caracteres) frente al acceso a datos puros binarios mediante .content.
  • Analizamos el comportamiento de las cabeceras HTTP a través de la estructura CaseInsensitiveDict, la cual elimina la sensibilidad a mayúsculas y minúsculas para prevenir errores al buscar metadatos del servidor.
  • Por último, estudiamos cómo rastrear la ruta de las peticiones que sufren desvíos en la red utilizando el atributo .history, y cómo tomar el control manual de estos saltos utilizando el parámetro allow_redirects=False.

© 2026 Esdocu. Contenido bajo licencia MIT.

Editar esta página