Python으로 Hacker News를 스크래핑하는 방법: 2가지 방법과 전체 코드

최종 업데이트: April 16, 2026

몇 달 전, Thunderbit 팀을 위해 매일 Hacker News 주요 소식을 정리한 다이제스트를 만들고 싶었습니다. 처음에는 사이트를 북마크해두고 매일 아침 훑어보면 되겠다고 생각했죠. 하지만 그 방식은 며칠도 못 갔습니다. 매일 20분씩 헤드라인을 읽고 링크를 스프레드시트에 복붙하는 데 시간을 쓰고 있다는 걸 깨달았기 때문입니다.

Hacker News는 인터넷에서 가장 밀도 높은 기술 인사이트 소스 중 하나입니다. 월간 방문 수는 약 에 달하고, 매일 약 1,300개의 새 글이 올라오며, 하루에 약 13,000개의 댓글이 생성됩니다. 떠오르는 기술 트렌드를 추적하든, 브랜드 언급을 모니터링하든, “Who’s Hiring” 스레드로 채용 파이프라인을 만들든, 아니면 개발자 커뮤니티가 무엇에 관심을 두는지 파악하고 싶든, 이 모든 걸 수동으로 따라가는 건 사실상 지는 싸움입니다.

좋은 소식은 있습니다. Python으로 Hacker News를 스크래핑하는 건 생각보다 훨씬 간단합니다. 이 가이드에서는 BeautifulSoup를 활용한 HTML 스크래핑과 공식 HN Firebase API라는 두 가지 완성형 방법을 모두 다룹니다. 여기에 페이지네이션, 데이터 내보내기, 프로덕션 환경용 패턴, 그리고 Python이 너무 과하게 느껴질 때 쓸 수 있는 노코드 대안까지 함께 소개합니다.

왜 Python으로 Hacker News를 스크래핑할까?

Hacker News는 단순한 링크 모음 사이트가 아닙니다. 커뮤니티가 직접 선별하는 피드로, 가장 흥미로운 기술 뉴스가 업보트와 활발한 토론을 통해 자연스럽게 상단으로 올라옵니다. 이용자층은 기술 직군 비중이 높고(), 직접 유입 비율이 66%라는 점은 이곳이 우연히 들르는 곳이 아니라 꾸준히 찾는 충성도 높은 독자층이라는 걸 보여줍니다.

HN 데이터를 스크래핑하는 이유는 다양합니다:

활용 사례얻을 수 있는 것
데일리 기술 다이제스트주요 스토리, 점수, 링크를 이메일이나 Slack으로 전달
브랜드/경쟁사 모니터링회사나 제품이 언급될 때 알림 수신
트렌드 분석어떤 기술, 언어, 주제가 점점 주목받는지 추적
채용"Who's Hiring" 스레드에서 채용 공고, 기술 스택, 연봉 신호 파악
콘텐츠 리서치글감이 될 만한 성과 좋은 주제 찾기
감성 분석제품, 출시, 업계 변화에 대한 커뮤니티 반응 파악

Stripe, Dropbox, Airbnb처럼 합산 시 4,000억 달러가 넘는 기업들도 Hacker News에서 초기 피드백과 사용자를 확보했습니다. Drew Houston은 2007년 4월 HN에 Dropbox 데모를 올렸고, 1위를 차지한 뒤 베타 대기자 명단이 하루 만에 5,000명에서 75,000명으로 폭발적으로 늘었습니다. HN 데이터는 단순히 흥미로운 수준을 넘어, 실제로 비즈니스 가치가 큽니다.

데이터는 공개되어 있지만, 사이트 구조상 수동 수집은 번거롭습니다. 그래서 Python으로 자동화하는 것이 가장 현실적인 해법입니다.

Python으로 Hacker News를 스크래핑하는 2가지 방법: 개요

이 가이드에서는 바로 실행 가능한 두 가지 완성형 접근법을 다룹니다.

  1. requests + BeautifulSoup를 이용한 HTML 스크래핑 — news.ycombinator.com의 원본 HTML을 가져와 파싱한 뒤 스토리 데이터를 추출합니다. 스크래핑 기초를 배우기에 좋고, 페이지에 보이는 정보를 그대로 가져오기에도 적합합니다.
  2. 공식 Hacker News Firebase API — HTML 파싱 없이 JSON 엔드포인트를 직접 호출합니다. 더 안정적인 데이터 파이프라인과 댓글, 히스토리 데이터 접근에 유리합니다.

어떤 방법이 맞는지 쉽게 비교할 수 있도록 표로 정리했습니다:

