¿Cómo analizar el contenido de la respuesta de una llamada requests en Python?

PythonBeginner
Practicar Ahora

Introducción

La biblioteca Python requests es una herramienta poderosa para interactuar con servicios web y APIs. En este tutorial, aprenderás a enviar peticiones HTTP y a analizar los datos de respuesta utilizando Python. Al final de este laboratorio, serás capaz de extraer información valiosa de diferentes tipos de respuestas de API, lo que te permitirá construir aplicaciones basadas en datos y automatizar interacciones web.

Instalación de la Biblioteca Requests y Realización de una Petición Básica

En este primer paso, instalaremos la biblioteca Python requests y realizaremos nuestra primera petición HTTP para recuperar datos de una API pública.

Instalación de Requests

La biblioteca requests es un paquete de terceros que necesita ser instalado usando pip, el instalador de paquetes de Python. Comencemos instalándola:

pip install requests

Deberías ver una salida que confirme que requests se instaló correctamente.

Realización de tu Primera Petición HTTP

Ahora, creemos un archivo Python para realizar una petición HTTP simple. En el WebIDE, crea un nuevo archivo llamado basic_request.py en el directorio /home/labex/project.

Añade el siguiente código al archivo:

import requests

## Realiza una petición GET a una API pública
response = requests.get("https://jsonplaceholder.typicode.com/todos/1")

## Imprime el código de estado
print(f"Código de estado: {response.status_code}")

## Imprime el contenido de la respuesta sin procesar
print("\nContenido de la respuesta sin procesar:")
print(response.text)

## Imprime las cabeceras de la respuesta
print("\nCabeceras de la respuesta:")
for header, value in response.headers.items():
    print(f"{header}: {value}")

Este código realiza una petición GET a un endpoint (punto final) de API de ejemplo e imprime información sobre la respuesta.

Comprensión del Objeto Response

Ejecutemos el código para ver qué información obtenemos. En la terminal, ejecuta:

python basic_request.py

Deberías ver una salida similar a esta:

Código de estado: 200

Contenido de la respuesta sin procesar:
{
  "userId": 1,
  "id": 1,
  "title": "delectus aut autem",
  "completed": false
}

Cabeceras de la respuesta:
Date: Mon, 01 Jan 2023 12:00:00 GMT
Content-Type: application/json; charset=utf-8
...

El objeto response contiene varios atributos importantes:

  • status_code: Código de estado HTTP (200 significa éxito)
  • text: El contenido de la respuesta como una cadena de texto (string)
  • headers: Un diccionario de cabeceras de respuesta

Al trabajar con peticiones web, estos atributos te ayudan a comprender la respuesta del servidor y a manejarla apropiadamente.

Códigos de Estado HTTP

Los códigos de estado HTTP indican si una petición tuvo éxito o falló:

  • 2xx (como 200): Éxito
  • 3xx (como 301): Redirección
  • 4xx (como 404): Errores del cliente
  • 5xx (como 500): Errores del servidor

Modifiquemos nuestro código para verificar una respuesta exitosa. Crea un nuevo archivo llamado check_status.py con este contenido:

import requests

try:
    ## Realiza una petición GET a una URL válida
    response = requests.get("https://jsonplaceholder.typicode.com/todos/1")

    ## Verifica si la petición fue exitosa
    if response.status_code == 200:
        print("¡Petición exitosa!")
    else:
        print(f"La petición falló con el código de estado: {response.status_code}")

    ## Prueba una URL inválida
    invalid_response = requests.get("https://jsonplaceholder.typicode.com/invalid")
    print(f"Código de estado de URL inválida: {invalid_response.status_code}")

except requests.exceptions.RequestException as e:
    print(f"Ocurrió un error: {e}")

Ejecuta este código para ver cómo diferentes URLs devuelven diferentes códigos de estado:

python check_status.py

Deberías ver que la URL válida devuelve el código de estado 200, mientras que la URL inválida devuelve el código de estado 404.

