Как я собираю Hacker News с помощью Python: 2 способа, полный код

Последнее обновление: July 9, 2026
Как я собираю Hacker News с помощью Python: 2 способа, полный код

Несколько месяцев назад я решил собрать ежедневную подборку самых интересных материалов с Hacker News для команды Thunderbit. Сначала мне показалось, что проще всего просто добавить сайт в закладки и просматривать его каждое утро. Но уже через три дня я понял, что трачу по 20 минут в день только на чтение заголовков и копирование ссылок в таблицу.

Hacker News — один из самых насыщенных и концентрированных источников техно-новостей в интернете: примерно 13 миллионов визитов в месяц, около 1300 новых публикаций ежедневно и примерно 13 000 комментариев в сутки. Если вы следите за новыми технологическими трендами, мониторите упоминания бренда, строите воронку найма из тредов «Who’s Hiring» или просто хотите понимать, чем живёт developer-сообщество, вручную отслеживать всё это — заведомо проигрышная стратегия.

Хорошая новость: собирать данные с Hacker News на Python на удивление просто. В этом руководстве я покажу два полноценных способа — парсинг HTML через BeautifulSoup и официальный HN Firebase API — а также разберу пагинацию, экспорт данных, production-подходы и no-code альтернативу на случай, если Python кажется вам избыточным.

Зачем собирать Hacker News с помощью Python?

Hacker News — это не просто ещё один агрегатор ссылок. Это кураторская, основанная на сообществе лента, где самые интересные техно-истории поднимаются наверх благодаря голосам и активному обсуждению. Аудитория сильно смещена в сторону IT-профессионалов (примерно 76% мужчин, основная возрастная группа 25–34), а показатель прямого трафика в 66% говорит о том, что это лояльные, привычные читатели, а не случайные посетители.

Вот зачем люди собирают данные HN:

СценарийЧто вы получаете
Ежедневный технодайджестТоп-истории, оценки и ссылки в почте или Slack
Мониторинг бренда и конкурентовУведомления, когда упоминают вашу компанию или продукт
Анализ трендовОтслеживание технологий, языков и тем, которые набирают популярность
РекрутингРазбор тредов "Who’s Hiring" для вакансий, стеков и сигналов по зарплатам
Исследование контентаПоиск тем, которые хорошо заходят у аудитории
Анализ тональностиОценка мнений сообщества о продуктах, запусках и изменениях в индустрии

Компании с совокупной капитализацией свыше 400 миллиардов долларов — Stripe, Dropbox, Airbnb — получили на Hacker News важную раннюю обратную связь и первых пользователей. Drew Houston в апреле 2007 года опубликовал демо Dropbox на HN, оно вышло на первое место, а лист ожидания бета-версии вырос с 5000 до 75 000 пользователей всего за один день. Данные HN не просто интересны — они приносят коммерческую ценность.

Данные доступны публично, но структура сайта делает ручной сбор слишком трудоёмким. Поэтому автоматизация на Python — наиболее практичное решение.

Два способа собирать Hacker News на Python: обзор

В этом руководстве я покажу два полностью рабочих подхода:

  1. Парсинг HTML с помощью requests + BeautifulSoup — загружаем сырой HTML с news.ycombinator.com и разбираем его, чтобы извлечь данные о публикациях. Отличный вариант, если вы хотите освоить основы парсинга и брать ровно то, что отображается на странице.
  2. Официальный Hacker News Firebase API — обращаемся напрямую к JSON-эндпоинтам, без разбора HTML. Более надёжный вариант для продакшн-пайплайнов, доступа к комментариям и историческим данным.

Вот сравнение в лоб, чтобы было проще выбрать подходящий метод:

КритерийHTML-парсинг (requests + BS4)HN Firebase APIThunderbit (No-code)
Сложность настройкиСредняя (нужно разбирать HTML-селекторы)Низкая (JSON-эндпоинты)Нет (расширение Chrome в 2 клика)
Актуальность данныхРеальное время на главной страницеРеальное время (любой элемент по ID)Реальное время
Риск ограничения по частотеСредний (robots.txt указывает задержку 30 сек)Низкий (официальный, щедрый)Управляется Thunderbit
Доступ к комментариямСложно (вложенный HTML)Просто (рекурсивные item ID)Функция парсинга подстраниц
Исторические данныеОграниченноЧерез Algolia Search APIНе поддерживается
Лучше всего подходит дляИзучения основ парсингаНадёжных пайплайновНетехнических пользователей, быстрых выгрузок

Оба метода включают полный рабочий Python-код. А если вам нужны только данные — без написания кода — я покажу и такой вариант.

Перед началом

  • Уровень сложности: от новичка до среднего
  • Время: примерно 15–20 минут на каждый метод
  • Что понадобится:
    • Python 3.11+ установлен
    • Терминал или редактор кода
    • Браузер Chrome (если хотите посмотреть HTML HN или попробовать no-code-способ)
    • Thunderbit Chrome Extension (необязательно, для no-code метода)

scrape-hacker-news-methods.webp

Настраиваем 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: собираем Hacker News на Python через BeautifulSoup

Это классический путь: скачиваем 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 символов. Это и есть сырой HTML главной страницы HN, уже загруженный в память и готовый к разбору.

Шаг 2: разбираем HTML-структуру

HN использует табличную вёрстку — без современных CSS grid или flexbox. Каждая публикация на странице состоит из двух ключевых строк <tr>:

  • Строка истории (<tr class="athing submission">): содержит номер, заголовок и ссылку
  • Строка метаданных (следующая строка <tr>): содержит очки, автора, время и число комментариев

Важные селекторы:

  • span.titleline > a — заголовок и URL истории
  • span.score — количество голосов (например, "118 points")
  • a.hnuser — имя пользователя автора
  • span.age — время публикации
  • Последний <a> в .subtext со словом "comment" — число комментариев

Если щёлкнуть правой кнопкой по любому заголовку на HN в Chrome и выбрать "Inspect", вы увидите примерно такое:

<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&nbsp;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()
        # Количество комментариев: последний <a> со словом "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,
    })

# Фильтруем истории с 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, которых может не быть в каждой строке (например, у job-постов нет оценки).
  • В HN между числом и словом "comments" используется \xa0 — неразрывный пробел, поэтому мы делим строку именно по нему.
  • Если история ведёт на другую страницу HN (например, посты вида "Ask HN"), URL будет относительным и начнётся с 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/...'},
 ...]

Это 30 историй с первой страницы. Но на HN всегда есть сотни активных публикаций. Пагинацию мы разберём чуть позже.

Способ 2: собираем Hacker News через официальный API

HN Firebase API — это официальный способ получать данные Hacker News. Без авторизации, без API-ключей, без разбора HTML. Вы получаете аккуратные JSON-ответы. Я использую этот метод для всего, что должно стабильно работать в продакшене.

Основные API-эндпоинты, которые нужно знать

Базовый URL: 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 ID историй формата "Ask HN"Тот же формат
/v0/showstories.jsonДо 200 ID историй формата "Show HN"Тот же формат
/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 историй за один запрос — без селекторов, без разбора HTML, просто 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)  # Будем вежливы — небольшая пауза между запросами

# Сортируем по score и показываем топ-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

Вы увидите структурированные данные по историям, а затем — вложенный предпросмотр комментариев. Данные чище, доступ к комментариям проще, и нет риска, что парсер сломается из-за изменения CSS-класса на стороне HN.

Выходим за пределы первой страницы: пагинация и исторические данные

Большинство туториалов по HN-скрейпингу останавливаются на первой странице — 30 историях. Для быстрого демо этого достаточно, но в реальных задачах часто нужно больше.

Парсим несколько страниц с BeautifulSoup

