幾個月前,我想替 Thunderbit 團隊做一份每天更新的 Hacker News 熱門故事摘要。剛開始,我本能地想說:先把網站加入書籤,然後每天早上看一下就好。結果撐了大概三天,我就發現自己每天都要花 20 分鐘,只是在看標題,還得把連結一條一條複製到試算表裡。
Hacker News 是網路上最精煉、也最有價值的科技情報來源之一——每月大約有 1,300 萬次造訪,每天約有 1,300 則新故事被提交,並產生大約 13,000 則留言。無論你是想追蹤新興技術趨勢、監測品牌聲量、從「Who’s Hiring」討論串建立招募名單,還是單純想掌握開發者社群在關注什麼,光靠人工追蹤幾乎是不可能的任務。
好消息是:用 Python 抓取 Hacker News 其實比想像中簡單很多。這篇指南會帶你走過兩種完整方法——使用 BeautifulSoup 做 HTML 抓取,以及使用官方 HN Firebase API——另外也會涵蓋分頁、資料匯出、適合正式環境的做法,還有一個當你覺得 Python 有點大材小用時,可以直接上手的免寫程式替代方案。
為什麼要用 Python 抓取 Hacker News?
Hacker News 不只是另一個連結聚合站。它是一個由社群共同篩選的資訊流,最有意思的科技故事會透過投票與熱烈討論一路浮上檯面。使用者輪廓很明顯偏向科技專業人士(約 76% 為男性,主要年齡層為 25–34 歲),而網站有 66% 的直接流量,也說明這是一群忠誠、習慣回訪的讀者,而不是隨便路過看看的人。
以下是大家會去抓取 HN 資料的原因:
| 使用情境 | 你能拿到什麼 |
|---|---|
| 每日科技摘要 | 把熱門故事、分數與連結送到你的信箱或 Slack |
| 品牌/競品監測 | 當你的公司或產品被提及時即時提醒 |
| 趨勢分析 | 追蹤哪些技術、語言或主題正在累積熱度 |
| 招募 | 從「Who’s Hiring」討論串中整理職缺、技術棧與薪資訊號 |
| 內容研究 | 找出表現好的主題,作為寫作或分享的方向 |
| 情緒分析 | 判斷社群對產品、發表或產業變化的看法 |
像 Stripe、Dropbox、Airbnb 這些合計市值超過 4,000 億美元的公司,都曾把 Hacker News 視為早期回饋與使用者的重要來源。Drew Houston 曾在 2007 年 4 月把 Dropbox demo 發到 HN,結果直接衝上榜首,beta 等候名單在一天內從 5,000 人暴增到 75,000 人。HN 資料不只是有趣,還有很明確的商業價值。
這些資料雖然是公開的,但網站結構讓人工蒐集非常費時。用 Python 自動化,才是最實際的做法。
用 Python 抓取 Hacker News 的兩種方式:總覽
本指南會介紹兩種可以直接執行的完整方法:
- 使用
requests+ BeautifulSoup 做 HTML 抓取——抓取 news.ycombinator.com 的原始 HTML,解析後萃取故事資料。很適合學習抓取基礎,也適合精準取得頁面上顯示的內容。 - 使用官方 Hacker News Firebase API——直接呼叫 JSON 端點,不需要解析 HTML。這種方式更適合穩定的資料管線、留言抓取與歷史資料分析。
下面這張比較表能幫你判斷哪種方式更適合你的需求:
| 比較項目 | HTML 抓取(requests + BS4) | HN Firebase API | Thunderbit(免寫程式) |
|---|---|---|---|
| 設定難度 | 中等(需解析 HTML 選擇器) | 低(JSON 端點) | 幾乎沒有(2 步 Chrome 擴充功能) |
| 資料即時性 | 即時首頁資料 | 即時(可依 ID 取得任何項目) | 即時 |
| 速率限制風險 | 中等(robots.txt 要求 30 秒抓取延遲) | 低(官方、限制寬鬆) | 由 Thunderbit 管理 |
| 留言抓取 | 困難(巢狀 HTML) | 容易(遞迴項目 ID) | 透過子頁面抓取功能 |
| 歷史資料 | 有限 | 可透過 Algolia Search API | 不適用 |
| 最適合 | 學習抓取基礎 | 穩定的資料管線 | 非工程師、快速匯出 |
兩種方法都附有完整、可執行的 Python 程式碼。如果你只是想拿到資料,不想自己寫任何程式,這篇也會一起教你。
開始前的準備
- 難度: 初學到中級
- 所需時間: 每種方法約 15–20 分鐘
- 你需要準備:
- 已安裝 Python 3.11+
- 終端機或程式碼編輯器
- Chrome 瀏覽器(如果你想檢查 HN 的 HTML,或試試免寫程式方案)
- Thunderbit Chrome 擴充功能(可選,用於免寫程式方法)