기준HTML 스크래핑 (requests + BS4)HN Firebase APIThunderbit (노코드)
설정 복잡도중간 (HTML 선택자 파싱 필요)낮음 (JSON 엔드포인트)없음 (2번 클릭하는 Chrome 확장 프로그램)
데이터 최신성실시간 프론트페이지실시간 (ID만 알면 어떤 항목이든)실시간
속도 제한 위험중간 (robots.txt에 30초 크롤링 지연 명시)낮음 (공식, 관대한 편)Thunderbit가 관리
댓글 접근어려움 (중첩 HTML)쉬움 (재귀적으로 아이템 ID 추적)서브페이지 스크래핑 기능
히스토리 데이터제한적Algolia Search API로 가능해당 없음
최적 용도스크래핑 기초 학습안정적인 데이터 파이프라인비개발자, 빠른 내보내기

두 방법 모두 바로 실행할 수 있는 전체 Python 코드를 포함합니다. 그리고 코드를 전혀 쓰지 않고 데이터만 필요하다면, 그것도 다룹니다.

시작하기 전에

  • 난이도: 초급~중급
  • 소요 시간: 방법당 약 15~20분
  • 준비물:
    • Python 3.11+ 설치
    • 터미널 또는 코드 편집기
    • Chrome 브라우저(HN의 HTML을 직접 확인하거나 노코드 옵션을 쓰려면 필요)
    • (선택 사항, 노코드용)

scrape-hacker-news-methods.webp

Python 환경 설정하기

HN 데이터를 만지기 전에 먼저 개발 환경부터 준비해봅시다. 프로젝트 의존성을 깔끔하게 관리하려면 가상 환경을 만드는 것을 추천합니다.

1# 가상 환경 생성 및 활성화
2python3 -m venv hn-scraper
3# macOS/Linux:
4source hn-scraper/bin/activate
5# Windows:
6hn-scraper\Scripts\activate
7# 두 방법 모두에 필요한 패키지 설치
8pip install requests==2.33.1 beautifulsoup4==4.14.3 pandas==3.0.2 openpyxl==3.1.5

나중에 프로덕션 패턴(캐싱, 재시도)을 적용하려면 아래도 설치해두면 좋습니다:

1pip 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 파일을 만들어보세요. 시작 코드는 다음과 같습니다:

1import requests
2from bs4 import BeautifulSoup
3> This paragraph contains content that cannot be parsed and has been skipped.
4print(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 — 스토리 제목과 URL
  • span.score — 점수(예: "118 points")
  • a.hnuser — 작성자 이름
  • span.age — 게시 시간
  • .subtext 안의 마지막 <a> 중 텍스트에 "comment"가 들어간 요소 — 댓글 수

Chrome에서 아무 스토리 제목을 오른쪽 클릭한 뒤 "검사"를 누르면 다음과 비슷한 구조를 볼 수 있습니다:

1<span class="titleline">
2  <a href="https://darkbloom.dev">DarkbloomPrivate inference on idle Macs</a>
3</span>

아래의 메타데이터 행은 이렇게 생깁니다:

1<span class="score" id="score_47788542">118 points</span>
2by <a href="user?id=twapi" class="hnuser">twapi</a>
3<span class="age" title="2026-04-16T04:06:39 1776312399">
4  <a href="item?id=47788542">2 hours ago</a>
5</span>
6| <a href="item?id=47788542">65&nbsp;comments</a>

이 선택자 구조를 이해하는 것이 중요합니다. HN이 마크업을 바꾸면 선택자도 함께 수정해야 하기 때문입니다. (힌트: API 방법을 쓰면 이 문제를 아예 피할 수 있습니다.)

3단계: 제목, 링크, 점수 추출하기

이제 본격적으로 추출해봅시다. 모든 스토리 행을 순회하면서, 스토리 행에서 제목과 링크를 가져오고, 바로 아래 메타데이터 행에서 점수를 가져옵니다.

1import requests
2from bs4 import BeautifulSoup
3from pprint import pprint
4> This paragraph contains content that cannot be parsed and has been skipped.
5stories = []
6story_rows = soup.select("tr.athing")
7for row in story_rows:
8    # 스토리 행에서 제목과 URL 추출
9    title_tag = row.select_one("span.titleline > a")
10    if not title_tag:
11        continue
12    title = title_tag.get_text()
13    link = title_tag.get("href", "")
14    # 바로 다음 행에서 메타데이터 추출
15    meta_row = row.find_next_sibling("tr")
16    score = 0
17    author = ""
18    comments = 0
19> This paragraph contains content that cannot be parsed and has been skipped.
20> This paragraph contains content that cannot be parsed and has been skipped.
21# 50점 이상 스토리만 필터링하고 점수 높은 순으로 정렬
22top_stories = sorted(
23    [s for s in stories if s["score"] >= 50],
24    key=lambda x: x["score"],
25    reverse=True,
26)
27pprint(top_stories[:10])

몇 가지 참고할 점:

  • 월러스 연산자(:=)는 Python 3.8+에서 사용할 수 있습니다. span.score처럼 모든 행에 항상 있지 않을 수 있는 요소를 한 줄에서 할당과 조건 검사를 동시에 할 때 유용합니다.
  • HN은 \xa0(줄바꿈 없는 공백)을 숫자와 "comments" 사이에 사용하므로, 이를 기준으로 분리합니다.
  • "Ask HN" 같은 HN 내부 페이지 링크는 item?id= 형태의 상대 URL을 가집니다. 이런 경우 https://news.ycombinator.com/를 앞에 붙이고 싶을 수 있습니다.

4단계: 실행하고 결과 확인하기

저장한 뒤 실행합니다:

1python scrape_hn_bs4.py

아래와 비슷한 출력이 나올 것입니다:

1[{'author': 'twapi',
2  'comments': 65,
3  'score': 118,
4  'title': 'Darkbloom – Private inference on idle Macs',
5  'url': 'https://darkbloom.dev'},
6 {'author': 'sebg',
7  'comments': 203,
8  'score': 247,
9  'title': 'Show HN: I built an open-source Perplexity alternative',
10  'url': 'https://github.com/...'},
11 ...]

이건 1페이지의 30개 스토리입니다. 하지만 HN에는 언제나 훨씬 더 많은 활성 스토리가 있습니다. 페이지네이션은 뒤에서 다룹니다.

방법 2: 공식 API로 Hacker News를 스크래핑하기

HN Firebase API는 Hacker News 데이터를 공식적으로 가져올 수 있는 방법입니다. 인증도, API 키도, HTML 파싱도 필요 없습니다. 깔끔한 JSON 응답만 받으면 됩니다. 저는 프로덕션에서 안정적으로 돌아가야 하는 작업에는 이 방법을 사용합니다.

꼭 알아야 할 핵심 API 엔드포인트

기본 URL은 https://hacker-news.firebaseio.com/v0/입니다. 중요한 엔드포인트는 다음과 같습니다:

This paragraph contains content that cannot be parsed and has been skipped.

스토리 아이템은 대략 이런 형태입니다:

1{
2  "by": "twapi",
3  "descendants": 65,
4  "id": 47788542,
5  "kids": [47789171, 47788769, 47788762],
6  "score": 118,
7  "time": 1776312399,
8  "title": "Darkbloom – Private inference on idle Macs",
9  "type": "story",
10  "url": "https://darkbloom.dev"
11}

kids 필드에는 직접 자식 댓글들의 ID가 들어 있습니다. 각 댓글도 하나의 아이템이며, 역시 자신만의 kids를 가질 수 있습니다. 이런 구조가 댓글 트리를 형성합니다.

1단계: 상위 스토리 ID 가져오기

scrape_hn_api.py 파일을 만듭니다:

1import requests
2import time
3from pprint import pprint
4API_BASE = "https://hacker-news.firebaseio.com/v0"
5# 상위 스토리 ID 가져오기
6response = requests.get(f"{API_BASE}/topstories.json")
7story_ids = response.json()
8print(f"Got {len(story_ids)} top story IDs")
9# 출력 예시: Got 500 top story IDs

한 번의 요청으로 500개의 스토리 ID를 가져옵니다. 파싱도, 선택자도 필요 없습니다. 그저 JSON 배열일 뿐입니다.

2단계: ID로 스토리 상세 정보 가져오기

이제 실제 스토리 데이터가 필요합니다. 여기서 팬아웃 문제가 생깁니다. 500개 스토리면 API 호출도 500번 필요하니까요. 제 벤치마크 기준으로 아이템 요청 하나는 순차적으로 약 1.2초 걸립니다. 500개면 거의 10분이 걸립니다.

대부분의 용도에서는 500개가 전부 필요하지 않습니다. 아래 코드는 상위 30개만 가져오는 예시입니다:

1def fetch_story(story_id):
2    """HN API에서 단일 스토리의 상세 정보를 가져옵니다."""
3    resp = requests.get(f"{API_BASE}/item/{story_id}.json")
4    return resp.json()
5> This paragraph contains content that cannot be parsed and has been skipped.
6# 점수 기준 정렬 후 상위 10개 표시
7top = sorted(stories, key=lambda x: x["score"], reverse=True)[:10]
8pprint(top)

time.sleep(0.1)은 작은 배려 차원의 지연입니다. Firebase API에 명시된 rate limit은 없지만, 어떤 API든 쉬지 않고 연속 호출하는 건 좋은 습관이 아닙니다.

3단계: 댓글 스크래핑하기(재귀 트리 탐색)

여기서 API 방식이 HTML 스크래핑보다 훨씬 빛을 발합니다. HN 댓글은 중첩이 깊습니다. 답글의 답글의 답글이 이어지기도 하죠. HTML로 하려면 복잡한 중첩 테이블 구조를 파싱해야 합니다. 하지만 API에서는 각 댓글의 kids 필드만 보면 자식 댓글 ID를 알 수 있으므로, 재귀적으로 트리를 따라가면 됩니다.

1def fetch_comments(item_id, depth=0, max_depth=3):
2    """max_depth까지 재귀적으로 댓글을 가져옵니다."""
3    item = requests.get(f"{API_BASE}/item/{item_id}.json").json()
4    if not item or item.get("type") != "comment":
5        return []
6> This paragraph contains content that cannot be parsed and has been skipped.
7    if depth < max_depth and item.get("kids"):
8        for kid_id in item["kids"]:
9            comments.extend(fetch_comments(kid_id, depth + 1, max_depth))
10            time.sleep(0.05)
11    return comments
12# 예시: 상위 스토리의 댓글 가져오기
13if stories:
14    top_story = stories[0]
15    top_story_full = requests.get(f"{API_BASE}/item/{top_story['id']}.json").json()
16    if top_story_full.get("kids"):
17        print(f"\nComments for: {top_story['title']}")
18        all_comments = []
19        for kid_id in top_story_full["kids"][:5]:  # 상위 레벨 댓글 5개만
20            all_comments.extend(fetch_comments(kid_id, depth=0, max_depth=2))
21            time.sleep(0.1)
22        for c in all_comments[:15]:
23            indent = "  " * c["depth"]
24            preview = c["text"][:80].replace("\n", " ") if c["text"] else "[no text]"
25            print(f"{indent}[{c['author']}] {preview}...")

이 재귀 방식은 중첩 댓글 스레드를 HTML로 억지로 파싱하는 것보다 훨씬 쉽습니다. 전체 댓글 트리가 필요하다면 API가 정답입니다.

4단계: 실행하고 결과 확인하기

1python scrape_hn_api.py

구조화된 스토리 데이터와 함께 중첩 댓글 미리보기가 표시될 것입니다. 데이터는 더 깔끔하고, 댓글 접근도 훨씬 쉽고, HN이 CSS 클래스 이름을 바꾸는 바람에 스크래퍼가 깨질 위험도 없습니다.

1페이지를 넘어: 페이지네이션과 히스토리 데이터

대부분의 HN 스크래핑 튜토리얼은 1페이지, 즉 30개 스토리에서 끝납니다. 빠른 데모에는 충분하지만, 실제 활용에서는 더 깊은 데이터가 필요할 때가 많습니다.

BeautifulSoup로 여러 페이지 스크래핑하기

HN의 페이지네이션은 ?p=2, ?p=3 같은 단순한 URL 패턴을 사용합니다. 각 페이지에는 30개의 스토리가 있고, 사이트는 약 20페이지(총 약 600개 스토리)까지 제공합니다. 그 이후에는 빈 페이지가 나옵니다.

1import time
2def scrape_hn_pages(num_pages=5):
3    """HN 프론트페이지의 여러 페이지를 스크래핑합니다."""
4    all_stories = []
5    for page in range(1, num_pages + 1):
6        url = f"https://news.ycombinator.com/news?p={page}"
7        response = requests.get(url, headers=headers)
8        soup = BeautifulSoup(response.text, "html.parser")
9        story_rows = soup.select("tr.athing")
10        if not story_rows:
11            print(f"Page {page}: no stories found, stopping.")
12            break
13        for row in story_rows:
14            title_tag = row.select_one("span.titleline > a")
15            if not title_tag:
16                continue
17            meta_row = row.find_next_sibling("tr")
18            score = 0
19            if meta_row and (score_tag := meta_row.select_one("span.score")):
20                score = int(score_tag.get_text().replace(" points", ""))
21> This paragraph contains content that cannot be parsed and has been skipped.
22        print(f"Page {page}: scraped {len(story_rows)} stories")
23        # robots.txt의 30초 크롤링 지연을 준수
24        if page < num_pages:
25            time.sleep(30)
26    return all_stories
27stories = scrape_hn_pages(5)
28print(f"\nTotal stories scraped: {len(stories)}")

time.sleep(30)은 중요합니다. HN의 는 명시적으로 30초의 크롤링 지연을 요청합니다. 이를 무시하면 속도 제한(HTTP 429)을 당하거나 일시적으로 차단될 수 있습니다. 30초 간격으로 5페이지를 돌리면 약 2.5분이 걸리지만, 그만큼 예의 있는 접근입니다.

페이지네이션 코드를 직접 관리하고 싶지 않은 사용자라면 가 클릭 기반 페이지네이션과 무한 스크롤을 자동으로 처리합니다. HN 페이지 하단의 "More" 버튼을 설정 없이 알아서 클릭해줍니다.

Algolia API로 과거 Hacker News 데이터 접근하기

Firebase API는 현재 데이터에 적합합니다. 하지만 "2023년에 Python 관련 상위 글은 뭐였지?" 또는 "지난 5년간 AI 관련 보도는 어떻게 바뀌었지?" 같은 과거 분석이 필요하다면 가 필요합니다.

1import requests
2ALGOLIA_BASE = "https://hn.algolia.com/api/v1"
3> This paragraph contains content that cannot be parsed and has been skipped.
4# 예시: 2024년 1월 이후 10점 이상인 Python 스크래핑 글 찾기
5results = search_hn(
6    query="python scraping",
7    tags="story",
8)
9print(f"Found {results['nbHits']} total results")
10for hit in results["hits"][:5]:
11    print(f"  [{hit.get('points', 0)} pts] {hit['title']}")

날짜 조건 검색을 하려면 numericFilters를 사용합니다:

1import calendar, datetime
2# 2024년 1월 1일 이후의 글
3start_date = datetime.datetime(2024, 1, 1)
4start_ts = int(calendar.timegm(start_date.timetuple()))
5> This paragraph contains content that cannot be parsed and has been skipped.
6Algolia API는 빠르고(서버 처리 시간 5~9ms), API 키가 필요 없으며, 최대 500페이지까지 페이지네이션을 지원합니다. 대량의 과거 데이터를 분석할 때는 가장 좋은 선택입니다.
7## 스크래핑한 Hacker News 데이터를 CSV, Excel, Google Sheets로 내보내기
8제가 본 거의 모든 HN 스크래핑 튜토리얼은 터미널에서 `pprint()` 결과를 보여주는 데서 끝납니다. 디버깅용으로는 괜찮지만, 데일리 다이제스트를 만들거나 트렌드 분석을 하려면 파일 형태로 저장해야 합니다. 방법을 알아봅시다.
9### Python으로 CSV로 내보내기
10```python
11import csv
12def export_to_csv(stories, filename="hn_stories.csv"):
13    """스크래핑한 스토리를 CSV 파일로 저장합니다."""
14    fieldnames = ["title", "url", "score", "author", "comments"]
15    with open(filename, "w", newline="", encoding="utf-8") as f:
16        writer = csv.DictWriter(f, fieldnames=fieldnames)
17        writer.writeheader()
18        writer.writerows(stories)
19    print(f"Saved {len(stories)} stories to {filename}")
20export_to_csv(stories)

Python으로 Excel로 내보내기

1import pandas as pd
2def export_to_excel(stories, filename="hn_stories.xlsx"):
3    """스크래핑한 스토리를 Excel 파일로 저장합니다."""
4    df = pd.DataFrame(stories)
5    df.to_excel(filename, index=False, engine="openpyxl")
6    print(f"Saved {len(stories)} stories to {filename}")
7export_to_excel(stories)

openpyxl이 설치되어 있어야 합니다. pandas가 Excel 엔진으로 이를 사용하기 때문입니다. 없으면 ImportError가 발생합니다.

Google Sheets로 보내기(선택 사항)

자동화된 워크플로를 만들고 있다면 gspread 라이브러리를 사용해 데이터를 Google Sheets로 바로 보낼 수 있습니다. 다만 Google Cloud 서비스 계정을 설정해야 합니다(한 번만 하면 됩니다):

1import gspread
2gc = gspread.service_account(filename="service_account.json")
3sh = gc.open("HN Daily Digest")
4worksheet = sh.sheet1
5# 스토리를 행 데이터로 변환
6header = list(stories[0].keys())
7rows = [list(s.values()) for s in stories]
8worksheet.clear()
9worksheet.update([header] + rows)
10print("Pushed to Google Sheets")

노코드 내보내기 대안

서비스 계정 설정과 내보내기 코드를 작성하는 일이 실제 스크래핑보다 더 번거롭게 느껴진다면, 충분히 이해합니다. Thunderbit에서는 스크래핑한 데이터를 Excel, Google Sheets, Airtable, Notion으로 바로 보낼 수 있는 무료 데이터 내보내기 기능을 제공합니다. 코드도, 자격 증명도, 유지할 파이프라인도 필요 없습니다. 단발성 데이터 추출이라면 훨씬 빠릅니다. 아래에서 더 설명하겠습니다.

스크래퍼를 프로덕션 수준으로 만들기: 오류 처리, 캐싱, 스케줄링

재미로 한 번 돌려보는 스크래퍼라면 위 코드로 충분합니다. 하지만 매일 워크플로의 일부로 돌린다면 몇 가지를 더 챙겨야 합니다.

오류 처리와 재시도 로직

네트워크는 실패합니다. 서버는 요청을 제한합니다. 한 번의 잘못된 요청 때문에 전체 스크래핑이 무너지면 안 됩니다. 지수 백오프를 적용한 재시도 함수는 다음과 같습니다:

1from tenacity import retry, stop_after_attempt, wait_exponential_jitter
2import requests
3@retry(stop=stop_after_attempt(5), wait=wait_exponential_jitter(initial=1, max=60))
4def fetch_with_retry(url):
5    """자동 재시도와 지수 백오프로 URL을 가져옵니다."""
6    response = requests.get(url, timeout=10)
7    response.raise_for_status()
8    return response
9# 사용 예시:
10try:
11    resp = fetch_with_retry("https://hacker-news.firebaseio.com/v0/topstories.json")
12    story_ids = resp.json()
13except Exception as e:
14    print(f"Failed after retries: {e}")

tenacity 라이브러리가 재시도 로직을 깔끔하게 처리해줍니다. 최대 5번까지, 초기 1초에서 시작해 최대 60초까지 점점 늘어나는 지수 백오프로 재시도합니다. HTTP 429(속도 제한), 503(서비스 불가), 일시적인 네트워크 오류를 부드럽게 처리할 수 있습니다.

다시 크롤링하지 않도록 응답 캐싱하기

개발 중에는 파싱 로직을 조정하면서 스크래퍼를 여러 번 실행하게 됩니다. 캐싱이 없으면 매번 같은 데이터를 HN 서버에서 다시 가져오게 됩니다. requests-cache는 두 줄이면 해결됩니다:

1import requests_cache
2requests_cache.install_cache("hn_cache", expire_after=3600)  # 1시간 캐시

이 줄을 스크립트 상단에 추가하면 requests.get() 호출이 자동으로 로컬 SQLite 데이터베이스에 캐시됩니다. 한 시간 안에 스크립트를 10번 실행해도 실제 네트워크 요청은 첫 번째만 발생합니다. 이 도구는 이유가 분명합니다.

크롤링과 파싱 분리하기

경험 많은 스크래퍼들이 중요하게 여기는 패턴이 하나 있습니다. 먼저 원본 데이터를 내려받고, 그다음 파싱하는 것입니다. 이렇게 하면 파싱 로직에 버그가 있어도 다시 가져오지 않고 파싱만 고치면 됩니다.

1import os, json
2def crawl_and_save(story_ids, output_dir="raw_data"):
3    """스토리 데이터를 가져와 원본 JSON을 디스크에 저장합니다."""
4    os.makedirs(output_dir, exist_ok=True)
5    for sid in story_ids:
6        filepath = os.path.join(output_dir, f"{sid}.json")
7        if os.path.exists(filepath):
8            continue  # 이미 가져온 항목은 건너뜀
9        resp = fetch_with_retry(f"{API_BASE}/item/{sid}.json")
10        with open(filepath, "w") as f:
11            json.dump(resp.json(), f)
12> This paragraph contains content that cannot be parsed and has been skipped.
132단계 접근법은 수백 개 항목을 스크래핑하면서 데이터 처리 방식을 빠르게 개선하고 싶을 때 특히 유용합니다.
14### 스케줄에 따라 스크래퍼 자동 실행하기
15매일 HN 다이제스트를 만들려면 스크래퍼가 자동으로 실행되어야 합니다. 흔한 방법 두 가지는 다음과 같습니다:
16**옵션 1: cron(Linux/Mac)**
17```bash
18# 매일 UTC 오전 8시 30분 실행
1930 8 * * * /usr/bin/python3 /home/user/scrape_hn.py >> /home/user/scrape.log 2>&1

옵션 2: GitHub Actions(무료, 별도 서버 불필요)

1name: Scrape Hacker News
2on:
3  schedule:
4    - cron: '30 8 * * *'  # 매일 UTC 오전 8시 30분
5  workflow_dispatch:        # 수동 실행 버튼
6jobs:
7  scrape:
8    runs-on: ubuntu-latest
9    steps:
10      - uses: actions/checkout@v4
11      - uses: actions/setup-python@v6
12        with:
13          python-version: '3.12'
14      - run: pip install requests beautifulsoup4 pandas openpyxl
15      - run: python scrape_hn.py
16      - run: |
17          git config user.name "GitHub Actions Bot"
18          git config user.email "actions@github.com"
19          git add -A
20          git diff --staged --quiet || git commit -m "Update HN data $(date -u +%Y-%m-%dT%H:%M:%SZ)"
21          git push

GitHub Actions 스케줄링에서 알아둘 점도 있습니다. cron 시간은 전부 UTC 기준이고, 15~60분 정도 지연되는 경우가 흔하므로 :00보다는 :30처럼 애매한 시간을 쓰는 것이 좋습니다. 또 60일 동안 활동이 없는 저장소에서는 예약 워크플로가 비활성화될 수 있습니다. 테스트용으로는 반드시 workflow_dispatch를 넣어 수동 실행이 가능하게 하세요.

더 간단한 방법을 원한다면, Thunderbit의 Scheduled Scraper 기능으로 "매일 아침 8시에 스크래핑"처럼 자연어로 스케줄을 지정할 수 있습니다. 서버나 cron 설정이 필요 없습니다.

Python이 과할 때: Hacker News를 노코드로 스크래핑하는 방법

저는 Python을 좋아하는 사람이고, 우리 팀은 개발자 도구를 만듭니다. 그럼에도 솔직히 말하면, 오늘의 HN 상위 100개 글만 스프레드시트로 바로 뽑아오고 싶은 경우라면 Python 스크립트를 쓰는 건 불필요하게 번거롭습니다. 가상 환경 만들기, 패키지 설치, 선택자 파악 같은 준비만 해도 실제 수집보다 시간이 더 걸릴 수 있습니다.

이럴 때 가 잘 맞습니다. 흐름은 다음과 같습니다:

  1. Chrome에서 news.ycombinator.com을 엽니다.
  2. Thunderbit 확장 프로그램 아이콘을 클릭한 뒤 "AI Suggest Fields"를 선택합니다.
  3. AI가 페이지를 읽고 Title, URL, Score, Author, Comment Count, Time Posted 같은 열을 제안합니다.
  4. 필요하면 필드를 조정합니다(이름 바꾸기, 삭제, 추가 가능 — "AI/DevTools/Web/Other로 분류" 같은 AI 프롬프트도 넣을 수 있습니다).
  5. "Scrape"를 클릭하면 데이터가 구조화된 표로 나타납니다.
  6. Excel, Google Sheets, Airtable, Notion으로 내보냅니다.

구조화된 데이터까지 클릭 두 번이면 끝입니다. 선택자도, 코드도, 유지보수도 필요 없습니다.

여기서 실제 장점 하나는 Thunderbit의 AI가 레이아웃 변화에 자동으로 적응한다는 점입니다. 전통적인 CSS 선택자 기반 스크래퍼는 사이트 마크업이 바뀌면 깨집니다. HN의 HTML은 비교적 안정적이지만, 변경된 적이 있습니다(class="athing submission"이 바뀌었고, 예전의 a.storylinkspan.titleline으로 대체되었습니다). AI 기반 스크래퍼는 매번 페이지를 새로 읽기 때문에 클래스 이름 변화에 덜 민감합니다.

python-vs-thunderbit-comparison.webp

Thunderbit는 페이지네이션도 처리합니다(HN의 "More" 버튼 자동 클릭) 그리고 서브페이지 스크래핑도 지원합니다(각 스토리의 댓글 페이지를 방문해 토론 데이터를 가져옴). 같은 작업에서는 2번 방법의 재귀 API 코드와 비슷한 일을 해주지만, 한 줄도 직접 쓰지 않아도 됩니다.

결국 선택은 간단합니다. Python은 커스텀 로직, 복잡한 데이터 변환, 예약 자동화 파이프라인, 혹은 코딩 학습이 필요할 때 적합합니다. Thunderbit는 데이터를 빨리 얻고 싶거나, 코드를 유지보수하고 싶지 않거나, 개발자가 아닐 때 적합합니다. 상황에 맞는 도구를 고르세요.

Python vs. API vs. 노코드: 어떤 방법을 선택해야 할까?

전체 의사결정 프레임워크는 다음과 같습니다:

기준BeautifulSoup(HTML)Firebase APIAlgolia APIThunderbit (노코드)
필요한 기술 수준중급 Python초급 Python초급 Python없음
설정 시간10–15분5–10분5–10분2분
유지보수 부담중간(선택자 깨짐 가능)낮음(안정적인 JSON)낮음(안정적인 JSON)없음
데이터 깊이프론트페이지만어떤 아이템이든, 사용자도 가능검색 + 히스토리프론트페이지 + 서브페이지
댓글어려움쉬움(재귀)쉬움(중첩 트리)서브페이지 스크래핑
히스토리 데이터아니오아니오예(전체 아카이브)아니오
내보내기 옵션직접 코딩직접 코딩직접 코딩내장(Excel, Sheets 등)
스케줄링cron / GitHub Actionscron / GitHub Actionscron / GitHub Actions내장 스케줄러
최적 용도스크래핑 학습안정적인 파이프라인리서치 & 분석빠른 데이터 추출

Python을 배우는 중이거나 맞춤형 무언가를 만들고 있다면 1번 또는 2번 방법을 선택하세요. 과거 데이터가 필요하면 Algolia API를 더하세요. 그냥 코드 없이 데이터를 원한다면 .

결론과 핵심 요약

이제 여러분의 도구상자에는 다음이 들어 있습니다:

  • Hacker News를 스크래핑하는 두 가지 완전한 Python 방법 — HTML 파싱용 BeautifulSoup와 깔끔한 JSON 데이터를 위한 Firebase API
  • 1페이지를 넘어가는 페이지네이션 기술 — 2007년까지 거슬러 올라가는 히스토리 데이터용 Algolia API 포함
  • CSV, Excel, Google Sheets로 내보내는 코드 — 터미널의 데이터는 팀원 누구에게도 쓸모가 없기 때문입니다
  • 프로덕션 패턴 — 재시도 로직, 캐싱, 크롤링/파싱 분리, cron 또는 GitHub Actions를 통한 예약 자동화
  • Python이 과할 때 쓸 수 있는 노코드 대안

제 추천은 이렇습니다. 대부분의 경우 2번인 Firebase API부터 시작하세요. 더 깔끔하고, 더 안정적이며, 중첩 HTML을 파싱하는 번거로움 없이 댓글에 접근할 수 있습니다. 과거 데이터가 필요할 때는 Algolia API를 더하세요. 그리고 그냥 빠른 스프레드시트가 필요하고 Python 프로젝트를 새로 띄우고 싶지 않은 순간을 위해 는 북마크해두세요.

더 깊이 파고들고 싶다면 HN 댓글을 용으로 스크래핑해보거나, GitHub Actions로 데일리 다이제스트 파이프라인을 만들거나, Algolia API를 탐색해서 지난 10년간 기술 트렌드가 어떻게 바뀌었는지 추적해보세요.

빠른 Hacker News 스크래핑을 위해 Thunderbit 사용해보기

자주 묻는 질문

Hacker News를 스크래핑하는 건 합법인가요?

HN의 데이터는 공개되어 있고, Y Combinator는 프로그램 방식 접근을 위한 공식 API도 제공합니다. 사이트의 는 읽기 전용 콘텐츠(프론트페이지, 아이템 페이지, 사용자 페이지)의 스크래핑을 허용하지만, 30초의 크롤링 지연을 요청합니다. 이 지연을 지키고, 상호작용이 필요한 엔드포인트(투표, 로그인)는 스크래핑하지 않는다면 문제 없습니다. 스크래핑 윤리에 대해서는 가이드를 참고하세요.

Hacker News에 공식 API가 있나요?

네. hacker-news.firebaseio.com/v0/는 무료이며, 인증이 필요 없고, 스토리, 댓글, 사용자 프로필, 모든 피드 타입(top, new, best, ask, show, jobs)을 제공합니다. 깔끔한 JSON을 반환하며 명시된 rate limit은 없지만, 요청 빈도는 예의 있게 조절하는 것이 좋습니다.

Python으로 Hacker News 댓글을 어떻게 스크래핑하나요?

Firebase API를 사용해 스토리 아이템을 가져오면 kids 필드(최상위 댓글 ID 배열)를 얻을 수 있습니다. 각 댓글도 자체 kids 필드를 가진 하나의 아이템이며, 답글을 가리킵니다. 각 댓글과 그 자식들을 가져오는 함수를 이용해 재귀적으로 트리를 따라가면 됩니다. 전체 코드는 위의 "댓글 스크래핑하기(재귀 트리 탐색)" 섹션을 참고하세요. 또는 는 중첩 댓글 트리 전체를 한 번에 반환하므로, 댓글이 많은 스토리에는 훨씬 빠릅니다.

코드를 작성하지 않고 Hacker News를 스크래핑할 수 있나요?

네. 는 Chrome 확장 프로그램으로 동작합니다. HN을 열고 "AI Suggest Fields"를 클릭하면 제목, URL, 점수, 작성자 같은 열을 자동으로 식별합니다. "Scrape"를 누르면 Excel, Google Sheets, Airtable, Notion으로 바로 내보낼 수 있습니다. 페이지네이션도 처리하고, 서브페이지를 방문해 댓글 데이터도 가져올 수 있습니다. Python도, 선택자도, 유지보수도 필요 없습니다.

Hacker News의 과거 데이터를 어떻게 얻나요?

가 가장 좋은 도구입니다. search_by_date 엔드포인트와 numericFilters=created_at_i>TIMESTAMP를 사용해 날짜 범위를 필터링하세요. 키워드로 검색하고, 스토리 유형으로 필터링하고, 최대 500페이지까지 결과를 페이지네이션할 수 있습니다. 대규모 히스토리 분석을 위한 공개 데이터셋도 (전체 아카이브), (2,800만 레코드), (400만 개 스토리)에서 이용할 수 있습니다.

더 알아보기

목차

Thunderbit 사용해 보기

리드와 기타 데이터를 단 2번 클릭으로 스크랩. AI 기반.

Thunderbit 받기 무료예요
AI로 데이터 추출하기
데이터를 Google Sheets, Airtable, Notion으로 쉽게 전송하세요
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week