Google Maps является одним из богатейших источников информации о местных предприятиях в открытом вебе. Поищите категорию в городе, и вы получите ранжированный список мест, каждое из которых содержит название, адрес, номер телефона, категорию, звёздный рейтинг и количество отзывов. Для генерации местных лидов, маркетинговых исследований и анализа конкурентов эти публичные данные листингов бесценны: они говорят о том, кто работает в данном районе, как оценивается качество их услуг и как с ними связаться.

Это руководство показывает, как надёжным способом парсить данные из Google Maps с помощью Python. Вы создадите небольшой рабочий скрапер, который загружает отрисованную страницу результатов поиска Google Maps через Crawling API, парсит каждую карточку места с помощью BeautifulSoup, собирает основные поля бизнеса, обрабатывает несколько результатов и экспортирует данные в JSON и CSV. Всё руководство ограничено публичными бизнес-листингами, которые любой желающий может видеть на карте без аккаунта, а раздел о правовых аспектах в конце, не формальность, поэтому прочитайте его перед направлением скрапера на реальный объём.

Что вы создадите

Скрипт на Python, который принимает публичный URL поиска по Google Maps, загружает отрисованный HTML через Crawling API и извлекает структурированную запись для каждого места на панели результатов. В качестве рабочего примера используется поиск «restaurants in New York» (рестораны в Нью-Йорке), аналогично оригинальной версии этого руководства. Из каждого листинга извлекаются следующие поля:

  • Name (название) название предприятия, отображаемое на карточке места.
  • Address (адрес) уличный адрес или метка района для данного места.
  • Phone (телефон) контактный номер, когда листинг его предоставляет.
  • Category (категория) тип предприятия, например «Italian restaurant».
  • Rating (рейтинг) средний звёздный рейтинг в числовом формате.
  • Review count (количество отзывов) сколько отзывов лежит в основе этого рейтинга.

По завершении у вас будут эти записи для каждого места на странице, записанные в maps_results.json и maps_results.csv, готовые для CRM, таблицы или базы данных.

Почему обычный запрос не работает с Google Maps

Если направить обычный HTTP-запрос из скрипта на URL Google Maps, вы не получите аккуратный список мест, видимый в браузере. Панель результатов формируется JavaScript после загрузки исходной страницы: сырой HTML, возвращаемый простым запросом requests.get, представляет собой в основном пустую оболочку, а карточки мест заполняются скриптами, выполняемыми в реальном браузере. Парсинг этой оболочки не даст ни одного из нужных полей.

Кроме того, Google внимательно следит за автоматизированным трафиком. Запросы, не похожие на реальный браузер или поступающие пачками с IP-адреса дата-центра, получают в ответ CAPTCHA, страницу согласия или прямую блокировку ещё до обращения к каким-либо листингам. Оригинальная версия этого руководства использовала внутренний JSON-эндпоинт для обхода отрисовки, но такие приватные URL меняются без предупреждения и перестают работать, когда Google перестраивает их. Надёжный подход состоит в отрисовке публичной страницы так, как это делает браузер.

Таким образом, рабочий скрапер Google Maps должен сочетать в одном запросе два компонента: IP-адрес, который платформа воспринимает как реального посетителя, и браузер, выполняющий JavaScript страницы, чтобы карточки мест действительно появились. Вы можете собрать это самостоятельно с помощью headless-браузера и пула ротируемых жилых прокси, но поддержание такого стека в рабочем состоянии само по себе является основной задачей. Crawling API объединяет оба компонента в одном вызове: вы отправляете ему URL, он выполняет запрос с доверенного жилого IP, отрисовывает страницу и возвращает готовый HTML для парсинга.

Предварительные требования

Перед написанием кода необходимо подготовить несколько вещей. Ни одна из них не займёт много времени.

Базовые знания Python. Вы должны уметь писать и запускать скрипты Python, а также устанавливать пакеты с помощью pip. Если BeautifulSoup для вас в новинку, наше руководство по использованию BeautifulSoup в Python охватывает основы парсинга, необходимые для этого руководства.

