Google Finance является одним из наиболее широко используемых ресурсов для чтения данных рынка в реальном времени, и его страницы котировок содержат именно те структурированные цифры, которые нужны для отслеживания цен, мониторинга портфелей и исследований: текущую цену, дневное изменение стоимости и в процентах, цену предыдущего закрытия и ключевые рыночные показатели, такие как рыночная капитализация, коэффициент P/E и дневные и годовые торговые диапазоны. Для тех, кто следит за корзиной тикеров, эти публичные рыночные данные являются исходным материалом, а их ручное копирование по десяткам символов занимает много времени и чревато ошибками.
В этом руководстве показано, как надёжно парсить Google Finance с помощью Python. Вы создадите небольшой, работающий скрапер, который получает отрендеренные страницы котировок через Crawling API, разбирает нужные поля с помощью BeautifulSoup, перебирает несколько тикеров и экспортирует чистый JSON. Всё руководство ограничено публичными рыночными данными, которые являются фактическими, а не персональными, и раздел о законности ближе к концу не является шаблонным, поэтому прочитайте его перед направлением реального объёма трафика.
Что вы создадите
Скрипт на Python, принимающий список URL котировок Google Finance (один на тикер, в формате SYMBOL:EXCHANGE, который использует Google), получающий каждую отрендеренную страницу через Crawling API и извлекающий структурированную запись на акцию. В качестве рабочего примера используются GOOGL, AAPL и MSFT на NASDAQ. Извлекаются следующие поля:
- Название, название компании, отображаемое в верхней части страницы котировки.
- Цена, текущая котировочная цена акции.
- Изменение, абсолютное изменение цены и его процент за сессию.
- Цена предыдущего закрытия, цена закрытия предыдущей сессии.
- Рыночные данные, блок ключевых показателей: рыночная капитализация, коэффициент P/E, дневной и годовой диапазоны там, где они присутствуют.
Почему обычный запрос не работает с Google Finance
Запросите URL котировки Google Finance с помощью обычного HTTP-клиента, и вы получите ответ со статусом 200, но лишь с частью данных в теле. Два фактора работают против вас. Во-первых, Google Finance заполняет свои страницы котировок в браузере через JavaScript, поэтому исходный HTML является тонкой оболочкой. Наглядный пример, процент изменения цены: извлеките его из этого первого ответа, и вы получите None, потому что это значение рендерится на стороне клиента после загрузки. Во-вторых, Google быстро помечает автоматический трафик. IP-адреса датацентров и паттерны запросов, не характерные для реального браузера, ограничиваются по частоте, блокируются по IP или проходят проверки ещё до получения отрендеренного контента.
Таким образом, работающий парсер Google Finance требует двух вещей в одном запросе: браузера, реально рендерящего страницу, и IP, который платформа воспринимает как реального посетителя. Можно собрать это самостоятельно, используя headless-браузер и пул ротируемых резидентских прокси, но поддерживать этот стек в рабочем состоянии, основная часть работы. Crawling API объединяет оба требования в одном вызове: отправьте URL с JavaScript-токеном, API рендерит страницу за надёжным IP и возвращает готовый HTML для разбора. Общий контекст по рендеримым целям см. в статье о том, как краулить JavaScript-сайты.
Crawlbase предлагает два типа токенов. Обычный токен получает статический HTML; JavaScript (JS) токен сначала рендерит страницу в реальном браузере. Google Finance заполняет цену, процент изменения и показатели на стороне клиента, поэтому здесь нужен JS-токен. Обычный токен возвращает ту же тонкую оболочку, что и обычный запрос, с отсутствующим процентом изменения, и из неё практически нечего разобрать.
Предварительные требования
Перед написанием кода необходимо наличие нескольких вещей. Ни одна из них не займёт много времени.
Базовое знание Python. Вы должны уметь писать и запускать скрипты Python и устанавливать пакеты с помощью pip. Если вы новичок в разборе HTML, руководство по BeautifulSoup будет полезным дополнением к этому руководству.
Python 3.8 или выше. Проверьте версию командой python --version. Если её нет, установите с python.org или через дистрибутив вроде Anaconda и убедитесь, что Python есть в PATH.
Аккаунт Crawlbase и JS-токен. Зарегистрируйтесь, откройте дашборд и скопируйте JavaScript (JS) токен со страницы документации аккаунта. Crawlbase включает до 20 000 бесплатных запросов для начала, чего вполне достаточно для работы с этим руководством, и оплата производится только за успешные запросы. Относитесь к токену как к паролю: он аутентифицирует ваши запросы, поэтому держите его вне системы контроля версий.
Настройка проекта
Создайте виртуальное окружение для изоляции зависимостей проекта, затем установите необходимые библиотеки.
python --version python -m venv google_finance_env source google_finance_env/bin/activate pip install crawlbase beautifulsoup4
В Windows активируйте окружение командой google_finance_env\Scripts\activate вместо строки с source. Две зависимости выполняют всю работу: crawlbase, официальный клиент для Crawling API, а beautifulsoup4 разбирает возвращённый HTML для извлечения отдельных полей по CSS-селектору. Модуль json поставляется со стандартной библиотекой, поэтому дополнительно устанавливать ничего не нужно для шага экспорта.
Шаг 1: получение отрендеренной страницы Google Finance
Начните с получения готовой страницы. Импортируйте класс CrawlingAPI, инициализируйте его JS-токеном и запросите URL котировки. Google Finance загружает значения асинхронно, поэтому передайте параметры ajax_wait и page_wait для ожидания динамического контента перед захватом страницы. Проверка cb_status (legacy pc_status) от Crawlbase перед разбором позволяет сделать сбои заметными, а не скрытыми.
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) OPTIONS = { "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__": quote_url = "https://www.google.com/finance/quote/GOOGL:NASDAQ" html = crawl(quote_url) print(html[:500] if html else "No HTML returned")
Два параметра ожидания важны для рендеримой на стороне клиента цели вроде Google Finance. ajax_wait говорит API ждать завершения загрузки асинхронного контента, а page_wait удерживает фиксированное количество миллисекунд после загрузки, чтобы значения с поздним рендерингом появились до захвата страницы. Пять секунд, разумная отправная точка; увеличьте, если процент изменения возвращается пустым. Запустите скрипт командой python google_finance_scraper.py, и вы должны увидеть реальную разметку страницы котировки, а не оболочку, которую возвращает обычный запрос. Это подтверждает работу рендеринга перед написанием первого селектора.
Google Finance требует отрендеренную страницу за надёжным IP в одном вызове, именно это настраивают параметры ajax_wait и page_wait выше. Crawling API принимает JS-токен, запускает страницу в реальном браузере, чтобы процент изменения реально загрузился, ротирует резидентские IP на стороне сервера и передаёт вам готовый HTML, избавляя от необходимости самостоятельно управлять парком headless-браузеров и пулом прокси. Начните с публичной страницы котировки на бесплатном тарифе.
Шаг 2: разбор цены, изменения и названия
Имея готовый HTML, загрузите его в BeautifulSoup и извлеките заголовочные поля. Google Finance хранит название компании, текущую цену и изменение за сессию в области main страницы, каждое в сгенерированном классе. Приведённые ниже селекторы взяты прямо из актуального макета страницы котировки: название находится в zzDege, цена в AHmHk, а процент изменения в JwB6zf. Каждый поиск защищён, чтобы отсутствующее поле возвращало None вместо аварийного завершения.
from bs4 import BeautifulSoup def get_price(soup): el = soup.find("main") if not el: return None price = el.find("div", "AHmHk") return price.get_text(strip=True) if price else None def get_change_percentage(soup): main = soup.find("main") if not main: return None change = main.find("div", "JwB6zf") return change.get_text(strip=True) if change else None def get_stock_title(soup): main = soup.find("main") if not main: return None title = main.find("div", "zzDege") return title.get_text(strip=True) if title else None
Элемент JwB6zf, тот, за которым нужно следить. В тонком, не отрендеренном ответе он отсутствует полностью, и get_change_percentage возвращает None; как только страница рендерится через JS-токен, он несёт изменение за сессию в стоимости и процентах. Это единственное поле, простейшее доказательство того, что рендеринг работает.
Сгенерированные имена классов Google, такие как AHmHk, JwB6zf и zzDege, меняются без предупреждения. Рассматривайте приведённые здесь селекторы как начальный шаблон, а не как контракт. Когда поле возвращает None на явно отрендеренной странице, повторно проверьте живую котировку в инструментах разработчика браузера и обновите класс. Периодическое обслуживание селекторов нормально для любого производственного парсера, это не признак поломки.
Шаг 3: разбор блока рыночных данных
Ниже заголовочных цифр Google Finance перечисляет ключевые показатели в виде строк с метками и значениями: цена предыдущего закрытия, дневной диапазон, годовой диапазон, рыночная капитализация, коэффициент P/E и другое. Каждая строка, контейнер gyFHrc, содержащий метку в mfs7Fc и значение в P6K39c. Обход каждой строки в словарь делает парсер устойчивым: какие бы показатели ни предоставлял данный тикер, они появляются как ключи, и вы читаете Previous close, Market cap, P/E ratio, Day range и Year range прямо из этого словаря.
def get_market_data(soup): rows = soup.find_all("div", {"class": "gyFHrc"}) data = {} for row in rows: label = row.find("div", {"class": "mfs7Fc"}) value = row.find("div", {"class": "P6K39c"}) if label and value: data[label.get_text(strip=True)] = value.get_text(strip=True) return data KEEP = ["Previous close", "Day range", "Year range", "Market cap", "P/E ratio"] def select_market_fields(market_data): return {key: market_data[key] for key in KEEP if key in market_data}
Вспомогательная функция get_market_data захватывает каждую пару метки и значения на странице, поэтому вам не нужно жёстко задавать селектор для каждого показателя. select_market_fields затем сужает его до текущей области: цена предыдущего закрытия, дневной и годовой диапазоны, рыночная капитализация и коэффициент P/E. Поскольку фильтр проверяет if key in market_data, тикер, не имеющий одного из этих полей, просто не включает этот ключ в запись, а не завершается ошибкой. Сохраните полный вывод get_market_data, если позже понадобятся средний объём или основная биржа.
Шаг 4: сборка полного скрипта
Теперь соберите части в один работающий скрипт: получайте каждую страницу котировки, разбирайте заголовочные поля и блок рыночных данных, экспортируйте записи в JSON. Функция extract_quote объединяет вспомогательные функции для каждого поля в одну запись, а main перебирает список URL SYMBOL:EXCHANGE, позволяя получить несколько тикеров за один запуск.
import json import time from crawlbase import CrawlingAPI from bs4 import BeautifulSoup api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) OPTIONS = { "ajax_wait": "true", "page_wait": 5000, } KEEP = ["Previous close", "Day range", "Year range", "Market cap", "P/E ratio"] 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 text_in_main(soup, class_name): main = soup.find("main") if not main: return None el = main.find("div", class_name) return el.get_text(strip=True) if el else None def get_market_data(soup): rows = soup.find_all("div", {"class": "gyFHrc"}) data = {} for row in rows: label = row.find("div", {"class": "mfs7Fc"}) value = row.find("div", {"class": "P6K39c"}) if label and value: data[label.get_text(strip=True)] = value.get_text(strip=True) return data def extract_quote(html, url): soup = BeautifulSoup(html, "html.parser") market = get_market_data(soup) return { "url": url, "title": text_in_main(soup, "zzDege"), "price": text_in_main(soup, "AHmHk"), "change": text_in_main(soup, "JwB6zf"), "previous_close": market.get("Previous close"), "market_data": {key: market[key] for key in KEEP if key in market}, } def main(): urls = [ "https://www.google.com/finance/quote/GOOGL:NASDAQ", "https://www.google.com/finance/quote/AAPL:NASDAQ", "https://www.google.com/finance/quote/MSFT:NASDAQ", ] records = [] for url in urls: html = crawl(url) if html: records.append(extract_quote(html, url)) time.sleep(2) with open("finance_data.json", "w") as f: json.dump(records, f, indent=2) print(f"Saved {len(records)} quotes") if __name__ == "__main__": main()
Единственная вспомогательная функция text_in_main заменяет три почти идентичные функции для названия, цены и изменения из предыдущего шага: она находит область main, ищет один сгенерированный класс и возвращает очищенный текст или None. extract_quote собирает одну запись на тикер, извлекая цену предыдущего закрытия из словаря рынка и вкладывая остальное под market_data. Двухсекундная пауза задаёт темп запуска, чтобы не нагружать сайт. Измените список urls для парсинга любых других символов.
Как выглядит результат
Запустите полный скрипт командой python google_finance_scraper.py, и вы получите чистую структурированную запись на тикер, готовую для анализа, базы данных или таблицы. Значения ниже иллюстративны; ваш запуск вернёт то, что показывает текущая котировка.
[ { "url": "https://www.google.com/finance/quote/GOOGL:NASDAQ", "title": "Alphabet Inc Class A", "price": "$163.79", "change": "+1.01 (0.62%)", "previous_close": "$162.78", "market_data": { "Previous close": "$162.78", "Day range": "$163.09 - $167.12", "Year range": "$103.71 - $174.71", "Market cap": "2.04T USD", "P/E ratio": "25.54" } }, { "url": "https://www.google.com/finance/quote/AAPL:NASDAQ", "title": "Apple Inc", "price": "$169.30", "change": "-0.61 (0.36%)", "previous_close": "$170.33", "market_data": { "Previous close": "$170.33", "Day range": "$169.11 - $172.71", "Year range": "$164.08 - $199.62", "Market cap": "2.61T USD", "P/E ratio": "26.34" } } ]
Обратите внимание, что поле change теперь несёт реальное значение, а не null. При обычном, не отрендеренном запросе оно возвращается пустым, потому что Google рисует его на стороне клиента; маршрутизация запроса через JS-токен делает его видимым. Отсюда записи напрямую поступают в pandas или любую таблицу для отслеживания движений цен, сравнения P/E по списку наблюдения или передачи в процесс ценовой аналитики.
Масштабирование до списка наблюдения и сохранение без блокировок
Приведённый выше скрипт получает три тикера, но форма масштабируется до полного списка наблюдения путём расширения списка urls. По мере роста запуска несколько привычек поддерживают его работоспособность, и они применимы к любой сложной цели.
- Задавайте темп запросов. Быстрый поток котировок в плотном цикле, самый быстрый способ получить ограничение или проверку. Двухсекундная пауза выше, это минимум, а не максимум; увеличивайте её для больших списков наблюдения и варьируйте символы вместо повторного получения одного пути на полной скорости.
- Полагайтесь на ротацию. Пул резидентских IP распределяет запросы по множеству адресов реальных пользователей, чтобы ни один не преодолел ограничение по частоте. Crawling API обрабатывает это за вас; если вы создаёте собственный стек, именно эту часть нужно сделать правильно.
-
Читайте коды статусов. Запуск, начинающий возвращать значения
cb_status, отличные от 200, сигнализирует, что текущей частоты или уровня IP недостаточно. Воспринимайте это как сигнал снизить интенсивность, а не как шум, который можно игнорировать.
Для более крупных задач асинхронный Crawler ставит запросы в очередь и доставляет результаты на вебхук, что подходит для обновления многих тикеров по расписанию без удержания открытых соединений. Общий подход см. в статье о том, как парсить сайты без блокировок. Тот же подход рендеринга и надёжного IP применим и к другим рыночным источникам, например при парсинге крипто-цен с CoinMarketCap.
Законно ли парсить Google Finance?
Допустимость парсинга Google Finance зависит от условий использования Google, вашей юрисдикции и того, что вы делаете с данными. Условия Google ограничивают автоматический доступ к его сервисам, поэтому парсинг может нарушать эти условия независимо от тщательности вашего инструментария. Код в этом руководстве этого не меняет; он лишь делает техническую часть рабочей. Прочитайте Условия использования Google и robots.txt для путей Google Finance и рассматривайте оба документа как границу для того, что собирать и как часто запрашивать.
Данные, на которые нацелено это руководство, находятся строго на публичной, не персональной стороне: цены акций, изменение за сессию, цена предыдущего закрытия, рыночная капитализация, коэффициент P/E и дневные и годовые диапазоны, это фактические рыночные показатели, а не чья-то личная информация. Это делает работу более ограниченной, чем парсинг сайтов с пользовательским контентом, но не ставит вас выше условий Google. Оставайтесь на страницах котировок, которые любой посетитель может загрузить без аккаунта, поддерживайте объём запросов достаточно низким, чтобы не нагружать серверы Google, и не перераспределяйте базовые рыночные данные в оптовых объёмах, поскольку биржи и поставщики данных лицензируют эти цифры, и лицензирование следует за данными.
Данное руководство намеренно ограничено публичными страницами котировок, поскольку это граница, делающая работу обоснованной. Оно не охватывает контент за авторизацией, данные с платным доступом или премиальные данные, а также попытки обхода аутентификации или ограничений частоты. Если ваш проект требует гарантированного аптайма, прав на распространение или объёма выше того, что допускает вежливый парсинг, правильный путь, лицензированный фид: используйте официальный API рыночных данных или лицензированный биржевой фид для производства. Это правильный маршрут для коммерческого или массового использования, а не более агрессивный парсер.
Ключевые выводы
- Google Finance рендерится на стороне клиента. Обычный запрос возвращает тонкую оболочку с отсутствующим процентом изменения цены, поэтому необходимо рендерить страницу перед разбором.
-
Нужны рендеринг и надёжный IP вместе. Crawling API с JS-токеном делает оба в одном вызове;
ajax_waitиpage_waitконтролируют, как долго ждать загрузки значений. -
Привязывайте поля к реальным классам. Название находится в
zzDege, цена вAHmHk, изменение за сессию вJwB6zf; блок показателей, набор строкgyFHrc, сочетающих меткуmfs7Fcсо значениемP6K39c. -
Перебирайте и экспортируйте. Передайте список URL котировок
SYMBOL:EXCHANGE, задайте темп запуска короткими паузами и запишите одну запись на тикер в JSON со вложенными рыночными данными. - Оставайтесь на публичных рыночных данных. Уважайте ToS Google и robots.txt, ограничивайтесь фактическими публичными котировками и используйте официальный API рыночных данных или лицензированный фид для производства или распространения.
Часто задаваемые вопросы
Почему процент изменения цены возвращается null при обычном запросе?
Потому что Google Finance рендерит это значение на стороне клиента с помощью JavaScript. Исходный HTML является оболочкой, и элемент JwB6zf, содержащий изменение за сессию, появляется только после выполнения скриптов страницы в браузере. Обычный HTTP-запрос возвращает статус 200 с отсутствующим изменением, поэтому поле возвращает None. Предварительный рендеринг страницы, который обрабатывает JS-токен Crawling API, делает значение присутствующим, чтобы BeautifulSoup мог его прочитать.
Нужен обычный токен или JS-токен для Google Finance?
JS-токен. Обычный токен получает статический HTML, который в Google Finance является той же тонкой оболочкой, что и обычный запрос, с отсутствующим процентом изменения. JS-токен рендерит страницу в реальном браузере перед возвратом HTML, поэтому цена, изменение и блок показателей присутствуют при разборе.
Какие данные можно парсить со страницы котировки Google Finance?
Публичные рыночные поля: название компании, текущая цена, изменение за сессию в стоимости и процентах, цена предыдущего закрытия и блок показателей, включающий рыночную капитализацию, коэффициент P/E и дневной и годовой диапазоны там, где они присутствуют. Это фактические публичные котировки, которые может загрузить любой посетитель, а не персональные данные. Оставайтесь в этой области и используйте лицензированный фид при необходимости прав на распространение.
Мои селекторы возвращают None. Что изменилось?
Почти наверняка разметка Google. Его сгенерированные имена классов (AHmHk для цены, JwB6zf для изменения, zzDege для названия, gyFHrc / mfs7Fc / P6K39c для строк показателей) меняются без предупреждения, поэтому работавшие в прошлом месяце селекторы могут сломаться. Повторно проверьте живую котировку в инструментах разработчика браузера и обновите строки классов. Периодическое обслуживание селекторов нормально для любого производственного парсера.
Как парсить несколько акций одновременно?
Google Finance адресует каждую акцию как SYMBOL:EXCHANGE в URL котировки, например GOOGL:NASDAQ. Создайте список таких URL и переберите его, вызывая ту же функцию extract_quote для каждой отрендеренной страницы, как показывает функция main выше. Поддерживайте короткую паузу между запросами для задания темпа запуска и расширяйте список до полного списка наблюдения.
Есть ли официальный Google Finance API для производственного использования?
Google не предлагает публичного, поддерживаемого API рыночных данных для Google Finance, поэтому для производственного или коммерческого использования надёжный путь, лицензированный поставщик рыночных данных или официальный биржевой фид, а не парсинг веб-страницы. Эти фиды поставляются с гарантиями аптайма и условиями использования, которых лишён парсинг публичной страницы, что имеет значение, как только точность данных или права на использование становятся частью требований.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
