Ir al contenido

Cómo Web Scrape con HTTPX y Python

El web scraping es el proceso de extraer datos de sitios web automáticamente. Es una técnica común que se utiliza para recopilar grandes cantidades de datos para análisis, aprendizaje automático y más. En Python, hay muchas bibliotecas excelentes que facilitan el web scraping. Una opción popular es HTTPX.

HTTPX es un cliente HTTP potente y moderno para Python. Fue creado por los desarrolladores detrás de Requests y se inspira en gran medida en Requests, al tiempo que agrega nuevas funciones como compatibilidad con HTTP/2.

En esta guía completa, exploraremos cómo raspar sitios web de manera efectiva en Python usando HTTPX.

Comenzando con HTTPX

Para comenzar, HTTPX se puede instalar mediante pip:

pip install httpx

Alternativamente, se puede utilizar Poesía:

poetry add httpx

Una vez instalado, HTTPX se puede importar y utilizar:

import httpx

response = httpx.get(‘https://example.com‘)
print(response.text)

Esto realizará una solicitud GET a example.com e imprimirá el HTML de la página de inicio.

HTTPX tiene una API simple y admite todos los verbos HTTP populares como GET, POST, PUT, DELETE, HEAD, OPTIONS, etc.

Algunas características clave incluyen:

  • Soporte HTTP/1.1 y HTTP/2
  • API asincrónica de estilo aiohttp
  • Agrupación de conexiones y keepalive
  • Soporte Proxy
  • Configuración de tiempo de espera
  • Persistencia de cookies
  • API de estilo de solicitudes familiar

A continuación, veamos algunos patrones de uso comunes.

Realizar solicitudes con HTTPX

Para realizar una solicitud GET, el httpx.get() se puede utilizar el método:

response = httpx.get(‘https://example.com‘)

De manera similar, los httpx.post(), httpx.put(), httpx.delete(), etc. se pueden utilizar para otros verbos HTTP.

Los parámetros como encabezados, cookies, tiempo de espera, etc. se pueden pasar como argumentos de palabras clave:

response = httpx.get(
  ‘https://httpbin.org/headers‘,
  headers = {
    ‘User-Agent‘: ‘MyBot 1.0‘
  },
  timeout = 10.0
)

La respuesta contiene propiedades como status_code, headers, text, json(), Etc.:

print(response.status_code)
print(response.headers)
print(response.text)
json = response.json()

También puede transmitir la respuesta de forma incremental iterando sobre el objeto de respuesta.

Usando un cliente HTTPX

Para la mayoría de las tareas de scraping, se recomienda utilizar un persistente httpx.Client ejemplo.

El cliente maneja cosas como agrupación de conexiones, sesiones, cookies, etc. en múltiples solicitudes.

import httpx

client = httpx.Client()

response = client.get(‘https://example.com‘)
print(response.text)

response = client.get(‘https://httpbin.org/cookies/set?name=foo‘)
print(response.text) 

response = client.get(‘https://httpbin.org/cookies‘)
print(response.json())

Aquí realizamos múltiples solicitudes utilizando el mismo cliente, que maneja la persistencia de las cookies automáticamente.

También puedes configurar opciones como encabezados, proxies, autenticación, etc. al crear el cliente:

client = httpx.Client(
  headers = {
    ‘User-Agent‘: ‘MyBot 1.0‘,
    ‘Authorization‘: ‘Bearer xxx‘   
  },
  proxies = ‘http://192.168.1.1:8181/‘,
  auth = (‘username‘, ‘password‘)
)

Ahora veamos cómo realizar solicitudes de forma asincrónica.

Solicitudes asincrónicas con HTTPX

Para realizar solicitudes de forma asincrónica en Python, HTTPX proporciona una AsyncClient:

import httpx

async with httpx.AsyncClient() as client:
  response = await client.get(‘https://example.com‘) 

Utilizamos async with para inicializar el cliente, await en la solicitud y cerrar automáticamente el cliente después.

Para extraer varias URL al mismo tiempo, podemos usar asyncio.gather():

import httpx
import asyncio

async def get_url(url):
  async with httpx.AsyncClient() as client:
    response = await client.get(url)
    return response.text

urls = [‘https://example.com‘, ‘https://httpbin.org‘, ‘https://python.org‘]

async def main():
  responses = await asyncio.gather(*[get_url(url) for url in urls])
  print(responses)

asyncio.run(main())

asyncio.gather() espera simultáneamente múltiples corrutinas y devuelve resultados en el orden de los awaitables.

También hay otras opciones como asyncio.as_completed() para procesarlos a medida que se completan:

tasks = [get_url(url) for url in urls]

async def main():
  for result in asyncio.as_completed(tasks):
    print(await result)

Async IO permite recuperar varias páginas a la vez, lo que resulta útil para acelerar el scraping.

A continuación, veamos cómo extraer datos de respuestas HTML y JSON.

Eliminación de respuestas HTML y JSON

Para el raspado de HTML, podemos usar un analizador como Beautiful Soup para extraer datos:

from bs4 import BeautifulSoup

response = httpx.get(‘https://en.wikipedia.org/wiki/Python_(programming_language)‘)

soup = BeautifulSoup(response.text, ‘html.parser‘)

for link in soup.select(‘.toctext‘):
  print(link.text.strip())

Esto imprime el contenido de la tabla de contenidos de la página de Wikipedia.

Para respuestas JSON, HTTPX proporciona una función incorporada .json() método:

response = httpx.get(‘https://api.github.com/repos/encode/httpx‘)

json = response.json()
print(json[‘description‘])

El json= El parámetro también se puede utilizar para serializar datos JSON en solicitudes.

Junto con un analizador, podemos crear raspadores para extraer datos de API y sitios web.

Manejo de problemas y errores

Durante el scraping, a menudo surgen problemas como errores de conexión, tiempos de espera, límites de velocidad, etc.

HTTPX proporciona excepciones y herramientas para manejarlas adecuadamente.

Tiempos de espera

Para manejar respuestas lentas, puede especificar un personalizado timeout parámetro. El valor predeterminado es 5 segundos.

response = httpx.get(‘https://example.com‘, timeout=10.0) 

Si esto se excede, un httpx.TimeoutException ocurre:

try:
  response = httpx.get(‘https://example.com‘, timeout=0.001)
except httpx.TimeoutException:
  print(‘Request timed out‘)

Es posible que se necesiten tiempos de espera más largos para ciertos sitios o páginas.

Errores HTTP

Para errores HTTP como 400, 500, etc., el response.raise_for_status() se puede utilizar el método:

try:
  response = httpx.get(‘https://httpbin.org/status/500‘)
  response.raise_for_status()
except httpx.HTTPStatusError:
  print(‘HTTP Error occurred‘)  

Esto generará una excepción en cualquier código de estado 4xx o 5xx.

Reintentar solicitudes

Para agregar lógica de reintento, paquetes externos como tenacity puede ser usado:

from tenacity import retry, stop_after_attempt

@retry(stop=stop_after_attempt(3))
def make_request():
  response = httpx.get(‘https://example.com‘)
  response.raise_for_status()
  return response.json()

data = make_request()

Aquí reintentamos hasta 3 veces en cualquier excepción. También se puede definir una lógica de reintento más avanzada.

Límites de solicitudes paralelas

Al realizar muchas solicitudes en paralelo, es posible que encuentre límites de conexión.

El limits El parámetro se puede utilizar para configurar opciones como conexiones máximas:

client = httpx.AsyncClient(
  limits = httpx.Limits(max_connections=20)
)

Ajustar este parámetro en función del sitio de destino puede ayudar a evitar límites.

Al abordar estos problemas comunes, se pueden construir raspadores más resistentes.

Mejores prácticas de scraping

A continuación se ofrecen algunos consejos para crear raspadores web eficaces con HTTPX:

  • Utilice un cliente HTTPX – Los clientes proporcionan agrupación de conexiones, persistencia de cookies y otros beneficios.

  • Raspe cortésmente – Limite las tasas de solicitudes para evitar saturar los servidores. Utilice retrasos y limitaciones aleatorios.

  • Manejar errores – Utilice bloques try/except, comprobaciones de estado y reintentos para solucionar problemas.

  • Usar E/S asíncrona – Raspe las páginas al mismo tiempo para mejorar la velocidad. Pero limite la concurrencia para evitar prohibiciones.

  • Aleatorizar agentes de usuario – Gire cadenas aleatorias de agentes de usuario para que parezcan más humanos.

  • Usar servidores proxy – Rotar diferentes proxies/IP para distribuir solicitudes.

  • Caché y datos persistentes – Guarde los datos extraídos en archivos/bases de datos para evitar volver a extraerlos.

Siguiendo mejores prácticas como estas, se pueden construir raspadores más robustos y fáciles de mantener.

Técnicas avanzadas de raspado

Veamos algunas capacidades de raspado más avanzadas de HTTPX.

Raspado de páginas de autenticación

Para extraer páginas autenticadas, HTTPX admite múltiples tipos de autenticación, como autenticación básica, implícita y de portador:

client = httpx.Client(
  auth = (‘username‘, ‘password‘)
)

response = client.get(‘https://api.example.com/users/me‘)

Las credenciales de autenticación se conservan automáticamente en todas las solicitudes.

Manejo de cookies

El cookies El parámetro se puede utilizar para enviar cookies personalizadas:

client = httpx.Client(
  cookies = {
    ‘sessionId‘: ‘xxxx‘
  }
)

response = client.get(‘https://example.com/dashboard‘) 

Y las cookies configuradas por el servidor persisten automáticamente en el cliente.

Respuestas de transmisión

Para respuestas grandes, puede transmitirlas de forma incremental iterando el objeto de respuesta:

response = client.get(‘https://example.com/bigfile‘)
for chunk in response:
  process(chunk) 

Esto evita tener que cargar la respuesta completa en la memoria.

Soporte de proxy

Para enrutar solicitudes a través de un servidor proxy, el proxies Se puede utilizar el parámetro:

proxy = ‘http://192.168.0.1:8888‘ 

client = httpx.Client(proxies = {‘http‘: proxy, ‘https‘: proxy})

Rotar diferentes servidores proxy ayuda a distribuir solicitudes de diferentes IP.

Encabezados personalizados

Puede falsificar o aleatorizar encabezados de solicitud como agentes de usuario:

client = httpx.Client(headers = {
  ‘User-Agent‘: ‘MyBot 1.0‘ 
})

Esto imita los encabezados de un navegador real.

A través de estas funciones más avanzadas, se pueden crear raspadores robustos utilizando HTTPX y Python.

Rascadores de ejemplo

Veamos ahora algunos ejemplos de scrapers creados con HTTPX.

Raspador de API de Reddit

Aquí hay un raspador básico para la API de Reddit:

import httpx

client = httpx.Client()

subreddit = ‘python‘ 
listing = ‘hot‘
limit = 10

response = client.get(f‘https://www.reddit.com/r/{subreddit}/{listing}.json?limit={limit}‘)

data = response.json()[‘data‘]

for post in data[‘children‘]:
   title = post[‘data‘][‘title‘]
   score = post[‘data‘][‘score‘]
   print(f"{title} (Score: {score})")

Esto recupera datos sobre las publicaciones principales del subreddit de Python. La API devuelve JSON que podemos analizar.

Podríamos ampliar este raspador para extraer datos de múltiples subreddits, almacenar resultados en una base de datos, etc.

Raspador de artículos de noticias

Aquí hay un raspador de noticias simple que extrae artículos de un sitio:

from bs4 import BeautifulSoup
import httpx

client = httpx.Client()

response = client.get("https://example.com/news")
soup = BeautifulSoup(response.text, ‘html.parser‘)

for article in soup.select(‘.article‘):

  title = article.select_one(‘.article-title‘).text
  content = article.select_one(‘.article-content‘).text

  print(title) 
  print(content)
  print() 

Esto encuentra todo .article elementos, extrae los campos de título y contenido e imprime los artículos.

Nuevamente, esto podría ampliarse para eliminar campos adicionales, analizar fechas, almacenar en una base de datos, etc.

Raspador de resultados de búsqueda

Y aquí hay un ejemplo de raspador para los resultados de búsqueda de Google:

import httpx

query = "httpx python"
url = f"https://www.google.com/search?q={query}"

client = httpx.Client()
response = client.get(url)

from bs4 import BeautifulSoup
soup = BeautifulSoup(response.text, ‘html.parser‘)

for result in soup.select(‘.tF2Cxc‘):

  title = result.select_one(‘.DKV0Md‘).text
  link = result.select_one(‘.yuRUbf a‘)[‘href‘]

  print(title)
  print(link) 
  print()

Esto busca en Google una consulta determinada, analiza los enlaces/títulos de los resultados y los imprime.

Nuevamente, se podrían realizar muchas mejoras, como extraer recuentos de resultados de búsqueda, campos adicionales, raspar páginas de resultados, detectar captchas, etc.

Estos ejemplos demuestran patrones de raspado comunes con HTTPX. Se pueden aplicar las mismas técnicas para crear raspadores para muchos sitios web y API.

Resumen

En resumen, HTTPX proporciona un potente cliente HTTP para crear raspadores web de Python. Aquí hay algunos puntos clave:

  • HTTPX tiene una API simple de estilo solicitudes para realizar solicitudes.

  • El soporte asíncrono permite realizar solicitudes al mismo tiempo.

  • Manejo sólido de errores con tiempos de espera, reintentos y comprobaciones de estado.

  • Extraiga páginas HTML con Beautiful Soup y las API JSON fácilmente.

  • Los clientes persistentes proporcionan agrupación de conexiones, sesiones y manejo de cookies.

  • Técnicas avanzadas como proxies, encabezados y autenticación permiten raspadores sofisticados.

  • Siga las mejores prácticas, como el uso de limitaciones, retrasos aleatorios y agentes de usuario.

HTTPX facilita el inicio del scraping con Python. Con un manejo sólido de errores y concurrencia asincrónica, se pueden desarrollar raspadores escalables.

¡Pruebe HTTPX en su próximo proyecto de web scraping de Python!

Tags:

Únase a la conversación

Su dirección de correo electrónico no será publicada. Las areas obligatorias están marcadas como requeridas *