Python 3.8 или выше. Проверьте версию командой python --version. Если Python не установлен, скачайте его с python.org или через дистрибутив наподобие Anaconda.

Аккаунт Crawlbase и токен. Зарегистрируйтесь, откройте панель управления и скопируйте токен запроса со страницы документации аккаунта. Первые 1000 запросов бесплатны без привязки кредитной карты. Google Maps требует отрисовки JavaScript, поэтому используйте JavaScript-токен для этих запросов. Относитесь к токену как к паролю: он аутентифицирует ваши запросы, поэтому не добавляйте его в систему контроля версий.

Настройка проекта

Создайте виртуальное окружение, чтобы зависимости проекта оставались изолированными, затем установите две необходимые библиотеки.

bash
python --version

python -m venv maps_env
source maps_env/bin/activate

pip install requests beautifulsoup4

В Windows активируйте окружение командой maps_env\Scripts\activate вместо строки с source. Две зависимости выполняют основную работу: requests отправляет HTTP-вызов к Crawling API, а beautifulsoup4 парсит возвращаемый HTML, позволяя извлекать отдельные поля по CSS-селектору.

Шаг 1: Получение URL поиска

Откройте Google Maps в браузере и выполните поиск «restaurants in New York». Места появятся на прокручиваемой панели слева, а URL в адресной строке обновится, кодируя запрос. Скопируйте этот URL. Это тот же адрес, который вы передадите скраперу, поэтому то, что вы видите в браузере, и будет отрисовывать API.

text
https://www.google.com/maps/search/restaurants+in+New+York

Путь /maps/search/ является стабильной публичной точкой входа для поиска по категории, где запрос соединяется знаками +. Вы можете подставить любую категорию и местоположение, например plumbers+in+Chicago или coffee+shops+in+Austin, и остальная часть скрапера останется неизменной. Избегайте длинных внутренних URL с параметром pb=, которые браузер иногда генерирует; они являются приватными и перестают работать без предупреждения.

Шаг 2: Загрузка отрисованной страницы через Crawling API

Начните с получения HTML. Напишите небольшую функцию crawl(), которая отправляет целевой URL в Crawling API с вашим JavaScript-токеном, запрашивает отрисовку страницы, проверяет, что базовая страница вернула статус 200, и возвращает тело HTML. Проверка статуса перед парсингом позволяет явно сообщать об ошибках, а не замалчивать их.

python
import json
import requests

API_TOKEN = "YOUR_CRAWLBASE_TOKEN"  # use your JavaScript token
API_ENDPOINT = "https://api.crawlbase.com/"

def crawl(url):
    params = {
        "token": API_TOKEN,
        "url": url,
        "page_wait": 4000,
    }
    response = requests.get(API_ENDPOINT, params=params)
    response.raise_for_status()

    data = json.loads(response.text)
    if data["original_status"] != 200:
        raise Exception(f"Unable to crawl '{url}'")

    return data["body"]

if __name__ == "__main__":
    url = "https://www.google.com/maps/search/restaurants+in+New+York"
    html = crawl(url)
    print(html[:500])

API возвращает JSON-конверт, поэтому вы загружаете ответ с помощью json.loads и считываете два поля: original_status, это статус, который вернул сам Google, а body, HTML отрисованной страницы. Параметр page_wait указывает рендереру подождать несколько секунд после загрузки, чтобы карточки мест успели заполниться перед захватом HTML. Проверка на original_status означает, что страница согласия или блокировка проявится как исключение, а не как пустая оболочка, переданная парсеру. Запустите скрипт командой python crawling.py, и вы должны увидеть реальную разметку карты в первых 500 символах, что подтверждает работу загрузки и отрисовки до написания первого селектора.

Crawlbase Crawling API

Проверка original_status возвращает только 200, потому что запрос достиг Google Maps как от реального посетителя, и страница была отрисована в реальном браузере. Crawling API выполняет запросы с ротируемых жилых IP-адресов, запускает JavaScript страницы, чтобы карточки мест действительно появились, и возвращает готовый HTML, поэтому вам не нужно самостоятельно запускать флот headless-браузеров и подбирать жилые прокси. Начните с бесплатного тарифа, направив скрапер на публичный URL поиска по Maps.

