Hace unos meses, quise montar un resumen diario con las noticias más destacadas de Hacker News para nuestro equipo en Thunderbit. Mi primera idea fue simplemente guardar la página en favoritos y revisarla cada mañana. Eso me aguantó unos tres días, hasta que me di cuenta de que estaba perdiendo 20 minutos al día solo leyendo titulares y copiando enlaces a una hoja de cálculo.
Hacker News es una de las fuentes más densas y valiosas de inteligencia tecnológica en internet: recibe alrededor de 13 millones de visitas mensuales, se publican unas 1,300 historias nuevas al día y se generan cerca de 13,000 comentarios diarios. Tanto si sigues tendencias tecnológicas emergentes, monitorizas tu marca, montas un pipeline de reclutamiento a partir de hilos de "Who's Hiring" o simplemente quieres estar al tanto de lo que le importa al mundo developer, intentar hacerlo todo a mano es una batalla perdida.
La buena noticia: extraer datos de Hacker News con Python es más fácil de lo que parece. En esta guía te mostraré dos métodos completos: scraping HTML con BeautifulSoup y la API oficial de HN Firebase. También verás paginación, exportación de datos, patrones listos para producción y una alternativa sin código para cuando Python se te queda grande.
¿Por qué extraer Hacker News con Python?
Hacker News no es un agregador de enlaces más. Es un feed curado por la comunidad, donde las historias tecnológicas más interesantes suben gracias a los votos y a la conversación activa. Su audiencia está muy orientada a profesionales de tecnología (aproximadamente 76% hombres, con predominio de 25 a 34 años), y el 66% de tráfico directo del sitio deja claro que se trata de una comunidad fiel y habitual, no de visitantes de paso.
Estas son algunas razones por las que la gente extrae datos de HN:
| Caso de uso | Lo que obtienes |
|---|---|
| Resumen diario de tecnología | Historias destacadas, puntuaciones y enlaces enviados a tu correo o Slack |
| Monitoreo de marca/competencia | Alertas cuando se menciona tu empresa o producto |
| Análisis de tendencias | Seguimiento de tecnologías, lenguajes o temas que ganan tracción con el tiempo |
| Reclutamiento | Extracción de hilos de "Who's Hiring" para vacantes, stacks tecnológicos y señales salariales |
| Investigación de contenido | Temas que funcionan bien para escribir o compartir |
| Análisis de sentimiento | Medición de la opinión de la comunidad sobre productos, lanzamientos o cambios del sector |
Empresas con un valor conjunto superior a 400 mil millones de dólares — Stripe, Dropbox y Airbnb — atribuyen a Hacker News una parte clave de sus primeros usuarios y comentarios. Drew Houston publicó la demo de Dropbox en HN en abril de 2007, llegó al puesto #1 y la lista de espera de la beta pasó de 5,000 a 75,000 usuarios en un solo día. Los datos de HN no solo son interesantes: también tienen valor comercial.
Los datos son públicos, pero la estructura del sitio hace que recogerlos a mano sea un trabajo pesado. La automatización con Python es la salida práctica.
Dos formas de extraer Hacker News con Python: resumen
Esta guía cubre dos enfoques completos y ejecutables:
- Scraping HTML con
requests+ BeautifulSoup — baja el HTML bruto de news.ycombinator.com y lo analiza para sacar los datos de cada historia. Ideal para aprender los fundamentos del scraping y capturar exactamente lo que aparece en la página. - La API oficial de Hacker News Firebase — consulta directamente endpoints JSON, sin necesidad de parsear HTML. Es mejor para canales de datos confiables, acceso a comentarios y datos históricos.
Aquí tienes una comparación lado a lado para ayudarte a elegir el enfoque que mejor se adapta a tus necesidades:
| Criterio | Scraping HTML (requests + BS4) | API de HN Firebase | Thunderbit (sin código) |
|---|---|---|---|
| Complejidad de configuración | Media (parsear selectores HTML) | Baja (endpoints JSON) | Ninguna (extensión de Chrome en 2 clics) |
| Actualización de datos | Tiempo real en la portada | Tiempo real (cualquier elemento por ID) | Tiempo real |
| Riesgo de límites de tasa | Medio (robots.txt indica 30 s de espera) | Bajo (oficial, generoso) | Gestionado por Thunderbit |
| Acceso a comentarios | Difícil (HTML anidado) | Fácil (IDs de elementos recursivos) | Función de scraping de subpáginas |
| Datos históricos | Limitados | A través de la API de búsqueda de Algolia | N/A |
| Mejor para | Aprender fundamentos del scraping | Canales de datos confiables | Personas no técnicas, exportaciones rápidas |
Ambos métodos incluyen código Python completo y ejecutable. Y si solo quieres los datos sin escribir nada de código, también te enseño cómo hacerlo.
Antes de empezar
- Nivel: Principiante a intermedio
- Tiempo necesario: ~15–20 minutos por método
- Lo que vas a necesitar:
- Python 3.11+ instalado
- Una terminal o editor de código
- Navegador Chrome (si quieres inspeccionar el HTML de HN o probar la opción sin código)
- Extensión de Chrome de Thunderbit (opcional, para el método sin código)

