Uns meses atrás, eu quis montar um resumo diário com as principais histórias do Hacker News para o time da Thunderbit. Minha primeira ideia foi a mais óbvia: salvar o site nos favoritos e dar uma olhada toda manhã. Durou uns três dias, até eu cair na real de que estava torrando 20 minutos por dia só lendo manchetes e copiando links para uma planilha.
O Hacker News é uma das fontes mais densas e concentradas de inteligência sobre tecnologia na internet — cerca de 13 milhões de visitas por mês, por volta de 1.300 novas histórias enviadas por dia e algo como 13.000 comentários gerados diariamente. Se você acompanha tendências de tecnologia que estão surgindo, monitora sua marca, monta um funil de recrutamento a partir dos tópicos "Who's Hiring" ou só quer ficar por dentro do que importa no mundo dev, tentar dar conta de tudo isso na mão é briga perdida.
A boa notícia: fazer scraping do Hacker News com Python é surpreendentemente tranquilo. Neste guia, vou mostrar dois métodos completos — scraping de HTML com BeautifulSoup e a API oficial do HN no Firebase —, além de paginação, exportação de dados, padrões prontos para produção e um atalho sem código para quando o Python parecer exagero.
Por que fazer scraping do Hacker News com Python?
O Hacker News não é só mais um agregador de links. É um feed curado pela comunidade, em que as histórias de tecnologia mais interessantes sobem ao topo por votos e discussão acalorada. O público pende fortemente para profissionais de tecnologia (cerca de 76% homens, faixa etária predominante de 25 a 34 anos), e a taxa de 66% de tráfego direto mostra que estamos diante de uma audiência fiel e de hábito — não de gente que cai ali por acaso.
Veja por que tanta gente coleta dados do HN:
| Caso de uso | O que você obtém |
|---|---|
| Resumo diário de tecnologia | Histórias em destaque, pontuação e links chegando no e-mail ou no Slack |
| Monitoramento de marca/concorrentes | Alertas quando sua empresa ou produto é mencionado |
| Análise de tendências | Acompanhe quais tecnologias, linguagens ou temas estão ganhando força ao longo do tempo |
| Recrutamento | Extraia vagas, stacks e sinais de salário dos tópicos "Who's Hiring" |
| Pesquisa de conteúdo | Encontre temas com bom desempenho para escrever ou compartilhar |
| Análise de sentimento | Meça a opinião da comunidade sobre produtos, lançamentos ou mudanças do setor |
Empresas que somadas valem mais de US$ 400 bilhões — Stripe, Dropbox, Airbnb — creditam ao Hacker News parte importante do feedback inicial e dos primeiros usuários. Em abril de 2007, Drew Houston publicou a demo do Dropbox no HN, ela foi para #1, e a lista de espera do beta saltou de 5.000 para 75.000 usuários em um único dia. Os dados do HN não são só curiosos — têm valor comercial.
Os dados são públicos, mas a estrutura do site torna a coleta manual cansativa. Automatizar com Python é a saída mais prática.
Duas formas de fazer scraping do Hacker News com Python: visão geral
Este guia cobre duas abordagens completas e executáveis:
- Scraping de HTML com
requests+ BeautifulSoup — baixa o HTML bruto de news.ycombinator.com e faz o parse para puxar os dados das histórias. É ótimo para aprender os fundamentos de scraping e capturar exatamente o que está na página. - A API oficial do Hacker News no Firebase — acessa endpoints JSON direto, sem precisar fazer parse de HTML. É melhor para pipelines confiáveis, acesso a comentários e dados históricos.
Aqui vai uma comparação lado a lado para ajudar você a escolher o que faz mais sentido:
| Critério | Scraping de HTML (requests + BS4) | API HN Firebase | Thunderbit (sem código) |
|---|---|---|---|
| Complexidade de configuração | Média (parsers de HTML) | Baixa (endpoints JSON) | Nenhuma (extensão Chrome em 2 cliques) |
| Atualidade dos dados | Página inicial em tempo real | Tempo real (qualquer item por ID) | Tempo real |
| Risco de limite de requisições | Médio (robots.txt pede atraso de 30 s) | Baixo (oficial e generoso) | Gerenciado pela Thunderbit |
| Acesso a comentários | Difícil (HTML aninhado) | Fácil (IDs recursivos) | Recurso de scraping de subpáginas |
| Dados históricos | Limitados | Via Algolia Search API | N/A |
| Melhor para | Aprender fundamentos de scraping | Pipelines confiáveis | Não desenvolvedores, exportações rápidas |
Os dois métodos vêm com código Python completo e executável. E, se você só quiser os dados sem escrever nada, eu também mostro essa opção.
Antes de começar
- Nível: Iniciante a intermediário
- Tempo necessário: ~15–20 minutos por método
- O que você vai precisar:
- Python 3.11+ instalado
- Um terminal ou editor de código
- Navegador Chrome (se quiser inspecionar o HTML do HN ou testar a opção sem código)
- Thunderbit Chrome Extension (opcional, para o método sem código)