Análisis de Datos de Respuesta JSON

Muchas APIs modernas devuelven datos en formato JSON (JavaScript Object Notation). En este paso, aprenderás a analizar respuestas JSON y a trabajar con los datos en Python.

Comprensión de JSON

JSON es un formato ligero de intercambio de datos que es fácil de leer y escribir para los humanos, y fácil de analizar y generar para las máquinas. Se basa en pares clave-valor, similar a los diccionarios de Python.

Aquí hay un ejemplo de un objeto JSON:

{
  "name": "John Doe",
  "age": 30,
  "email": "john@example.com",
  "is_active": true,
  "hobbies": ["reading", "swimming", "cycling"]
}

Análisis de Respuestas JSON

La biblioteca requests facilita el análisis de respuestas JSON utilizando el método .json(). Creemos un nuevo archivo llamado parse_json.py y agreguemos el siguiente código:

import requests

## Realiza una petición a un endpoint de la API de GitHub que devuelve datos JSON
response = requests.get("https://api.github.com/users/python")

## Verifica si la petición fue exitosa
if response.status_code == 200:
    ## Analiza la respuesta JSON
    data = response.json()

    ## Imprime los datos analizados
    print("Datos JSON analizados:")
    print(f"Nombre de usuario: {data['login']}")
    print(f"Nombre: {data.get('name', 'No proporcionado')}")
    print(f"Seguidores: {data['followers']}")
    print(f"Repositorios públicos: {data['public_repos']}")

    ## Imprime el tipo para verificar que es un diccionario de Python
    print(f"\nTipo de datos analizados: {type(data)}")

    ## Accede a datos anidados
    print("\nAccediendo a elementos específicos:")
    print(f"URL del avatar: {data['avatar_url']}")
else:
    print(f"La petición falló con el código de estado: {response.status_code}")

Ejecuta este script para ver cómo los datos JSON se analizan en un diccionario de Python:

python parse_json.py

Deberías ver una salida que muestra información sobre el usuario de GitHub, incluyendo su nombre de usuario, el conteo de seguidores y el conteo de repositorios.

Trabajando con Listas de Datos

Muchas APIs devuelven listas de objetos. Veamos cómo manejar este tipo de respuesta. Crea un archivo llamado json_list.py con este contenido:

import requests

## Realiza una petición a una API que devuelve una lista de posts
response = requests.get("https://jsonplaceholder.typicode.com/posts")

## Verifica si la petición fue exitosa
if response.status_code == 200:
    ## Analiza la respuesta JSON (esto será una lista de posts)
    posts = response.json()

    ## Imprime el número total de posts
    print(f"Total de posts: {len(posts)}")

    ## Imprime detalles de los primeros 3 posts
    print("\nPrimeros 3 posts:")
    for i, post in enumerate(posts[:3], 1):
        print(f"\nPost #{i}")
        print(f"User ID: {post['userId']}")
        print(f"Post ID: {post['id']}")
        print(f"Título: {post['title']}")
        print(f"Cuerpo: {post['body'][:50]}...")  ## Imprime solo el principio del cuerpo
else:
    print(f"La petición falló con el código de estado: {response.status_code}")

Ejecuta este script para ver cómo procesar una lista de objetos JSON:

python json_list.py

Deberías ver información sobre los primeros tres posts, incluyendo sus títulos y el principio de su contenido.

Manejo de Errores con el Análisis JSON

A veces, una respuesta podría no contener datos JSON válidos. Veamos cómo manejar esto de manera adecuada. Crea un archivo llamado json_error.py con este código:

import requests
import json

def get_and_parse_json(url):
    try:
        ## Realiza la petición
        response = requests.get(url)

        ## Verifica si la petición fue exitosa
        response.raise_for_status()

        ## Intenta analizar el JSON
        try:
            data = response.json()
            return data
        except json.JSONDecodeError:
            print(f"La respuesta de {url} no es JSON válido")
            print(f"Respuesta sin procesar: {response.text[:100]}...")  ## Imprime parte de la respuesta sin procesar
            return None

    except requests.exceptions.HTTPError as e:
        print(f"Error HTTP: {e}")
    except requests.exceptions.RequestException as e:
        print(f"Error de petición: {e}")

    return None

