Raspado web con HTTPX en Python

Descubre HTTPX, un potente cliente HTTP de Python para el raspado web. Conoce la configuración, las funciones, las técnicas avanzadas, así como su comparación con Requests.
15 min read
web scraping with httpx and python blog image

En este artículo, aprenderás:

  • Qué es HTTP y qué funciones ofrece
  • Cómo usar HTTPX para el raspado web en una sección guiada
  • Funciones avanzadas de HTTPX para el raspado web
  • Una comparación entre HTTPX y Requests para realizar solicitudes automatizadas

¡Vamos allá!

¿Qué es HTTPX?

HTTPX es un cliente HTTP con todas las funciones para Python 3, creado sobre la biblioteca retryablehttp.  Está diseñado para garantizar resultados fiables incluso con un elevado número de hilos. HTTPX proporciona API síncronas y asíncronas, compatibles con los protocolos HTTP/1.1 y HTTP/2.

⚙️Funciones

  • Base de código sencilla y modular, que facilita la contribución.
  • Indicadores rápidos y totalmente configurables para inspeccionar varios elementos.
  • Compatible con varios métodos de inspección basados en HTTP.
  • Alternativa automática e inteligente de HTTPS a HTTP de forma predeterminada.
  • Acepta hosts, URL y CIDR como entrada.
  • Compatible con proxies, encabezados HTTP personalizados, tiempos de espera personalizados, autenticación básica y mucho más.

👍 Ventajas

  • Disponible desde la línea de comandos mediante httpx[cli].
  • Repleto de funciones, incluida la compatibilidad con HTTP/2 y una API asíncrona.
  • Este proyecto se desarrolla activamente…

👎 Desventajas

  • … tiene actualizaciones frecuentes que pueden introducir cambios importantes en las nuevas versiones.
  • Es menos popular que la biblioteca Requests

Raspado web con HTTPX: una guía paso a paso

HTTPX es un cliente HTTP, por lo que te ayuda a recuperar los contenidos HTML sin procesar de una página. Luego, para analizar y extraer datos del HTML, necesitarás un analizador HTML como BeautifulSoup.

En realidad, HTTPX no es un cliente HTTP cualquiera, sino uno de los mejores clientes HTTP de Python para el raspado web.

¡Sigue este tutorial para aprender a usar HTTPX para el raspado web con BeautifulSoup!

Importante: Si bien HTTPX solo se usa en las primeras etapas del proceso, te guiaremos a través de un flujo de trabajo completo. Si te interesan las técnicas de raspado web con HTTPX más avanzadas, puede pasar al capítulo siguiente después del paso 3.

Paso 1: configura el proyecto

Asegúrate de tener Python 3+ instalado en el ordenador. Si no es así, descárgalo del sitio oficial y sigue las instrucciones de instalación.

Ahora, usa el siguiente comando para crear un directorio para tu proyecto de raspado HTTPX:

mkdir httpx-scraper

Navega hasta este e inicializa un entorno virtual en su interior:

cd httpx-scraper
python -m venv env

Abre la carpeta del proyecto en tu IDE de Python preferido. Visual Studio Code con la extensión de Python o PyCharm Community Edition son opciones válidas.

Ahora, crea un archivo scraper.py dentro de la carpeta del proyecto. Actualmente, scraper.py es un script de Python vacío, pero pronto contendrá la lógica de raspado.

En la terminal de tu IDE, activa el entorno virtual. En Linux o macOS, ejecuta:

./env/bin/activate

De manera equivalente, para Windows, ejecuta:

env/Scripts/activate

¡Increíble! Ya está todo listo.

Paso 2: instala las librerías de raspado

En un entorno virtual activado, instala HTTPX y BeautifulSoup con el siguiente comando:

pip install httpx beautifulsoup4

De este modo, se añadirán httpx y beautifulsoup4 a las dependencias de tu proyecto.

Impórtalos a tu script scraper.py :

import httpx
from bs4 import BeautifulSoup

¡Genial! Ya puedes pasar al siguiente paso de tu flujo de trabajo de raspado.

Paso 3: recupera el HTML de la página objetivo

En este ejemplo, la página objetivo será el sitio «Quotes to Scrape»:

Página de inicio de Quotes To Scrape

Usa HTTPX para recuperar el HTML de la página de inicio con el método get()

# Make an HTTP GET request to the target page
response = httpx.get("http://quotes.toscrape.com")

En segundo plazo, HTTPX realizará una solicitud HTTP GET al servidor, que responderá con el HTML de la página. Puedes acceder a los contenidos HTML mediante el atributo response.text

html = response.text
print(html)