Пагинация HN использует простой шаблон URL: ?p=2, ?p=3 и так далее. Каждая страница содержит 30 историй, а сайт отдаёт примерно до 20 страниц (около 600 историй в сумме). Дальше идут пустые страницы.

import time

def scrape_hn_pages(num_pages=5):
    """Собирает истории с нескольких страниц главной HN."""
    all_stories = []

    for page in range(1, num_pages + 1):
        url = f"https://news.ycombinator.com/news?p={page}"
        response = requests.get(url, headers=headers)
        soup = BeautifulSoup(response.text, "html.parser")

        story_rows = soup.select("tr.athing")
        if not story_rows:
            print(f"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")

        # Соблюдаем crawl-delay 30 секунд из 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) здесь очень важен. В robots.txt HN прямо просит соблюдать 30-секундную задержку между обходами. Если игнорировать это требование, можно получить ограничение по частоте запросов (HTTP 429) или временную блокировку. Пять страниц с интервалом 30 секунд займут около 2,5 минут — не мгновенно, но уважительно.

Если вы не хотите вручную управлять пагинацией, Thunderbit автоматически обрабатывает пагинацию по кликам и бесконечную прокрутку. Он нажимает кнопку "More" внизу страниц HN без какой-либо настройки.

Собирать страницы Hacker News с помощью AI

Доступ к историческим данным Hacker News через Algolia API

Firebase API даёт актуальные данные. Для исторического анализа — например, «Какие Python-истории были в топе в 2023 году?» или «Как менялся охват темы AI за последние 5 лет?» — нужен 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):
    """Ищет по HN через Algolia API."""
    params = {
        "query": query,
        "tags": tags,
        "page": page,
        "hitsPerPage": hits_per_page,
    }
    resp = requests.get(f"{ALGOLIA_BASE}/search", params=params)
    return resp.json()

# Пример: найти истории про Python scraping с 10+ очками начиная с января 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']}")

Для запросов с фильтрацией по дате используйте numericFilters:

import calendar, datetime

# Истории с 1 января 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 работает быстро (5–9 мс серверного времени обработки), не требует API-ключа и поддерживает пагинацию до 500 страниц. Для массового исторического анализа это лучший вариант.

Экспорт данных Hacker News в CSV, Excel и Google Sheets

Практически каждый туториал по HN заканчивается выводом pprint() в терминал. Для отладки это удобно, но если вы делаете ежедневный дайджест или анализ трендов, данные нужно сохранять в файл. Вот как это сделать.

Экспорт в CSV на Python

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)

Экспорт в Excel на Python

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 (по желанию)

Для автоматизированных сценариев вы можете отправлять данные напрямую в Google Sheets через библиотеку gspread. Для этого нужно один раз настроить service account в Google Cloud:

import gspread

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

# Преобразуем истории в строки
header = list(stories[0].keys())
rows = [list(s.values()) for s in stories]

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

No-code альтернатива экспорту

Если настройка service account и написание кода для экспорта кажется более сложной задачей, чем сам сбор данных, я вас понимаю. В Thunderbit мы сделали бесплатный экспорт данных, который позволяет отправлять собранную информацию напрямую в Excel, Google Sheets, Airtable или Notion — без кода, без учётных данных, без поддержки пайплайна. Для разовой выгрузки это действительно быстрее. Об этом ещё ниже.

Делаем скрейпер готовым к продакшену: обработка ошибок, кеширование и расписание

Если вы запускаете скрейпер один раз ради интереса, код выше подойдёт. Но если он должен работать ежедневно в составе процесса, нужны дополнительные элементы.

Обработка ошибок и повторные попытки