## Prueba con un endpoint JSON válido
json_data = get_and_parse_json("https://jsonplaceholder.typicode.com/posts/1")
if json_data:
    print("\nRespuesta JSON válida:")
    print(f"Título: {json_data['title']}")

## Prueba con un endpoint que no es JSON
html_data = get_and_parse_json("https://www.example.com")
if html_data:
    print("\nEsto no debería imprimirse ya que example.com devuelve HTML, no JSON")
else:
    print("\nComo se esperaba, no se pudo analizar HTML como JSON")

Ejecuta este script para ver cómo manejar diferentes tipos de respuestas:

python json_error.py

Deberías ver que el código maneja con éxito tanto las respuestas JSON válidas como las respuestas que no son JSON.

Análisis de Contenido HTML con BeautifulSoup

Al trabajar con datos web, a menudo te encontrarás con respuestas HTML. Para analizar HTML, la biblioteca BeautifulSoup de Python es una herramienta excelente. En este paso, aprenderemos a extraer información de las respuestas HTML.

Instalación de BeautifulSoup

Primero, instalemos BeautifulSoup y su analizador HTML:

pip install beautifulsoup4

Análisis HTML Básico

Creemos un archivo llamado parse_html.py para obtener y analizar una página web:

import requests
from bs4 import BeautifulSoup

## Realiza una petición a una página web
url = "https://www.example.com"
response = requests.get(url)

## Verifica si la petición fue exitosa
if response.status_code == 200:
    ## Analiza el contenido HTML
    soup = BeautifulSoup(response.text, 'html.parser')

    ## Extrae el título de la página
    title = soup.title.text
    print(f"Título de la página: {title}")

    ## Extrae todos los párrafos
    paragraphs = soup.find_all('p')
    print(f"\nNúmero de párrafos: {len(paragraphs)}")

    ## Imprime el texto del primer párrafo
    if paragraphs:
        print(f"\nTexto del primer párrafo: {paragraphs[0].text.strip()}")

    ## Extrae todos los enlaces
    links = soup.find_all('a')
    print(f"\nNúmero de enlaces: {len(links)}")

    ## Imprime el atributo href del primer enlace
    if links:
        print(f"Href del primer enlace: {links[0].get('href')}")

else:
    print(f"La petición falló con el código de estado: {response.status_code}")

Ejecuta este script para ver cómo extraer información básica de una página HTML:

python parse_html.py

Deberías ver una salida que muestra el título de la página, el número de párrafos, el texto del primer párrafo, el número de enlaces y la URL del primer enlace.

Encontrar Elementos Específicos

Ahora veamos cómo encontrar elementos específicos usando selectores CSS. Crea un archivo llamado html_selectors.py:

import requests
from bs4 import BeautifulSoup

## Realiza una petición a una página web con una estructura más compleja
url = "https://quotes.toscrape.com/"
response = requests.get(url)

## Verifica si la petición fue exitosa
if response.status_code == 200:
    ## Analiza el contenido HTML
    soup = BeautifulSoup(response.text, 'html.parser')

    ## Encuentra todos los elementos de cita
    quote_elements = soup.select('.quote')
    print(f"Número de citas encontradas: {len(quote_elements)}")

    ## Procesa las primeras 3 citas
    print("\nPrimeras 3 citas:")
    for i, quote_element in enumerate(quote_elements[:3], 1):
        ## Extrae el texto de la cita
        text = quote_element.select_one('.text').text

        ## Extrae el autor
        author = quote_element.select_one('.author').text

        ## Extrae las etiquetas
        tags = [tag.text for tag in quote_element.select('.tag')]

        ## Imprime la información
        print(f"\nCita #{i}")
        print(f"Texto: {text}")
        print(f"Autor: {author}")
        print(f"Etiquetas: {', '.join(tags)}")