Шаг 3: Парсинг каждой карточки места с помощью BeautifulSoup

Имея на руках отрисованный HTML, загрузите его в BeautifulSoup и извлеките каждый результат по его селектору. Google Maps оборачивает каждое место на панели результатов в элемент article, и каждое поле занимает предсказуемое место внутри этой карточки. Проверьте актуальную страницу в инструментах разработчика браузера (щёлкните правой кнопкой мыши на карточке места, затем «Просмотр кода»), чтобы убедиться в актуальности имён классов и атрибутов; приведённые ниже селекторы соответствуют макету на момент написания.

python
import re
from bs4 import BeautifulSoup

def parse_place(card):
    name_el = card.select_one("div.qBF1Pd")
    rating_el = card.select_one("span.MW4etd")
    reviews_el = card.select_one("span.UY7F9")
    info_rows = card.select("div.W4Efsd > div.W4Efsd")

    category, address, phone = None, None, None
    for row in info_rows:
        text = row.get_text(" ", strip=True)
        phone_match = re.search(r"(\(?\d[\d\-\s\(\)]{7,}\d)", text)
        if phone_match and not phone:
            phone = phone_match.group(1).strip()
        elif "·" in text and not category:
            parts = [p.strip() for p in text.split("·")]
            category = parts[0]
            address = parts[-1] if len(parts) > 1 else None

    return {
        "name": name_el.get_text(strip=True) if name_el else None,
        "category": category,
        "address": address,
        "phone": phone,
        "rating": float(rating_el.get_text(strip=True)) if rating_el else None,
        "reviews": parse_reviews(reviews_el),
    }

def parse_reviews(el):
    if not el:
        return 0
    digits = re.sub(r"[^\d]", "", el.get_text(strip=True))
    return int(digits) if digits else 0

def scrape_html(html):
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select("div[role='article']")
    return [parse_place(card) for card in cards if card.select_one("div.qBF1Pd")]

Селектор div[role='article'] соответствует каждой карточке места на панели результатов, а вспомогательная функция считывает именованные поля внутри неё. Название предприятия находится в div.qBF1Pd, звёздный рейтинг в span.MW4etd, а количество отзывов в span.UY7F9 в виде текста вроде «(1,234)», который parse_reviews приводит к целому числу. Категория, адрес и телефон находятся во вспомогательных строках информации под div.W4Efsd, разделённых символом-межточием, поэтому мы разделяем по «·» для извлечения категории и адреса, и используем небольшое регулярное выражение для определения номера телефона. Защитное чтение каждого поля с запасным значением None означает, что листинг без телефона или рейтинга создаёт чистую запись, а не завершает цикл с ошибкой.

Селекторы меняются

Имена классов Google, такие как qBF1Pd и MW4etd, представляют собой обфусцированные артефакты сборки, изменяющиеся при каждом обновлении фронтенда. Относитесь к приведённым выше селекторам как к стартовому шаблону, а не как к гарантии. Когда поле возвращает пустое значение для всех результатов, заново проверьте актуальную карточку места в инструментах разработчика вашего браузера и обновите селектор. Периодическое обслуживание селекторов является нормой для любого производственного скрапера, а не признаком неисправности.

Шаг 4: Обработка нескольких результатов и сборка скрипта

Панель результатов содержит множество мест, а парсер уже возвращает по одной записи на карточку, поэтому обработка нескольких результатов встроена. Теперь объедините загрузку и парсинг в один рабочий скрипт и экспортируйте записи в JSON и CSV. CSV является форматом, который чаще всего требуется в рабочих процессах генерации лидов и исследования, поскольку он напрямую загружается в таблицу или при импорте в CRM.

python
import csv
import json
import re
import requests
from bs4 import BeautifulSoup

API_TOKEN = "YOUR_CRAWLBASE_TOKEN"
API_ENDPOINT = "https://api.crawlbase.com/"
FIELDS = ["name", "category", "address", "phone", "rating", "reviews"]