Esto imprimirá los contenidos HTML sin procesar de la página:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Quotes to Scrape</title>
    <link rel="stylesheet" href="/static/bootstrap.min.css">
    <link rel="stylesheet" href="/static/main.css">
</head>
<body>
    <!-- omitted for brevity... -->
</body>
</html>

¡Fantástico! Es hora de analizar estos contenidos y extraer los datos que necesitas.

Paso 4: analiza el HTML

Introduce los contenidos HTML al constructor BeautifulSoup para analizarlos:

# Parse the HTML content using
BeautifulSoup soup = BeautifulSoup(html, "html.parser")

html.parser es el analizador HTML estándar de Python que se utilizará para analizar los contenidos.

La variable soup ahora contiene el HTML analizado y expone los métodos para extraer los datos que necesitas.

HTTPX se ha ocupado de recuperar el HTML y ahora pasarás a la fase tradicional de análisis de datos con BeautifulSoup. Para obtener más información, consulta nuestro tutorial sobre raspado web con Beautiful Soup.

Paso 5: extrae datos de la página

Puedes raspar los datos de las citas de la página usando las siguientes líneas de código:

# Where to store the scraped data
quotes = []

# Extract all quotes from the page
quote_elements = soup.find_all("div", class_="quote")

# Loop through quotes and extract text, author, and tags
for quote_element in quote_elements:
    text = quote_element.find("span", class_="text").get_text().get_text().replace("“", "").replace("”", "")
    author = quote_element.find("small", class_="author")
    tags = [tag.get_text() for tag in quote_element.find_all("a", class_="tag")]

    # Store the scraped data
    quotes.append({
        "text": text,
        "author": author,
        "tags": tags
    })

Este fragmento define una lista denominada quotes para almacenar los datos raspados. A continuación, selecciona todos los elementos HTML de la cita y los recorre en iteración para extraer el texto, el autor y las etiquetas de la cita. Cada cita extraída se almacena como un diccionario dentro de la lista quotes y organiza los datos para su posterior uso o exportación.

¡Bien! Se ha implementado la lógica de raspado.

Paso 6: exporta los datos raspados

Usa la siguiente lógica para exportar los datos raspados a un archivo CSV:

# Specify the file name for export
with open("quotes.csv", mode="w", newline="", encoding="utf-8") as file:
    writer = csv.DictWriter(file, fieldnames=["text", "author", "tags"])

    # Write the header row
    writer.writeheader()

    # Write the scraped quotes data
    writer.writerows(quotes)

Este fragmento abre un archivo llamado quotes.csv en modo escritura, define los encabezados de las columnas (textauthor y tags), escribe los encabezados en el archivo y, a continuación, escribe cada diccionario de la lista quotes en el archivo CSV.  csv.DictWriter gestiona el formato, lo que facilita el almacenamiento de datos estructurados.

No te olvides de importar csv de la biblioteca estándar de Python:

import csv

Paso 7: júntalo todo

El script final de raspado web con HTTPX contendrá:

import httpx
from bs4 import BeautifulSoup
import csv

# Make an HTTP GET request to the target page
response = httpx.get("http://quotes.toscrape.com")

# Access the HTML of the target page
html = response.text

# Parse the HTML content using BeautifulSoup
soup = BeautifulSoup(html, "html.parser")

# Where to store the scraped data
quotes = []

# Extract all quotes from the page
quote_elements = soup.find_all("div", class_="quote")

# Loop through quotes and extract text, author, and tags
for quote_element in quote_elements:
    text = quote_element.find("span", class_="text").get_text().replace("“", "").replace("”", "")
    author = quote_element.find("small", class_="author").get_text()
    tags = [tag.get_text() for tag in quote_element.find_all("a", class_="tag")]

    # Store the scraped data
    quotes.append({
        "text": text,
        "author": author,
        "tags": tags
    })

# Specify the file name for export
with open("quotes.csv", mode="w", newline="", encoding="utf-8") as file:
    writer = csv.DictWriter(file, fieldnames=["text", "author", "tags"])

    # Write the header row
    writer.writeheader()

    # Write the scraped quotes data
    writer.writerows(quotes)

Ejecútalo con:

python scraper.py

O, en Linux/macOS:

python3 scraper.py

Aparecerá un archivo quotes.csv en la carpeta raíz de tu proyecto. Ábrelo y verás:

El CSV que contiene los datos extraídos

¡Y ya está! Acabas de aprender a realizar el raspado web con HTTPX y BeautifulSoup.

Funciones y técnicas avanzadas de raspado web con HTTPX

Ahora que sabes usar HTTPX para realizar raspado web en un caso básico, puedes verlo en acción con casos prácticos más complejos.