else:
    print(f"La petición falló con el código de estado: {response.status_code}")

Ejecuta este script para ver cómo usar selectores CSS para extraer elementos específicos:

python html_selectors.py

Deberías ver una salida que muestra información sobre las primeras tres citas, incluyendo el texto de la cita, el autor y las etiquetas.

Construyendo un Rascador Web Simple

Juntemos todo para construir un rascador web simple que extrae datos estructurados de una página web. Crea un archivo llamado quotes_scraper.py:

import requests
from bs4 import BeautifulSoup
import json
import os

def scrape_quotes_page(url):
    ## Realiza una petición a la página web
    response = requests.get(url)

    ## Verifica si la petición fue exitosa
    if response.status_code != 200:
        print(f"La petición falló con el código de estado: {response.status_code}")
        return None

    ## Analiza el contenido HTML
    soup = BeautifulSoup(response.text, 'html.parser')

    ## Extrae todas las citas
    quotes = []
    for quote_element in soup.select('.quote'):
        ## Extrae el texto de la cita
        text = quote_element.select_one('.text').text.strip('"')

        ## Extrae el autor
        author = quote_element.select_one('.author').text

        ## Extrae las etiquetas
        tags = [tag.text for tag in quote_element.select('.tag')]

        ## Añade la cita a nuestra lista
        quotes.append({
            'text': text,
            'author': author,
            'tags': tags
        })

    ## Verifica si hay una página siguiente
    next_page = soup.select_one('.next a')
    next_page_url = None
    if next_page:
        next_page_url = 'https://quotes.toscrape.com' + next_page['href']

    return {
        'quotes': quotes,
        'next_page': next_page_url
    }

## Raspa la primera página
result = scrape_quotes_page('https://quotes.toscrape.com/')

if result:
    ## Imprime información sobre las citas encontradas
    quotes = result['quotes']
    print(f"Encontradas {len(quotes)} citas en la primera página")

    ## Imprime las primeras 2 citas
    print("\nPrimeras 2 citas:")
    for i, quote in enumerate(quotes[:2], 1):
        print(f"\nCita #{i}")
        print(f"Texto: {quote['text']}")
        print(f"Autor: {quote['author']}")
        print(f"Etiquetas: {', '.join(quote['tags'])}")

    ## Guarda las citas en un archivo JSON
    output_dir = '/home/labex/project'
    with open(os.path.join(output_dir, 'quotes.json'), 'w') as f:
        json.dump(quotes, f, indent=2)

    print(f"\nGuardadas {len(quotes)} citas en {output_dir}/quotes.json")

    ## Imprime información sobre la página siguiente
    if result['next_page']:
        print(f"\nURL de la página siguiente: {result['next_page']}")
    else:
        print("\nNo hay página siguiente disponible")

Ejecuta este script para raspar citas de un sitio web:

python quotes_scraper.py

Deberías ver una salida que muestra información sobre las citas encontradas en la primera página, y las citas se guardarán en un archivo JSON llamado quotes.json.

Revisa el archivo JSON para ver los datos estructurados:

cat quotes.json

El archivo debería contener un array JSON de objetos de cita, cada uno con propiedades de texto, autor y etiquetas.

Trabajando con Contenido de Respuesta Binario

Hasta ahora, nos hemos centrado en respuestas basadas en texto como JSON y HTML. Sin embargo, la biblioteca requests también puede manejar contenido binario como imágenes, PDFs y otros archivos. En este paso, aprenderemos a descargar y procesar contenido binario.

Descargando una Imagen

Comencemos descargando una imagen. Crea un archivo llamado download_image.py:

import requests
import os

## URL de una imagen para descargar
image_url = "https://httpbin.org/image/jpeg"

## Realiza una petición para obtener la imagen
response = requests.get(image_url)