Preparando seu ambiente Python
Antes de mexer em qualquer dado do HN, vamos deixar o ambiente pronto. Recomendo criar um ambiente virtual para manter as dependências do projeto bem separadas.
# Crie e ative um ambiente virtual
python3 -m venv hn-scraper
# macOS/Linux:
source hn-scraper/bin/activate
# Windows:
hn-scraper\Scripts\activate
# Instale os pacotes que vamos usar nos dois métodos
pip install requests==2.33.1 beautifulsoup4==4.14.3 pandas==3.0.2 openpyxl==3.1.5
Para os padrões de produção lá na frente (cache, retries), você também vai querer:
pip install requests-cache==1.3.1 tenacity==9.1.4
Sem chaves de API especiais, sem tokens de autenticação. Os dados do HN são abertos.
Método 1: fazer scraping do Hacker News com Python usando BeautifulSoup
Essa é a abordagem clássica — baixar o HTML, fazer o parse e puxar os dados que interessam. É assim que a maioria aprende web scraping, e o layout simples em tabela do HN faz dele um ótimo campo de treino.
Etapa 1: buscar a página inicial do Hacker News
Abra seu editor e crie um arquivo chamado scrape_hn_bs4.py. Aqui está o 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")
Execute. Você deve ver Status: 200 e um tamanho de página em torno de 40.000 a 50.000 caracteres. Esse é o HTML bruto da página inicial do HN carregado na memória, pronto para ser analisado.
Etapa 2: entender a estrutura do HTML
O HN usa um layout baseado em tabela — nada de grid ou flex modernos. Cada história na página é formada por duas linhas <tr> principais:
- A linha da história (
<tr class="athing submission">): traz a posição, o título e o link - A linha de metadados (a
<tr>seguinte): traz pontos, autor, horário e contagem de comentários
Os seletores que importam:
span.titleline > a— título da história e URLspan.score— quantidade de votos (por exemplo, "118 points")a.hnuser— nome de usuário do autorspan.age— horário da publicação- O último
<a>em.subtextcom "comment" no texto — contagem de comentários
Se você clicar com o botão direito em qualquer título no Chrome e escolher "Inspecionar", vai ver algo parecido com isto:
<span class="titleline">
<a href="https://darkbloom.dev">Darkbloom – Private inference on idle Macs</a>
</span>
E a linha de metadados logo abaixo:
<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 esses seletores é fundamental — se o HN mudar o markup, você vai ter que atualizar isso. (Spoiler: o método da API evita essa dor de cabeça por completo.)
Etapa 3: extrair títulos, links e pontuações
Agora vem a parte prática. Vamos percorrer cada linha de história, pegar o título e o link da linha principal e, em seguida, coletar a pontuação da linha de metadados logo abaixo.
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 e URL da linha da história
title_tag = row.select_one("span.titleline > a")
if not title_tag:
continue
title = title_tag.get_text()
link = title_tag.get("href", "")
# Metadados da próxima linha irmã
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()
# Contagem de comentários: último <a> com "comment" no texto
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 histórias com 50+ pontos, ordenadas por pontuação
top_stories = sorted(
[s for s in stories if s["score"] >= 50],
key=lambda x: x["score"],
reverse=True,
)
pprint(top_stories[:10])
Alguns comentários sobre o código:
- O operador walrus (
:=) funciona no Python 3.8+. Ele permite atribuir e checar numa linha só — útil para elementos opcionais comospan.score, que podem não existir em todas as linhas (posts de vagas, por exemplo, não têm pontuação). - O HN usa
\xa0(espaço sem quebra) entre o número e "comments", então fazemos o split nesse caractere. - Histórias que apontam para outras páginas do HN (como posts "Ask HN") vêm com URLs relativas começando em
item?id=. Pode valer a pena prefixarhttps://news.ycombinator.com/nesses casos.
Etapa 4: executar e ver os resultados
Salve e rode:
python scrape_hn_bs4.py
Você deve ver uma saída parecida com:
[{'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/...'},
...]
São 30 histórias da página 1. Mas o HN tem centenas de histórias ativas a qualquer momento. Vamos falar de paginação numa seção mais à frente.
Método 2: fazer scraping do Hacker News com Python usando a API oficial
A API Firebase do HN é a forma oficialmente autorizada de acessar dados do Hacker News. Sem autenticação, sem chave de API, sem parse de HTML. Você recebe respostas JSON limpas. Eu uso esse método para tudo que precisa rodar de forma confiável em produção.
Principais endpoints da API que você precisa conhecer
A URL base é https://hacker-news.firebaseio.com/v0/. Aqui estão os endpoints que importam:
| Endpoint | Retorna | Exemplo |
|---|---|---|
/v0/topstories.json | Array com até 500 IDs das principais histórias | [47788542, 47787901, ...] |
/v0/newstories.json | Até 500 IDs das histórias mais novas | Mesmo formato |
/v0/beststories.json | Até 500 IDs das melhores histórias | Mesmo formato |
/v0/askstories.json | Até 200 IDs de histórias "Ask HN" | Mesmo formato |
/v0/showstories.json | Até 200 IDs de histórias "Show HN" | Mesmo formato |
/v0/jobstories.json | Até 200 IDs de vagas | Mesmo formato |
/v0/item/{id}.json | Detalhes completos de qualquer item (história, comentário, enquete) | Objeto JSON |
/v0/user/{username}.json | Perfil de usuário | Objeto JSON |
/v0/maxitem.json | ID máximo atual | Inteiro (por exemplo, 47789427) |
Um item de história tem esta cara:
{
"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"
}
O campo kids traz os IDs dos comentários filhos diretos. Cada comentário também é um item que pode ter os próprios kids — é assim que a árvore de comentários se estrutura.
Etapa 1: buscar os IDs das principais histórias
Crie um arquivo chamado scrape_hn_api.py:
import requests
import time
from pprint import pprint
API_BASE = "https://hacker-news.firebaseio.com/v0"
# Buscar IDs das principais histórias
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 de histórias numa única requisição — sem parse, sem seletores, só um array JSON.
Etapa 2: buscar os detalhes de cada história por ID
Agora precisamos dos dados de verdade das histórias. É aqui que aparece o problema de escala: 500 histórias significam 500 chamadas individuais à API. Nos meus testes, cada requisição de item leva cerca de 1,2 segundo de forma sequencial. Para 500 histórias, isso dá uns 10 minutos.
Na maioria dos casos, você não precisa das 500. Aqui vai um código para buscar as 30 primeiras:
def fetch_story(story_id):
"""Busca os detalhes de uma única história na API do HN."""
resp = requests.get(f"{API_BASE}/item/{story_id}.json")
return resp.json()
# Buscar os detalhes das 30 principais histórias
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) # Seja educado — pequeno intervalo entre as requisições
# Ordenar por pontuação e mostrar as 10 primeiras
top = sorted(stories, key=lambda x: x["score"], reverse=True)[:10]
pprint(top)
O time.sleep(0.1) dá uma pausa por cortesia. A Firebase API não informa um limite de requisições, mas martelar qualquer API sem intervalo é má prática.
Etapa 3: fazer scraping dos comentários (percurso recursivo da árvore)
É aqui que a API realmente brilha em comparação com o scraping de HTML. Os comentários no HN são profundamente aninhados — respostas de respostas de respostas. Em HTML, isso significaria encarar estruturas de tabela complexas e aninhadas. Com a API, cada comentário tem seu campo kids com os IDs dos filhos, e você simplesmente percorre a árvore de forma recursiva.
def fetch_comments(item_id, depth=0, max_depth=3):
"""Busca comentários recursivamente até 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
# Exemplo: buscar comentários da história 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]: # Primeiros 5 comentários de nível 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}...")
Essa abordagem recursiva é muito mais simples do que tentar interpretar threads de comentários aninhadas em HTML. Se você precisa da árvore completa de comentários, a API é o caminho certo.
Etapa 4: executar e ver os resultados
python scrape_hn_api.py
Você vai ver os dados estruturados das histórias, seguidos por uma prévia hierárquica dos comentários. Os dados saem mais limpos, o acesso aos comentários é trivial e não existe o risco de o seu scraper quebrar porque o HN trocou o nome de uma classe CSS.
Indo além da página 1: paginação e dados históricos
A maioria dos tutoriais de scraping do HN para na página 1 — 30 histórias. Isso basta para uma demo rápida, mas os casos reais muitas vezes pedem mais profundidade.
Fazendo scraping de várias páginas com BeautifulSoup
A paginação do HN segue um padrão simples de URL: ?p=2, ?p=3 etc. Cada página devolve 30 histórias, e o site vai até a página 20, mais ou menos (cerca de 600 histórias no total). Depois disso, as páginas vêm vazias.
import time
def scrape_hn_pages(num_pages=5):
"""Faz scraping de várias páginas da seção inicial do 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"Página {page}: nenhuma história encontrada, encerrando.")
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"Página {page}: {len(story_rows)} histórias coletadas")
# Respeite o crawl-delay de 30 segundos do robots.txt
if page < num_pages:
time.sleep(30)
return all_stories
stories = scrape_hn_pages(5)
print(f"\nTotal de histórias coletadas: {len(stories)}")
Esse time.sleep(30) é importante. O robots.txt do HN pede explicitamente um atraso de 30 segundos entre crawls. Ignorar isso pode render rate limit (HTTP 429) ou até bloqueio temporário. Cinco páginas com intervalos de 30 segundos levam cerca de 2,5 minutos — não é instantâneo, mas é respeitoso.
Para quem não quer mexer em código de paginação, a Thunderbit faz a paginação por clique e a rolagem infinita automaticamente. Ela clica no botão "More" no fim das páginas do HN sem nenhuma configuração.
Faça scraping das páginas do Hacker News com IA
Como acessar dados históricos do Hacker News com a API do Algolia
A Firebase API entrega dados atuais. Para análise histórica — "Quais foram as principais histórias sobre Python em 2023?" ou "Como a cobertura sobre IA mudou nos últimos 5 anos?" — você precisa da HN Algolia Search API.
import requests
ALGOLIA_BASE = "https://hn.algolia.com/api/v1"
def search_hn(query, tags="story", page=0, hits_per_page=20):
"""Pesquisa no HN via API do Algolia."""
params = {
"query": query,
"tags": tags,
"page": page,
"hitsPerPage": hits_per_page,
}
resp = requests.get(f"{ALGOLIA_BASE}/search", params=params)
return resp.json()
# Exemplo: encontrar histórias sobre scraping em Python com 10+ pontos desde jan/2024
results = search_hn(
query="python scraping",
tags="story",
)
print(f"Encontrados {results['nbHits']} resultados no total")
for hit in results["hits"][:5]:
print(f" [{hit.get('points', 0)} pts] {hit['title']}")
Para consultas com filtro de data, use numericFilters:
import calendar, datetime
# Histórias desde 1º de janeiro 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"Encontradas {data['nbHits']} histórias sobre web scraping em Python desde 2024 com mais de 10 pontos")
A API do Algolia é rápida (5–9 ms de processamento no servidor), não pede chave de API e suporta paginação de até 500 páginas. Para análise histórica em volume, é a melhor opção que existe.
Exportando os dados coletados do Hacker News para CSV, Excel e Google Sheets
Todo tutorial de scraping do HN que eu vi termina com saída de pprint() no terminal. Isso é ótimo para depurar, mas, se você está montando um resumo diário ou fazendo análise de tendências, precisa colocar os dados num arquivo. Veja como.
Exportar para CSV com Python
import csv
def export_to_csv(stories, filename="hn_stories.csv"):
"""Salva as histórias coletadas em um arquivo 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"Salvas {len(stories)} histórias em {filename}")
export_to_csv(stories)
Exportar para Excel com Python
import pandas as pd
def export_to_excel(stories, filename="hn_stories.xlsx"):
"""Salva as histórias coletadas em um arquivo Excel."""
df = pd.DataFrame(stories)
df.to_excel(filename, index=False, engine="openpyxl")
print(f"Salvas {len(stories)} histórias em {filename}")
export_to_excel(stories)
Garanta que o openpyxl esteja instalado — o pandas o usa como mecanismo para Excel. Se faltar, você vai tomar um ImportError.
Enviar para o Google Sheets (opcional)
Para fluxos automatizados, talvez você queira mandar os dados direto para o Google Sheets usando a biblioteca gspread. Isso exige configurar uma conta de serviço do Google Cloud (um processo que se faz uma vez só):
import gspread
gc = gspread.service_account(filename="service_account.json")
sh = gc.open("HN Daily Digest")
worksheet = sh.sheet1
# Converter histórias em linhas
header = list(stories[0].keys())
rows = [list(s.values()) for s in stories]
worksheet.clear()
worksheet.update([header] + rows)
print("Enviado para o Google Sheets")
A alternativa sem código para exportar
Se configurar contas de serviço e escrever código de exportação parece dar mais trabalho do que o próprio scraping, eu entendo. Na Thunderbit, criamos uma exportação de dados gratuita que manda os dados coletados direto para Excel, Google Sheets, Airtable ou Notion — sem código, sem credenciais, sem pipeline para manter. Para uma extração pontual, é realmente mais rápido. Falo mais sobre isso abaixo.
Deixando seu scraper pronto para produção: tratamento de erros, cache e agendamento
Se você vai rodar um scraper só por diversão, o código acima já resolve. Se a ideia é rodá-lo todo dia como parte de um fluxo, vai precisar de alguns elementos extras.
Tratamento de erros e lógica de retry
Redes falham. Servidores limitam requisições. Uma requisição ruim não deveria derrubar a coleta inteira. Aqui vai uma função de retry com backoff 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):
"""Busca uma URL com retries automáticos e backoff 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"Falhou após as tentativas: {e}")
A biblioteca tenacity cuida da lógica de retry com elegância. Ela tenta até 5 vezes com backoff exponencial com jitter — começando em 1 segundo e indo até 60 segundos. Isso dá conta de HTTP 429 (limite de requisições), 503 (serviço indisponível) e falhas de rede passageiras.
Cache de respostas para evitar recrawling
Durante o desenvolvimento, você vai rodar o scraper várias vezes enquanto ajusta a lógica de parse. Sem cache, cada execução bate de novo nos servidores do HN para buscar os mesmos dados. A biblioteca requests-cache resolve isso em duas linhas:
import requests_cache
requests_cache.install_cache("hn_cache", expire_after=3600) # Cache por 1 hora
Depois de colocar essas linhas no topo do script, todas as chamadas requests.get() passam a ser armazenadas automaticamente num banco SQLite local. Rode o script 10 vezes em uma hora, e só a primeira execução vai realmente bater na rede. É uma ferramenta que usuários de fórum recomendam o tempo todo, e com razão.
Separar coleta de parse
Um padrão de que quem já faz scraping há tempo gosta muito: primeiro baixe os dados brutos, depois faça o parse. Assim, se a sua lógica de parse tiver um bug, você corrige e reprocessa sem ter que baixar tudo de novo.
import os, json
def crawl_and_save(story_ids, output_dir="raw_data"):
"""Busca os dados das histórias e salva o JSON bruto em 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 # Ignora itens já baixados
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"):
"""Converte os arquivos JSON salvos em uma lista estruturada de histórias."""
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
Essa abordagem em duas etapas é especialmente útil quando você está coletando centenas de itens e quer iterar rápido sobre a forma de processar os dados.
Automatizando seu scraper num cronograma
Para um resumo diário do HN, você precisa que o scraper rode sozinho. Duas opções comuns:
Opção 1: cron (Linux/Mac)
# Executar todo dia às 8:30 UTC
30 8 * * * /usr/bin/python3 /home/user/scrape_hn.py >> /home/user/scrape.log 2>&1
Opção 2: GitHub Actions (gratuito, sem servidor)
name: Scrape Hacker News
on:
schedule:
- cron: '30 8 * * *' # Diário às 8:30 UTC
workflow_dispatch: # Botão para execução 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 "Atualiza dados do HN $(date -u +%Y-%m-%dT%H:%M:%SZ)"
git push
Alguns pontos de atenção com o agendamento no GitHub Actions: todos os horários do cron são em UTC, atrasos de 15 a 60 minutos são comuns (prefira horários fora da hora cheia, como :30 em vez de :00), e o GitHub pode desligar workflows agendados em repositórios sem atividade por 60 dias. Inclua sempre o workflow_dispatch para conseguir disparar manualmente durante os testes.
Se você quiser algo mais simples, o recurso Scheduled Scraper da Thunderbit deixa você descrever o agendamento em linguagem natural — algo como "extrair toda manhã às 8h" — sem servidor e sem configuração de cron.
Quando Python é exagero: a forma sem código de fazer scraping do Hacker News
Vou ser franco aqui, mesmo sendo fã de Python e mesmo com meu time construindo ferramentas para desenvolvedores. Se você só precisa das 100 principais histórias do HN de hoje numa planilha — agora, uma única vez —, escrever, depurar e rodar um script Python é sobrecarga desnecessária. Só a configuração (ambiente virtual, instalar pacotes, descobrir seletores) leva mais tempo do que a própria coleta dos dados.
É aí que entra a Thunderbit. O fluxo é este:
- Abra
news.ycombinator.comno Chrome - Clique no ícone da extensão Thunderbit e depois em "AI Suggest Fields"
- A IA lê a página e propõe colunas: Título, URL, Pontuação, Autor, Contagem de comentários, Hora da publicação
- Ajuste os campos se quiser (renomeie, remova ou adicione campos personalizados — dá até para incluir um prompt de IA como "Classificar como IA/DevTools/Web/Outro")
- Clique em "Scrape" — os dados aparecem numa tabela estruturada
- Exporte para Excel, Google Sheets, Airtable ou Notion
Dois cliques até os dados estruturados. Sem seletores, sem código, sem manutenção.
Uma vantagem real aqui: a IA da Thunderbit se ajusta sozinha a mudanças de layout. Scrapers tradicionais baseados em seletores CSS quebram quando um site muda o markup — e, embora o HTML do HN seja relativamente estável, ele já mudou (a classe class="athing submission" foi atualizada, e span.titleline substituiu o antigo a.storylink). Um scraper com IA relê a página a cada execução, então não depende de nomes de classe.

A Thunderbit também cuida da paginação (clicando sozinha no botão "More" do HN) e do scraping de subpáginas (visitando a página de comentários de cada história para coletar a discussão). Para o caso de enriquecimento de comentários, isso equivale ao código recursivo da API no Método 2 — mas sem escrever uma única linha.
As diferenças são simples: Python é a escolha certa quando você precisa de lógica personalizada, transformações complexas de dados, pipelines automatizados num cronograma ou está aprendendo a programar. A Thunderbit é a escolha certa quando você precisa dos dados rápido, não quer manter código ou não é desenvolvedor. Escolha a ferramenta que combina com a sua situação.
Python vs. API vs. sem código: qual método escolher?
Aqui está o framework completo de decisão:
| Critério | BeautifulSoup (HTML) | API Firebase | API Algolia | Thunderbit (sem código) |
|---|---|---|---|---|
| Nível técnico necessário | Python intermediário | Python iniciante | Python iniciante | Nenhum |
| Tempo de configuração | 10–15 min | 5–10 min | 5–10 min | 2 min |
| Esforço de manutenção | Médio (seletores quebram) | Baixo (JSON estável) | Baixo (JSON estável) | Nenhum |
| Profundidade dos dados | Apenas página inicial | Qualquer item, usuários | Busca + histórico | Página inicial + subpáginas |
| Comentários | Difícil | Fácil (recursivo) | Fácil (árvore aninhada) | Scraping de subpáginas |
| Dados históricos | Não | Não | Sim (arquivo completo) | Não |
| Opções de exportação | Você mesmo programa | Você mesmo programa | Você mesmo programa | Integrado (Excel, Sheets etc.) |
| Agendamento | cron / GitHub Actions | cron / GitHub Actions | cron / GitHub Actions | Agendador integrado |
| Melhor para | Aprender scraping | Pipelines confiáveis | Pesquisa e análise | Extrações rápidas |
Se você está aprendendo Python ou construindo algo personalizado, vá de Método 1 ou 2. Se precisa de análise histórica, acrescente a API Algolia. Se só quer os dados sem escrever código, experimente a Thunderbit.
Experimente a Thunderbit para fazer scraping do Hacker News
Conclusão e principais aprendizados
Agora você tem no seu kit:
- Dois métodos completos em Python para fazer scraping do Hacker News — BeautifulSoup para parse de HTML e a API Firebase para dados JSON limpos
- Técnicas de paginação para ir além da página 1, incluindo a API Algolia para dados históricos que vão até 2007
- Código de exportação para CSV, Excel e Google Sheets — porque dado parado no terminal não ajuda ninguém do seu time
- Padrões de produção — retry, cache, separação entre coleta e parse e automação agendada com cron ou GitHub Actions
- Uma alternativa sem código para quando Python é mais ferramenta do que você precisa
Minha recomendação: comece pela API Firebase (Método 2) na maioria dos casos. Ela é mais limpa, mais confiável e dá acesso aos comentários sem o transtorno de fazer parse de HTML aninhado. Acrescente a API Algolia quando precisar de dados históricos. E mantenha a Thunderbit nos favoritos para quando você só precisar de uma planilha rápida e não quiser criar um projeto Python inteiro.
Se quiser ir mais fundo, tente coletar comentários do HN para análise de sentimento, monte um pipeline diário com GitHub Actions ou explore a API Algolia para acompanhar como as tendências de tecnologia mudaram ao longo da última década.
Experimente a Thunderbit para fazer scraping rápido do Hacker News Get Started Free
FAQs
É legal fazer scraping do Hacker News?
Os dados do HN são públicos, e o Y Combinator fornece uma API oficial justamente para acesso programático. O robots.txt do site permite a coleta de conteúdo apenas para leitura (página inicial, páginas de itens e páginas de usuários), mas pede um atraso de 30 segundos entre crawls. Respeite esse intervalo, não faça scraping de endpoints interativos (votação, login) e você fica numa boa posição. Para saber mais sobre ética em scraping, veja nosso guia sobre implicações legais do web scraping.
O Hacker News tem uma API oficial?
Tem. A HN Firebase API em hacker-news.firebaseio.com/v0/ é gratuita, não exige autenticação e dá acesso a histórias, comentários, perfis de usuário e todos os tipos de feed (top, new, best, ask, show, jobs). Ela retorna JSON limpo e não informa limite oficial de requisições, embora seja sempre recomendável manter uma frequência educada.
Como faço scraping dos comentários do Hacker News com Python?
Usando a API Firebase, busque um item de história para obter o campo kids (um array com os IDs dos comentários de nível superior). Cada comentário também é um item com seu próprio campo kids para as respostas. Percorra a árvore recursivamente com uma função que busca cada comentário e seus filhos. Veja a seção "Fazer scraping dos comentários (percurso recursivo da árvore)" acima para o código completo. Como alternativa, o endpoint /items/<id> da API Algolia retorna a árvore completa de comentários aninhados em uma única requisição — bem mais rápido para histórias com muitos comentários.
Posso fazer scraping do Hacker News sem escrever código?
Pode. O AI web scraper da Thunderbit funciona como extensão do Chrome — abra o HN, clique em "AI Suggest Fields" e ele identifica sozinho colunas como título, URL, pontuação e autor. Clique em "Scrape" e exporte direto para Excel, Google Sheets, Airtable ou Notion. Ele dá conta da paginação e ainda pode visitar subpáginas para coletar dados de comentários. Sem Python, sem seletores, sem manutenção.
Como consigo dados históricos do Hacker News?
A HN Algolia Search API é a melhor ferramenta para isso. Use o endpoint search_by_date com numericFilters=created_at_i>TIMESTAMP para filtrar por intervalo de datas. Você pode pesquisar por palavra-chave, filtrar por tipo de história e paginar até 500 páginas de resultados. Para análise histórica em grande escala, também há conjuntos de dados públicos no Google BigQuery (arquivo completo), ClickHouse (28 milhões de registros) e Hugging Face (4 milhões de histórias).
Saiba mais