def crawl(url):
    params = {"token": API_TOKEN, "url": url, "page_wait": 4000}
    response = requests.get(API_ENDPOINT, params=params)
    response.raise_for_status()
    data = json.loads(response.text)
    if data["original_status"] != 200:
        raise Exception(f"Unable to crawl '{url}'")
    return data["body"]

def parse_reviews(el):
    if not el:
        return 0
    digits = re.sub(r"[^\d]", "", el.get_text(strip=True))
    return int(digits) if digits else 0

def parse_place(card):
    name_el = card.select_one("div.qBF1Pd")
    rating_el = card.select_one("span.MW4etd")
    reviews_el = card.select_one("span.UY7F9")
    info_rows = card.select("div.W4Efsd > div.W4Efsd")

    category, address, phone = None, None, None
    for row in info_rows:
        text = row.get_text(" ", strip=True)
        phone_match = re.search(r"(\(?\d[\d\-\s\(\)]{7,}\d)", text)
        if phone_match and not phone:
            phone = phone_match.group(1).strip()
        elif "·" in text and not category:
            parts = [p.strip() for p in text.split("·")]
            category = parts[0]
            address = parts[-1] if len(parts) > 1 else None

    return {
        "name": name_el.get_text(strip=True) if name_el else None,
        "category": category,
        "address": address,
        "phone": phone,
        "rating": float(rating_el.get_text(strip=True)) if rating_el else None,
        "reviews": parse_reviews(reviews_el),
    }

def scrape_html(html):
    soup = BeautifulSoup(html, "html.parser")
    cards = soup.select("div[role='article']")
    return [parse_place(c) for c in cards if c.select_one("div.qBF1Pd")]

