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.
- 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
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.
Preparación
Descarga el sitio de prueba del repositorio y ejecútalo:
# 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
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
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:
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.
process_link: Descarga cada enlace, verifica el código de estado y el Content-Type (solo HTML), luego parsea y busca el texto.
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.
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
$ echo "feedparser==5.2.1" >> requirements.txt $ pip install -r requirements.txt
Cómo hacerlo
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'])
44
Venezuela Says Guaidó Can't Leave Country and Freezes His Assets
https://www.nytimes.com/2019/01/29/world/americas/venezuela-guaido...
Newsboat (terminal) o Feedly (web) perfectos para esto.
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.
Cómo hacerlo
Usaremos jsonplaceholder.typicode.com como servidor de prueba RESTful:
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
{'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'}
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.
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.
Cómo hacerlo
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
{'comments': '', 'custemail': 'sean@oconnell.ie', 'custname': "Sean O'Connell",
'delivery': '20:30', 'size': 'small', 'topping': ['bacon', 'onion']}
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.