martes, 24 de marzo de 2026

Automatizacion con Python - Cap3 - parte 2

Capítulo 3 // Parte 2 de 3
Python Automation Cookbook

Crawling, Feeds RSS y APIs Web

Rastreo de sitios, suscripción a feeds y consumo de APIs RESTful con Python

En esta segunda parte avanzamos hacia técnicas más potentes: rastrear sitios completos siguiendo enlaces automáticamente, consumir feeds RSS para monitorear publicaciones, acceder a APIs RESTful con JSON e interactuar con formularios HTML de forma programática.

// Contenido del capítulo completo
  • Descargar páginas web
  • Analizar HTML
  • Rastrear la web (crawling)
  • Suscribirse a feeds RSS
  • Acceder a APIs web
  • Interactuar con formularios
  • Selenium para interacción avanzada
  • Páginas protegidas por contraseña
  • Acelerar el web scraping
RECETA_03

Rastrear la Web (Crawling)

Dada la naturaleza de las páginas de hiperenlaces, comenzar desde un punto conocido y seguir los enlaces automáticamente hacia otras páginas es una herramienta indispensable. Rastrearemos una página buscando una frase y recuperaremos todos los párrafos que la contengan, restringiéndonos al mismo dominio.

Al rastrear la web, recuerda establecer límites al descargar. Es muy fácil rastrear demasiadas páginas. Como puede confirmar cualquiera que haya consultado Wikipedia, internet es potencialmente infinito.

Preparación

Descarga el sitio de prueba del repositorio y ejecútalo:

terminal
# Descargar el sitio de prueba desde:
# https://github.com/PacktPublishing/Python-Automation-Cookbook-Second-Edition/tree/master/Chapter03/test_site

$ python simple_delay_server.py
# Sirve el blog en http://localhost:8000

Cómo hacerlo

crawling_web_step1.py (partes clave)
from urllib.parse import urlparse, urljoin
import requests, re, http.client
from bs4 import BeautifulSoup

def main(base_url, to_search):
    checked_links = set()
    to_check = [base_url]
    max_checks = 10          # Límite de seguridad
    while to_check and max_checks:
        link = to_check.pop(0)
        links = process_link(link, text=to_search)
        checked_links.add(link)
        for link in links:
            if link not in checked_links:
                checked_links.add(link)
                to_check.append(link)
        max_checks -= 1

def process_link(source_link, text):
    parsed_source = urlparse(source_link)
    result = requests.get(source_link)
    if result.status_code != http.client.OK:
        return []
    if 'html' not in result.headers['Content-type']:
        return []
    page = BeautifulSoup(result.text, 'html.parser')
    search_text(source_link, page, text)
    return get_links(parsed_source, page)

def get_links(parsed_source, page):
    links = []
    for element in page.find_all('a'):
        link = element.get('href')
        if not link or link.startswith('#') or link.startswith('mailto:'):
            continue
        if not link.startswith('http'):
            path = urljoin(parsed_source.path, link)
            link = f'{parsed_source.scheme}://{parsed_source.netloc}{path}'
        if parsed_source.netloc not in link:
            continue                     # Solo mismo dominio
        links.append(link)
    return links
python crawling_web_step1.py http://localhost:8000/ -p python
Link http://localhost:8000/: --> A smaller article, that contains a reference to Python
Link http://localhost:8000/files/5eabef...html: --> A smaller article...
Link http://localhost:8000/files/33714f...html: --> This is the actual bit with a python reference...

Cómo funciona

El crawler tiene tres componentes principales que trabajan en conjunto:

Función main: Mantiene dos estructuras — checked_links (ya visitados) y to_check (pendientes) — con un límite de 10 páginas máximo para no rastrear indefinidamente.
Función process_link: Descarga cada enlace, verifica el código de estado y el Content-Type (solo HTML), luego parsea y busca el texto.
Función get_links: Extrae todos los <a href> de la página, normaliza los enlaces relativos a absolutos y filtra los que pertenecen al mismo dominio usando urlparse.
RECETA_04

Suscribirse a Feeds RSS

RSS es probablemente el mayor secreto de internet. En su núcleo, RSS presenta una sucesión de referencias ordenadas (artículos, episodios de podcasts, publicaciones de YouTube) con sus tiempos de publicación. Usaremos el módulo feedparser.

Preparación

terminal
$ echo "feedparser==5.2.1" >> requirements.txt
$ pip install -r requirements.txt

Cómo hacerlo

leer_feed.py
import feedparser, requests

# Analizar el feed RSS del New York Times
rss = feedparser.parse('http://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml')

# Información general del feed
print(rss.feed.title)       # 'NYT > Top Stories'
print(len(rss.entries))     # 44 entradas

# Acceder a entradas individuales
entries = rss.entries
print(entries[0]['title'])   # Título del artículo más reciente
print(entries[0]['link'])    # URL completa del artículo
print(entries[0]['summary']) # Resumen del artículo

# Ver todas las claves disponibles en una entrada
print(entries[5].keys())
# dict_keys(['title', 'title_detail', 'links', 'link', 'id',
#            'summary', 'published', 'published_parsed', 'tags', ...])

# Descargar el artículo completo si es relevante
if 'python' in entries[0]['summary'].lower():
    full_article = requests.get(entries[0]['link'])