def main():
    url = "https://www.google.com/maps/search/restaurants+in+New+York"
    html = crawl(url)
    places = scrape_html(html)

    with open("maps_results.json", "w", encoding="utf-8") as f:
        json.dump(places, f, ensure_ascii=False, indent=2)

    with open("maps_results.csv", "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        writer.writeheader()
        writer.writerows(places)

    print(f"Saved {len(places)} places to JSON and CSV")

if __name__ == "__main__":
    main()

Запустите полный скрипт командой python main.py. Он загружает отрисованную страницу результатов для поиска «restaurants in New York», извлекает запись для каждой карточки места и записывает всё в maps_results.json и maps_results.csv. DictWriter использует тот же список FIELDS как для заголовка CSV, так и для порядка столбцов, чтобы два вывода оставались синхронизированными. Замените запрос в URL, и парсер обработает всё, что вернётся.

Как выглядит результат

Вы получаете чистый список записей мест, каждая с шестью полями, готовый к записи в JSON, CSV или базу данных. Ниже приведён сокращённый пример JSON.

json
[
  {
    "name": "Carmine's Italian Restaurant",
    "category": "Italian restaurant",
    "address": "200 W 44th St",
    "phone": "(212) 221-3800",
    "rating": 4.5,
    "reviews": 12873
  },
  {
    "name": "Katz's Delicatessen",
    "category": "Deli",
    "address": "205 E Houston St",
    "phone": "(212) 254-2246",
    "rating": 4.6,
    "reviews": 48091
  }
]

CSV содержит те же данные, по одному месту в строке, столбцы расположены в порядке FIELDS: name,category,address,phone,rating,reviews. Этот формат чисто импортируется в таблицу для дедупликации или в CRM в качестве начального списка местных потенциальных клиентов.

Масштабирование на несколько городов и запросов

Один поиск в одном городе подходит для демонстрации; реальная задача запускает тот же скрапер по множеству пар категорий и местоположений. Изменяется только URL, поэтому создайте список запросов и перебирайте их, парся каждый одной и той же функцией. Единственной привычкой, поддерживающей здоровье длительного запуска, является регулирование скорости, поэтому делайте паузы между запросами, а не отправляйте их в плотном цикле.

python
import time
from urllib.parse import quote_plus

queries = [
    "restaurants in New York",
    "coffee shops in Austin",
    "plumbers in Chicago",
]

all_places = []
for q in queries:
    url = f"https://www.google.com/maps/search/{quote_plus(q)}"
    html = crawl(url)
    all_places.extend(scrape_html(html))
    time.sleep(3)

print(f"Collected {len(all_places)} places across {len(queries)} queries")

Crawlbase по умолчанию обрабатывает до 20 запросов в секунду, что создаёт достаточный запас для скрапера, регулирующего скорость; если вам действительно нужно больше, служба поддержки может увеличить лимит. Любой 5XX-ответ от API не тарифицируется, поэтому повторная попытка на заблокированном или недоступном URL ничего не стоит. Панель результатов загружает первый пакет мест и раскрывает больше по мере прокрутки, поэтому один запрос возвращает лучшие результаты для запроса; для более глубокого охвата сужайте запрос по районам, а не пытайтесь прокрутить бесконечный список в одном запросе. Подробнее об этом паттерне читайте в нашем руководстве по парсингу листингов местных предприятий.

Как избежать блокировки

Даже при наличии доверенного IP и решённой задаче отрисовки Google отслеживает трафик, характерный для скраперов. Несколько привычек помогают поддерживать работоспособность.

  • Делайте паузы между запросами. Атака Maps в плотном цикле, это самый быстрый способ получить проверку. Распределяйте запросы и варьируйте запросы, а не листайте один термин на полной скорости.
  • Используйте ротацию. Пул жилых IP-адресов распределяет запросы по множеству адресов реальных пользователей, чтобы ни один из них не достиг ограничения. Crawling API делает это за вас; если вы строите собственный стек, именно этой части уделите особое внимание.
  • Читайте коды статусов. Запуск, при котором начинают возвращаться страницы согласия или CAPTCHA, сигнализирует о том, что текущая скорость или уровень IP больше недостаточны. Воспринимайте это как сигнал к снижению интенсивности, а не как шум, который можно игнорировать.
  • Проверяйте при пустых полях. Google периодически меняет разметку. Если карточки мест перестают парситься, откройте актуальную страницу в инструментах разработчика и обновите селекторы.

Более широкое руководство читайте в статье о парсинге сайтов без блокировок. Поскольку панель Maps отрисовывается JavaScript, наше руководство по сбору данных с JavaScript-сайтов объясняет, почему отрисовка важна и как её включить. Если ваш проект также затрагивает страницу обычных результатов поиска, наше руководство по парсингу страниц поиска Google охватывает эту область.

Законно ли парсить Google Maps?

Допустимость парсинга Google Maps зависит от условий использования Google, вашей юрисдикции и того, как вы используете данные. Условия Google ограничивают автоматизированный доступ к своим продуктам, поэтому парсинг может противоречить этим условиям вне зависимости от тщательности вашего подхода. Ни один код в этом руководстве этого не меняет: он лишь обеспечивает техническую сторону вопроса. Ознакомьтесь с условиями Google и файлом robots.txt, и относитесь к обоим как к границам того, что вы собираете.

Несколько принципов, которых стоит придерживаться. Собирайте только публичные данные бизнес-листингов: названия, адреса, номера телефонов, категории, рейтинги и количество отзывов, которые любой посетитель может видеть на карте без аккаунта. Это деловая информация, опубликованная для того, чтобы клиенты могли найти место. Не парсите содержимое отдельных отзывов, имена и профили рецензентов или любые другие персональные данные, и не распространяйте фотографии или другой защищённый авторским правом медиаконтент из листингов. Поддерживайте объём запросов достаточно низким, чтобы не перегружать серверы Google, и регулируйте скорость обхода, а не запускайте его на полной скорости.

Если вам нужны данные о местах в большом объёме или с договорной гарантией, Google предлагает официальный Places API, который является санкционированным способом запроса деловых данных и правильным путём для всего, что выходит за рамки лёгкого публичного исследования. Данное руководство намеренно ограничено публичными полями листингов, видимыми на панели результатов, поскольку это та граница, которая делает работу обоснованной. Только публичные деловые данные. Если ваш проект требует большего, правильным путём является официальный API или соглашение о данных, а не более изощрённый скрапер.

Итоги

Ключевые выводы

  • Панель отрисовывается JavaScript. Обычный запрос получает пустую оболочку, поэтому вам нужен реальный браузер для выполнения страницы, прежде чем карточки мест появятся для парсинга.
  • Crawling API загружает и отрисовывает страницу. Отправьте ему URL Maps с JavaScript-токеном, он ротирует жилые IP-адреса, выполняет скрипты страницы и возвращает готовый HTML.
  • BeautifulSoup извлекает шесть полей из каждой карточки. Выберите каждый элемент div[role='article'], затем считайте название, категорию, адрес, телефон, рейтинг и количество отзывов, ожидая дрейфа обфусцированных имён классов.
  • Экспортируйте в JSON и CSV. Одна запись на место напрямую загружается в таблицу или CRM для генерации местных лидов и маркетинговых исследований.
  • Работайте только с публичными данными. Соблюдайте условия использования и robots.txt Google, пропускайте отзывы и персональные данные и предпочитайте официальный Places API для любой работы в масштабе.

Часто задаваемые вопросы

Почему обычный запрос не возвращает места из Google Maps?

Панель результатов формируется JavaScript после загрузки страницы, поэтому обычный requests.get возвращает в основном пустую оболочку без карточек мест, которые вы видите в браузере. Google также блокирует трафик, не похожий на реальный браузер. Загрузка через Crawling API с включённой отрисовкой выполняет скрипты страницы с доверенного жилого IP, поэтому карточки мест появляются в получаемом HTML.

Можно ли парсить Google Maps с помощью Python?

Да. С помощью requests и BeautifulSoup вы можете загрузить отрисованную страницу результатов и извлечь название, адрес, телефон, категорию, рейтинг и количество отзывов для каждого места. Crawling API выступает мостом, доставляющим ваш запрос к Google с доверенного IP и отрисовывающим JavaScript, поэтому запросы обрабатываются без блокировок. Для более широкого введения в Python читайте наше руководство по парсингу сайтов с Python.

Какие поля бизнес-данных можно извлечь из листинга Google Maps?

Этот учебник извлекает шесть полей из каждой карточки места: название предприятия, категория, адрес, номер телефона, звёздный рейтинг и количество отзывов. Это публичные поля листинга, которые любой посетитель может видеть на карте. Оставайтесь в рамках этих публичных данных и избегайте содержимого отзывов, профилей рецензентов или любых других персональных данных.

Нужна ли отрисовка JavaScript для парсинга Google Maps?

Да. В отличие от простой страницы результатов поиска, панель результатов Maps не существует в исходном HTML; она отрисовывается скриптами в браузере. Используйте JavaScript-токен Crawlbase, чтобы Crawling API отрисовал страницу перед её захватом. Наше руководство по парсингу JavaScript-страниц с Python описывает, когда необходима отрисовка.

Как парсить Google Maps по нескольким городам?

Составьте список запросов по категориям и местоположениям, преобразуйте каждый в URL вида /maps/search/ и перебирайте их, загружая каждый через Crawling API и парся той же функцией. Делайте паузу в несколько секунд между запросами, чтобы регулировать скорость, а не атаковать их, и собирайте записи в один объединённый список перед экспортом.

Есть ли официальная альтернатива парсингу Google Maps?

Да. Официальный Places API Google является санкционированным способом запроса деловых данных, таких как название, адрес, телефон, рейтинг и отзывы, с договором и квотами. Для любого объёма, превышающего лёгкое публичное исследование, или для производственного масштаба официальный API является правильным путём; парсинг публичной панели лучше всего ограничивать небольшими, уважительными задачами с данными, которые любой желающий может видеть на карте.

Начать создавать

Обходите любой сайт в масштабе, без борьбы с инфраструктурой.

Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.

Самообслуживание · Звонок отдела продаж не требуется · Доступны корпоративные объёмы краулинга