Jak scrapuję Hacker News w Pythonie (2 metody, pełny kod)

Ostatnia aktualizacja: August 11, 2026
Jak scrapuję Hacker News w Pythonie (2 metody, pełny kod)

Kilka miesięcy temu chciałem zbudować codzienny zestaw najważniejszych artykułów z Hacker News dla zespołu w Thunderbit. Pierwszy odruch? Po prostu dodać stronę do zakładek i przeglądać ją każdego ranka. Wytrzymało to jakieś trzy dni — potem zorientowałem się, że codziennie tracę 20 minut tylko na czytanie nagłówków i kopiowanie linków do arkusza kalkulacyjnego.

Hacker News to jedno z najbogatszych i najbardziej skoncentrowanych źródeł wiedzy technologicznej w internecie — około 13 milionów wizyt miesięcznie, mniej więcej 1 300 nowych wpisów dziennie i około 13 000 komentarzy każdego dnia. Niezależnie od tego, czy śledzisz rodzące się trendy technologiczne, monitorujesz swoją markę, budujesz pipeline rekrutacyjny z wątków „Who’s Hiring”, czy po prostu chcesz wiedzieć, czym żyje społeczność deweloperów, ręczne śledzenie tego wszystkiego to walka z góry przegrana.

Dobra wiadomość: scrapowanie Hacker News w Pythonie jest zaskakująco proste. W tym poradniku pokażę Ci dwie kompletne metody — scrapowanie HTML z BeautifulSoup oraz oficjalne API HN Firebase — a do tego omówię paginację, eksport danych, praktyki gotowe do wdrożenia oraz skrót bez kodu na wypadek, gdy Python wydaje się przesadą.

Dlaczego warto scrapować Hacker News w Pythonie?

Hacker News to nie jest kolejny zwykły agregator linków. To kuratorowany, napędzany społecznością feed, w którym najciekawsze historie technologiczne wypływają na szczyt dzięki głosom użytkowników i aktywnej dyskusji. Publiczność jest mocno przesunięta w stronę specjalistów technologicznych (około 76% mężczyzn, główna grupa wiekowa 25–34 lata), a 66% ruchu bezpośredniego pokazuje, że to lojalna, nawykowa społeczność — nie przypadkowi odwiedzający.

Oto, po co ludzie scrapują dane z HN:

ZastosowanieCo otrzymujesz
Codzienny digest technologicznyNajlepsze historie, wyniki i linki trafiające do skrzynki mailowej lub Slacka
Monitoring marki / konkurencjiPowiadomienia, gdy Twoja firma lub produkt zostanie wspomniany
Analiza trendówŚledzenie, które technologie, języki lub tematy zyskują popularność z czasem
RekrutacjaParsowanie wątków „Who’s Hiring” pod kątem ofert pracy, stacków technologicznych i sygnałów płacowych
Research treściWyszukiwanie tematów o wysokim potencjale, o których warto pisać lub które warto udostępniać
Analiza sentymentuOcenianie opinii społeczności o produktach, premierach i zmianach w branży

Firmy o łącznej wartości przekraczającej 400 miliardów dolarów — Stripe, Dropbox, Airbnb — przypisują Hacker News kluczowe wczesne opinie i pierwszych użytkowników. Drew Houston opublikował demo Dropboxa na HN w kwietniu 2007 roku, strona wskoczyła na 1. miejsce, a lista oczekujących na betę urosła z 5 000 do 75 000 użytkowników w jeden dzień. Dane z HN są nie tylko ciekawe — są też realnie wartościowe biznesowo.

Dane są publicznie dostępne, ale struktura serwisu sprawia, że ręczne zbieranie ich jest uciążliwe. Automatyzacja w Pythonie to po prostu praktyczniejsze rozwiązanie.

Dwa sposoby na scrapowanie Hacker News w Pythonie: przegląd

Ten poradnik obejmuje dwie kompletne, działające metody:

  1. Scrapowanie HTML z requests + BeautifulSoup — pobierasz surowy HTML z news.ycombinator.com i parsujesz go, aby wyciągnąć dane o wpisach. Świetne do nauki podstaw scrapowania i pobierania dokładnie tego, co widać na stronie.
  2. Oficjalne API Hacker News Firebase — wysyłasz żądania bezpośrednio do endpointów JSON, bez parsowania HTML. Lepsze do niezawodnych pipeline’ów danych, pobierania komentarzy i danych historycznych.