Configurar tu entorno de Python
Antes de tocar datos de HN, vamos a dejar listo el entorno. Te recomiendo crear un entorno virtual para mantener limpias las dependencias del proyecto.
# Crear y activar un entorno virtual
python3 -m venv hn-scraper
# macOS/Linux:
source hn-scraper/bin/activate
# Windows:
hn-scraper\Scripts\activate
# Instalar los paquetes que necesitaremos para ambos métodos
pip install requests==2.33.1 beautifulsoup4==4.14.3 pandas==3.0.2 openpyxl==3.1.5
Para los patrones de producción más adelante (caché, reintentos), también te conviene instalar:
pip install requests-cache==1.3.1 tenacity==9.1.4
Sin claves API especiales, sin tokens de autenticación. Los datos de HN son abiertos.
Método 1: extraer Hacker News con Python usando BeautifulSoup
Este es el enfoque clásico: descargar el HTML, analizarlo y sacar los datos que necesitas. Es la forma en que la mayoría aprende web scraping, y la estructura sencilla de HN basada en tablas lo convierte en un excelente campo de práctica.
Paso 1: obtener la portada de Hacker News
Abre tu editor y crea un archivo llamado scrape_hn_bs4.py. Este es el código inicial:
import requests
from bs4 import BeautifulSoup
url = "https://news.ycombinator.com/news"
headers = {"User-Agent": "Mozilla/5.0 (educational HN scraper)"}
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")
print(f"Status: {response.status_code}, Page length: {len(response.text)} chars")
Ejecuta el script. Deberías ver Status: 200 y una longitud de página de unas 40,000–50,000 caracteres. Ese es el HTML crudo de la portada de HN cargado en memoria y listo para analizar.
Paso 2: entender la estructura HTML
HN usa una estructura basada en tablas, no un grid moderno ni flexbox. Cada historia de la página se compone de dos filas <tr> clave:
- La fila de la historia (
<tr class="athing submission">): contiene la posición, el título y el enlace - La fila de metadatos (la siguiente fila
<tr>): contiene puntos, autor, hora y número de comentarios
Los selectores importantes son:
span.titleline > a— título y URL de la historiaspan.score— cantidad de votos (por ejemplo, "118 points")a.hnuser— nombre de usuario del autorspan.age— hora de publicación- El último
<a>en.subtextque contenga la palabra "comment" — número de comentarios
Si haces clic derecho sobre cualquier título de HN en Chrome y eliges "Inspect", verás algo así:
<span class="titleline">
<a href="https://darkbloom.dev">Darkbloom – Private inference on idle Macs</a>
</span>
Y debajo, la fila de metadatos:
<span class="score" id="score_47788542">118 points</span>
by <a href="user?id=twapi" class="hnuser">twapi</a>
<span class="age" title="2026-04-16T04:06:39 1776312399">
<a href="item?id=47788542">2 hours ago</a>
</span>
| <a href="item?id=47788542">65 comments</a>
Entender estos selectores es fundamental: si HN cambia su marcado, tendrás que ajustarlos. (Spoiler: el método con API evita ese problema por completo.)
Paso 3: extraer títulos, enlaces y puntuaciones
Ahora viene el trabajo real. Vamos a recorrer cada fila de historia, sacar el título y el enlace de la fila principal, y después obtener la puntuación de la fila de metadatos justo debajo.
import requests
from bs4 import BeautifulSoup
from pprint import pprint
url = "https://news.ycombinator.com/news"
headers = {"User-Agent": "Mozilla/5.0 (educational HN scraper)"}
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")
stories = []
story_rows = soup.select("tr.athing")
for row in story_rows:
# Título y URL desde la fila de la historia
title_tag = row.select_one("span.titleline > a")
if not title_tag:
continue
title = title_tag.get_text()
link = title_tag.get("href", "")
# Metadatos desde la siguiente fila hermana
meta_row = row.find_next_sibling("tr")
score = 0
author = ""
comments = 0
if meta_row:
if score_tag := meta_row.select_one("span.score"):
score = int(score_tag.get_text().replace(" points", ""))
if author_tag := meta_row.select_one("a.hnuser"):
author = author_tag.get_text()
# Conteo de comentarios: último <a> que contenga "comment"
for a_tag in meta_row.select("a"):
text = a_tag.get_text()
if "comment" in text:
comments = int(text.split("\xa0")[0])
stories.append({
"title": title,
"url": link,
"score": score,
"author": author,
"comments": comments,
})
# Filtrar historias con 50+ puntos, ordenadas por puntuación
top_stories = sorted(
[s for s in stories if s["score"] >= 50],
key=lambda x: x["score"],
reverse=True,
)
pprint(top_stories[:10])
Algunas notas sobre el código:
- El operador walrus (
:=) funciona en Python 3.8+. Te permite asignar y comprobar en una sola línea, algo muy útil para elementos opcionales comospan.score, que pueden no existir en todas las filas (por ejemplo, las publicaciones de empleo no tienen puntuación). - HN usa
\xa0(espacio de no separación) entre el número y la palabra "comments", así que lo separamos por ahí. - Las historias que enlazan a otras páginas de HN (como los posts "Ask HN") tendrán URLs relativas que empiezan con
item?id=. Quizá quieras anteponerhttps://news.ycombinator.com/en esos casos.
Paso 4: ejecutarlo y ver los resultados
Guarda y ejecuta:
python scrape_hn_bs4.py
Deberías ver una salida como esta:
[{'author': 'twapi',
'comments': 65,
'score': 118,
'title': 'Darkbloom – Private inference on idle Macs',
'url': 'https://darkbloom.dev'},
{'author': 'sebg',
'comments': 203,
'score': 247,
'title': 'Show HN: I built an open-source Perplexity alternative',
'url': 'https://github.com/...'},
...]
Eso corresponde a 30 historias de la página 1. Pero HN tiene cientos de historias activas en cualquier momento. Más adelante veremos cómo manejar la paginación.
Método 2: extraer Hacker News con Python usando la API oficial
La API de HN Firebase es la forma oficialmente admitida de acceder a los datos de Hacker News. Sin autenticación, sin claves API y sin parsing de HTML. Obtienes respuestas JSON limpias. Yo uso este método para cualquier cosa que tenga que funcionar de forma fiable en producción.
Endpoints clave de la API que debes conocer
La URL base es https://hacker-news.firebaseio.com/v0/. Estos son los endpoints importantes:
| Endpoint | Devuelve | Ejemplo |
|---|---|---|
/v0/topstories.json | Matriz de hasta 500 IDs de historias destacadas | [47788542, 47787901, ...] |
/v0/newstories.json | Hasta 500 IDs de historias más recientes | Mismo formato |
/v0/beststories.json | Hasta 500 IDs de las mejores historias | Mismo formato |
/v0/askstories.json | Hasta 200 IDs de historias "Ask HN" | Mismo formato |
/v0/showstories.json | Hasta 200 IDs de historias "Show HN" | Mismo formato |
/v0/jobstories.json | Hasta 200 IDs de historias de empleo | Mismo formato |
/v0/item/{id}.json | Detalles completos de cualquier elemento (historia, comentario, encuesta) | Objeto JSON |
/v0/user/{username}.json | Perfil de usuario | Objeto JSON |
/v0/maxitem.json | ID máximo actual | Entero (por ejemplo, 47789427) |
Un elemento de historia se ve así:
{
"by": "twapi",
"descendants": 65,
"id": 47788542,
"kids": [47789171, 47788769, 47788762],
"score": 118,
"time": 1776312399,
"title": "Darkbloom – Private inference on idle Macs",
"type": "story",
"url": "https://darkbloom.dev"
}
El campo kids contiene los IDs de los comentarios hijos directos. Cada comentario es a su vez un elemento que puede tener sus propios kids; así se estructura el árbol de comentarios.
Paso 1: obtener los IDs de las historias destacadas
Crea un archivo llamado scrape_hn_api.py:
import requests
import time
from pprint import pprint
API_BASE = "https://hacker-news.firebaseio.com/v0"
# Obtener los IDs de las historias destacadas
response = requests.get(f"{API_BASE}/topstories.json")
story_ids = response.json()
print(f"Got {len(story_ids)} top story IDs")
# Output: Got 500 top story IDs
500 IDs en una sola petición: sin parsing, sin selectores, solo una matriz JSON.
Paso 2: obtener los detalles de cada historia por ID
Ahora necesitamos los datos reales. Aquí aparece el problema de dispersión: 500 historias implican 500 llamadas API individuales. En mis pruebas, cada solicitud de elemento tarda unos 1.2 segundos de forma secuencial. Para 500 historias, eso ronda los 10 minutos.
Para la mayoría de los casos no necesitas las 500. Aquí tienes código para obtener las 30 primeras:
def fetch_story(story_id):
"""Obtiene los detalles de una historia desde la API de HN."""
resp = requests.get(f"{API_BASE}/item/{story_id}.json")
return resp.json()
# Obtener detalles de las 30 primeras historias
stories = []
for sid in story_ids[:30]:
story = fetch_story(sid)
if story and story.get("type") == "story":
stories.append({
"title": story.get("title", ""),
"url": story.get("url", ""),
"score": story.get("score", 0),
"author": story.get("by", ""),
"comments": story.get("descendants", 0),
"time": story.get("time", 0),
"id": story.get("id"),
})
time.sleep(0.1) # Sé considerado: pequeña pausa entre peticiones
# Ordenar por puntuación y mostrar las 10 mejores
top = sorted(stories, key=lambda x: x["score"], reverse=True)[:10]
pprint(top)
El time.sleep(0.1) añade una pequeña pausa de cortesía. La API Firebase no declara un límite de tasa específico, pero bombardear cualquier API sin pausas es mala práctica.
Paso 3: extraer comentarios (recorrido recursivo del árbol)
Aquí es donde la API realmente brilla frente al scraping HTML. Los comentarios de HN están profundamente anidados: respuestas a respuestas a respuestas. En HTML, eso significa analizar estructuras de tablas complejas. Con la API, cada comentario tiene un campo kids con los IDs de sus hijos, y simplemente recorres el árbol de forma recursiva.
def fetch_comments(item_id, depth=0, max_depth=3):
"""Obtiene comentarios recursivamente hasta max_depth."""
item = requests.get(f"{API_BASE}/item/{item_id}.json").json()
if not item or item.get("type") != "comment":
return []
comments = [{
"author": item.get("by", "[deleted]"),
"text": item.get("text", ""),
"depth": depth,
"id": item.get("id"),
}]
if depth < max_depth and item.get("kids"):
for kid_id in item["kids"]:
comments.extend(fetch_comments(kid_id, depth + 1, max_depth))
time.sleep(0.05)
return comments
# Ejemplo: obtener comentarios de la historia principal
if stories:
top_story = stories[0]
top_story_full = requests.get(f"{API_BASE}/item/{top_story['id']}.json").json()
if top_story_full.get("kids"):
print(f"\nComments for: {top_story['title']}")
all_comments = []
for kid_id in top_story_full["kids"][:5]: # Primeros 5 comentarios de nivel superior
all_comments.extend(fetch_comments(kid_id, depth=0, max_depth=2))
time.sleep(0.1)
for c in all_comments[:15]:
indent = " " * c["depth"]
preview = c["text"][:80].replace("\n", " ") if c["text"] else "[no text]"
print(f"{indent}[{c['author']}] {preview}...")
Este enfoque recursivo es mucho más sencillo que intentar parsear hilos de comentarios HTML anidados. Si necesitas árboles completos de comentarios, la API es el camino correcto.
Paso 4: ejecutar y ver resultados
python scrape_hn_api.py
Verás datos estructurados de las historias seguidos de una vista previa de comentarios anidados. Los datos son más limpios, el acceso a comentarios es trivial y no existe el riesgo de que tu scraper falle porque HN cambió el nombre de una clase CSS.
Ir más allá de la página 1: paginación y datos históricos
La mayoría de los tutoriales sobre scraping de HN se quedan en la página 1, con 30 historias. Eso está bien para una demo rápida, pero en casos reales a menudo se necesita más profundidad.
Extraer varias páginas con BeautifulSoup
La paginación de HN usa un patrón de URL muy simple: ?p=2, ?p=3, etc. Cada página devuelve 30 historias, y el sitio ofrece hasta la página 20 aproximadamente (unos 600 resultados en total). Más allá de eso, obtendrás páginas vacías.
import time
def scrape_hn_pages(num_pages=5):
"""Extrae varias páginas de historias de la portada de HN."""
all_stories = []
for page in range(1, num_pages + 1):
url = f"https://news.ycombinator.com/news?p={page}"
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")
story_rows = soup.select("tr.athing")
if not story_rows:
print(f"Page {page}: no stories found, stopping.")
break
for row in story_rows:
title_tag = row.select_one("span.titleline > a")
if not title_tag:
continue
meta_row = row.find_next_sibling("tr")
score = 0
if meta_row and (score_tag := meta_row.select_one("span.score")):
score = int(score_tag.get_text().replace(" points", ""))
all_stories.append({
"title": title_tag.get_text(),
"url": title_tag.get("href", ""),
"score": score,
})
print(f"Page {page}: scraped {len(story_rows)} stories")
# Respetar el crawl-delay de 30 segundos indicado en robots.txt
if page < num_pages:
time.sleep(30)
return all_stories
stories = scrape_hn_pages(5)
print(f"\nTotal stories scraped: {len(stories)}")
Ese time.sleep(30) es importante. El robots.txt de HN pide explícitamente un retraso de rastreo de 30 segundos. Si lo ignoras, podrías recibir limitación de tasa (HTTP 429) o un bloqueo temporal. Cinco páginas con intervalos de 30 segundos tardan unos 2.5 minutos: no es instantáneo, pero sí respetuoso.
Para quienes no quieran gestionar código de paginación, Thunderbit maneja automáticamente la paginación por clic y el scroll infinito. Hace clic en el botón "More" al final de las páginas de HN sin que tengas que configurar nada.
Extraer páginas de Hacker News con IA
Acceder a datos históricos de Hacker News con la API de Algolia
La API de Firebase te da datos actuales. Para análisis históricos — "¿Cuáles fueron las mejores historias sobre Python en 2023?" o "¿Cómo cambió la cobertura sobre IA en los últimos 5 años?" — necesitas la API de búsqueda de Algolia de HN.
import requests
ALGOLIA_BASE = "https://hn.algolia.com/api/v1"
def search_hn(query, tags="story", page=0, hits_per_page=20):
"""Busca en HN mediante la API de Algolia."""
params = {
"query": query,
"tags": tags,
"page": page,
"hitsPerPage": hits_per_page,
}
resp = requests.get(f"{ALGOLIA_BASE}/search", params=params)
return resp.json()
# Ejemplo: encontrar historias sobre scraping en Python con 10+ puntos desde enero de 2024
results = search_hn(
query="python scraping",
tags="story",
)
print(f"Found {results['nbHits']} total results")
for hit in results["hits"][:5]:
print(f" [{hit.get('points', 0)} pts] {hit['title']}")
Para consultas filtradas por fecha, usa numericFilters:
import calendar, datetime
# Historias desde el 1 de enero de 2024
start_date = datetime.datetime(2024, 1, 1)
start_ts = int(calendar.timegm(start_date.timetuple()))
params = {
"query": "python web scraping",
"tags": "story",
"numericFilters": f"created_at_i>{start_ts},points>10",
"hitsPerPage": 50,
}
resp = requests.get(f"{ALGOLIA_BASE}/search_by_date", params=params)
data = resp.json()
print(f"Found {data['nbHits']} stories about Python web scraping since 2024 with >10 points")
La API de Algolia es rápida (5–9 ms de tiempo de procesamiento en el servidor), no requiere clave API y admite paginación de hasta 500 páginas. Para análisis histórico a gran escala, es la mejor opción disponible.
Exportar datos de Hacker News a CSV, Excel y Google Sheets
Todos los tutoriales de scraping de HN que he visto terminan con salida pprint() en la terminal. Eso sirve para depurar, pero si estás montando un resumen diario o analizando tendencias, necesitas los datos en un archivo. Así es como puedes hacerlo.
Exportar a CSV con Python
import csv
def export_to_csv(stories, filename="hn_stories.csv"):
"""Guarda las historias extraídas en un archivo CSV."""
fieldnames = ["title", "url", "score", "author", "comments"]
with open(filename, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(stories)
print(f"Saved {len(stories)} stories to {filename}")
export_to_csv(stories)
Exportar a Excel con Python
import pandas as pd
def export_to_excel(stories, filename="hn_stories.xlsx"):
"""Guarda las historias extraídas en un archivo de Excel."""
df = pd.DataFrame(stories)
df.to_excel(filename, index=False, engine="openpyxl")
print(f"Saved {len(stories)} stories to {filename}")
export_to_excel(stories)
Asegúrate de tener instalado openpyxl, porque pandas lo usa como motor para Excel. Si falta, te saldrá un ImportError.
Enviar a Google Sheets (opcional)
Para flujos de trabajo automatizados, quizá quieras enviar los datos directamente a Google Sheets usando la librería gspread. Esto requiere configurar una cuenta de servicio de Google Cloud, pero solo una vez:
import gspread
gc = gspread.service_account(filename="service_account.json")
sh = gc.open("HN Daily Digest")
worksheet = sh.sheet1
# Convertir stories a filas
header = list(stories[0].keys())
rows = [list(s.values()) for s in stories]
worksheet.clear()
worksheet.update([header] + rows)
print("Pushed to Google Sheets")
La alternativa sin código para exportar
Si montar cuentas de servicio y escribir código de exportación te parece más trabajo que el propio scraping, te entiendo. En Thunderbit, creamos exportación de datos gratuita para enviar lo extraído directamente a Excel, Google Sheets, Airtable o Notion, sin código, sin credenciales y sin canalizaciones que mantener. Para una extracción puntual, de verdad es más rápido. Más abajo te cuento más.
Hacer que tu scraper esté listo para producción: manejo de errores, caché y programación
Si ejecutas un scraper una sola vez por curiosidad, el código anterior está bien. Si lo ejecutas cada día como parte de un flujo de trabajo, necesitas algunas piezas más.
Manejo de errores y lógica de reintento
Las redes fallan. Los servidores limitan. Una sola petición mala no debería tumbar todo el scraping. Aquí tienes una función de reintento con retroceso exponencial:
from tenacity import retry, stop_after_attempt, wait_exponential_jitter
import requests
@retry(stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=1, max=60))
def fetch_with_retry(url):
"""Obtiene una URL con reintentos automáticos y retroceso exponencial."""
response = requests.get(url, timeout=10)
response.raise_for_status()
return response
# Uso:
try:
resp = fetch_with_retry("https://hacker-news.firebaseio.com/v0/topstories.json")
story_ids = resp.json()
except Exception as e:
print(f"Failed after retries: {e}")
La librería tenacity gestiona muy bien la lógica de reintento. Reintentará hasta 5 veces con retroceso exponencial con jitter, empezando en 1 segundo y llegando hasta 60 segundos. Esto maneja de forma elegante errores HTTP 429 (límite de tasa), 503 (servicio no disponible) y fallos de red transitorios.
Cachear respuestas para evitar volver a rastrear
Durante el desarrollo, ejecutarás el scraper muchas veces mientras ajustas la lógica de parsing. Sin caché, cada ejecución volverá a golpear los servidores de HN con los mismos datos. La librería requests-cache lo arregla en dos líneas:
import requests_cache
requests_cache.install_cache("hn_cache", expire_after=3600) # Cache durante 1 hora
Después de añadir esas líneas al inicio del script, todas las llamadas requests.get() se almacenarán automáticamente en una base de datos SQLite local. Si vuelves a ejecutar el script 10 veces en una hora, solo la primera ejecución hará la petición real a la red. Esta es una herramienta que los usuarios de foros recomiendan con frecuencia, y con razón.
Separar el rastreo del parsing
Un patrón que los scrapers con experiencia recomiendan mucho: primero descargar los datos crudos y después analizarlos. Así, si tu lógica de parsing tiene un error, puedes corregirla y volver a procesar sin tener que descargar todo otra vez.
import os, json
def crawl_and_save(story_ids, output_dir="raw_data"):
"""Obtiene datos de historias y guarda el JSON crudo en disco."""
os.makedirs(output_dir, exist_ok=True)
for sid in story_ids:
filepath = os.path.join(output_dir, f"{sid}.json")
if os.path.exists(filepath):
continue # Saltar elementos ya obtenidos
resp = fetch_with_retry(f"{API_BASE}/item/{sid}.json")
with open(filepath, "w") as f:
json.dump(resp.json(), f)
def parse_saved_data(input_dir="raw_data"):
"""Convierte los JSON guardados en una lista estructurada de historias."""
stories = []
for filename in os.listdir(input_dir):
with open(os.path.join(input_dir, filename)) as f:
item = json.load(f)
if item and item.get("type") == "story":
stories.append({
"title": item.get("title", ""),
"url": item.get("url", ""),
"score": item.get("score", 0),
"author": item.get("by", ""),
"comments": item.get("descendants", 0),
})
return stories
Este enfoque en dos fases es especialmente útil cuando extraes cientos de elementos y quieres iterar rápido sobre la forma en que procesas los datos.
Automatizar tu scraper con un horario
Para un resumen diario de HN, necesitas que el scraper se ejecute automáticamente. Dos opciones comunes:
Opción 1: cron (Linux/Mac)
# Ejecutar todos los días a las 8:30 AM UTC
30 8 * * * /usr/bin/python3 /home/user/scrape_hn.py >> /home/user/scrape.log 2>&1
Opción 2: GitHub Actions (gratis, sin servidor)
name: Scrape Hacker News
on:
schedule:
- cron: '30 8 * * *' # Diariamente a las 8:30 AM UTC
workflow_dispatch: # Botón de ejecución manual
jobs:
scrape:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v6
with:
python-version: '3.12'
- run: pip install requests beautifulsoup4 pandas openpyxl
- run: python scrape_hn.py
- run: |
git config user.name "GitHub Actions Bot"
git config user.email "actions@github.com"
git add -A
git diff --staged --quiet || git commit -m "Update HN data $(date -u +%Y-%m-%dT%H:%M:%SZ)"
git push
Unos cuantos detalles a tener en cuenta con la programación en GitHub Actions: todas las horas cron usan UTC, son comunes los retrasos de 15 a 60 minutos (usa horas con minuto no exacto como :30 en lugar de :00) y GitHub puede desactivar workflows programados en repositorios sin actividad durante 60 días. Incluye siempre workflow_dispatch para poder ejecutarlo manualmente en pruebas.
Para una opción más simple, la función Scheduled Scraper de Thunderbit te permite describir el horario en lenguaje natural — algo como "scrape every morning at 8am" — sin servidor ni configuración de cron.
Cuando Python es demasiado: la forma sin código de extraer Hacker News
Voy a ser sincero, aunque me encanta Python y mi equipo crea herramientas para desarrolladores. Si solo necesitas las 100 historias más destacadas de hoy en una hoja de cálculo — ahora mismo, una sola vez — escribir, depurar y ejecutar un script de Python es una carga innecesaria. Solo la configuración (entorno virtual, instalación de paquetes, averiguar selectores) tarda más que la propia recopilación de datos.
Aquí es donde encaja Thunderbit. El flujo es este:
- Abre
news.ycombinator.comen Chrome - Haz clic en el icono de la extensión Thunderbit y luego en "AI Suggest Fields"
- La IA analiza la página y propone columnas: Title, URL, Score, Author, Comment Count, Time Posted
- Ajusta los campos si quieres (renombrar, quitar o añadir personalizados; incluso puedes agregar una instrucción como "Categorize as AI/DevTools/Web/Other")
- Haz clic en "Scrape" y los datos aparecen en una tabla estructurada
- Exporta a Excel, Google Sheets, Airtable o Notion
Dos clics para obtener datos estructurados. Sin selectores, sin código, sin mantenimiento.
Una ventaja real: la IA de Thunderbit se adapta automáticamente a cambios de diseño. Los scrapers tradicionales basados en selectores CSS se rompen cuando un sitio cambia su marcado, y aunque el HTML de HN es bastante estable, sí ha cambiado (la clase class="athing submission" se actualizó y span.titleline sustituyó al antiguo a.storylink). Un scraper impulsado por IA relee la página cada vez, así que no le afectan los cambios de nombres de clase.