En los ejemplos siguientes, el sitio objetivo será el punto final HTTPBin.io /anything Se trata de una API especial que devuelve la dirección IP, los encabezados y otra información que envía la persona que llama.

¡Domina HTTPX para el raspado web!

Configura encabezados personalizados

HTTPX te permite especificar encabezados personalizados gracias al argumento headers

import httpx

# Custom headers for the request
headers = {
    "accept": "application/json",
    "accept-language": "en-US,en;q=0.9,fr-FR;q=0.8,fr;q=0.7,es-US;q=0.6,es;q=0.5,it-IT;q=0.4,it;q=0.3"
}

# Make a GET request with custom headers
response = httpx.get("https://httpbin.io/anything", headers=headers)
# Handle the response...

Establece un User-Agent personalizado

User-Agent es uno de los encabezados HTTP más importantes en el raspado web. Por defecto, HTTPX utiliza el siguiente User-Agent:

python-httpx/<VERSION>

Este valor puede revelar fácilmente que tus solicitudes están automatizadas, lo que podría hacer que el sitio objetivo las bloquee.

Para evitarlo, puedes configurar un User-Agent personalizado para imitar un navegador real, de la siguiente manera:

import httpx

# Define a custom User-Agent
headers = {
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36"
}

# Make a GET request with the custom User-Agent
response = httpx.get("https://httpbin.io/anything", headers=headers)
# Handle the response...

¡Descubre los mejores agentes de usuario para el raspado web!

Configura cookies

Al igual que los encabezados HTTP, puedes configurar cookies en HTTPX usando el argumento cookies

import httpx

# Define cookies as a dictionary
cookies = {
    "session_id": "3126hdsab161hdabg47adgb",
    "user_preferences": "dark_mode=true"
}

# Make a GET request with custom cookies
response = httpx.get("https://httpbin.io/anything", cookies=cookies)
# Handle the response...

Esto te permite incluir los datos de sesión necesarios para tus solicitudes de raspado web.

Integración de proxy

Puedes enrutar tus solicitudes HTTPX a través de un proxy para proteger tu identidad y evitar prohibiciones de IP mientras realizas el raspado web. Para ello, puedes utilizar el argumento proxies

import httpx

# Replace with the URL of your proxy server
proxy = "<YOUR_PROXY_URL>"

# Make a GET request through a proxy server
response = httpx.get("https://httpbin.io/anything", proxy=proxy)
# Handle the response...

Obtén más información en nuestra guía sobre cómo usar HTTPX con un proxy.

Gestión de errores

Por defecto, HTTPX solo genera errores para problemas de conexión o red. Para generar excepciones también para las respuestas HTTP con códigos de estado 4xx y 5xx, usa el método raise_for_status() de la siguiente manera:

import httpx

try:
    response = httpx.get("https://httpbin.io/anything")
    # Raise an exception for 4xx and 5xx responses
    response.raise_for_status()
    # Handle the response...
except httpx.HTTPStatusError as e:
    # Handle HTTP status errors
    print(f"HTTP error occurred: {e}")
except httpx.RequestError as e:
    # Handle connection or network errors
    print(f"Request error occurred: {e}")

Gestión de sesiones

Al usar la API de nivel superior en HTTPX, se establece una nueva conexión para cada solicitud. En otras palabras, las conexiones TCP no se reutilizan. A medida que aumenta la cantidad de solicitudes a un host, ese enfoque pierde eficacia.

Por el contrario, usar una instancia httpx.Client permite la agrupación de conexiones HTTP. De este modo, varias solicitudes al mismo host pueden reutilizar una conexión TCP existente en vez de crear una nueva para cada solicitud.

Las ventajas de usar un Client en lugar de la API de nivel superior son:

  • Menor latencia en las solicitudes (evitando los «handshakes» repetidos)
  • Menor uso de la CPU y menos idas y vueltas
  • Menor congestión de la red

Además, las instancias de Client admiten la gestión de sesiones con funciones que no están disponibles en la API de nivel superior, como:

  • Persistencia de las cookies en todas las solicitudes.
  • Aplicación de la configuración en todas las solicitudes salientes.
  • Envío de solicitudes a través de proxies HTTP.

La forma recomendada de usar un Client en HTTPX es con un gestor de contexto (instrucción with): 

import httpx

with httpx.Client() as client:
    # Make an HTTP request using the client
    response = client.get("https://httpbin.io/anything")

    # Extract the JSON response data and print it
    response_data = response.json()
    print(response_data)

Como alternativa, puedes gestionar el cliente manualmente y cerrar el grupo de conexiones de forma explícita con client.close():