Oto porównanie obok siebie, które pomoże Ci wybrać właściwą opcję:

KryteriumScrapowanie HTML (requests + BS4)API HN FirebaseThunderbit (bez kodu)
Złożoność konfiguracjiŚrednia (selektory HTML)Niska (endpointy JSON)Brak (rozszerzenie Chrome w 2 kliknięciach)
Aktualność danychFront page w czasie rzeczywistymW czasie rzeczywistym (dowolny element po ID)W czasie rzeczywistym
Ryzyko limitówŚrednie (robots.txt podaje 30 s opóźnienia crawl)Niskie (oficjalne, dość hojne)Zarządzane przez Thunderbit
Dostęp do komentarzyTrudny (zagnieżdżony HTML)Łatwy (rekurencyjne ID elementów)Funkcja scrapowania podstron
Dane historyczneOgraniczonePrzez Algolia Search APIBrak
Najlepsze doNauki podstaw scrapowaniaNiezawodnych pipeline’ów danychOsób nietechnicznych, szybkich eksportów

Obie metody zawierają pełny, gotowy do uruchomienia kod Python. A jeśli po prostu chcesz dostać dane bez pisania jakiegokolwiek kodu, też pokażę Ci taką opcję.

Zanim zaczniesz

  • Poziom trudności: początkujący do średnio zaawansowanego
  • Czas potrzebny: około 15–20 minut na każdą metodę
  • Czego potrzebujesz:
    • Python 3.11+ zainstalowany
    • Terminal lub edytor kodu
    • Przeglądarka Chrome (jeśli chcesz podejrzeć HTML HN albo wypróbować opcję bez kodu)
    • Rozszerzenie Thunderbit do Chrome (opcjonalnie, do metody bez kodu)

scrape-hacker-news-methods.webp

Konfiguracja środowiska Python

Zanim dotkniemy jakichkolwiek danych z HN, przygotujmy środowisko. Polecam utworzyć wirtualne środowisko, żeby zależności projektu były czyste i odseparowane.

# Utwórz i aktywuj wirtualne środowisko
python3 -m venv hn-scraper
# macOS/Linux:
source hn-scraper/bin/activate
# Windows:
hn-scraper\Scripts\activate

# Zainstaluj pakiety potrzebne do obu metod
pip install requests==2.33.1 beautifulsoup4==4.14.3 pandas==3.0.2 openpyxl==3.1.5

Do dalszych, bardziej produkcyjnych scenariuszy (cache, retry) przydadzą się też:

pip install requests-cache==1.3.1 tenacity==9.1.4

Bez specjalnych kluczy API, bez tokenów uwierzytelniających. Dane HN są publiczne.

Metoda 1: scrapowanie Hacker News w Pythonie z użyciem BeautifulSoup

To klasyczne podejście — pobierasz HTML, parsujesz go i wyciągasz potrzebne dane. W ten sposób większość osób uczy się web scrapingu, a prosta, tabelaryczna struktura HN sprawia, że to świetne środowisko do ćwiczeń.

Krok 1: pobierz stronę główną Hacker News

Otwórz edytor i utwórz plik scrape_hn_bs4.py. Oto kod startowy:

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")

Uruchom go. Powinieneś zobaczyć Status: 200 i długość strony około 40 000–50 000 znaków. To surowy HTML strony głównej HN załadowany do pamięci, gotowy do parsowania.

Krok 2: zrozum strukturę HTML

HN korzysta z układu opartego na tabelach — bez nowoczesnego CSS grid czy flexboxa. Każda historia na stronie składa się z dwóch kluczowych wierszy <tr>:

  • Wiersz historii (<tr class="athing submission">): zawiera pozycję, tytuł i link
  • Wiersz metadanych (następny <tr>): zawiera liczbę punktów, autora, czas i liczbę komentarzy

Najważniejsze selektory:

  • span.titleline > a — tytuł wpisu i URL
  • span.score — liczba głosów (np. „118 points”)
  • a.hnuser — nazwa użytkownika autora
  • span.age — czas publikacji
  • Ostatni <a> w .subtext z tekstem zawierającym „comment” — liczba komentarzy