## Verifica si la petición fue exitosa
if response.status_code == 200:
    ## Obtiene el tipo de contenido
    content_type = response.headers.get('Content-Type', '')
    print(f"Tipo de contenido: {content_type}")

    ## Verifica si el contenido es una imagen
    if 'image' in content_type:
        ## Crea un directorio para guardar la imagen si no existe
        output_dir = '/home/labex/project/downloads'
        os.makedirs(output_dir, exist_ok=True)

        ## Guarda la imagen en un archivo
        image_path = os.path.join(output_dir, 'sample_image.jpg')
        with open(image_path, 'wb') as f:
            f.write(response.content)

        ## Imprime información sobre la imagen guardada
        print(f"Imagen guardada en: {image_path}")
        print(f"Tamaño de la imagen: {len(response.content)} bytes")
    else:
        print("La respuesta no contiene una imagen")
else:
    print(f"La petición falló con el código de estado: {response.status_code}")

Ejecuta este script para descargar una imagen:

python download_image.py

Deberías ver una salida que confirma que la imagen fue descargada y guardada en /home/labex/project/downloads/sample_image.jpg.

Descargando un Archivo con Progreso

Al descargar archivos grandes, puede ser útil mostrar un indicador de progreso. Creemos un script que muestre el progreso de la descarga. Crea un archivo llamado download_with_progress.py:

import requests
import os
import sys

def download_file(url, filename):
    ## Realiza una petición para obtener el archivo
    ## Transmite la respuesta para manejar archivos grandes de manera eficiente
    response = requests.get(url, stream=True)

    ## Verifica si la petición fue exitosa
    if response.status_code != 200:
        print(f"La petición falló con el código de estado: {response.status_code}")
        return False

    ## Obtiene el tamaño total del archivo si está disponible
    total_size = int(response.headers.get('Content-Length', 0))
    if total_size:
        print(f"Tamaño total del archivo: {total_size/1024:.2f} KB")
    else:
        print("Encabezado Content-Length no encontrado. No se puede determinar el tamaño del archivo.")

    ## Crea un directorio para guardar el archivo si no existe
    os.makedirs(os.path.dirname(filename), exist_ok=True)

    ## Descarga el archivo en fragmentos y muestra el progreso
    print(f"Descargando {url} a {filename}...")

    ## Inicializa variables para el seguimiento del progreso
    downloaded = 0
    chunk_size = 8192  ## Fragmentos de 8 KB

    ## Abre el archivo para escribir
    with open(filename, 'wb') as f:
        ## Itera a través de los fragmentos de la respuesta
        for chunk in response.iter_content(chunk_size=chunk_size):
            if chunk:  ## Filtra los fragmentos keep-alive
                f.write(chunk)
                downloaded += len(chunk)

                ## Calcula y muestra el progreso
                if total_size:
                    percent = downloaded * 100 / total_size
                    sys.stdout.write(f"\rProgreso: {percent:.1f}% ({downloaded/1024:.1f} KB)")
                    sys.stdout.flush()
                else:
                    sys.stdout.write(f"\rDescargado: {downloaded/1024:.1f} KB")
                    sys.stdout.flush()

    ## Imprime una nueva línea para asegurar que la siguiente salida comience en una nueva línea
    print()

    return True

## URL de un archivo para descargar
file_url = "https://speed.hetzner.de/100MB.bin"

## Ruta donde se guardará el archivo
output_path = '/home/labex/project/downloads/test_file.bin'

## Descarga el archivo
success = download_file(file_url, output_path)

if success:
    ## Obtiene las estadísticas del archivo
    file_size = os.path.getsize(output_path)
    print(f"\n¡Descarga completa!")
    print(f"Archivo guardado en: {output_path}")
    print(f"Tamaño del archivo: {file_size/1024/1024:.2f} MB")
else:
    print("\nDescarga fallida.")

Ejecuta este script para descargar un archivo con seguimiento del progreso:

python download_with_progress.py