設定 Python 環境
在碰 HN 資料之前,先把開發環境準備好。我建議建立虛擬環境,讓專案依賴保持乾淨。
# 建立並啟用虛擬環境
python3 -m venv hn-scraper
# macOS/Linux:
source hn-scraper/bin/activate
# Windows:
hn-scraper\Scripts\activate
# 安裝兩種方法都會用到的套件
pip install requests==2.33.1 beautifulsoup4==4.14.3 pandas==3.0.2 openpyxl==3.1.5
如果之後要做正式環境常見的處理(快取、重試),也建議一起安裝:
pip install requests-cache==1.3.1 tenacity==9.1.4
不需要特別的 API 金鑰,也不需要驗證權杖。HN 資料是公開的。
方法 1:用 BeautifulSoup 搭配 Python 抓取 Hacker News
這是最經典的做法——先抓 HTML,再解析並取出你要的資料。這也是大多數人學網頁爬蟲的起點,而 HN 的表格式結構非常簡單,很適合拿來練手。
步驟 1:抓取 Hacker News 首頁
打開編輯器,建立一個名為 scrape_hn_bs4.py 的檔案。先寫入以下程式:
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")
執行後,你應該會看到 Status: 200,以及大約 40,000–50,000 字元的頁面長度。這代表 HN 首頁的原始 HTML 已經載入到記憶體中,準備好讓你解析了。
步驟 2:理解 HTML 結構
HN 採用的是表格式排版——沒有現代 CSS grid 或 flexbox。每則故事都由兩個關鍵的 <tr> 列組成:
- 故事列(
<tr class="athing submission">):包含排名、標題與連結 - 資訊列(下一個
<tr>):包含分數、作者、時間與留言數
重要的選擇器如下:
span.titleline > a—— 故事標題與 URLspan.score—— 投票數(例如「118 points」)a.hnuser—— 作者帳號span.age—— 發文時間.subtext中最後一個包含「comment」字樣的<a>—— 留言數
如果你在 Chrome 上對任一故事標題按右鍵並選擇「Inspect(檢查)」,你會看到類似這樣的 HTML:
<span class="titleline">
<a href="https://darkbloom.dev">Darkbloom – Private inference on idle Macs</a>
</span>
下面的資訊列則可能像這樣:
<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>
理解這些選擇器非常重要——如果 HN 之後改了標記結構,你就需要更新它們。(小劇透:使用 API 的方法可以避開這個問題。)
步驟 3:擷取標題、連結與分數
現在進入重點。我們會逐一遍歷每個故事列,先從故事列中抓標題與連結,再從它下方的資訊列抓分數。
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:
# 從故事列取得標題與 URL
title_tag = row.select_one("span.titleline > a")
if not title_tag:
continue
title = title_tag.get_text()
link = title_tag.get("href", "")
# 從下一列取得資訊
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()
# 留言數:找出文字中含有 "comment" 的最後一個 <a>
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,
})
# 篩選出 50 分以上的故事,並依分數排序
top_stories = sorted(
[s for s in stories if s["score"] >= 50],
key=lambda x: x["score"],
reverse=True,
)
pprint(top_stories[:10])
幾個重點說明:
- 海象運算子(
:=)是 Python 3.8 以上才有的語法。它能讓你在同一行中完成指定與判斷,對像span.score這種不一定存在的元素特別方便,例如職缺貼文通常就沒有分數。 - HN 在「留言數」與數字之間使用
\xa0(不換行空白),因此要用這個字元來分割。 - 連到 HN 其他頁面的故事(例如「Ask HN」)會使用
item?id=開頭的相對網址。你可能會想在前面補上https://news.ycombinator.com/。
步驟 4:執行並查看結果
儲存後執行:
python scrape_hn_bs4.py
你應該會看到類似以下的輸出:
[{'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/...'},
...]
這就是第 1 頁的 30 則故事。不過 HN 在任何時間點其實都有數百則活躍故事。我們會在後面介紹分頁處理。
方法 2:使用官方 API 搭配 Python 抓取 Hacker News
HN Firebase API 是官方認可的 Hacker News 資料存取方式。沒有驗證、沒有 API 金鑰、也不需要解析 HTML。你會直接拿到乾淨的 JSON 回應。我在需要穩定跑在正式環境的情境下,通常都會用這個方法。
你需要知道的核心 API 端點
基礎網址是 https://hacker-news.firebaseio.com/v0/。以下是最重要的端點:
| 端點 | 回傳內容 | 範例 |
|---|---|---|
/v0/topstories.json | 最多 500 個熱門故事 ID 的陣列 | [47788542, 47787901, ...] |
/v0/newstories.json | 最多 500 個最新故事 ID | 格式相同 |
/v0/beststories.json | 最多 500 個最佳故事 ID | 格式相同 |
/v0/askstories.json | 最多 200 個「Ask HN」故事 ID | 格式相同 |
/v0/showstories.json | 最多 200 個「Show HN」故事 ID | 格式相同 |
/v0/jobstories.json | 最多 200 個職缺故事 ID | 格式相同 |
/v0/item/{id}.json | 任一項目(故事、留言、投票)的完整資料 | JSON 物件 |
/v0/user/{username}.json | 使用者個人資料 | JSON 物件 |
/v0/maxitem.json | 目前最大 item ID | 整數(例如 47789427) |
一則故事項目大致會長這樣:
{
"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"
}
kids 欄位包含直接子留言的 ID。每則留言本身也是一個 item,並且可能還有自己的 kids——這就是留言樹的結構。
步驟 1:抓取熱門故事 ID
建立一個名為 scrape_hn_api.py 的檔案:
import requests
import time
from pprint import pprint
API_BASE = "https://hacker-news.firebaseio.com/v0"
# 抓取熱門故事 ID
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——不需要解析,不需要選擇器,直接就是一個 JSON 陣列。
步驟 2:依 ID 抓取故事詳情
接著我們需要實際的故事資料。這時會遇到 fan-out 問題:500 則故事就代表要發出 500 次 API 呼叫。根據我的測試,每次 item 請求連續執行時大約要 1.2 秒。500 則故事大概要 10 分鐘。
多數情境根本不需要全部 500 則。下面的程式會抓取前 30 則:
def fetch_story(story_id):
"""從 HN API 抓取單一故事詳情。"""
resp = requests.get(f"{API_BASE}/item/{story_id}.json")
return resp.json()
# 抓取前 30 則故事的詳情
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) # 禮貌一點——每次請求之間稍微停一下
# 依分數排序,顯示前 10 筆
top = sorted(stories, key=lambda x: x["score"], reverse=True)[:10]
pprint(top)
time.sleep(0.1) 是一個小小的禮貌延遲。Firebase API 沒有明確公告速率限制,但對任何 API 連續猛打、不留緩衝都不是好習慣。
步驟 3:抓取留言(遞迴樹狀走訪)
這就是 API 相較於 HTML 抓取特別有優勢的地方。HN 的留言結構非常深,會有回覆的回覆、回覆的回覆的回覆。若用 HTML,得處理非常複雜的巢狀表格結構;但用 API 時,每則留言的 kids 欄位都會告訴你它的子留言 ID,你只要遞迴地走訪整棵樹就好。
def fetch_comments(item_id, depth=0, max_depth=3):
"""以遞迴方式抓取留言,最多到 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
# 範例:抓取熱門故事的留言
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]: # 前 5 則頂層留言
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}...")
這種遞迴做法,遠比解析巢狀 HTML 留言串容易。如果你需要完整留言樹,API 就是首選。
步驟 4:執行並查看結果
python scrape_hn_api.py
你會看到結構化的故事資料,後面接著巢狀留言預覽。資料更乾淨、留言更容易取得,而且不用擔心 HN 只是改了一個 CSS class 就讓你的爬蟲壞掉。
超越第 1 頁:分頁與歷史資料
大多數 HN 抓取教學都只做到第 1 頁——也就是 30 則故事。這對快速示範夠用,但真正的情境通常需要更深入的資料。
使用 BeautifulSoup 抓取多個頁面
HN 的分頁網址很簡單:?p=2、?p=3 以此類推。每頁會有 30 則故事,而網站最多大約提供到第 20 頁(總共約 600 則故事)。再往後就會是空頁。
import time
def scrape_hn_pages(num_pages=5):
"""抓取 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")
# 尊重 robots.txt 規定的 30 秒抓取延遲
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) 非常重要。HN 的 robots.txt 明確要求 30 秒抓取延遲。若忽略這個規定,可能會被限流(HTTP 429)或暫時封鎖。五個頁面每 30 秒抓一次,總共大約要 2.5 分鐘——不算快,但很有禮貌。
如果你不想自己處理分頁程式碼,Thunderbit 可以自動處理點擊式與無限滾動分頁。它會自動點 HN 頁面底部的「More」按鈕,不需要任何額外設定。
用 Algolia API 取得 Hacker News 歷史資料
Firebase API 提供的是目前資料。如果你要做歷史分析——例如「2023 年最熱門的 Python 故事是什麼?」或「過去 5 年 AI 相關內容的變化趨勢如何?」——你就需要 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):
"""透過 Algolia API 搜尋 HN。"""
params = {
"query": query,
"tags": tags,
"page": page,
"hitsPerPage": hits_per_page,
}
resp = requests.get(f"{ALGOLIA_BASE}/search", params=params)
return resp.json()
# 範例:找出 2024 年 1 月以來、分數 10 分以上的 Python 抓取故事
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']}")
若要依日期篩選,使用 numericFilters:
import calendar, datetime
# 2024 年 1 月 1 日之後的故事
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 速度很快(伺服器處理時間約 5–9 毫秒)、不需要 API 金鑰,且支援最多 500 頁分頁。若要做大量歷史分析,這是最理想的方案。
將抓到的 Hacker News 資料匯出成 CSV、Excel 與 Google Sheets
我看過的每一篇 HN 抓取教學,最後通常都只在終端機印出 pprint() 的結果。這對除錯很好,但如果你要做每日摘要或趨勢分析,資料還是要放進檔案裡。以下教你怎麼做。
使用 Python 匯出 CSV
import csv
def export_to_csv(stories, filename="hn_stories.csv"):
"""把抓到的故事儲存成 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)
使用 Python 匯出 Excel
import pandas as pd
def export_to_excel(stories, filename="hn_stories.xlsx"):
"""把抓到的故事儲存成 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)
記得要先安裝 openpyxl,因為 pandas 會把它當成 Excel 引擎。如果沒裝,執行時就會出現 ImportError。
推送到 Google Sheets(可選)
如果你想做自動化流程,可以用 gspread 直接把資料送到 Google Sheets。不過這需要先設定 Google Cloud service account(一次性流程):
import gspread
gc = gspread.service_account(filename="service_account.json")
sh = gc.open("HN Daily Digest")
worksheet = sh.sheet1
# 把 stories 轉成列資料
header = list(stories[0].keys())
rows = [list(s.values()) for s in stories]
worksheet.clear()
worksheet.update([header] + rows)
print("Pushed to Google Sheets")
免寫程式的匯出替代方案
如果光是設定 service account 和寫匯出程式,就比抓資料本身還麻煩,我完全懂。在 Thunderbit,我們做了免費的資料匯出功能,能把抓好的資料直接送到 Excel、Google Sheets、Airtable 或 Notion——不用寫程式、不用憑證、也不用維護管線。若只是單次抓取,這真的會快很多。下面還會再多說一些。
讓爬蟲更接近正式環境:錯誤處理、快取與排程
如果你只是玩一次,前面的程式已經夠用;但如果你要把它每天放進工作流程,就還需要幾個關鍵元件。
錯誤處理與重試邏輯
網路會失敗,伺服器會限流,單次錯誤不應該讓整個抓取流程整個垮掉。以下是一個帶有指數退避的重試函式:
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):
"""自動重試並採用指數退避來抓取網址。"""
response = requests.get(url, timeout=10)
response.raise_for_status()
return response
# 用法:
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}")
tenacity 會把重試邏輯整理得很乾淨。它最多會重試 5 次,並使用帶抖動的指數退避——從 1 秒開始,最多到 60 秒。這可以比較優雅地處理 HTTP 429(速率限制)、503(服務不可用)與暫時性的網路錯誤。
快取回應,避免重複抓取
在開發時,你會一再執行爬蟲,調整解析邏輯。如果沒有快取,每次執行都會重新向 HN 伺服器請求相同資料。requests-cache 只要兩行就能解決:
import requests_cache
requests_cache.install_cache("hn_cache", expire_after=3600) # 快取 1 小時
把這兩行加到腳本最前面後,所有 requests.get() 呼叫都會自動快取到本機 SQLite 資料庫裡。你在一小時內重跑 10 次,真正打到網路的只會是第一次。這也是論壇使用者常常推薦的工具,而且真的很有道理。
把抓取與解析分開
有經驗的爬蟲工程師很推的一個做法:先下載原始資料,再進行解析。這樣一來,如果解析邏輯有 bug,你可以修正後直接重新解析,不必重抓資料。
import os, json
def crawl_and_save(story_ids, output_dir="raw_data"):
"""抓取故事資料並把原始 JSON 存到磁碟。"""
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 # 跳過已抓取項目
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"):
"""把已儲存的 JSON 檔解析成結構化故事列表。"""
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
這種「先抓後解析」的兩階段流程,特別適合你要處理數百筆資料、又想快速迭代資料處理邏輯的情境。
把爬蟲排程化
如果你要做每日 HN 摘要,就需要讓爬蟲自動執行。常見做法有兩種:
方案 1:cron(Linux / Mac)
# 每天 UTC 早上 8:30 執行
30 8 * * * /usr/bin/python3 /home/user/scrape_hn.py >> /home/user/scrape.log 2>&1
方案 2:GitHub Actions(免費,不需要自己的伺服器)
name: Scrape Hacker News
on:
schedule:
- cron: '30 8 * * *' # 每天 UTC 8:30
workflow_dispatch: # 手動觸發按鈕
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
使用 GitHub Actions 排程時有幾個小提醒:所有 cron 時間都是 UTC,常見會有 15–60 分鐘延遲(所以像 :30 這種非整點時間通常比 :00 更穩),而且 GitHub 可能會停用 60 天沒有活動的專案之排程工作流程。務必加入 workflow_dispatch,這樣你測試時才能手動觸發。
如果你想要更簡單的方式,Thunderbit 的 Scheduled Scraper 功能可以直接用自然語言描述排程,例如「每天早上 8 點抓一次」,完全不需要自己架伺服器或設定 cron。
當 Python 太重時:免寫程式抓取 Hacker News 的方式
老實說,就算我是 Python 愛好者,而且我們團隊也在做開發者工具,如果你只是想把今天前 100 則 HN 熱門故事匯出到試算表,而且只做一次,那用 Python 其實有點多此一舉。光是環境設定(虛擬環境、安裝套件、找選擇器)就比真正抓資料還花時間。
這就是 Thunderbit 的用武之地。流程大概是這樣:
- 在 Chrome 打開
news.ycombinator.com - 點 Thunderbit 擴充功能圖示,然後選「AI Suggest Fields」
- AI 會自動讀取頁面並建議欄位:Title、URL、Score、Author、Comment Count、Time Posted
- 你可以自行調整欄位(重新命名、刪除或新增自訂欄位——甚至可以加上像「Categorize as AI/DevTools/Web/Other」這樣的 AI 提示詞)
- 點「Scrape」——資料就會變成結構化表格
- 匯出到 Excel、Google Sheets、Airtable 或 Notion
兩次點擊就能拿到結構化資料。沒有選擇器、沒有程式碼、沒有維護成本。
這裡真正的優勢是:Thunderbit 的 AI 會自動適應版面變動。傳統的 CSS 選擇器爬蟲,一旦網站改了標記就很容易壞掉——雖然 HN 的 HTML 相對穩定,但它確實改過(像 class="athing submission" 有更新,span.titleline 也取代了舊的 a.storylink)。AI 爬蟲每次都會重新讀頁面,所以不會被 class 名稱改動影響。

