Reddit, один из крупнейших публичных дискуссионных архивов в сети, и публичные листинги сабреддитов являются полезным сигналом для исследований: какие темы в тренде, как сообщество ранжирует ссылки, какие внешние источники публикуются и как меняются оценки и счётчики комментариев со временем. В этом руководстве показано, как скрапить публичные данные Reddit с помощью Python, используя Crawlbase Crawling API, причём всё ограничено только публичными страницами листинга.
Сразу уточним: всё здесь касается публичных, агрегированных данных из публичных сабреддитов и тематических листингов. Это означает заголовки постов, оценки и количество голосов, счётчики комментариев, сабреддит, к которому относится пост, и ссылку, на которую указывает каждый пост. Это не охватывает ничего за логином, закрытые сабреддиты, личные сообщения или персональные данные отдельных пользователей. Условия использования Reddit ограничивают автоматизированный доступ, поэтому прочитайте раздел о правовых аспектах в конце, прежде чем направить скрапер на что-либо реальное, и предпочитайте официальный Reddit API для любого производственного использования.
Что вы построите
Небольшой Python-скрипт, который берёт публичный URL сабреддита или тематического листинга, загружает полностью отрендеренную страницу через Crawling API с JavaScript-токеном и парсит несколько публичных полей из каждого поста в листинге:
- Заголовок, публичный текст заголовка каждого поста.
- Оценка / голоса, совокупный счётчик голосов, отображаемый у поста.
- Количество комментариев, число комментариев у поста в виде числа.
-
Сабреддит, сообщество, к которому относится пост (например,
r/technology). - Ссылка, постоянная ссылка или внешний URL, на который указывает пост.
Обратите внимание, что намеренно отсутствует: никаких имён пользователей, профилей авторов, текста комментариев, разбивки по голосам. Это персональные данные людей, и их сбор выходит за рамки данного руководства намеренно. Мы агрегируем на уровне поста и сообщества, но не конкретного человека.
Почему обычный запрос не возвращает данные с Reddit
Запросите URL листинга Reddit с обычным HTTP-клиентом, и вы, как правило, получите что-то близкое к бесполезному: JavaScript-оболочку, интерстициальную страницу согласия на куки или страницу-блокировку. Текущий фронтенд Reddit рендерит листинги постов на стороне клиента, поэтому заголовки, оценки и ссылки появляются только после того, как скрипты страницы выполнятся в браузере. Кроме того, Reddit быстро помечает автоматизированный трафик. Диапазоны IP датацентров, отсутствующее поведение браузера и повторяющиеся паттерны запросов ограничиваются по скорости или блокируются задолго до загрузки листинга.
Таким образом, рабочий скрапер Reddit требует двух вещей в одном запросе: реальный браузер, рендерящий страницу, и IP-адрес, который Reddit воспринимает как обычного посетителя. Можно построить это самостоятельно с headless-браузером и пулом ротирующих резидентных прокси, но поддержание этого стека в рабочем состоянии и является большей частью работы. Crawling API объединяет оба компонента в одном вызове. Вы передаёте ему URL с JavaScript-токеном, он рендерит страницу через доверенный резидентный IP и возвращает готовый HTML для парсинга. Более подробный обзор см. в нашем руководстве как краулить JavaScript-сайты.
Crawlbase предлагает два типа токенов. Обычный токен загружает статический HTML; JavaScript (JS) токен сначала рендерит страницу в реальном браузере. Листинги Reddit рендерятся на стороне клиента, поэтому здесь нужен JS-токен. Обычный токен возвращает ту же оболочку, что и обычный запрос, без ничего полезного для парсинга.
Предварительные требования
Несколько вещей, которые нужно подготовить заранее. Ни одна не займёт много времени.
Базовый Python. Вы должны уметь запускать скрипты и устанавливать пакеты через pip. Если вы только начинаете работать с парсингом HTML, наш вводный материал как использовать BeautifulSoup в Python охватывает сторону извлечения.
Python 3.8 или новее. Проверьте командой python --version. Если его нет, установите с python.org.
Аккаунт Crawlbase и JS-токен. Зарегистрируйтесь, откройте дашборд и скопируйте JavaScript (JS) токен со страницы документации аккаунта. Обращайтесь с ним как с паролем: он аутентифицирует ваши запросы, поэтому не добавляйте его в систему контроля версий. Бесплатный уровень даёт до 5 000 запросов для тестирования.
Настройка проекта
Создайте изолированное виртуальное окружение, затем установите две библиотеки, необходимые скраперу.
python --version python -m venv reddit_env source reddit_env/bin/activate pip install crawlbase beautifulsoup4
В Windows активируйте командой reddit_env\Scripts\activate вместо строки с source. Две зависимости выполняют работу: crawlbase, официальный клиент Crawling API, а beautifulsoup4 парсит возвращаемый HTML и позволяет извлекать отдельные поля по селектору.
Шаг 1: загрузка отрендеренного листинга
Начните с получения готовой страницы. Импортируйте CrawlingAPI, инициализируйте его JS-токеном и запросите публичный URL листинга. Предыдущая версия этого туториала указывала на публичный тематический листинг https://www.reddit.com/t/technology/, который является хорошей обезличенной целью для начала. Проверяйте код статуса перед парсингом, чтобы сбои были очевидны, а не молчаливы.
from crawlbase import CrawlingAPI crawlbase_token = "YOUR_CRAWLBASE_TOKEN" api = CrawlingAPI({"token": crawlbase_token}) def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 5000} response = api.get(page_url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None if __name__ == "__main__": listing_url = "https://www.reddit.com/t/technology/" html = crawl(listing_url) print(html[:500] if html else "No HTML returned")
Два параметра ожидания важны для клиентски рендерируемой цели. ajax_wait указывает API ждать завершения загрузки асинхронного контента, а page_wait выдерживает фиксированное время в миллисекундах после загрузки, чтобы посты с поздним рендерингом появились до захвата страницы. Пять секунд, разумная отправная точка; увеличьте, если листинг возвращается пустым. Запустите скрипт, и вы должны увидеть реальную разметку листинга, что подтвердит работу рендеринга до написания хотя бы одного селектора.
Листинг заполнился только потому, что страница была отрендерена через доверенный IP в одном вызове. Crawling API принимает JS-токен, запускает страницу в реальном браузере, на стороне сервера ротирует резидентные IP и возвращает готовый HTML, избавляя вас от запуска headless-флота и пула прокси. Попробуйте на публичном сабреддите на бесплатном уровне.
Шаг 2: парсинг публичных полей постов
Получив отрендеренный HTML, загрузите его в BeautifulSoup и извлеките публичные поля из каждого поста. Разметка листинга Reddit группирует каждый пост внутри кастомного элемента shreddit-post, а полезные значения находятся в атрибутах этого элемента, а не в глубоко вложенных, часто переименовываемых CSS-классах. Нужные атрибуты: post-title, score, comment-count, subreddit-prefixed-name и permalink. Чтение атрибутов намного устойчивее, чем погоня за отрендеренными виджетами.
from bs4 import BeautifulSoup BASE = "https://www.reddit.com" def to_int(value): try: return int(value) except (TypeError, ValueError): return None def scrape_listing(html): soup = BeautifulSoup(html, "html.parser") posts = [] for post in soup.select("shreddit-post"): permalink = post.get("permalink", "") link = f"{BASE}{permalink}" if permalink.startswith("/") else permalink posts.append({ "title": post.get("post-title"), "score": to_int(post.get("score")), "comment_count": to_int(post.get("comment-count")), "subreddit": post.get("subreddit-prefixed-name"), "link": link, }) return posts
Каждый пост становится плоской записью публичных агрегированных полей. Атрибуты score и comment-count возвращаются как строки, поэтому to_int приводит их к числам и возвращает None при отсутствии атрибута, не прерывая выполнение. Атрибут permalink является относительным путём сайта вида /r/technology/comments/<id>/<slug>/, поэтому мы присоединяем его к базовому хосту, чтобы получить полную ссылку. Заметьте: поля автора в этой записи нет намеренно.
Reddit меняет разметку без предупреждения, поэтому данный код читает атрибуты элемента shreddit-post, а не хрупкие вложенные классы. Если поле возвращается как None, заново проверьте live-страницу в инструментах разработчика браузера и обновите имя атрибута. Периодическое обслуживание, норма для любого боевого скрапера, а не признак поломки. Для обновления знаний о выборе устойчивых селекторов см. скрапинг сайтов без блокировки.
Шаг 3: объединяем всё с пагинацией
Одна страница листинга показывает только первую партию постов. Более старые, дружественные к рендерингу эндпоинты листинга Reddit принимают токен after для следующей страницы, но наиболее надёжный способ пагинировать JS-рендеренный листинг через Crawling API, запрашивать публичный эндпоинт-компаньон .json листинга, который публичен и возвращает те же посты плюс курсор after. Здесь мы делаем всё просто: пагинируем публичный листинг сабреддита, собирая фиксированное количество страниц с паузами между запросами.
import json import time from crawlbase import CrawlingAPI from bs4 import BeautifulSoup crawlbase_token = "YOUR_CRAWLBASE_TOKEN" api = CrawlingAPI({"token": crawlbase_token}) BASE = "https://www.reddit.com" def crawl(page_url): options = {"ajax_wait": "true", "page_wait": 5000} response = api.get(page_url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print(f"Request failed: {response['status_code']}") return None def scrape_subreddit(subreddit, max_pages=3): records = [] after = None for _ in range(max_pages): url = f"{BASE}/r/{subreddit}/.json?limit=25" if after: url += f"&after={after}" body = crawl(url) if not body: break data = json.loads(body)["data"] for child in data["children"]: post = child["data"] records.append({ "title": post.get("title"), "score": post.get("score"), "comment_count": post.get("num_comments"), "subreddit": f"r/{post.get('subreddit')}", "link": f"{BASE}{post.get('permalink', '')}", }) after = data.get("after") if not after: break time.sleep(3) return records if __name__ == "__main__": posts = scrape_subreddit("technology", max_pages=3) print(json.dumps(posts, indent=2, ensure_ascii=False))
Публичный эндпоинт .json возвращает те же агрегированные поля с более чистыми ключами: title, score, num_comments, subreddit и permalink. Курсор after в каждом ответе, единственное состояние, необходимое пагинации, поэтому цикл запрашивает следующую страницу, пока Reddit не перестаёт возвращать курсор или не достигается max_pages. Пауза time.sleep(3) между страницами не декоративна: темп является единственным наиболее важным фактором, определяющим, остаётся ли запуск здоровым. Если вы предпочитаете парсить отрендеренный HTML, замените crawl(url) URL листинга из шага 1 и передайте тело в scrape_listing.
Как выглядит вывод
Запустите скрипт, и вы получите чистый список публичных агрегированных записей, готовых к записи в JSON или CSV.
[ { "title": "Researchers demo a swallowable device that tracks vital signs", "score": 8421, "comment_count": 312, "subreddit": "r/technology", "link": "https://www.reddit.com/r/technology/comments/17xmvmg/swallowable_device_tracking_vital_signs_inside/" } ]
Для сохранения этих записей несколько строк из стандартной библиотеки превращают список в CSV или JSON-файл. Поля плоские и однородные, поэтому специальной обработки не требуется.
import csv import json def save_json(records, path="reddit_posts.json"): with open(path, "w", encoding="utf-8") as f: json.dump(records, f, indent=2, ensure_ascii=False) def save_csv(records, path="reddit_posts.csv"): fields = ["title", "score", "comment_count", "subreddit", "link"] with open(path, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fields) writer.writeheader() writer.writerows(records)
Отсюда данные готовы для анализа: ранжирование сабреддитов по медианной оценке, отслеживание внешних доменов, на которые ссылается сообщество, или построение графика активности комментариев за окно страниц. Если позднее вы планируете передать данные в модель, наш гайд как структурировать и очищать данные веб-скрапинга для AI и ML охватывает шаг нормализации, а веб-скрапинг для машинного обучения рассказывает, что делать с данными дальше.
Обработка ограничений скорости и ошибок
Два уровня могут ограничить запуск на Reddit, и устойчивый скрипт учитывает оба. Reddit ограничивает автоматизированный трафик по скорости и возвращает 429 Too Many Requests или 403 Forbidden, когда вы давите слишком сильно; Crawling API также имеет ограничения по тарифным планам. Следующие привычки помогут оставаться в пределах обоих.
-
Соблюдайте темп запросов. Пауза
time.sleep(3)между страницами, это минимум, а не максимум. Плотный цикл запросов, самый быстрый способ попасть под ограничение, поэтому добавляйте реальные паузы и не увлекайтесь параллелизмом. -
Следите за кодами статусов. Запуск, начинающий возвращать
429или403, сигнализирует о том, что текущий темп уже недостаточен. Снижайте темп, а не давите сильнее, и рассмотрите экспоненциальную задержку с несколькими повторными попытками. - Полагайтесь на ротацию. Пул резидентных IP распределяет запросы по множеству реальных пользовательских адресов, чтобы ни один не достигал лимита. Crawling API делает это за вас; если вы строите собственный стек, именно здесь стоит сосредоточиться.
- Держите объём низким, а цели разнообразными. Исследование публичных данных не требует обхода всей истории сабреддита. Выборочно запрашивайте нужные страницы и останавливайтесь.
Законен ли скрапинг Reddit?
Этот раздел нужно прочитать перед написанием производственного кода. Пользовательское соглашение Reddit и его Политика публичного контента ограничивают автоматизированный доступ и массовый сбор контента, а robots.txt Reddit определяет, что могут трогать краулеры. Автоматизированный скрапинг может нарушать эти условия вне зависимости от того, насколько аккуратны ваши инструменты, и ни один из приведённых выше кодов не меняет этого. Он лишь решает техническую часть. Сначала прочитайте условия Reddit и robots.txt и воспринимайте оба как границу того, что вы собираете.
Честные, ограничительные правила, которых стоит придерживаться. Собирайте только публичные агрегированные данные: заголовки постов, оценки, счётчики комментариев, сабреддит и ссылку, всё то, что любой может видеть без входа в систему. Рассматривайте имена пользователей, псевдонимы авторов, данные профилей и текст отдельных комментариев как персональные данные и не собирайте их, не создавайте профили идентифицируемых людей и не привязывайте контент к конкретному человеку. Никогда не скрапьте закрытые сабреддиты, контент за логином, личные сообщения или что-либо требующее аутентификации, и никогда не обходите логин или проверку для его получения. Когда речь идёт о персональных данных, применяются законы о конфиденциальности, такие как GDPR и CCPA: вам нужно законное основание для их обработки и вы должны выполнять запросы на удаление. Скрапер в этом руководстве намеренно остаётся на агрегированной, неперсональной стороне всех этих границ.
Для любого реального или коммерческого использования правильным инструментом является официальный Reddit API. Он создан для санкционированного доступа, даёт гарантированную структуру, предоставляет чёткие ограничения скорости и позволяет оставаться в рамках условий Reddit. Эта статья, технический обзор, строго ограниченный публичными агрегированными данными листинга. Она не является одобрением массового сбора персональных данных и не охватывает ничего за логином. Если вашему проекту нужно больше, чем небольшая выборка публичных полей, Reddit API или формальное соглашение о данных, правильный путь, а не более умный скрапер.
Ключевые выводы
- Листинги Reddit рендерятся на стороне клиента и защищены от ботов. Обычный запрос возвращает оболочку или блокировку, поэтому нужно отрендерить страницу перед парсингом.
-
Рендеринг и доверенный IP должны быть в одном вызове. Crawling API с JS-токеном делает и то, и другое;
ajax_waitиpage_waitуправляют временем ожидания контента. -
Парсите устойчивые сигналы. Атрибуты
shreddit-post(или ключи публичного эндпоинта.json) более надёжны, чем хрупкие вложенные классы. - Только агрегированные публичные поля. Извлекайте заголовок, оценку, количество комментариев, сабреддит и ссылку; никогда не берите имена пользователей, профили авторов или текст комментариев.
- Соблюдайте темп, ротируйте и предпочитайте официальный API. Держите объём низким, используйте резидентную ротацию и применяйте Reddit API для любого реального или коммерческого использования.
Часто задаваемые вопросы
Почему обычный запрос не возвращает данные с Reddit?
Потому что текущий фронтенд Reddit рендерит листинги постов на стороне клиента с помощью JavaScript и блокирует автоматизированный трафик. Обычный HTTP-запрос возвращает почти пустую оболочку, интерстициальную страницу куки или страницу-блокировку. Чтобы получить реальные публичные данные, нужно сначала отрендерить страницу, что и делает JS-токен Crawling API.
Нужен ли обычный токен или JS-токен для Reddit?
JS-токен для отрендеренных страниц листинга, поскольку обычный токен возвращает ту же оболочку, что и обычный запрос. Если вместо этого использовать публичный эндпоинт .json, ответ, это простой JSON, но маршрутизация через Crawling API всё равно даёт преимущество в виде доверенного IP и ротации, не позволяющих блокировать запуск.
Какие данные Reddit безопасно скрапить?
Только публичные агрегированные данные: заголовки постов, оценки и количество голосов, счётчики комментариев, сабреддит и ссылку, на которую указывает каждый пост. Имена пользователей, профили авторов и текст отдельных комментариев являются персональными данными и запрещены здесь. Закрытые сабреддиты, контент за логином и личные сообщения полностью выходят за рамки.
Стоит ли использовать официальный Reddit API или скрапить сайт?
Для любого реального, постоянного или коммерческого использования применяйте официальный Reddit API. Это санкционированный маршрут, дающий гарантированную структуру и чёткие ограничения скорости. Скрапинг небольшой выборки публичных полей листинга с подходом, описанным здесь, подходит для лёгких публичных исследований без API-доступа, при условии соблюдения условий Reddit, robots.txt и ограничений скорости.
Как обрабатывать пагинацию на многих страницах?
Запросите публичный эндпоинт листинга .json с параметром limit, прочитайте курсор after из каждого ответа и передайте его обратно как &after=<cursor> в следующем запросе. Повторяйте цикл, пока Reddit не перестаёт возвращать курсор или не достигается ограничение страниц, и делайте паузу несколько секунд между страницами, чтобы оставаться в пределах ограничений скорости.
Как избежать блокировки при скрапинге Reddit?
Держите частоту запросов низкой, добавляйте реальные паузы между страницами, чередуйте цели вместо обхода всей истории одного сабреддита и маршрутизируйте через ротирующие резидентные IP, чтобы ни один адрес не достигал лимита. Crawling API управляет ротацией и пулом доверенных IP. Следите за ответами 429 и 403 и снижайте темп, как только увидите их.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