Jeśli klikniesz prawym przyciskiem myszy na dowolny tytuł w Chrome i wybierzesz „Inspect”, zobaczysz coś takiego:

<span class="titleline">
  <a href="https://darkbloom.dev">Darkbloom – Private inference on idle Macs</a>
</span>

A poniżej wiersz z metadanymi:

<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&nbsp;comments</a>

Zrozumienie tych selektorów jest kluczowe — jeśli HN kiedykolwiek zmieni markup, trzeba będzie je zaktualizować. (Spoiler: metoda API całkowicie omija ten problem.)

Krok 3: wyciągnij tytuły, linki i wyniki

Teraz właściwa część. Przejdziemy po każdym wierszu historii, pobierzemy tytuł i link z wiersza historii, a następnie score z wiersza metadanych znajdującego się bezpośrednio pod nim.

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:
    # Tytuł i URL z wiersza historii
    title_tag = row.select_one("span.titleline > a")
    if not title_tag:
        continue
    title = title_tag.get_text()
    link = title_tag.get("href", "")

    # Metadane z następnego wiersza
    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()
        # Liczba komentarzy: ostatni <a> z tekstem zawierającym "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,
    })

# Filtrujemy historie z 50+ punktami i sortujemy po score
top_stories = sorted(
    [s for s in stories if s["score"] >= 50],
    key=lambda x: x["score"],
    reverse=True,
)

pprint(top_stories[:10])

Kilka uwag do kodu:

  • Operator walrus (:=) działa od Pythona 3.8. Pozwala przypisać i sprawdzić w jednej linii — przydatne dla opcjonalnych elementów, takich jak span.score, które nie występują w każdym wierszu (np. wpisy rekrutacyjne nie mają wyniku).
  • HN używa \xa0 (twardej spacji) między liczbą a słowem „comments”, więc dzielimy po tym znaku.
  • Wpisy prowadzące do innych stron HN (np. posty „Ask HN”) będą miały względne URL-e zaczynające się od item?id=. Warto dodać wtedy prefiks https://news.ycombinator.com/.

Krok 4: uruchom i zobacz wyniki

Zapisz plik i uruchom:

python scrape_hn_bs4.py

Powinieneś zobaczyć wynik podobny do tego:

[{'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/...'},
 ...]

To 30 historii z pierwszej strony. Ale HN ma przecież setki aktywnych wpisów naraz. Paginację omówimy w dalszej części.

Metoda 2: scrapowanie Hacker News w Pythonie z użyciem oficjalnego API

HN Firebase API to oficjalnie rekomendowany sposób dostępu do danych Hacker News. Bez uwierzytelniania, bez kluczy API, bez parsowania HTML. Dostajesz czyste odpowiedzi JSON. Ja używam tej metody do wszystkiego, co musi działać niezawodnie w produkcji.

Kluczowe endpointy API, które warto znać

Bazowy adres to https://hacker-news.firebaseio.com/v0/. Oto najważniejsze endpointy:

EndpointCo zwracaPrzykład
/v0/topstories.jsonTablica do 500 ID najlepszych historii[47788542, 47787901, ...]
/v0/newstories.jsonDo 500 najnowszych ID historiiTen sam format
/v0/beststories.jsonDo 500 najlepszych ID historiiTen sam format
/v0/askstories.jsonDo 200 ID wpisów typu „Ask HN”Ten sam format
/v0/showstories.jsonDo 200 ID wpisów typu „Show HN”Ten sam format
/v0/jobstories.jsonDo 200 ID ofert pracyTen sam format
/v0/item/{id}.jsonPełne szczegóły dowolnego elementu (historia, komentarz, ankieta)Obiekt JSON
/v0/user/{username}.jsonProfil użytkownikaObiekt JSON
/v0/maxitem.jsonAktualne maksymalne ID elementuLiczba całkowita (np. 47789427)

Przykładowy element historii wygląda tak:

{
  "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"
}

Pole kids zawiera ID bezpośrednich komentarzy potomnych. Każdy komentarz jest osobnym elementem i może mieć własne kids — właśnie tak zbudowane jest drzewo komentarzy.

Krok 1: pobierz ID najpopularniejszych historii

Utwórz plik scrape_hn_api.py:

import requests
import time
from pprint import pprint

API_BASE = "https://hacker-news.firebaseio.com/v0"