python leer_feed.py
NYT > Top Stories
44
Venezuela Says Guaidó Can't Leave Country and Freezes His Assets
https://www.nytimes.com/2019/01/29/world/americas/venezuela-guaido...
La estrategia básica con feeds: analizar, recorrer entradas verificando si son interesantes (por resumen o título), descargar el artículo completo solo si vale la pena, y almacenar la última fecha de publicación para no revisar entradas antiguas en la siguiente ejecución.
Los feeds RSS son también una excelente herramienta personal para curar tu propia selección de fuentes de noticias. Existen lectores de feeds como Newsboat (terminal) o Feedly (web) perfectos para esto.
RECETA_05

Acceder a APIs Web (REST)

Las APIs RESTful son interfaces que describen recursos a través de URLs HTTP. Cada URL representa un recurso particular y los recursos se manipulan con métodos HTTP estándar. requests incluye soporte nativo para JSON.

GET
Recuperar un recurso. No requiere datos en el cuerpo.
POST
Crear un nuevo recurso. Requiere datos en el cuerpo.
PATCH
Actualización parcial de un recurso existente.
PUT
Reemplazar un recurso completo.
DELETE
Eliminar un recurso. No requiere datos en el cuerpo.
.json()
Decodifica automáticamente la respuesta JSON a dict Python.

Cómo hacerlo

Usaremos jsonplaceholder.typicode.com como servidor de prueba RESTful:

api_rest.py
import requests

BASE = 'https://jsonplaceholder.typicode.com'

# GET: Obtener colección de recursos
result = requests.get(f'{BASE}/posts')
print(result.status_code)         # 200
print(result.json()[-1])           # Último post

# POST: Crear nuevo recurso
new_post = {'userId': 10, 'title': 'a title', 'body': 'some content'}
result = requests.post(f'{BASE}/posts', json=new_post)
print(result.status_code)         # 201 (Created)
print(result.headers['Location']) # URL del nuevo recurso

# GET: Obtener recurso individual
result = requests.get(f'{BASE}/posts/2')
print(result.json())              # {'userId': 1, 'id': 2, ...}

# PATCH: Actualización parcial
update = {'body': 'new body'}
result = requests.patch(f'{BASE}/posts/2', json=update)
print(result.json())              # Recurso actualizado
python api_rest.py
200
{'userId': 10, 'id': 100, 'title': 'at nam consequatur ea labore ea harum', ...}
201
http://jsonplaceholder.typicode.com/posts/101
{'userId': 1, 'id': 2, 'title': 'qui est esse', 'body': 'new body'}
La diferencia entre PATCH y PUT: PATCH realiza una actualización parcial (solo los campos enviados), mientras que PUT reemplaza el recurso completo. Usa PATCH para actualizar campos específicos.
RECETA_06

Interactuar con Formularios

Los formularios web son la forma estándar de enviar datos a un servidor. Los navegadores los presentan visualmente, pero podemos replicar este proceso programáticamente con requests.

Enviar datos a un sitio es un asunto más delicado que recibirlos. Enviar comentarios automáticos es básicamente spam. Verifica siempre que tu caso de uso sea válido y ético antes de automatizar envíos de formularios.

Cómo hacerlo

interactuar_formulario.py
import requests, re
from bs4 import BeautifulSoup

# Paso 1: Descargar el formulario y analizar sus campos
response = requests.get('https://httpbin.org/forms/post')
page = BeautifulSoup(response.text, 'html.parser')
form = page.find('form')
fields = {field.get('name') for field in form.find_all(re.compile('input|textarea'))}
print(fields)
# {'delivery', 'topping', 'size', 'custemail', 'comments', 'custtel', 'custname'}

# Paso 2: Preparar los datos según los campos del formulario
data = {
    'custname':  "Sean O'Connell",
    'custtel':   '123-456-789',
    'custemail': 'sean@oconnell.ie',
    'size':      'small',
    'topping':   ['bacon', 'onion'],  # Lista para checkboxes múltiples
    'delivery':  '20:30',
    'comments':  ''
}

# Paso 3: POST a la URL de acción del formulario
# (distinta de la URL que muestra el formulario)
response = requests.post('https://httpbin.org/post', data)
print(response.status_code)    # 200
print(response.json()['form'])  # Confirma los datos enviados
python interactuar_formulario.py
200
{'comments': '', 'custemail': 'sean@oconnell.ie', 'custname': "Sean O'Connell",
 'delivery': '20:30', 'size': 'small', 'topping': ['bacon', 'onion']}
Tokens CSRF: Muchos formularios incluyen un token anti-falsificación. Debes descargarlo primero, extraer el token y reenviarlo junto con los datos: form.find(attrs={'name': 'token'}).get('value'). El nombre del campo varía por sitio.

Resumen // Parte 2

03 // Web Crawling

Seguir enlaces de forma recursiva con límites de seguridad, filtrando por dominio y Content-Type.

04 // Feeds RSS

feedparser convierte cualquier feed en una lista de entradas con título, enlace, resumen y fecha.

05 // APIs REST

GET, POST, PATCH, PUT, DELETE sobre URLs de recursos. Respuestas JSON decodificadas con .json().

06 // Formularios

POST de datos en formato URL-encoded. Analizar el HTML para identificar campos y URL de acción.

Basado en Python Automation Cookbook, 2nd Edition // Jaime Buelta