Muutama kuukausi sitten halusin rakentaa tiimillemme Thunderbitillä päivittäisen koonnin Hacker Newsin kiinnostavimmista jutuista. Ensimmäinen ajatukseni oli vain tallentaa sivu kirjanmerkkeihin ja käydä läpi jutut joka aamu. Se toimi noin kolme päivää, kunnes tajusin käyttäväni 20 minuuttia päivässä pelkkien otsikoiden lukemiseen ja linkkien kopioimiseen taulukkoon.
Hacker News on yksi internetin rikkaimmista ja tiheimmin pakatuista teknologiasignaalien lähteistä — noin 13 miljoonaa käyntiä kuukaudessa, suunnilleen 1 300 uutta juttua päivässä ja noin 13 000 kommenttia vuorokaudessa. Seuraatpa nousevia teknologiatrendejä, valvot brändiäsi, rakennat rekrytointiputkea "Who's Hiring" -ketjuista tai haluat vain pysyä kärryillä siitä, mistä kehittäjämaailma puhuu, kaiken tämän seuraaminen käsin on häviävä peli.
Hyvä uutinen: Hacker Newsin poimiminen Pythonilla on yllättävän suoraviivaista. Tässä oppaassa käyn läpi kaksi valmista tapaa — HTML-poiminta BeautifulSoupilla sekä virallinen HN Firebase API — ja lisäksi kerron sivutuksesta, datan viennistä, tuotantokelpoisista käytännöistä sekä koodittomasta oikotiestä tilanteisiin, joissa Python tuntuu ylimitoitetulta.
Miksi poimia Hacker Newsia Pythonilla?
Hacker News ei ole vain yksi linkkikokoelma muiden joukossa. Se on kuratoitu, yhteisölähtöinen virta, jossa kiinnostavimmat teknologiatarinat nousevat kärkeen äänien ja aktiivisen keskustelun ansiosta. Yleisö painottuu vahvasti teknologia-alan ammattilaisiin (noin 76 % miehiä, pääikäryhmä 25–34), ja sivuston 66 % suoran liikenteen osuus kertoo uskollisesta, tottuneesta lukijakunnasta — ei satunnaisista kävijöistä.
Tässä syitä, miksi HN-dataa poimitaan:
| Käyttötapaus | Mitä saat |
|---|---|
| Päivittäinen teknologiadigest | Kärkiuutiset, pisteet ja linkit sähköpostiin tai Slackiin |
| Brändin/ kilpailijan seuranta | Hälytykset, kun yrityksesi tai tuotteesi mainitaan |
| Trendianalyysi | Seuraa, mitkä teknologiat, kielet tai aiheet keräävät vetoa ajan myötä |
| Rekrytointi | Jäsennä "Who's Hiring" -ketjut työpaikkailmoituksiksi, teknologiapinoiksi ja palkkasignaaleiksi |
| Sisältötutkimus | Löydä aiheita, joista kannattaa kirjoittaa tai jakaa sisältöä |
| Sentimenttianalyysi | Arvioi yhteisön mielipidettä tuotteista, julkaisuista tai alan muutoksista |
Yli 400 miljardin dollarin arvoiset yhtiöt yhteensä — Stripe, Dropbox, Airbnb — ovat saaneet Hacker Newsista ratkaisevaa varhaista palautetta ja käyttäjiä. Drew Houston julkaisi Dropboxin demon HN:ssä huhtikuussa 2007, se nousi ykköseksi, ja beta-jono paisui 5 000 käyttäjästä 75 000 käyttäjään yhdessä päivässä. HN-data ei ole vain kiinnostavaa — se on kaupallisesti arvokasta.
Data on julkisesti saatavilla, mutta sivuston rakenne tekee manuaalisesta keräämisestä työlästä. Python-automaatio on käytännöllinen ratkaisu.
Kaksi tapaa poimia Hacker News Pythonilla: yleiskatsaus
Tämä opas kattaa kaksi täysin toimivaa lähestymistapaa:
- HTML-poiminta
requests+ BeautifulSoupilla — hae news.ycombinator.com-sivun raakaa HTML:ää ja pura siitä juttudata. Hyvä tapa oppia poiminnan perusteet ja napata juuri ne tiedot, jotka sivulla näkyvät. - Virallinen Hacker News Firebase API — hae JSON-päätteistä suoraan, ilman HTML:n purkua. Parempi luotettaviin datavirtoihin, kommentteihin ja historiatietoihin.
Tässä rinnakkainen vertailu, joka auttaa valitsemaan sopivimman tavan:
| Kriteeri | HTML-poiminta (requests + BS4) | HN Firebase API | Thunderbit (kooditon) |
|---|---|---|---|
| Käyttöönoton vaikeus | Keskitaso (HTML-valitsimet) | Matala (JSON-päätteet) | Ei mitään (2 klikkausta Chrome-laajennuksella) |
| Datan ajantasaisuus | Etusivun reaaliaikainen data | Reaaliaikainen (mikä tahansa kohde ID:n perusteella) | Reaaliaikainen |
| Rajoitusriski | Keskitaso (robots.txt ilmoittaa 30 s crawl-delayn) | Matala (virallinen, salliva) | Thunderbitin hallinnoima |
| Kommenttien haku | Vaikea (sisäkkäinen HTML) | Helppo (rekursiiviset item ID:t) | Alisivujen poimintatoiminto |
| Historiadata | Rajoitettu | Algolia Search API:n kautta | Ei sovellu |
| Paras käyttötapa | Poiminnan perusteiden opettelu | Luotettavat datavirrat | Ei-kehittäjät, nopeat viennit |
Molemmat menetelmät sisältävät täydellisen, ajettavan Python-koodin. Ja jos haluat datan ilman että kirjoitat koodia lainkaan, käsittelen senkin.
Ennen kuin aloitat
- Vaikeustaso: aloittelijasta keskitason käyttäjään
- Aikaa kuluu: noin 15–20 minuuttia per menetelmä
- Tarvitset:
- Python 3.11+ asennettuna
- Pääte tai koodieditori
- Chrome-selain (jos haluat tarkistaa HN:n HTML:n tai kokeilla kooditonta vaihtoehtoa)
- Thunderbit Chrome -laajennus (valinnainen, kooditonta menetelmää varten)