# Pobieramy ID top stories
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 ID historii w jednym żądaniu — bez parsowania, bez selektorów, po prostu tablica JSON.

Krok 2: pobierz szczegóły historii po ID

Teraz potrzebujemy właściwych danych. Tutaj pojawia się problem „fan-out”: 500 historii oznacza 500 osobnych wywołań API. W moich testach pojedyncze żądanie o element trwa około 1,2 sekundy sekwencyjnie. Dla 500 historii to mniej więcej 10 minut.

Dla większości zastosowań nie potrzebujesz wszystkich 500. Oto kod pobierający top 30:

def fetch_story(story_id):
    """Pobiera szczegóły pojedynczej historii z API HN."""
    resp = requests.get(f"{API_BASE}/item/{story_id}.json")
    return resp.json()

# Pobieramy szczegóły dla 30 najlepszych historii
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)  # Bądź uprzejmy — krótka przerwa między żądaniami

# Sortujemy po score i pokazujemy top 10
top = sorted(stories, key=lambda x: x["score"], reverse=True)[:10]
pprint(top)

time.sleep(0.1) dodaje niewielkie opóźnienie z grzeczności. Firebase API nie podaje oficjalnego limitu, ale bombardowanie jakiegokolwiek API bez pauz to zła praktyka.

Krok 3: scrapowanie komentarzy (rekurencyjne przejście po drzewie)

Tutaj API naprawdę błyszczy na tle scrapowania HTML. Komentarze na HN są głęboko zagnieżdżone — odpowiedzi na odpowiedzi na odpowiedzi. W HTML oznacza to walkę ze skomplikowaną, zagnieżdżoną strukturą tabel. W API każde kids zawiera ID dzieci, więc po prostu przechodzisz po drzewie rekurencyjnie.

def fetch_comments(item_id, depth=0, max_depth=3):
    """Rekurencyjnie pobiera komentarze do 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

# Przykład: pobieramy komentarze dla top story
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]:  # Pierwsze 5 komentarzy najwyższego poziomu
            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 "[brak tekstu]"
            print(f"{indent}[{c['author']}] {preview}...")

To rekurencyjne podejście jest znacznie prostsze niż parsowanie zagnieżdżonych wątków komentarzy w HTML. Jeśli potrzebujesz pełnych drzew komentarzy, API będzie najlepszym wyborem.

Krok 4: uruchom i zobacz wyniki

python scrape_hn_api.py

Zobaczysz uporządkowane dane o historiach, a potem zagnieżdżony podgląd komentarzy. Dane są czystsze, dostęp do komentarzy jest banalny, a ryzyko, że scraper przestanie działać, bo HN zmieni nazwę klasy CSS, praktycznie znika.

Dalej niż pierwsza strona: paginacja i dane historyczne

Większość tutoriali o scrapowaniu HN kończy się na pierwszej stronie — 30 historiach. To wystarczy do szybkiej demonstracji, ale w realnych zastosowaniach często potrzebujesz więcej.

Scrapowanie wielu stron z BeautifulSoup

Paginacja HN opiera się na prostym wzorcu URL: ?p=2, ?p=3 itd. Każda strona zwraca 30 historii, a serwis obsługuje mniej więcej do 20. strony (czyli około 600 historii łącznie). Powyżej tego pojawiają się puste strony.

import time

def scrape_hn_pages(num_pages=5):
    """Scrapuje wiele stron z front page Hacker News."""
    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")

        # Szanuj crawl-delay 30 sekund z robots.txt
        if page < num_pages:
            time.sleep(30)

    return all_stories

stories = scrape_hn_pages(5)
print(f"\nTotal stories scraped: {len(stories)}")

time.sleep(30) jest tu ważne. Plik robots.txt HN wyraźnie prosi o 30-sekundowe opóźnienie crawl. Zignorujesz to i możesz dostać limitowanie żądań (HTTP 429) albo tymczasową blokadę. Pięć stron w odstępach 30 sekund zajmuje około 2,5 minuty — nie jest to natychmiastowe, ale jest to zgodne z zasadami.

Dla osób, które nie chcą zarządzać kodem paginacji, Thunderbit automatycznie obsługuje paginację klikaniem i infinite scroll. Kliknie przycisk „More” na dole stron HN bez żadnej konfiguracji.

Scrapuj strony Hacker News z pomocą AI

Dostęp do historycznych danych Hacker News przez Algolia API

Firebase API daje dane bieżące. Do analizy historycznej — „Jakie były najlepsze historie o Pythonie w 2023 roku?” albo „Jak zmieniało się pokrycie tematu AI przez ostatnie 5 lat?” — potrzebujesz 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):
    """Wyszukuje w HN przez API Algolia."""
    params = {
        "query": query,
        "tags": tags,
        "page": page,
        "hitsPerPage": hits_per_page,
    }
    resp = requests.get(f"{ALGOLIA_BASE}/search", params=params)
    return resp.json()

# Przykład: znajdź historie o scrapowaniu w Pythonie z 10+ punktami od stycznia 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']}")

Do zapytań z filtrem daty użyj numericFilters:

import calendar, datetime

# Historie od 1 stycznia 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")

Algolia API jest szybkie (5–9 ms czasu przetwarzania po stronie serwera), nie wymaga klucza API i obsługuje paginację do 500 stron. Do masowej analizy historycznej to najlepsza dostępna opcja.

Eksport scraped danych z Hacker News do CSV, Excela i Google Sheets

Każdy poradnik o scrapowaniu HN, jaki widziałem, kończy się wynikiem pprint() w terminalu. To świetne do debugowania, ale jeśli budujesz codzienny digest albo analizujesz trendy, dane muszą trafić do pliku. Oto jak to zrobić.

Eksport do CSV w Pythonie

import csv

def export_to_csv(stories, filename="hn_stories.csv"):
    """Zapisuje zebrane historie do pliku 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)