Thunderbit 也能處理分頁(自動點擊 HN 的「More」按鈕)與子頁面抓取(逐一進入每篇故事的留言頁,抓取討論資料)。如果是 留言補充 這類情境,它其實就相當於方法 2 的遞迴 API 程式碼,只是你不用寫任何一行。
取捨很直接:當你需要自訂邏輯、複雜資料轉換、排程化自動管線,或你本來就在學寫程式時,Python 是正確選擇;當你需要快速拿資料、不想維護程式,或者你不是開發者時,Thunderbit 會更適合。選擇最符合你情境的工具就好。
Python vs. API vs. 免寫程式:你該選哪一種?
下面是完整的決策表:
| 比較項目 | BeautifulSoup(HTML) | Firebase API | Algolia API | Thunderbit(免寫程式) |
|---|---|---|---|---|
| 所需技術能力 | 中級 Python | 初階 Python | 初階 Python | 不需要 |
| 設定時間 | 10–15 分鐘 | 5–10 分鐘 | 5–10 分鐘 | 2 分鐘 |
| 維護成本 | 中等(選擇器可能失效) | 低(穩定 JSON) | 低(穩定 JSON) | 幾乎沒有 |
| 資料深度 | 只有首頁 | 任意項目、使用者 | 搜尋 + 歷史資料 | 首頁 + 子頁面 |
| 留言 | 困難 | 容易(遞迴) | 容易(巢狀樹) | 子頁面抓取 |
| 歷史資料 | 否 | 否 | 是(完整封存) | 否 |
| 匯出選項 | 自己寫程式 | 自己寫程式 | 自己寫程式 | 內建(Excel、Sheets 等) |
| 排程 | cron / GitHub Actions | cron / GitHub Actions | cron / GitHub Actions | 內建排程器 |
| 最適合 | 學習爬蟲 | 穩定資料管線 | 研究與分析 | 快速抓資料 |
如果你在學 Python,或是在做客製化工具,就選方法 1 或 2;如果你要做歷史分析,再加上 Algolia API;如果你只是想直接拿資料、不想寫程式,試試 Thunderbit。
結論與重點整理
現在你手上已經有這些工具:
- 兩種完整的 Python 方法來抓取 Hacker News——用 BeautifulSoup 解析 HTML,或用 Firebase API 取得乾淨的 JSON 資料
- 分頁技巧,可以抓超過第 1 頁,還能透過 Algolia API 往回分析 2007 年以來的歷史資料
- 匯出程式碼,可輸出成 CSV、Excel 與 Google Sheets——因為資料只待在終端機裡,對團隊其他人沒什麼幫助
- 正式環境做法——重試邏輯、快取、抓取/解析分離,以及透過 cron 或 GitHub Actions 排程自動化
- 免寫程式替代方案,當 Python 對你的需求來說太重時可直接使用
我的建議是:多數情況先從 Firebase API(方法 2)開始。它更乾淨、更穩定,而且可以直接抓留言,不必費力解析巢狀 HTML。當你需要歷史資料時,再加入 Algolia API。至於 Thunderbit,建議先收藏起來,當你只想快速拿一份試算表、又不想啟動整個 Python 專案時,它會非常好用。
如果你想更進一步,可以試著抓 HN 留言做 情緒分析,用 GitHub Actions 建立每日摘要管線,或研究 Algolia API,追蹤過去十年間科技趨勢如何變化。
試試 Thunderbit,快速抓取 Hacker News Get Started Free
常見問題
抓取 Hacker News 合法嗎?
HN 的資料是公開可存取的,而且 Y Combinator 也提供了官方 API,專門用於程式化存取。網站的 robots.txt 允許抓取只讀內容(首頁、項目頁、使用者頁),但要求 30 秒的抓取延遲。只要你尊重這個延遲、不抓互動式端點(投票、登入),基本上就沒問題。若想進一步了解抓取倫理,可參考我們的 網頁爬蟲法律影響 指南。
Hacker News 有官方 API 嗎?
有。HN Firebase API 位於 hacker-news.firebaseio.com/v0/,免費、無須驗證,可存取故事、留言、使用者個人資料,以及所有 feed 類型(top、new、best、ask、show、jobs)。它回傳乾淨的 JSON,而且沒有明示的速率限制,但仍建議以禮貌的頻率發送請求。
我要怎麼用 Python 抓取 Hacker News 留言?
使用 Firebase API 時,先抓故事項目取得它的 kids 欄位(也就是頂層留言 ID 陣列)。每則留言本身也是一個 item,且有自己的 kids 欄位代表回覆。你可以寫一個函式,以遞迴方式抓取每則留言與其子留言。上面的「抓取留言(遞迴樹狀走訪)」章節有完整範例。或者,你也可以用 Algolia API 的 /items/<id> 端點 一次取得完整的巢狀留言樹,處理留言很多的文章會快非常多。
不寫程式也能抓 Hacker News 嗎?
可以。Thunderbit 的 AI 網頁爬蟲 是 Chrome 擴充功能:打開 HN,點「AI Suggest Fields」,它會自動辨識標題、URL、分數、作者等欄位。按下「Scrape」後,就能直接匯出到 Excel、Google Sheets、Airtable 或 Notion。它還能處理分頁,甚至能進入子頁面抓取留言資料。完全不用 Python、不用選擇器,也不用維護。
我要怎麼取得 Hacker News 的歷史資料?
HN Algolia Search API 是這個需求的最佳工具。使用 search_by_date 端點,搭配 numericFilters=created_at_i>TIMESTAMP 就能依日期區間篩選。你可以依關鍵字搜尋、依故事類型過濾,並在最多 500 頁結果中分頁查找。若要做大量歷史分析,也可使用 Google BigQuery(完整封存)、ClickHouse(2,800 萬筆紀錄)與 Hugging Face(400 萬則故事)的公開資料集。
延伸閱讀