Python-ympäristön valmistelu
Ennen kuin koskemme HN-dataan, valmistellaan ympäristö. Suosittelen virtuaaliympäristön luomista, jotta projektin riippuvuudet pysyvät siisteinä.
# Luo ja aktivoi virtuaaliympäristö
python3 -m venv hn-scraper
# macOS/Linux:
source hn-scraper/bin/activate
# Windows:
hn-scraper\Scripts\activate
# Asenna molemmissa menetelmissä tarvittavat paketit
pip install requests==2.33.1 beautifulsoup4==4.14.3 pandas==3.0.2 openpyxl==3.1.5
Tuotantokelpoisia käytäntöjä varten (välimuisti, uudelleenyritykset) kannattaa asentaa myös:
pip install requests-cache==1.3.1 tenacity==9.1.4
Ei erityisiä API-avaimia, ei tunnuksia. HN:n data on avointa.
Menetelmä 1: Hacker Newsin poiminta Pythonilla BeautifulSoupin avulla
Tämä on klassinen tapa — hae HTML, pura se ja poimi tarvitsemasi tiedot. Näin useimmat oppivat web-poiminnan, ja HN:n yksinkertainen taulukkopohjainen rakenne tekee siitä mainion harjoituskentän.
Vaihe 1: Hae Hacker Newsin etusivu
Avaa editori ja luo tiedosto nimeltä scrape_hn_bs4.py. Tässä aloituskoodi:
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}, Sivun pituus: {len(response.text)} merkkiä")
Aja se. Pitäisi näkyä Status: 200 ja sivun pituudeksi noin 40 000–50 000 merkkiä. Se on HN:n etusivun raaka HTML muistissa valmiina purettavaksi.
Vaihe 2: Ymmärrä HTML-rakenne
HN käyttää taulukkopohjaista rakennetta — ei modernia CSS gridia tai flexiä. Jokainen sivun juttu koostuu kahdesta keskeisestä <tr>-rivistä:
- Jutturivi (
<tr class="athing submission">): sisältää sijoituksen, otsikon ja linkin - Metatietorivi (seuraava
<tr>): sisältää pisteet, kirjoittajan, ajan ja kommenttien määrän
Tärkeät valitsimet:
span.titleline > a— jutun otsikko ja URLspan.score— äänimäärä (esim. "118 points")a.hnuser— kirjoittajan käyttäjänimispan.age— julkaisuaika.subtext-elementin viimeinen<a>, jossa tekstissä on "comment" — kommenttien määrä
Jos klikkaat Chrome-selaimessa minkä tahansa jutun otsikkoa oikealla ja valitset "Inspect", näet jotain tällaista:
<span class="titleline">
<a href="https://darkbloom.dev">Darkbloom – Private inference on idle Macs</a>
</span>
Ja sen alla metatietorivin:
<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>
Näiden valitsimien ymmärtäminen on tärkeää — jos HN joskus muuttaa merkintätapaansa, valitsimet pitää päivittää. (Spoileri: API-menetelmä kiertää tämän ongelman kokonaan.)
Vaihe 3: Poimi otsikot, linkit ja pisteet
Nyt varsinaiseen työhön. Käymme läpi jokaisen jutturivin, poimimme otsikon ja linkin jutturiviltä ja sitten metatietoriviltä sen alta pisteet.
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:
# Otsikko ja URL jutturiviltä
title_tag = row.select_one("span.titleline > a")
if not title_tag:
continue
title = title_tag.get_text()
link = title_tag.get("href", "")
# Metatiedot seuraavalta riviltä
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()
# Kommenttien määrä: viimeinen <a>, jossa tekstissä on "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,
})
# Suodata vähintään 50 pistettä saaneet jutut ja järjestä pisteiden mukaan
top_stories = sorted(
[s for s in stories if s["score"] >= 50],
key=lambda x: x["score"],
reverse=True,
)
pprint(top_stories[:10])
Muutama huomio koodista:
- Walrus-operaattori (
:=) toimii Python 3.8+:ssa. Sen avulla voit sekä asettaa että tarkistaa arvon yhdellä rivillä — hyödyllinen esimerkiksi valinnaisille elementeille kutenspan.score, joita ei ole jokaisella rivillä (esim. työpaikkajulkaisuissa ei ole pisteitä). - HN käyttää
\xa0:aa eli sitovaa välilyöntiä numeron ja "comments"-sanan välissä, joten jaamme merkkijonon sen kohdalta. - HN:n sisäisiin sivuihin osoittavissa jutuissa (kuten "Ask HN") URL voi olla suhteellinen, esimerkiksi
item?id=. Saatat haluta lisätä eteenhttps://news.ycombinator.com/.
Vaihe 4: Aja koodi ja katso tulokset
Tallenna ja aja:
python scrape_hn_bs4.py
Näet suunnilleen tällaista tulostetta:
[{'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/...'},
...]
Se on 30 juttua sivulta 1. HN:ssä on kuitenkin satoja aktiivisia juttuja millä tahansa hetkellä. Käsittelemme sivutuksen myöhemmin.
Menetelmä 2: Hacker Newsin poiminta Pythonilla virallisen API:n avulla
HN Firebase API on virallisesti hyväksytty tapa käyttää Hacker Newsin dataa. Ei tunnistautumista, ei API-avaimia, ei HTML:n purkua. Saat siistit JSON-vastaukset. Käytän tätä tapaa kaikkeen, minkä pitää toimia luotettavasti tuotannossa.
Tärkeimmät API-päätteet, jotka kannattaa tuntea
Perus-URL on https://hacker-news.firebaseio.com/v0/. Tässä olennaiset päätteet:
| Pääte | Palauttaa | Esimerkki |
|---|---|---|
/v0/topstories.json | Taulukon, jossa enintään 500 kärkiuutisen ID:tä | [47788542, 47787901, ...] |
/v0/newstories.json | Enintään 500 uusimman jutun ID:tä | Sama muoto |
/v0/beststories.json | Enintään 500 parhaan jutun ID:tä | Sama muoto |
/v0/askstories.json | Enintään 200 "Ask HN" -jutun ID:tä | Sama muoto |
/v0/showstories.json | Enintään 200 "Show HN" -jutun ID:tä | Sama muoto |
/v0/jobstories.json | Enintään 200 työpaikkajutun ID:tä | Sama muoto |
/v0/item/{id}.json | Täydelliset tiedot mistä tahansa kohteesta (juttu, kommentti, äänestys) | JSON-objekti |
/v0/user/{username}.json | Käyttäjäprofiili | JSON-objekti |
/v0/maxitem.json | Nykyinen suurin item-ID | Kokonaisluku (esim. 47789427) |
Juttuobjekti näyttää tältä:
{
"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"
}
Kenttä kids sisältää suorien alakommenttien ID:t. Jokainen kommentti on oma iteminsä, jolla voi olla omia kids-alaisiaan — näin kommenttipuu rakentuu.
Vaihe 1: Hae kärkiuutisten ID:t
Luo tiedosto nimeltä scrape_hn_api.py:
import requests
import time
from pprint import pprint
API_BASE = "https://hacker-news.firebaseio.com/v0"
# Hae kärkiuutisten ID:t
response = requests.get(f"{API_BASE}/topstories.json")
story_ids = response.json()
print(f"Sain {len(story_ids)} kärkiuutis-ID:tä")
# Tulos: Sain 500 kärkiuutis-ID:tä
500 jutun ID:tä yhdellä pyynnöllä — ei parsintaa, ei valitsimia, vain JSON-taulukko.
Vaihe 2: Hae jutun tiedot ID:n perusteella
Nyt tarvitsemme varsinaisen jutun datan. Tässä tulee fan-out-ongelma: 500 juttua tarkoittaa 500 erillistä API-kutsua. Omissa testeissäni yksi item-pyyntö kestää noin 1,2 sekuntia peräkkäin tehtynä. 500 jutulla se on noin 10 minuuttia.
Useimmissa käyttötapauksissa et tarvitse kaikkia 500:tä. Tässä koodi, joka hakee 30 kärkipäivää:
def fetch_story(story_id):
"""Hae yhden jutun tiedot HN API:sta."""
resp = requests.get(f"{API_BASE}/item/{story_id}.json")
return resp.json()
# Hae 30 kärkiuutisen tiedot
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) # Ole kohtelias — pieni viive pyyntöjen väliin
# Järjestä pisteiden mukaan, näytä 10 parasta
top = sorted(stories, key=lambda x: x["score"], reverse=True)[:10]
pprint(top)
time.sleep(0.1) lisää pienen kohteliaisuusviiveen. Firebase API:lla ei ole ilmoitettua rajoitusta, mutta minkä tahansa API:n hakkaaminen ilman taukoja on huono tapa.
Vaihe 3: Poimi kommentit (rekursiivinen puukävely)
Tässä API todella loistaa verrattuna HTML-poimintaan. HN:n kommentit voivat olla syvästi sisäkkäisiä — vastauksia vastauksiin vastauksiin. HTML:ssä tämä tarkoittaa monimutkaisten sisäkkäisten taulukoiden purkamista. API:ssa taas jokaisen kommentin kids kertoo sen alaelementit, ja puuta voi kulkea rekursiivisesti.
def fetch_comments(item_id, depth=0, max_depth=3):
"""Hae kommentit rekursiivisesti enintään max_depth-tasoon asti."""
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
# Esimerkki: hae kommentit kärkipostaukselle
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"\nKommentit jutulle: {top_story['title']}")
all_comments = []
for kid_id in top_story_full["kids"][:5]: # Ensimmäiset 5 ylintä kommenttia
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 "[ei tekstiä]"
print(f"{indent}[{c['author']}] {preview}...")
Tämä rekursiivinen lähestymistapa on huomattavasti helpompi kuin yrittää purkaa sisäkkäisiä HTML-kommenttiketjuja. Jos tarvitset täydet kommenttipuut, API on oikea tie.
Vaihe 4: Aja ja tarkastele tuloksia
python scrape_hn_api.py
Näet jäsenneltyä juttudataa ja sen jälkeen sisennetyn kommenttien esikatselun. Data on siistimpää, kommenttien haku on suoraviivaista eikä ole riskiä, että poimija hajoaa siksi, että HN vaihtaa CSS-luokan nimeä.
Etusivua pidemmälle: sivutus ja historiadata
Useimmat HN-poimintaoppaat jäävät sivulle 1 — 30 juttuun. Se riittää pikademoon, mutta oikeissa käyttötapauksissa tarvitaan usein enemmän syvyyttä.
Useamman sivun poiminta BeautifulSoupilla
HN:n sivutus käyttää yksinkertaista URL-mallia: ?p=2, ?p=3 jne. Jokaisella sivulla on 30 juttua, ja sivusto näyttää noin sivulle 20 asti (yhteensä noin 600 juttua). Sen jälkeen sivut tyhjenevät.
import time
def scrape_hn_pages(num_pages=5):
"""Poimi useita HN:n etusivun sivuja."""
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"Sivu {page}: ei juttuja löytynyt, lopetetaan.")
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"Sivu {page}: poimittiin {len(story_rows)} juttua")
# Kunnioita robots.txt:n 30 sekunnin crawl-delayta
if page < num_pages:
time.sleep(30)
return all_stories
stories = scrape_hn_pages(5)
print(f"\nPoimittuja juttuja yhteensä: {len(stories)}")
time.sleep(30) on tärkeä. HN:n robots.txt pyytää nimenomaisesti 30 sekunnin crawl-delayta. Jos jätät sen huomiotta, sinut voidaan rajoittaa (HTTP 429) tai estää tilapäisesti. Viisi sivua 30 sekunnin välein vie noin 2,5 minuuttia — ei välitön, mutta kohtelias.
Niille, jotka eivät halua hallita sivutuskoodia, Thunderbit hoitaa klikkauspohjaisen ja äärettömän vierityksen sivutuksen automaattisesti. Se klikkaa HN-sivujen alalaidan "More"-painiketta ilman mitään asetuksia.
Poimi Hacker News -sivuja AI:lla
Historiallisen Hacker News -datan hakeminen Algolia API:lla
Firebase API antaa nykyisen datan. Historiallista analyysiä varten — "Mitkä olivat vuoden 2023 parhaat Python-jutut?" tai "Miten AI-keskustelu on muuttunut viimeisen 5 vuoden aikana?" — tarvitset HN Algolia Search API:n.
import requests
ALGOLIA_BASE = "https://hn.algolia.com/api/v1"
def search_hn(query, tags="story", page=0, hits_per_page=20):
"""Hae HN-dataa Algolia API:n kautta."""
params = {
"query": query,
"tags": tags,
"page": page,
"hitsPerPage": hits_per_page,
}
resp = requests.get(f"{ALGOLIA_BASE}/search", params=params)
return resp.json()
# Esimerkki: löydä Python-poimintaan liittyviä juttuja, joilla on vähintään 10 pistettä, tammikuusta 2024 alkaen
results = search_hn(
query="python scraping",
tags="story",
)
print(f"Löytyi yhteensä {results['nbHits']} tulosta")
for hit in results["hits"][:5]:
print(f" [{hit.get('points', 0)} pts] {hit['title']}")
Päivämäärärajoituksilla käytä numericFilters-parametria:
import calendar, datetime
# Jutut 1. tammikuuta 2024 alkaen
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"Löytyi {data['nbHits']} Python-web-poimintaan liittyvää juttua vuodesta 2024 lähtien ja yli 10 pisteellä")
Algolia API on nopea (5–9 ms palvelinprosessointi), ei vaadi API-avainta ja tukee sivutusta jopa 500 sivuun asti. Suurten historiatietoanalyysien kannalta se on paras saatavilla oleva vaihtoehto.
Poimitun Hacker News -datan vienti CSV:ksi, Exceliin ja Google Sheetsiin
Jokainen näkemäni HN-poimintaopas päättyy pprint()-tulosteeseen terminaalissa. Se on hyvä debuggaamiseen, mutta jos rakennat päivittäistä koontia tai teet trendianalyysiä, data pitää saada tiedostoon. Näin se onnistuu.
Vie CSV:ksi Pythonilla
import csv
def export_to_csv(stories, filename="hn_stories.csv"):
"""Tallenna poimitut jutut CSV-tiedostoon."""
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"Tallennettu {len(stories)} juttua tiedostoon {filename}")
export_to_csv(stories)
Vie Exceliin Pythonilla
import pandas as pd
def export_to_excel(stories, filename="hn_stories.xlsx"):
"""Tallenna poimitut jutut Excel-tiedostoon."""
df = pd.DataFrame(stories)
df.to_excel(filename, index=False, engine="openpyxl")
print(f"Tallennettu {len(stories)} juttua tiedostoon {filename}")
export_to_excel(stories)
Varmista, että openpyxl on asennettuna — pandas käyttää sitä Excel-moottorina. Jos se puuttuu, saat ImportError-virheen.
Vie Google Sheetsiin (valinnainen)
Automaattisissa työnkuluissa voit haluta puskea datan suoraan Google Sheetsiin gspread-kirjaston avulla. Tämä vaatii Google Cloud -palvelutilin asetuksen (kertaluontoinen prosessi):
import gspread
gc = gspread.service_account(filename="service_account.json")
sh = gc.open("HN Daily Digest")
worksheet = sh.sheet1
# Muunna jutut riveiksi
header = list(stories[0].keys())
rows = [list(s.values()) for s in stories]
worksheet.clear()
worksheet.update([header] + rows)
print("Puskettiin Google Sheetsiin")
Kooditon vientivaihtoehto
Jos palvelutilien asetukset ja vientikoodin kirjoittaminen tuntuvat työläämmiltä kuin varsinainen poiminta, ymmärrän täysin. Thunderbitillä olemme rakentaneet ilmaisen dataviennin, jolla voit lähettää poimitun datan suoraan Exceliin, Google Sheetsiin, Airtableen tai Notioniin — ilman koodia, ilman tunnuksia, ilman ylläpidettävää putkea. Yksittäisessä tietojen haussa se on aidosti nopeampi tapa. Lisää siitä alempana.
Tee poimijastasi tuotantokelpoinen: virheenkäsittely, välimuisti ja ajastus
Jos ajat poimijaa kerran huvin vuoksi, yllä oleva koodi riittää. Jos ajat sitä päivittäin osana työnkulkua, tarvitset muutaman lisäpalikan.
Virheenkäsittely ja uudelleenyritykset
Verkot pettävät. Palvelimet hidastavat. Yksi huono pyyntö ei saisi kaataa koko poimintaa. Tässä uudelleenyritysfunktio eksponentiaalisella odotusajalla:
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):
"""Hae URL automaattisilla uudelleenyrityksillä ja eksponentiaalisella backoffilla."""
response = requests.get(url, timeout=10)
response.raise_for_status()
return response
# Käyttö:
try:
resp = fetch_with_retry("https://hacker-news.firebaseio.com/v0/topstories.json")
story_ids = resp.json()
except Exception as e:
print(f"Epäonnistui uudelleenyritysten jälkeen: {e}")
tenacity-kirjasto hoitaa uudelleenyritykset siististi. Se yrittää enintään 5 kertaa jitteröidyllä eksponentiaalisella odotuksella — alkaen 1 sekunnista ja nousten enintään 60 sekuntiin. Tämä käsittelee sulavasti HTTP 429 -tilanteet (rajoitettu), 503-virheet (palvelu ei käytettävissä) ja hetkelliset verkkovirheet.
Vastausten välimuistiin tallentaminen uudelleenpoiminnan välttämiseksi
Kehitysvaiheessa ajat poimijaa monta kertaa samalla kun säädät purkulogiiikkaa. Ilman välimuistia jokainen ajo osuu uudelleen HN:n palvelimiin samoilla tiedoilla. requests-cache korjaa tämän kahdella rivillä:
import requests_cache
requests_cache.install_cache("hn_cache", expire_after=3600) # Välimuisti 1 tunniksi
Kun lisäät nämä rivit skriptin alkuun, kaikki requests.get()-kutsut tallennetaan automaattisesti paikalliseen SQLite-tietokantaan. Aja skripti uudelleen 10 kertaa tunnin aikana, ja vain ensimmäinen ajo käy oikeasti verkossa. Tämä on työkalu, jota foorumien käyttäjät suosittelevat usein, syystäkin.
Poiminnan ja parsinnan erottaminen toisistaan
Kokeneet poimijat vannovat yhden tavan nimeen: lataa raaka data ensin, pura se vasta sitten. Näin jos parsintalogiiikassa on virhe, voit korjata sen ja parsia uudelleen ilman uutta hakua.
import os, json
def crawl_and_save(story_ids, output_dir="raw_data"):
"""Hae jutun data ja tallenna raaka JSON levylle."""
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 # Ohita jo haetut kohteet
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"):
"""Parsi tallennetut JSON-tiedostot juttulistaksi."""
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
Tämä kaksivaiheinen malli on erityisen hyödyllinen, kun poimit satoja kohteita ja haluat kehittää käsittelyä nopeasti.
Poimijan automaatio aikataululla
Päivittäistä HN-koontia varten poimijan pitää ajaa automaattisesti. Kaksi yleistä vaihtoehtoa:
Vaihtoehto 1: cron (Linux/Mac)
# Aja joka päivä klo 8.30 UTC
30 8 * * * /usr/bin/python3 /home/user/scrape_hn.py >> /home/user/scrape.log 2>&1
Vaihtoehto 2: GitHub Actions (ilmainen, ei omaa palvelinta)
name: Poimi Hacker News
on:
schedule:
- cron: '30 8 * * *' # Päivittäin klo 8.30 UTC
workflow_dispatch: # Manuaalinen käynnistysnappi
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 "Päivitä HN-data $(date -u +%Y-%m-%dT%H:%M:%SZ)"
git push
Muutama huomio GitHub Actions -ajastuksista: kaikki cron-ajat ovat UTC-aikaa, 15–60 minuutin viiveet ovat tavallisia (käytä :30-tyyppisiä minuutteja mieluummin kuin :00), ja GitHub voi poistaa käytöstä ajoitetut workflow't repoilta, jotka ovat olleet 60 päivää ilman toimintaa. Lisää aina workflow_dispatch, jotta voit käynnistää työnkulun käsin testaukseen.
Yksinkertaisempana vaihtoehtona Thunderbitin Scheduled Scraper -toiminnolla voit kuvata aikataulun luonnollisella kielellä — esimerkiksi "poimi joka aamu klo 8" — ilman palvelinta tai cron-asetuksia.
Kun Python on liikaa: kooditon tapa poimia Hacker News
Sanon tämän suoraan, vaikka olen Pythonin ystävä ja tiimini rakentaa kehittäjätyökaluja. Jos tarvitset vain tämän päivän 100 kärki-HN-juttua taulukkoon — heti, kertaluonteisesti — Python-skriptin kirjoittaminen, debuggaus ja ajaminen on turhaa ylipainoa. Pelkkä käyttöönotto (virtuaaliympäristö, paketit, valitsimien selvittely) vie enemmän aikaa kuin itse datan keruu.
Tähän Thunderbit sopii. Työnkulku on tämä:
- Avaa
news.ycombinator.comChromessa - Klikkaa Thunderbit-laajennuksen kuvaketta ja valitse sitten "AI Suggest Fields"
- AI lukee sivun ja ehdottaa sarakkeet: Title, URL, Score, Author, Comment Count, Time Posted
- Säädä kenttiä halutessasi (nimeä uudelleen, poista tai lisää omia — voit jopa lisätä AI-kehotteen kuten "Luokittele AI/DevTools/Web/Muu")
- Klikkaa "Scrape" — data ilmestyy jäsenneltyyn taulukkoon
- Vie Exceliin, Google Sheetsiin, Airtableen tai Notioniin
Kaksi klikkausta jäsenneltyyn dataan. Ei valitsimia, ei koodia, ei ylläpitoa.
Yksi iso etu tässä: Thunderbitin AI mukautuu asettelumuutoksiin automaattisesti. Perinteiset CSS-valitsimiin nojaavat poimijat hajoavat, kun sivun merkintä muuttuu — ja vaikka HN:n HTML on melko vakaa, sitä on muutettu (esimerkiksi class="athing submission" päivitettiin ja span.titleline korvasi vanhemman a.storylink-rakenteen). AI-pohjainen poimija lukee sivun aina tuoreena, joten luokkien nimet eivät haittaa.