Verás una barra de progreso actualizándose a medida que se descarga el archivo. Ten en cuenta que esto descarga un archivo de 100MB, lo que podría llevar algún tiempo dependiendo de la velocidad de tu conexión.

Para cancelar la descarga, puedes presionar Ctrl+C.

Trabajando con Encabezados de Respuesta y Metadatos

Al descargar archivos, los encabezados de respuesta a menudo contienen metadatos útiles. Creemos un script que examine los encabezados de respuesta en detalle. Crea un archivo llamado response_headers.py:

import requests

def check_url(url):
    print(f"\nComprobando URL: {url}")

    try:
        ## Realiza una petición HEAD primero para obtener los encabezados sin descargar el contenido completo
        head_response = requests.head(url)

        print(f"Código de estado de la petición HEAD: {head_response.status_code}")

        if head_response.status_code == 200:
            ## Imprime todos los encabezados
            print("\nEncabezados de respuesta:")
            for header, value in head_response.headers.items():
                print(f"  {header}: {value}")

            ## Extrae el tipo de contenido y el tamaño
            content_type = head_response.headers.get('Content-Type', 'Desconocido')
            content_length = head_response.headers.get('Content-Length', 'Desconocido')

            print(f"\nTipo de contenido: {content_type}")

            if content_length != 'Desconocido':
                size_kb = int(content_length) / 1024
                size_mb = size_kb / 1024

                if size_mb >= 1:
                    print(f"Tamaño del contenido: {size_mb:.2f} MB")
                else:
                    print(f"Tamaño del contenido: {size_kb:.2f} KB")
            else:
                print("Tamaño del contenido: Desconocido")

            ## Verifica si el servidor soporta peticiones de rango
            accept_ranges = head_response.headers.get('Accept-Ranges', 'none')
            print(f"Soporta peticiones de rango: {'Sí' if accept_ranges != 'none' else 'No'}")

        else:
            print(f"La petición HEAD falló con el código de estado: {head_response.status_code}")

    except requests.exceptions.RequestException as e:
        print(f"Error: {e}")

## Comprueba algunas URLs diferentes
check_url("https://httpbin.org/image/jpeg")
check_url("https://speed.hetzner.de/100MB.bin")
check_url("https://example.com")

Ejecuta este script para ver información detallada sobre los encabezados de respuesta:

python response_headers.py

Verás una salida que muestra los encabezados para diferentes tipos de contenido, incluyendo imágenes, archivos binarios y páginas HTML.

Comprender los encabezados de respuesta es crucial para muchas tareas de desarrollo web, como:

  • Determinar los tipos y tamaños de archivos antes de descargarlos
  • Implementar descargas reanudables con peticiones de rango
  • Comprobar las políticas de caché y las fechas de caducidad
  • Manejar redirecciones y autenticación

Resumen

En este laboratorio, has aprendido a trabajar con la biblioteca requests de Python para interactuar con servicios web y APIs. Ahora tienes las habilidades para:

  1. Realizar peticiones HTTP y manejar códigos de estado y errores de respuesta
  2. Analizar datos JSON de respuestas de API
  3. Extraer información de contenido HTML utilizando BeautifulSoup
  4. Descargar y procesar contenido binario como imágenes y archivos
  5. Trabajar con encabezados de respuesta y metadatos

Estas habilidades forman la base para muchas aplicaciones de Python, incluyendo web scraping, integración de API, recopilación de datos y automatización. Ahora puedes construir aplicaciones que interactúan con servicios web, extraen información útil de sitios web y procesan varios tipos de contenido web.

Para continuar aprendiendo, podrías explorar:

  • Métodos de autenticación para acceder a APIs protegidas
  • Trabajar con APIs más complejas que requieren encabezados específicos o formatos de petición
  • Construir un proyecto completo de web scraping que recopile y analice datos
  • Crear una aplicación Python que se integre con múltiples APIs

Recuerda que al realizar web scraping o usar APIs, es importante verificar los términos de servicio y respetar los límites de frecuencia (rate limits) para evitar ser bloqueado.