Eksport do Excela w Pythonie

import pandas as pd

def export_to_excel(stories, filename="hn_stories.xlsx"):
    """Zapisuje zebrane historie do pliku 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)

Upewnij się, że openpyxl jest zainstalowany — pandas używa go jako silnika Excela. Jeśli go brakuje, pojawi się ImportError.

Wysyłka do Google Sheets (opcjonalnie)

W automatycznych workflowach możesz chcieć wysyłać dane bezpośrednio do Google Sheets przy użyciu biblioteki gspread. Wymaga to jednorazowego skonfigurowania konta usługi Google Cloud:

import gspread

gc = gspread.service_account(filename="service_account.json")
sh = gc.open("HN Daily Digest")
worksheet = sh.sheet1

# Zamieniamy historie na wiersze
header = list(stories[0].keys())
rows = [list(s.values()) for s in stories]

worksheet.clear()
worksheet.update([header] + rows)
print("Pushed to Google Sheets")

Alternatywa bez kodu dla eksportu

Jeśli konfiguracja kont serwisowych i pisanie kodu eksportowego brzmi jak więcej pracy niż samo scrapowanie, rozumiem to. W Thunderbit zbudowaliśmy darmowy eksport danych, który pozwala wysyłać zeskrobane dane bezpośrednio do Excela, Google Sheets, Airtable lub Notion — bez kodu, bez poświadczeń, bez utrzymywania pipeline’u. Przy jednorazowym pobraniu danych to naprawdę szybsze rozwiązanie. O tym niżej.

Jak przygotować scraper do pracy produkcyjnej: obsługa błędów, cache i harmonogram

Jeśli uruchamiasz scraper raz dla zabawy, powyższy kod wystarczy. Jeśli ma działać codziennie jako część workflow, potrzebujesz kilku dodatkowych elementów.

Obsługa błędów i logika ponawiania

Sieć bywa zawodna. Serwery ograniczają ruch. Pojedyncze nieudane żądanie nie powinno wywracać całego procesu. Oto funkcja retry z wykładniczym backoffem:

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):
    """Pobiera URL z automatycznymi ponowieniami i wykładniczym backoffem."""
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    return response

# Użycie:
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}")

Biblioteka tenacity elegancko obsługuje retry. Spróbuje ponownie do 5 razy z losowanym wykładniczym backoffem — start od 1 sekundy, maksymalnie 60 sekund. To dobrze radzi sobie z HTTP 429 (limit), 503 (service unavailable) i przejściowymi błędami sieci.

Cache odpowiedzi, żeby nie scrapować w kółko

Podczas desenvolpmentu wielokrotnie uruchamiasz scraper, poprawiając logikę parsowania. Bez cache każdorazowo trafiasz ponownie na serwery HN z tymi samymi danymi. Biblioteka requests-cache rozwiązuje to w dwóch liniach:

import requests_cache

requests_cache.install_cache("hn_cache", expire_after=3600)  # Cache na 1 godzinę

Po dodaniu tych linii na początku skryptu wszystkie wywołania requests.get() są automatycznie cachowane w lokalnej bazie SQLite. Uruchom skrypt 10 razy w ciągu godziny, a tylko pierwsze uruchomienie faktycznie połączy się z siecią. To narzędzie użytkownicy forów często polecają i nie bez powodu.

Rozdzielenie pobierania od parsowania

Jednym z patternów, na który przysięgają doświadczeni scraperzy, jest najpierw pobrać surowe dane, a dopiero potem je parsować. Dzięki temu, jeśli logika parsowania ma błąd, poprawiasz go i parsujesz ponownie bez ponownego pobierania.

import os, json

def crawl_and_save(story_ids, output_dir="raw_data"):
    """Pobiera dane historii i zapisuje surowy JSON na dysku."""
    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  # Pomijamy już pobrane elementy
        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"):
    """Parsuje zapisane pliki JSON do uporządkowanej listy historii."""
    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

To podejście dwuetapowe jest szczególnie przydatne, gdy scrapujesz setki elementów i chcesz szybko iterować nad sposobem przetwarzania danych.

Automatyzacja scrapera według harmonogramu

Do codziennego digestu HN scraper musi działać automatycznie. Dwie popularne opcje:

Opcja 1: cron (Linux/Mac)

# Uruchamiaj codziennie o 8:30 UTC
30 8 * * * /usr/bin/python3 /home/user/scrape_hn.py >> /home/user/scrape.log 2>&1

Opcja 2: GitHub Actions (darmowe, bez własnego serwera)

name: Scrape Hacker News

on:
  schedule:
    - cron: '30 8 * * *'  # Codziennie o 8:30 UTC
  workflow_dispatch:        # Przycisk do ręcznego uruchomienia

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

Kilka pułapek przy harmonogramach w GitHub Actions: wszystkie czasy cron są w UTC, opóźnienia 15–60 minut są dość częste (warto używać nietypowych minut, np. :30 zamiast :00), a GitHub może wyłączyć zaplanowane workflowy w repozytoriach bez aktywności przez 60 dni. Zawsze dodawaj workflow_dispatch, żeby móc uruchamiać workflow ręcznie podczas testów.

Prostsza opcja: funkcja Scheduled Scraper w Thunderbit pozwala opisać harmonogram zwykłym językiem — np. „scrape every morning at 8am” — bez serwera i bez konfiguracji crona.

Gdy Python to przesada: sposób bez kodu na scrapowanie Hacker News

Będę szczery — mimo że uwielbiam Pythona i mój zespół tworzy narzędzia dla developerów. Jeśli potrzebujesz po prostu dzisiejszych 100 najlepszych historii z HN w arkuszu kalkulacyjnym — teraz, jednorazowo — pisanie, debugowanie i uruchamianie skryptu Pythona to zbędny narzut. Sama konfiguracja (wirtualne środowisko, instalacja pakietów, ogarnianie selektorów) trwa dłużej niż samo zebranie danych.

Tutaj pojawia się Thunderbit. Workflow wygląda tak:

  1. Otwórz news.ycombinator.com w Chrome
  2. Kliknij ikonę rozszerzenia Thunderbit, a potem „AI Suggest Fields”
  3. AI odczytuje stronę i proponuje kolumny: Title, URL, Score, Author, Comment Count, Time Posted
  4. W razie potrzeby dopasuj pola (zmień nazwę, usuń lub dodaj własne — możesz nawet dodać prompt AI typu „Categorize as AI/DevTools/Web/Other”)
  5. Kliknij „Scrape” — dane pojawiają się w uporządkowanej tabeli
  6. Wyeksportuj do Excela, Google Sheets, Airtable lub Notion

Dwa kliknięcia do danych strukturalnych. Bez selektorów, bez kodu, bez utrzymania.

Jedna ważna przewaga: AI w Thunderbit automatycznie dostosowuje się do zmian w układzie strony. Tradycyjne scrapery oparte na selektorach CSS psują się, gdy serwis zmienia markup — a choć HTML HN jest dość stabilny, to jednak się zmieniał (klasa class="athing submission" została zaktualizowana, span.titleline zastąpił starszy a.storylink). Scraper oparty na AI czyta stronę „na świeżo” za każdym razem, więc zmiany nazw klas go nie obchodzą.

python-vs-thunderbit-comparison.webp

Thunderbit obsługuje też paginację (automatycznie klikając przycisk „More” na HN) oraz scrapowanie podstron (odwiedzanie strony komentarzy każdej historii, aby pobrać dyskusję). W przypadku wzbogacania komentarzy to odpowiednik rekurencyjnego kodu z Metody 2 — tylko bez pisania ani jednej linii.

Wady i zalety są proste: Python to lepszy wybór, gdy potrzebujesz własnej logiki, złożonych transformacji danych, planowanych pipeline’ów automatyzacji albo po prostu uczysz się programować. Thunderbit sprawdzi się lepiej, gdy potrzebujesz danych szybko, nie chcesz utrzymywać kodu albo nie jesteś developerem. Wybierz narzędzie dopasowane do sytuacji.

Python vs API vs no-code: którą metodę wybrać?

Oto pełna macierz decyzyjna:

KryteriumBeautifulSoup (HTML)Firebase APIAlgolia APIThunderbit (bez kodu)
Wymagane umiejętności technicznePython średniozaawansowanyPython dla początkującychPython dla początkującychBrak
Czas konfiguracji10–15 min5–10 min5–10 min2 min
Koszt utrzymaniaŚredni (selektory się psują)Niski (stabilny JSON)Niski (stabilny JSON)Brak
Głębokość danychTylko strona głównaDowolny element, użytkownicyWyszukiwanie + historiaStrona główna + podstrony
KomentarzeTrudneŁatwe (rekurencyjnie)Łatwe (zagnieżdżone drzewo)Scrapowanie podstron
Dane historyczneNieNieTak (pełne archiwum)Nie
Opcje eksportuSamodzielnie w kodzieSamodzielnie w kodzieSamodzielnie w kodzieWbudowane (Excel, Sheets itd.)
Harmonogramcron / GitHub Actionscron / GitHub Actionscron / GitHub ActionsWbudowany scheduler
Najlepsze doNauki scrapowaniaNiezawodnych pipeline’ówResearchu i analizySzybkiego pobierania danych

Jeśli uczysz się Pythona albo budujesz coś własnego, wybierz metodę 1 lub 2. Jeśli potrzebujesz analizy historycznej, dodaj API Algolia. A jeśli po prostu chcesz dostać dane bez kodu, wypróbuj Thunderbit.

Wypróbuj Thunderbit do scrapowania Hacker News

Podsumowanie i najważniejsze wnioski

Teraz masz w arsenale:

  • Dwie kompletne metody w Pythonie do scrapowania Hacker News — BeautifulSoup do parsowania HTML i Firebase API do czystych danych JSON
  • Techniki paginacji do wyjścia poza pierwszą stronę, w tym API Algolia do danych historycznych sięgających 2007 roku
  • Kod eksportu do CSV, Excela i Google Sheets — bo dane w terminalu nie pomagają nikomu poza Tobą
  • Wzorce produkcyjne — retry logic, cache, rozdzielenie crawl/parsing i automatyzacja według harmonogramu przez cron lub GitHub Actions
  • Alternatywę bez kodu na momenty, gdy Python jest bardziej narzędziem niż potrzebujesz

Moja rekomendacja: zacznij od Firebase API (Metoda 2) w większości zastosowań. Jest czyściej, bardziej niezawodnie i daje dostęp do komentarzy bez bólu parsowania zagnieżdżonego HTML. API Algolia dołóż wtedy, gdy potrzebujesz danych historycznych. A Thunderbit trzymaj w zakładkach na momenty, gdy potrzebujesz po prostu szybkiego arkusza i nie chcesz uruchamiać całego projektu Python.

Jeśli chcesz pójść głębiej, spróbuj scrapować komentarze HN do analizy sentymentu, zbuduj codzienny pipeline digestu z GitHub Actions albo przejrzyj API Algolia, by zobaczyć, jak zmieniały się trendy technologiczne w ostatniej dekadzie.

Wypróbuj Thunderbit do szybkiego scrapowania Hacker News Get Started Free

FAQ

Czy scrapowanie Hacker News jest legalne?

Dane HN są publicznie dostępne, a Y Combinator udostępnia oficjalne API do programowego dostępu. Plik robots.txt serwisu zezwala na scrapowanie treści tylko do odczytu (strona główna, strony elementów, strony użytkowników), ale prosi o 30-sekundowe opóźnienie crawl. Przestrzegaj tej przerwy, nie scrapuj interaktywnych endpointów (głosowanie, logowanie) i jesteś po bezpiecznej stronie. Więcej o etyce scrapowania przeczytasz w naszym poradniku o prawnych aspektach web scrapingu.

Czy Hacker News ma oficjalne API?

Tak. HN Firebase API pod adresem hacker-news.firebaseio.com/v0/ jest darmowe, nie wymaga uwierzytelniania i zapewnia dostęp do historii, komentarzy, profili użytkowników oraz wszystkich typów feedów (top, new, best, ask, show, jobs). Zwraca czysty JSON i nie ma oficjalnie podanego limitu, choć uprzejme tempo wysyłania żądań jest zawsze wskazane.

Jak scrapować komentarze z Hacker News w Pythonie?

Korzystając z API Firebase, pobierz element historii, aby dostać pole kids (tablicę ID komentarzy najwyższego poziomu). Każdy komentarz jest osobnym elementem z własnym kids dla odpowiedzi. Przechodź po drzewie rekurencyjnie funkcją, która pobiera każdy komentarz i jego dzieci. Pełny kod znajdziesz w sekcji „Scrapowanie komentarzy (rekurencyjne przejście po drzewie)” powyżej. Alternatywnie endpoint /items/<id> w API Algolia zwraca pełne zagnieżdżone drzewo komentarzy w jednym żądaniu — znacznie szybciej przy wątkach z dużą liczbą komentarzy.

Czy mogę scrapować Hacker News bez pisania kodu?

Tak. AI web scraper Thunderbit działa jako rozszerzenie Chrome — otwierasz HN, klikasz „AI Suggest Fields”, a narzędzie automatycznie rozpoznaje kolumny takie jak tytuł, URL, score i autor. Klikasz „Scrape” i eksportujesz dane bezpośrednio do Excela, Google Sheets, Airtable lub Notion. Obsługuje paginację i potrafi też odwiedzać podstrony, aby pobrać komentarze. Bez Pythona, bez selektorów, bez utrzymania.

Jak zdobyć historyczne dane z Hacker News?

Najlepszym narzędziem do tego jest HN Algolia Search API. Użyj endpointu search_by_date z numericFilters=created_at_i>TIMESTAMP, aby filtrować po zakresie dat. Możesz wyszukiwać po słowach kluczowych, filtrować po typie historii i przechodzić przez nawet 500 stron wyników. Do analizy historycznej na dużą skalę dostępne są też publiczne zbiory danych w Google BigQuery (pełne archiwum), ClickHouse (28 milionów rekordów) oraz Hugging Face (4 miliony historii).

Dowiedz się więcej

Shuai Guan
Shuai Guan
CEO w Thunderbit | Ekspert ds. automatyzacji danych AI Shuai Guan jest CEO Thunderbit oraz absolwentem wydziału inżynierii University of Michigan. Czerpiąc z niemal dekady doświadczenia w branży technologicznej i architekturze SaaS, specjalizuje się w przekuwaniu złożonych modeli AI w praktyczne, niewymagające kodowania narzędzia do استخراج danych. Na tym blogu dzieli się szczerymi, sprawdzonymi w boju spostrzeżeniami na temat web scrapingu i strategii automatyzacji, które pomogą Ci tworzyć mądrzejsze, oparte na danych procesy pracy. Gdy nie optymalizuje przepływów danych, z tą samą dbałością o szczegóły oddaje się swojej pasji do fotografii.
Spis treści
Thunderbit · Agent AI do danych z sieci

Wyodrębnij dane z dowolnej strony w 1 klik

Zaufało nam ponad 250 000 użytkowników
dostępny darmowy plan
Od strony WWW do arkusza
Opisz, czego potrzebujesz — agent AI Thunderbit to zeskrapuje i wyeksportuje do Excel, Google Sheets, Airtable lub Notion. Start jest darmowy.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week