Thunderbit hoitaa myös sivutuksen (klikkaa HN:n "More"-painiketta automaattisesti) sekä alisivujen poiminnan (käy jokaisen jutun kommenttisivulla hakemassa keskusteludatan). Kommenttien rikastamisen käyttötapauksessa tämä vastaa menetelmän 2 rekursiivista API-koodia — mutta ilman yhden yhtäkään kirjoitettua riviä.
Valinta on lopulta selkeä: Python on oikea ratkaisu, kun tarvitset omaa logiikkaa, monimutkaisia muunnoksia, ajastettuja automaatioputkia tai olet oppimassa koodaamista. Thunderbit on oikea ratkaisu, kun tarvitset dataa nopeasti, et halua ylläpitää koodia tai et ole kehittäjä. Valitse tilanteeseesi sopiva työkalu.
Python vs. API vs. kooditon: mikä menetelmä kannattaa valita?
Tässä koko päätösmalli:
| Kriteeri | BeautifulSoup (HTML) | Firebase API | Algolia API | Thunderbit (kooditon) |
|---|---|---|---|---|
| Tarvittava tekninen taito | Pythonin keskitaso | Pythonin alkeistaso | Pythonin alkeistaso | Ei mitään |
| Käyttöönottoaika | 10–15 min | 5–10 min | 5–10 min | 2 min |
| Ylläpitotaakka | Keskitaso (valitsimet voivat hajota) | Matala (vakaa JSON) | Matala (vakaa JSON) | Ei mitään |
| Datan syvyys | Vain etusivu | Mikä tahansa kohde, käyttäjät | Haku + historia | Etusivu + alisivut |
| Kommentit | Vaikea | Helppo (rekursiivinen) | Helppo (sisäkkäinen puu) | Alisivujen poiminta |
| Historiadata | Ei | Ei | Kyllä (täysi arkisto) | Ei |
| Vientivaihtoehdot | Koodaa itse | Koodaa itse | Koodaa itse | Sisäänrakennettu (Excel, Sheets jne.) |
| Ajastus | cron / GitHub Actions | cron / GitHub Actions | cron / GitHub Actions | Sisäänrakennettu ajastin |
| Paras käytössä | Poiminnan opetteluun | Luotettaviin putkiin | Tutkimukseen ja analyysiin | Nopeat datanhaut |
Jos opettelet Pythonia tai rakennat jotain räätälöityä, valitse menetelmä 1 tai 2. Jos tarvitset historiatietoa, lisää Algolia API. Jos haluat vain datan ilman koodia, kokeile Thunderbitia.
Kokeile Thunderbitia Hacker Newsin poimintaan
Yhteenveto ja tärkeimmät opit
Nyt työkalupakissasi on:
- Kaksi täydellistä Python-menetelmää Hacker Newsin poimintaan — BeautifulSoup HTML:n purkuun ja Firebase API siistiin JSON-dataan
- Sivutustekniikat etusivua pidemmälle, mukaan lukien Algolia API historiatietoon aina vuoteen 2007 asti
- Vientikoodi CSV:lle, Excelille ja Google Sheetsille — koska data terminaalissa ei auta muuta tiimiä
- Tuotantokelpoiset käytännöt — uudelleenyritykset, välimuisti, poiminnan ja parsinnan erottaminen sekä ajastettu automaatio cronilla tai GitHub Actionsilla
- Kooditon vaihtoehto tilanteisiin, joissa Python on enemmän työkalu kuin tarpeen
Suositukseni: aloita useimmissa tapauksissa Firebase API:lla (menetelmä 2). Se on siistimpi, luotettavampi ja antaa kommenttien haun ilman sisäkkäisen HTML:n aiheuttamaa vaivaa. Lisää Algolia API, kun tarvitset historiadataa. Ja pidä Thunderbit kirjanmerkkeissä niitä hetkiä varten, kun tarvitset vain nopean taulukon etkä halua käynnistää kokonaista Python-projektia.
Jos haluat mennä syvemmälle, kokeile poimia HN-kommentteja sentimenttianalyysiä varten, rakenna päivittäinen koontiputki GitHub Actionsilla tai tutki Algolia API:a ja seuraa, miten teknologiatrendit ovat muuttuneet viimeisen vuosikymmenen aikana.
Kokeile Thunderbitia nopeaan Hacker News -poimintaan Get Started Free
Usein kysytyt kysymykset
Onko Hacker Newsin poimiminen laillista?
HN:n data on julkisesti saatavilla, ja Y Combinator tarjoaa virallisen API:n nimenomaan ohjelmallista käyttöä varten. Sivuston robots.txt sallii vain lukuoikeudellisen sisällön poiminnan (etusivu, item-sivut, käyttäjäsivut), mutta pyytää 30 sekunnin crawl-delayta. Kun kunnioitat viivettä etkä poimi vuorovaikutteisia päätepisteitä (äänestys, kirjautuminen), olet vahvoilla. Lisää web-poiminnan etiikasta oppaassamme web scrapingin juridiset vaikutukset.
Onko Hacker Newsilla virallinen API?
Kyllä. HN Firebase API osoitteessa hacker-news.firebaseio.com/v0/ on ilmainen, ei vaadi tunnistautumista ja tarjoaa pääsyn juttuihin, kommentteihin, käyttäjäprofiileihin ja kaikkiin syöttyyppeihin (top, new, best, ask, show, jobs). Se palauttaa siistiä JSONia eikä ilmoita varsinaista rate limit -rajaa, vaikka kohtelias pyyntövauhti on aina suositeltavaa.
Miten poimin Hacker News -kommentit Pythonilla?
Firebase API:lla haet jutun itemin ja saat sen kids-kentän (taulukko ylimmän tason kommentti-ID:itä). Jokainen kommentti on myös item, jolla on oma kids-kenttänsä vastauksia varten. Kävele puu läpi rekursiivisella funktiolla, joka hakee jokaisen kommentin ja sen alikommentit. Katso täydellinen koodi yllä kohdasta "Poimi kommentit (rekursiivinen puukävely)". Vaihtoehtoisesti Algolia API:n /items/<id>-pääte palauttaa koko sisäkkäisen kommenttipuun yhdellä pyynnöllä — paljon nopeampi kommenttipainotteisille jutuille.
Voinko poimia Hacker Newsin ilman koodia?
Kyllä. Thunderbitin AI-web-poimija toimii Chrome-laajennuksena — avaa HN, klikkaa "AI Suggest Fields", ja se tunnistaa automaattisesti sarakkeet kuten otsikko, URL, pisteet ja kirjoittaja. Klikkaa "Scrape" ja vie data suoraan Exceliin, Google Sheetsiin, Airtableen tai Notioniin. Se hoitaa sivutuksen ja voi jopa käydä alisivuille hakemassa kommenttidataa. Ei Pythonia, ei valitsimia, ei ylläpitoa.
Miten saan historiallista Hacker News -dataa?
HN Algolia Search API on tähän paras työkalu. Käytä search_by_date-päätettä ja numericFilters=created_at_i>TIMESTAMP-suodatinta päivämäärävälin rajaamiseen. Voit hakea avainsanalla, suodattaa juttutyyppien mukaan ja selata jopa 500 tulossivua. Suurten historiatietojen analysointiin julkisia aineistoja on saatavilla myös Google BigQueryssa (täysi arkisto), ClickHousessa (28 miljoonaa riviä) ja Hugging Facessa (4 miljoonaa juttua).
Lue lisää


