Криптовалютные рынки работают круглосуточно, и публичные страницы рынков на таких сайтах, как CoinGecko, публикуют именно структурированные данные, которые лежат в основе отслеживания цен, портфельных дашбордов и аналитики: название и символ монеты, текущая цена, изменение за 24 часа, объём торгов за 24 часа и рыночная капитализация. Эта таблица обновляется постоянно, а читать её вручную по десяткам монет медленно и чревато ошибками.
В этом руководстве показано, как извлечь данные крипторынка с помощью Python надёжным способом. Вы создаёте небольшой работоспособный скрапер, который получает отрисованную страницу рынка через Crawling API, парсит каждую строку с BeautifulSoup, обрабатывает пагинацию и экспортирует чистые JSON и CSV. Весь процесс ограничен публичными рыночными данными, теми, что любой посетитель видит без аккаунта, а раздел о законности ближе к концу не является шаблонным, поэтому прочитайте его.
Что вы создадите
Python-скрипт, который получает публичную страницу крипторынка, обходит каждую строку монеты в отрисованной таблице и извлекает структурированную запись на монету. В качестве рабочего примера используется основная таблица рынков на CoinGecko. Мы извлекаем следующие поля:
- Name полное название монеты, например Bitcoin или Ethereum.
- Symbol тикер-символ, например BTC или ETH.
- Price текущая цена в выбранной котировочной валюте.
- Change 24h процентное изменение цены за последние 24 часа.
- Volume 24h суммарный объём торгов за последние 24 часа.
- Market cap общая рыночная капитализация монеты.
Почему обычный запрос не работает на странице крипторынка
Если вы запросите URL крипторынка с простым HTTP-клиентом, то получите статус 200, но только часть данных в теле. Против вас работают два фактора. Во-первых, эти таблицы рынков загружают строки в браузере через JavaScript, поэтому исходный HTML является тонкой оболочкой, которая заполняется только после выполнения скриптов страницы. Парсите этот первый ответ, и вы получите пустую таблицу вместо полного списка монет. Во-вторых, высокотрафиковые сайты рынков быстро помечают автоматический трафик: IP-адреса дата-центров и паттерны запросов, не похожие на настоящий браузер, получают ограничения по скорости или проверки до того, как достигнут отрисованного контента.
Поэтому работоспособный скрапер рыночных данных требует двух вещей в одном запросе: браузера, отрисовывающего страницу, и IP-адреса, который платформа воспринимает как настоящего посетителя. Вы можете собрать это самостоятельно с headless-браузером и пулом ротируемых резидентских прокси, но сборка и поддержание их в рабочем состоянии и составляет основную часть работы. Crawling API объединяет оба в один вызов: вы передаёте ему URL с JavaScript-токеном, он отрисовывает страницу за доверенным IP и возвращает готовый HTML для парсинга.
Crawlbase предлагает два типа токенов. Обычный токен получает статический HTML; JavaScript (JS) токен сначала отрисовывает страницу в настоящем браузере. Таблица крипторынка заполняет строки на стороне клиента, поэтому здесь нужен JS-токен. Обычный токен возвращает такую же тонкую оболочку, что и обычный запрос, из которой почти нечего парсить.
Предварительные требования
Перед написанием кода вам нужно подготовить несколько вещей. Ни одна из них не займёт много времени.
Базовые знания Python. Вы должны уметь писать и запускать Python-скрипт и устанавливать пакеты с помощью pip. Если вы новичок в парсинге, руководство по BeautifulSoup является хорошим дополнением к этому учебнику.
Python версии 3.8 или новее. Проверьте свою версию командой python --version. Если у вас её нет, установите с python.org или через дистрибутив вроде Anaconda и убедитесь, что Python есть в PATH.
Аккаунт и JS-токен Crawlbase. Зарегистрируйтесь, откройте панель управления и скопируйте JavaScript (JS) токен со страницы документации аккаунта. Crawlbase включает 1000 бесплатных запросов для старта, чего вполне достаточно для этого руководства. Относитесь к токену как к паролю: он аутентифицирует ваши запросы, поэтому не добавляйте его в систему контроля версий.
Настройка проекта
Создайте виртуальное окружение, чтобы зависимости проекта оставались изолированными, а затем установите библиотеки, необходимые скраперу.
python --version python -m venv crypto_env source crypto_env/bin/activate pip install crawlbase beautifulsoup4
В Windows активируйте окружение командой crypto_env\Scripts\activate вместо строки с source. Две зависимости выполняют основную работу: crawlbase является официальным клиентом для Crawling API, а beautifulsoup4 парсит возвращаемый HTML, чтобы вы могли извлечь отдельные поля по CSS-селекторам. Оба модуля json и csv входят в стандартную библиотеку, поэтому для шага экспорта больше ничего устанавливать не нужно.
Шаг 1: Получение отрисованной страницы рынка
Начните с получения готовой страницы. Импортируйте класс CrawlingAPI, инициализируйте его с вашим JS-токеном и запросите URL рынка. Таблица загружается асинхронно, поэтому передайте ajax_wait и page_wait, чтобы дождаться динамического контента до захвата страницы. Проверка Crawlbase cb_status (legacy pc_status) перед парсингом делает ошибки явными, а не скрытыми.
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"}) OPTIONS = { "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/122.0", "ajax_wait": "true", "page_wait": 5000, } def crawl(page_url): response = api.get(page_url, OPTIONS) if response["headers"]["cb_status"] == "200": return response["body"].decode("utf-8") print(f"Request failed: {response['headers']['cb_status']}") return None if __name__ == "__main__": market_url = "https://www.coingecko.com/" html = crawl(market_url) print(html[:500] if html else "No HTML returned")
Два параметра ожидания важны для цели, отрисовываемой на стороне клиента. ajax_wait указывает API дождаться загрузки асинхронного контента, а page_wait выдерживает фиксированное количество миллисекунд после загрузки, чтобы строки, обновляющиеся в реальном времени, установились до захвата страницы. Пять секунд являются разумным стартом; увеличьте значение, если таблица возвращается неполной. Запустите python crypto_scraper.py, и вы должны увидеть реальную разметку рынка, а не оболочку, возвращаемую обычным запросом: это подтверждает работу рендеринга до написания первого селектора.
Страница крипторынка требует отрисованной таблицы за доверенным IP, в одном вызове, именно это настраивают параметры ajax_wait и page_wait выше. Crawling API принимает JS-токен, запускает страницу в настоящем браузере, ротирует через резидентские IP на стороне сервера и передаёт вам готовый HTML, поэтому вам не нужно самостоятельно запускать флот headless-браузеров и пул прокси. Для начала направьте его на публичную страницу рынка в рамках бесплатного тарифа.
Шаг 2: Парсинг строки одной монеты
Страница рынка, это таблица, где каждая tr, одна монета. Загрузите отрисованный HTML в BeautifulSoup и читайте нужные ячейки. Столбцы располагаются в известном порядке, а название и символ находятся вместе в ячейке монеты, поэтому небольшой вспомогательный метод, отображающий каждое поле на его ячейку, делает парсинг читаемым. Каждый поиск защищён, поэтому отсутствующее поле возвращает None вместо сбоя при запуске.
from bs4 import BeautifulSoup ROW_SELECTOR = 'table[data-coin-table-target="table"] > tbody > tr' def text_of(node, selector): el = node.select_one(selector) return el.get_text(strip=True) if el else None def parse_row(row): return { "name": text_of(row, 'td[data-view-component="true"] a span.tw-text-gray-700'), "symbol": text_of(row, 'td[data-view-component="true"] a span.tw-text-gray-500'), "price": text_of(row, 'td[data-target="price.price"]'), "change_24h": text_of(row, 'td.tw-text-right span[data-target="price-change-percentage-24h"]'), "volume_24h": text_of(row, 'td.tw-text-right span[data-coin-table-target="totalVolume"]'), "market_cap": text_of(row, 'td.tw-text-right span[data-coin-table-target="marketCap"]'), }
Вспомогательный метод text_of запрашивает один элемент внутри строки и возвращает его очищенный текст или None при отсутствии элемента, поэтому монета с пропущенным полем не прерывает цикл. Ячейка монеты содержит и полное name, и symbol в двух вложенных span-элементах, тогда как цена, изменение за 24 часа, объём и рыночная капитализация располагаются каждое в своей ячейке с выравниванием по правому краю. Чтение каждой строки как единого целого сохраняет выравнивание каждого поля с правильной монетой даже при сдвиге столбца.
Сгенерированные имена классов и атрибуты data-target рыночного сайта меняются без предупреждения. Относитесь к приведённым здесь селекторам как к стартовому шаблону, а не как к контракту. Когда поле возвращается со значением None, повторно осмотрите живую страницу в DevTools браузера и обновите селектор. Периодическое обслуживание селекторов является нормой для любого продакшн-скрапера, а не признаком поломки.
Шаг 3: Обход всех строк на странице
Имея парсер строк, выберите все строки в таблице и сопоставьте каждую с записью. Небольшая обёртка повтора вокруг получения защищает от того, чтобы один медленный запрос завершил весь запуск.
import time def fetch_html(page_url, max_retries=2): for attempt in range(max_retries + 1): html = crawl(page_url) if html: return html if attempt < max_retries: print(f"Retrying ({attempt + 1}/{max_retries})...") time.sleep(1) print(f"Unable to fetch {page_url}") return None def parse_market(html): soup = BeautifulSoup(html, "html.parser") rows = soup.select(ROW_SELECTOR) return [parse_row(r) for r in rows if r.select_one('td[data-target="price.price"]')]
fetch_html повторяет неудавшееся получение до двух раз с коротким ожиданием, возвращая HTML при успехе и None после прекращения попыток. parse_market выбирает каждую строку монеты и сопоставляет каждую с записью, пропуская любую строку без ячейки с ценой, чтобы строки-разделители или строки заголовков не создавали пустые записи. Результатом является чистый список словарей монет с одной страницы.
Шаг 4: Обработка пагинации по всему рынку
Одна страница является срезом всего рынка. CoinGecko использует пагинацию с параметром ?page=, поэтому вы обходите каждую страницу до заданного максимума и собираете строки со всех них. Ограничение краулинга аргументом max_pages не даёт большому рынку выйти из-под контроля, а короткий сон между страницами регулирует темп запуска, чтобы не перегружать сайт.
def collect_all_coins(base_url, max_pages): records = [] for page in range(1, max_pages + 1): page_url = f"{base_url}?page={page}" html = fetch_html(page_url) if not html: continue page_coins = parse_market(html) if not page_coins: break records.extend(page_coins) print(f"Page {page}: {len(page_coins)} coins") time.sleep(2) return records
collect_all_coins последовательно запрашивает каждую страницу, парсит строки и останавливается досрочно, если страница не возвращает монет, что происходит после прохождения последней заполненной страницы. time.sleep(2) между страницами распределяет запросы. Настройте max_pages, чтобы управлять глубиной просмотра рейтинга рынка; только первая страница уже охватывает крупнейшие монеты по рыночной капитализации.
Шаг 5: Сборка полного скрипта
Теперь объедините части в один работоспособный скрипт: соберите монеты по страницам, а затем экспортируйте записи в JSON и CSV.
import csv import json import time from crawlbase import CrawlingAPI from bs4 import BeautifulSoup api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"}) OPTIONS = { "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/122.0", "ajax_wait": "true", "page_wait": 5000, } ROW_SELECTOR = 'table[data-coin-table-target="table"] > tbody > tr' def crawl(page_url): response = api.get(page_url, OPTIONS) if response["headers"]["cb_status"] == "200": return response["body"].decode("utf-8") print(f"Request failed: {response['headers']['cb_status']}") return None def fetch_html(page_url, max_retries=2): for attempt in range(max_retries + 1): html = crawl(page_url) if html: return html if attempt < max_retries: time.sleep(1) return None def text_of(node, selector): el = node.select_one(selector) return el.get_text(strip=True) if el else None def parse_row(row): return { "name": text_of(row, 'td[data-view-component="true"] a span.tw-text-gray-700'), "symbol": text_of(row, 'td[data-view-component="true"] a span.tw-text-gray-500'), "price": text_of(row, 'td[data-target="price.price"]'), "change_24h": text_of(row, 'td.tw-text-right span[data-target="price-change-percentage-24h"]'), "volume_24h": text_of(row, 'td.tw-text-right span[data-coin-table-target="totalVolume"]'), "market_cap": text_of(row, 'td.tw-text-right span[data-coin-table-target="marketCap"]'), } def parse_market(html): soup = BeautifulSoup(html, "html.parser") rows = soup.select(ROW_SELECTOR) return [parse_row(r) for r in rows if r.select_one('td[data-target="price.price"]')] def collect_all_coins(base_url, max_pages): records = [] for page in range(1, max_pages + 1): html = fetch_html(f"{base_url}?page={page}") if not html: continue page_coins = parse_market(html) if not page_coins: break records.extend(page_coins) time.sleep(2) return records def save_outputs(records): with open("crypto_market.json", "w") as f: json.dump(records, f, indent=2) if not records: return with open("crypto_market.csv", "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=records[0].keys()) writer.writeheader() writer.writerows(records) def main(): market_url = "https://www.coingecko.com/" coins = collect_all_coins(market_url, max_pages=2) save_outputs(coins) print(f"Saved {len(coins)} coins") if __name__ == "__main__": main()
Скрипт собирает записи монет на двух страницах рынка и регулирует цикл двухсекундным ожиданием. save_outputs записывает как JSON-файл, так и CSV, используя ключи первой записи в качестве заголовка, поэтому данные доступны в той форме, которую хочет ваш инструмент. Настройте max_pages и URL рынка в соответствии с тем, какую часть рейтинга вы хотите охватить.
Как выглядит результат
Запустите полный скрипт командой python crypto_scraper.py, и вы получите чистую структурированную запись на монету, готовую для анализа, базы данных или таблицы.
[ { "name": "Bitcoin", "symbol": "BTC", "price": "$86,650.00", "change_24h": "2.4%", "volume_24h": "$28,540,118,233", "market_cap": "$1,712,884,991,402" }, { "name": "Ethereum", "symbol": "ETH", "price": "$2,015.42", "change_24h": "1.1%", "volume_24h": "$14,902,551,870", "market_cap": "$243,118,440,905" } ]
Соответствующий CSV содержит те же столбцы, по одной строке на монету, что позволяет напрямую загрузить его в pandas или любую таблицу для сортировки по рыночной капитализации, фильтрации по изменению за 24 часа или построения графика объёма. Приведённые выше значения иллюстративны; живые цены и проценты меняются постоянно, что и является причиной их регулярного сбора по расписанию, а не однократно.
Как оставаться незаблокированным при масштабировании
Даже при обработанном рендеринге высокотрафиковый рыночный сайт следит за трафиком, похожим на скрапер. Несколько привычек поддерживают работоспособность более длинного запуска, и они применимы к любой сложной коммерческой цели.
- Регулируйте темп запросов. Обращение к страницам в плотном цикле является самым быстрым способом получить ограничения или проверки. Двухсекундные паузы выше являются минимумом, а не потолком; увеличивайте их для более крупных задач и избегайте запроса одной и той же страницы в быстром цикле.
- Полагайтесь на ротацию. Пул резидентских IP распределяет запросы по многим адресам реальных пользователей, чтобы ни один не превышал лимит скорости. Crawling API обрабатывает это за вас; если вы собираете свой стек, это та часть, которую нужно сделать правильно.
-
Читайте коды статусов. Запуск, начинающий возвращать значения
cb_status, отличные от 200, сообщает вам, что текущего темпа или уровня IP уже недостаточно. Воспринимайте это как сигнал замедлиться, а не как шум для игнорирования.
Для более крупных краулингов асинхронный Crawler ставит запросы в очередь и доставляет результаты на вебхук, что подходит для запуска многих страниц рынка без удержания открытых соединений. Более широкое руководство по защите от блокировок см. в статье как скрапить сайты, не попадая в блок. Если вы хотите использовать эти данные в рабочем процессе мониторинга, тот же подход переносится на ценовую аналитику, а параллельное руководство охватывает скрапинг криптовалютных цен с CoinMarketCap, если вам нужен второй источник.
Законно ли скрапить данные крипторынка?
Допустимость скрапинга сайта крипторынка зависит от условий использования этого сайта, вашей юрисдикции и того, что вы делаете с данными. Сайты вроде CoinGecko открыто публикуют свои таблицы рынков, а сами числа (цена, изменение за 24 часа, объём, рыночная капитализация) являются фактическими публичными рыночными данными, а не персональными данными, что делает набор полей в этом руководстве низкорискованным с точки зрения конфиденциальности. Однако это не освобождает вас от условий сайта: большинство рыночных сайтов ограничивают тяжёлый автоматизированный доступ в своих условиях использования, поэтому прочитайте эти условия и robots.txt сайта и рассматривайте оба как границу того, что и в каком количестве вы собираете.
Несколько принципов, которых стоит придерживаться. Собирайте только публичные рыночные данные, цифры, которые любой посетитель видит без аккаунта, и поддерживайте объём запросов достаточно низким, чтобы не перегружать серверы сайта. Не скрапьте ничего за логином, пейволлом или аккаунтом, и не собирайте и не создавайте профили вокруг персональных данных, что выходит за рамки данной статьи и повлечёт обязательства по GDPR и CCPA. Уважайте авторское право на любой редакционный контент, публикуемый рыночным сайтом рядом со своими таблицами: ценовые числа являются фактами, но написанный рыночный анализ не ваш для переиздания.
Для продакшн-использования более чистым путём является официальный API. Большинство сайтов крипторынков, включая CoinGecko, предлагают публичный API, возвращающий те же поля цены, объёма и рыночной капитализации в виде структурированного JSON с чёткими лимитами частоты и условиями. API более стабилен, чем HTML-селекторы, он не ломается при изменении макета страницы и удерживает вас в рамках разрешённого использования провайдера. Скрапьте отрисованную страницу для быстрого одноразового случая или поля, которое API не раскрывает; обращайтесь к официальному API или лицензированному фиду данных, когда создаёте что-то долговечное или коммерческое.
Ключевые выводы
- Таблицы рынков отрисовываются на стороне клиента. Обычный запрос возвращает тонкую оболочку с пустой таблицей, поэтому перед парсингом необходимо отрисовать страницу.
-
Вам нужны рендеринг и доверенный IP вместе. Crawling API с JS-токеном делает оба в одном вызове;
ajax_waitиpage_waitуправляют временем ожидания контента. -
Парсите строку за строкой. Читайте каждую
trкак одну монету и сопоставляйте её ячейки с полями name, symbol, price, change_24h, volume_24h и market_cap, чтобы каждое поле оставалось привязано к правильной монете. -
Пагинация и экспорт. Проходите по параметру
?page=до заданного максимума, регулируйте запуск короткими паузами и записывайте записи в JSON и CSV. - Для продакшна предпочитайте официальный API. Оставайтесь на публичных рыночных данных, соблюдайте ToS и robots.txt сайта и используйте публичный API провайдера или лицензированный фид для всего долговечного или коммерческого.
Часто задаваемые вопросы
Почему обычный запрос возвращает пустую таблицу крипторынка?
Потому что страница рынка загружает свои строки на стороне клиента с помощью JavaScript. Исходный HTML является оболочкой, которая заполняется только после того, как скрипты выполнятся в браузере, поэтому обычный HTTP-запрос возвращает статус 200 с пустой таблицей. Чтобы получить строки, необходимо сначала отрисовать страницу, что и обрабатывает JS-токен Crawling API.
Нужен ли мне обычный токен или JS-токен?
JS-токен. Обычный токен получает статический HTML, который на странице крипторынка является той же пустой оболочкой, что и обычный запрос. JS-токен сначала отрисовывает страницу в настоящем браузере, поэтому строки монет присутствуют при парсинге BeautifulSoup.
Какие поля можно извлечь со страницы рынка?
Публичные данные на монету: название и тикер-символ, текущая цена, изменение в процентах за 24 часа, объём торгов за 24 часа и рыночная капитализация. Оставайтесь на данных, видимых любому посетителю без аккаунта, и рассматривайте написанный рыночный анализ или редакционный контент как защищённый авторским правом, а не как то, что можно переиздавать.
Мои селекторы возвращают None. Что изменилось?
Почти наверняка разметка сайта. Сгенерированные имена классов и атрибуты data-target (хуки data-coin-table-target, классы ячеек с выравниванием по правому краю) меняются без предупреждения, поэтому работавшие в прошлом месяце селекторы могут сломаться. Повторно осмотрите живую страницу в DevTools браузера и обновите селекторы. Периодическое обслуживание селекторов является нормой для любого продакшн-скрапера.
Скрапить страницу или использовать официальный API?
Для всего долговечного или коммерческого предпочитайте официальный API. Большинство сайтов крипторынков, включая CoinGecko, предоставляют публичный API, возвращающий данные о цене, объёме и рыночной капитализации в виде структурированного JSON с чёткими условиями и лимитами частоты, что более стабильно, чем парсинг HTML. Скрапинг отрисованной страницы лучше всего подходит для быстрого одноразового случая или поля, которое API не раскрывает.
Как часто следует собирать данные крипторынка?
Зависит от вашего варианта использования. Для дашборда, близкого к реальному времени, вы можете запрашивать данные каждые несколько минут; для исследования тенденций обычно достаточно почасовых или ежедневных снимков. Какой бы ни была периодичность, регулируйте темп запросов и соблюдайте лимиты скорости сайта, чтобы частое расписание не превратилось в трафик, похожий на скрапер, который сайт заблокирует.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