Thunderbit también gestiona la paginación (haciendo clic automáticamente en el botón "More" de HN) y el scraping de subpáginas (visitando la página de comentarios de cada historia para extraer la discusión). Para el caso de uso de enriquecimiento de comentarios, eso equivale al código recursivo de la API del Método 2, pero sin escribir una sola línea.
Las diferencias son claras: Python es la mejor opción cuando necesitas lógica personalizada, transformaciones complejas de datos, automatizaciones programadas o estás aprendiendo a programar. Thunderbit es la mejor opción cuando necesitas datos rápido, no quieres mantener código o no eres desarrollador. Elige la herramienta que encaje con tu situación.
Python vs. API vs. sin código: ¿qué método deberías elegir?
Aquí tienes el marco de decisión completo:
| Criterio | BeautifulSoup (HTML) | API Firebase | API Algolia | Thunderbit (sin código) |
|---|---|---|---|---|
| Nivel técnico necesario | Python intermedio | Python básico | Python básico | Ninguno |
| Tiempo de configuración | 10–15 min | 5–10 min | 5–10 min | 2 min |
| Mantenimiento | Medio (los selectores se rompen) | Bajo (JSON estable) | Bajo (JSON estable) | Ninguno |
| Profundidad de datos | Solo portada | Cualquier elemento, usuarios | Búsqueda + histórico | Portada + subpáginas |
| Comentarios | Difícil | Fácil (recursivo) | Fácil (árbol anidado) | Scraping de subpáginas |
| Datos históricos | No | No | Sí (archivo completo) | No |
| Opciones de exportación | Programarlas tú | Programarlas tú | Programarlas tú | Integradas (Excel, Sheets, etc.) |
| Programación | cron / GitHub Actions | cron / GitHub Actions | cron / GitHub Actions | Programador integrado |
| Mejor para | Aprender scraping | Canales fiables | Investigación y análisis | Extracciones rápidas |
Si estás aprendiendo Python o construyendo algo a medida, elige el Método 1 o 2. Si necesitas análisis histórico, añade la API de Algolia. Si solo quieres los datos sin escribir código, prueba Thunderbit.
Prueba Thunderbit para extraer Hacker News
Conclusión y puntos clave
Esto es lo que ahora tienes en tu caja de herramientas:
- Dos métodos completos en Python para extraer Hacker News: BeautifulSoup para parsear HTML y la API de Firebase para obtener JSON limpio
- Técnicas de paginación para ir más allá de la página 1, incluida la API de Algolia para datos históricos que llegan hasta 2007
- Código de exportación para CSV, Excel y Google Sheets, porque los datos en una terminal no sirven mucho al resto de tu equipo
- Patrones listos para producción: reintentos, caché, separación entre rastreo y parsing, y automatización programada con cron o GitHub Actions
- Una alternativa sin código para cuando Python es más herramienta de la que realmente necesitas
Mi recomendación: empieza con la API de Firebase (Método 2) para la mayoría de los casos. Es más limpia, más fiable y te da acceso a comentarios sin el dolor de parsear HTML anidado. Añade la API de Algolia cuando necesites datos históricos. Y guarda Thunderbit en favoritos para esos momentos en los que solo necesitas una hoja de cálculo rápida y no quieres montar un proyecto de Python completo.
Si quieres profundizar más, prueba a extraer comentarios de HN para análisis de sentimiento, crea un pipeline diario de resúmenes con GitHub Actions o explora la API de Algolia para ver cómo han cambiado las tendencias tecnológicas durante la última década.
Prueba Thunderbit para extraer Hacker News rápidamente Get Started Free
Preguntas frecuentes
¿Es legal extraer Hacker News?
Los datos de HN son públicos, y Y Combinator ofrece una API oficial específicamente para acceso programático. El robots.txt del sitio permite rastrear contenido de solo lectura (portada, páginas de elementos, páginas de usuarios), pero pide un retraso de 30 segundos. Si respetas ese retraso y no rastreas endpoints interactivos (votación, inicio de sesión), estás en terreno seguro. Para más información sobre ética en scraping, consulta nuestra guía sobre implicaciones legales del web scraping.
¿Hacker News tiene una API oficial?
Sí. La API de HN Firebase en hacker-news.firebaseio.com/v0/ es gratuita, no requiere autenticación y da acceso a historias, comentarios, perfiles de usuario y todos los tipos de feed (top, new, best, ask, show, jobs). Devuelve JSON limpio y no indica un límite de tasa específico, aunque siempre conviene ser prudente con la frecuencia de las solicitudes.
¿Cómo extraigo comentarios de Hacker News con Python?
Usando la API de Firebase, obtén un elemento de historia para acceder a su campo kids (una matriz con los IDs de los comentarios de nivel superior). Cada comentario es a su vez un elemento con su propio campo kids para las respuestas. Recorre el árbol de forma recursiva con una función que obtenga cada comentario y sus hijos. Mira la sección "Extraer comentarios (recorrido recursivo del árbol)" de arriba para ver el código completo. Como alternativa, el endpoint /items/<id> de la API de Algolia devuelve el árbol completo de comentarios anidados en una sola petición, mucho más rápido para historias con muchos comentarios.
¿Puedo extraer Hacker News sin escribir código?
Sí. El AI web scraper de Thunderbit funciona como una extensión de Chrome: abre HN, haz clic en "AI Suggest Fields" y detectará automáticamente columnas como título, URL, puntuación y autor. Haz clic en "Scrape" y exporta directamente a Excel, Google Sheets, Airtable o Notion. También gestiona la paginación y puede visitar subpáginas para extraer datos de comentarios. Sin Python, sin selectores, sin mantenimiento.
¿Cómo obtengo datos históricos de Hacker News?
La API de búsqueda de Algolia de HN es la mejor herramienta para esto. Usa el endpoint search_by_date con numericFilters=created_at_i>TIMESTAMP para filtrar por rango de fechas. Puedes buscar por palabra clave, filtrar por tipo de historia y paginar hasta 500 páginas de resultados. Para análisis históricos masivos, también hay conjuntos de datos públicos en Google BigQuery (archivo completo), ClickHouse (28 millones de registros) y Hugging Face (4 millones de historias).
Saber más