import httpx

client = httpx.Client()
try:
    # Make an HTTP request using the client
    response = client.get("https://httpbin.io/anything")

    # Extract the JSON response data and print it
    response_data = response.json()
    print(response_data)
except:
  # Handle the error...
  pass
finally:
  # Close the client connections and release resources
  client.close()

Nota: si ya conoces la biblioteca requestshttpx.Client() tiene un propósito similar al de requests.Session().

API asíncrona

Por defecto, HTTPX expone una API síncrona estándar. Al mismo tiempo, ofrece un cliente asíncrono para los casos en que sea necesario. Si trabajas con asyncio, es esencial usar un cliente asíncrono para enviar las solicitudes HTTP salientes de manera eficiente.

La programación asíncrona es un modelo de simultaneidad mucho más eficiente que el multihilo. Ofrece grandes mejoras de rendimiento y admite conexiones de red de larga duración como WebSockets. Esto la convierte en un factor clave para acelerar el raspado web.

Para realizar solicitudes asíncronas en HTTPX, necesitarás un AsyncClient. Inicialízalo y úsalo para hacer una solicitud GET como se muestra a continuación:

import httpx
import asyncio

async def fetch_data():
    async with httpx.AsyncClient() as client:
        # Make an async HTTP request
        response = await client.get("https://httpbin.io/anything")

        # Extract the JSON response data and print it
        response_data = response.json()
        print(response_data)

# Run the async function
asyncio.run(fetch_data())

La instrucción with garantiza que el cliente se cierre automáticamente cuando finaliza el bloque. Otra posibilidad, si gestionas el cliente manualmente, es cerrarlo explícitamente con await client.close().

Recuerda que todos los métodos de solicitud HTTPX (get()post(), etc.) son asíncronos cuando se usa un AsyncClient. Por lo tanto, debes añadir await antes de llamarlos para obtener una respuesta.

Reintenta las solicitudes fallidas

La inestabilidad de la red durante el raspado de datos web puede causar fallos de conexión o tiempos de espera. HTTPX simplifica la gestión de estos problemas a través de su interfaz HTTPTransport.  Este mecanismo reintenta las solicitudes cuando se produce un httpx.ConnectError o httpx.ConnectTimeout

El siguiente ejemplo muestra cómo configurar un transporte para reintentar las solicitudes hasta 3 veces:

import httpx

# Configure transport with retry capability on connection errors or timeouts
transport = httpx.HTTPTransport(retries=3)

# Use the transport with an HTTPX client
with httpx.Client(transport=transport) as client:
    # Make a GET request
    response = client.get("https://httpbin.io/anything")
    # Handle the response...

Ten en cuenta que solo los errores relacionados con la conexión dan lugar a un reintento. Para gestionar errores de lectura/escritura o códigos de estado HTTP específicos, debes implementar una lógica de reintento personalizada con bibliotecas como tenacity.

Comparativa de HTTPX y Requests para realizar raspado web

A continuación, se muestra una tabla resumida para comparar HTTPX y Requests para realizar raspado web:

Función HTTPX Requests
Estrellas en GitHub 8000 52 400
Compatibilidad asíncrona ✔️
Agrupación de conexiones ✔️ ✔️
Compatibilidad con HTTP/2 ✔️
Personalización de User-Agent ✔️ ✔️
Compatibilidad con proxy ✔️ ✔️
Gestión de cookies ✔️ ✔️
Tiempos de espera Personalizable para la conexión y la lectura Personalizable para la conexión y la lectura
Mecanismo de reintento Disponible mediante transportes Disponible mediante HTTPAdapters
Rendimiento Alto Medio
Popularidad y apoyo de la comunidad Creciente Grande

Conclusión

En este artículo, has examinado la biblioteca httpx para realizar raspado web. Has aprendido qué es, qué ofrece y cuáles son sus ventajas. HTTPX es una opción rápida y fiable para realizar solicitudes HTTP al recopilar datos en línea.

El problema es que las solicitudes HTTP automatizadas revelan tu dirección IP pública, lo que puede exponer tu identidad y ubicación. Por tanto, tu privacidad se ve comprometida. Para mejorar tu seguridad y privacidad, uno de los métodos más eficaces es utilizar un servidor proxy para ocultar tu dirección IP.

Bright Data controla los mejores servidores proxy del mundo y trabaja para empresas de la lista Fortune 500 y para más de 20 000 clientes. Su oferta incluye una gran variedad de tipos de proxy:

¡Crea una cuenta gratuita de Bright Data hoy mismo para probar nuestros proxies y soluciones de raspado web!

No se requiere tarjeta de crédito