Сети падают. Серверы ограничивают частоту. Одна неудачная попытка не должна валить весь сбор. Вот функция retry с экспоненциальной задержкой:

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):
    """Загружает 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 аккуратно берёт на себя retry-логику. Она попробует повторить запрос до 5 раз с экспоненциальной задержкой и jitter — начиная с 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 раз в течение часа — и только первый запуск действительно пойдёт в сеть. Это инструмент, который часто рекомендуют на форумах, и не зря.

Разделяйте сбор и разбор данных

Паттерн, который любят опытные скрейперы: сначала скачайте сырые данные, потом разбирайте их. Так, если в логике парсинга окажется баг, вы исправите его и просто заново выполните разбор без повторной загрузки.

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)

# Запуск каждый день в 8:30 UTC
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 * * *'  # Ежедневно в 8:30 UTC
  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 может отключить запланированные workflow в репозиториях без активности в течение 60 дней. Всегда добавляйте workflow_dispatch, чтобы можно было запускать workflow вручную для теста.

Если хотите более простой вариант, Scheduled Scraper в Thunderbit позволяет задать расписание обычным языком — например, «собирать каждое утро в 8:00» — без сервера и без настройки cron.

Когда Python избыточен: no-code способ собрать Hacker News

Скажу честно, даже несмотря на то, что я люблю Python и моя команда делает инструменты для разработчиков. Если вам нужны просто сегодняшние топ-100 историй HN в таблице — прямо сейчас, один раз — писать, отлаживать и запускать Python-скрипт не нужно. Одна только подготовка окружения (виртуальное окружение, установка пакетов, поиск селекторов) занимает больше времени, чем сам сбор данных.

Вот здесь и подходит Thunderbit. Рабочий процесс такой:

  1. Откройте news.ycombinator.com в Chrome
  2. Нажмите иконку расширения Thunderbit, затем выберите "AI Suggest Fields"
  3. AI проанализирует страницу и предложит столбцы: Title, URL, Score, Author, Comment Count, Time Posted
  4. При необходимости отредактируйте поля — переименуйте, удалите или добавьте свои; можно даже добавить AI-подсказку вроде "Categorize as AI/DevTools/Web/Other"
  5. Нажмите "Scrape" — данные появятся в структурированной таблице
  6. Экспортируйте в Excel, Google Sheets, Airtable или Notion

Два клика — и у вас структурированные данные. Без селекторов, без кода, без сопровождения.

Ещё одно важное преимущество: AI в Thunderbit автоматически подстраивается под изменения вёрстки. Классические скрейперы на CSS-селекторах ломаются, когда сайт меняет разметку — а у HN HTML хоть и довольно стабилен, но всё же менялся (например, класс class="athing submission" обновлялся, а span.titleline заменил старый a.storylink). AI-скрейпер каждый раз читает страницу заново, поэтому ему всё равно, как называются CSS-классы.

python-vs-thunderbit-comparison.webp

Thunderbit также умеет работать с пагинацией (автоматически нажимает кнопку "More" на HN) и собирать данные со страниц подстраниц (переходить на страницу комментариев каждой истории и вытаскивать обсуждение). Для сценария обогащения комментариями это аналог рекурсивного API-кода из Способа 2 — только без единой строчки кода.

Компромисс простой: Python — правильный выбор, если вам нужна кастомная логика, сложные преобразования данных, автоматизация по расписанию или вы учитесь программировать. Thunderbit — правильный выбор, если данные нужны быстро, вы не хотите поддерживать код или вы не разработчик. Выбирайте инструмент под задачу.

Python против API против no-code: какой способ выбрать?

Вот полная схема принятия решения:

КритерийBeautifulSoup (HTML)Firebase APIAlgolia APIThunderbit (No-code)
Нужный уровень навыкаPython среднего уровняPython для новичковPython для новичковНе нужен
Время на настройку10–15 мин5–10 мин5–10 мин2 мин
Нагрузка на поддержкуСредняя (селекторы ломаются)Низкая (стабильный JSON)Низкая (стабильный JSON)Нет
Глубина данныхТолько главная страницаЛюбой item, пользователиПоиск + историяГлавная + подстраницы
КомментарииСложноПросто (рекурсивно)Просто (вложенное дерево)Парсинг подстраниц
Исторические данныеНетНетДа (полный архив)Нет
ЭкспортПишете самиПишете самиПишете самиВстроенный (Excel, Sheets и т. д.)
Расписаниеcron / GitHub Actionscron / GitHub Actionscron / GitHub ActionsВстроенный планировщик
Лучший вариант дляОбучения скрейпингуНадёжных пайплайновИсследований и аналитикиБыстрых выгрузок

Если вы учите Python или делаете что-то кастомное, выбирайте Способ 1 или 2. Если нужны исторические данные, добавьте Algolia API. Если хотите просто получить данные без кода, попробуйте Thunderbit.

Попробовать Thunderbit для сбора данных с Hacker News

Итоги и ключевые выводы

Теперь у вас в арсенале есть:

  • Два полноценных Python-способа собирать данные с Hacker News — BeautifulSoup для парсинга HTML и Firebase API для чистых JSON-данных
  • Пагинация для сбора данных дальше первой страницы, включая Algolia API для исторических данных, уходящих в 2007 год
  • Код экспорта в CSV, Excel и Google Sheets — потому что данные, лежащие в терминале, никому в команде не помогут
  • Production-подходы — retry-логика, кеширование, разделение сбора и парсинга, а также автоматический запуск по cron или GitHub Actions
  • No-code альтернативу на случай, если Python — это уже лишний инструмент

Моя рекомендация: для большинства задач начинайте с Firebase API (Способ 2). Он чище, надёжнее и позволяет получать комментарии без мучений с разбором вложенного HTML. Если нужны исторические данные — подключайте Algolia API. А Thunderbit держите под рукой на случай, когда нужен быстрый spreadsheet и нет желания поднимать целый 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/ бесплатен, не требует авторизации и даёт доступ к историям, комментариям, профилям пользователей и всем типам лент (top, new, best, ask, show, jobs). Он возвращает чистый JSON и не заявляет жёсткого лимита по частоте, хотя соблюдать разумную частоту запросов всегда стоит.

Как собирать комментарии Hacker News на Python?

Через Firebase API сначала получите item истории, чтобы достать её поле kids — массив ID комментариев первого уровня. Каждый комментарий сам является item со своим kids для ответов. Обходите дерево рекурсивно функцией, которая загружает каждый комментарий и его потомков. Полный код есть в разделе "Собираем комментарии (рекурсивный обход дерева)" выше. Альтернативно, эндпоинт /items/<id> в Algolia API возвращает полное вложенное дерево комментариев за один запрос — это гораздо быстрее для историй с большим количеством обсуждений.

Можно ли собрать Hacker News без кода?

Да. AI web scraper Thunderbit работает как расширение Chrome: откройте HN, нажмите "AI Suggest Fields", и система автоматически определит столбцы вроде title, URL, score и author. Нажмите "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 (28 миллионов записей) и Hugging Face (4 миллиона историй).

Узнать больше

Shuai Guan
Shuai Guan
Генеральный директор Thunderbit | Эксперт по автоматизации данных с помощью ИИ Shuai Guan — генеральный директор Thunderbit и выпускник инженерного факультета Мичиганского университета. Опираясь почти на десятилетний опыт в сфере технологий и SaaS-архитектуры, он специализируется на том, чтобы превращать сложные модели ИИ в практичные no-code инструменты для извлечения данных. В этом блоге он делится честными, проверенными в деле инсайтами о веб-скрейпинге и стратегиях автоматизации, чтобы помочь вам выстраивать более умные, data-driven рабочие процессы. Когда он не занимается оптимизацией потоков данных, тот же внимательный подход к деталям он переносит в своё увлечение фотографией.

Попробуй Thunderbit

Собирай лиды и другие данные всего в 2 клика. На базе AI.

Получить Thunderbit Это бесплатно
Извлекай данные с помощью AI
Легко передавай данные в Google Sheets, Airtable или Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week