Foursquare содержит публичные данные о местах для миллионов заведений: ресторанов, кафе, парков, баров и музеев, каждое из которых имеет название, категорию, адрес и публичный рейтинг. Для исследования местного бизнеса, анализа рынка или создания функции рекомендаций эти публичные данные о заведениях представляют реальную ценность. Сложность в том, что Foursquare рендерит свои страницы с помощью JavaScript, поэтому обычный HTTP-запрос возвращает практически пустую оболочку вместо списка заведений, который вы видите в браузере.
В этом руководстве показано, как извлекать публичные данные о заведениях Foursquare с помощью Python через Crawling API, который рендерит страницу и маршрутизирует запрос через доверенный IP в одном вызове. Всё здесь ограничено публичными данными о заведениях и местах: названиями, категориями, адресами и публичными рейтингами. Руководство не охватывает ничего, что находится за логином, и не касается персональных данных отдельных пользователей или их чекинов. Для промышленного использования официальный Foursquare Places API является правильным инструментом, и раздел о правовых аспектах в конце объясняет почему.
Что вы создадите
Небольшой скрапер на Python, который принимает публичный URL поиска Foursquare или URL отдельного заведения, загружает полностью отрендеренную страницу через Crawling API и парсит несколько публичных полей заведения:
- Venue name название бизнеса или места, отображаемое в листинге.
- Category тип заведения, например тайская кухня, пекарня или бар.
- Address публичный почтовый адрес заведения.
- Rating совокупный публичный рейтинг, отображаемый для заведения.
- Link постоянная ссылка на публичную страницу заведения.
Скрипт обрабатывает несколько результатов со страницы поиска, обходит каждый листинг и экспортирует собранные записи в JSON и CSV, чтобы данные были готовы для исследования местного бизнеса. Обратите внимание на то, чего намеренно нет: никаких профилей отдельных пользователей, никаких историй чекинов, никаких персональных данных, привязанных к конкретному человеку. Это выходит за рамки данного руководства сознательно.
Почему обычный запрос не работает на Foursquare
Запросите страницу поиска Foursquare с помощью обычного HTTP-клиента, и вы получите технически успешный, но практически бесполезный ответ. Контент заведений загружается динамически: реальные листинги появляются только после того, как скрипты страницы выполнятся в браузере и запросят данные из внутренних конечных точек. Обычный запрос захватывает страницу до того, как всё это произойдёт, поэтому парсить нечего.
Помимо рендеринга, Foursquare следит за автоматизированным трафиком. IP-диапазоны датацентров и повторяющиеся паттерны запросов блокируются или ограничиваются по скорости ещё до загрузки интересного контента. Поэтому работающий скрапер требует двух вещей в одном запросе: реального браузера, рендерящего страницу, и IP-адреса, который платформа воспринимает как обычного посетителя. Это можно реализовать с помощью headless-браузера и пула резидентских прокси, но поддержание такого стека в рабочем состоянии составляет большую часть работы. Crawling API объединяет оба компонента в один вызов. Вы передаёте ему URL, он рендерит страницу за доверенным резидентским IP и возвращает готовый HTML для парсинга. Подробнее об этом см. в нашем руководстве по обходу JavaScript-сайтов.
Crawlbase предлагает два типа токенов. Обычный токен получает статичный HTML; JavaScript (JS) токен сначала рендерит страницу в реальном браузере. Foursquare рендерится на стороне клиента, поэтому здесь нужен JS-токен. Обычный токен вернёт ту же оболочку, что и обычный запрос, с которой ничего полезного не получить.
Предварительные требования
Несколько вещей, которые нужно подготовить заранее. Всё это займёт немного времени.
Базовые знания Python. Вы должны уметь запускать скрипт и устанавливать пакеты с помощью pip. Если вы только знакомитесь с парсингом HTML, наш вводный материал о использовании BeautifulSoup в Python охватывает сторону извлечения данных.
Python 3.8 или новее. Проверьте командой python --version. Если Python не установлен, скачайте его с python.org.
Аккаунт Crawlbase и JS-токен. Зарегистрируйтесь, откройте панель управления и скопируйте JavaScript (JS) токен. Crawlbase предоставляет до 20 000 бесплатных запросов на старт, и вы платите только за успешные запросы. Относитесь к токену как к паролю: он аутентифицирует ваши запросы, поэтому не храните его в системе контроля версий.
Настройка проекта
Создайте изолированное виртуальное окружение, затем установите две библиотеки, необходимые скраперу.
python --version python -m venv foursquare_env source foursquare_env/bin/activate pip install crawlbase beautifulsoup4
В Windows активируйте окружение командой foursquare_env\Scripts\activate вместо строки с source. Две зависимости выполняют основную работу: crawlbase является официальным клиентом для Crawling API, а beautifulsoup4 парсит возвращаемый HTML, позволяя извлекать отдельные поля по селектору.
Шаг 1: Получение отрендеренной страницы поиска
Начните с получения готовой страницы. Импортируйте CrawlingAPI, инициализируйте его с вашим JS-токеном и запросите публичный URL поиска. Поскольку Foursquare загружает листинги асинхронно, передайте параметры ajax_wait и page_wait, чтобы API дождался рендеринга контента. Проверяйте статус перед парсингом, чтобы ошибки сразу становились заметными.
from crawlbase import CrawlingAPI crawling_api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def make_crawlbase_request(url): options = { "ajax_wait": "true", "page_wait": "5000", } response = crawling_api.get(url, options) if response["headers"]["cb_status"] == "200": return response["body"].decode("utf-8") print(f"Failed to fetch the page. Crawlbase status: {response['headers']['cb_status']}") return None if __name__ == "__main__": url = "https://foursquare.com/explore?near=New%20York&q=Food" html = make_crawlbase_request(url) print(html[:500] if html else "No HTML returned")
Оба параметра ожидания важны для клиентски-рендеримого источника. ajax_wait указывает API дождаться завершения загрузки асинхронного контента, а page_wait задерживает выполнение на фиксированное количество миллисекунд после загрузки, чтобы поздно рендеримые листинги появились до захвата страницы. Пяти секунд достаточно для начала; увеличьте значение, если листинги возвращаются пустыми. Проверка статуса читает cb_status (legacy pc_status) из заголовков ответа, который является статусом самого обхода в Crawlbase. Запустите скрипт и вы должны увидеть реальную разметку заведений, что подтверждает работу рендеринга, прежде чем вы напишете хоть один селектор.
Foursquare требует отрендеренную страницу за доверенным IP в одном вызове. Crawling API принимает JS-токен, запускает страницу в реальном браузере, чтобы листинги с ajax_wait действительно загружались, ротирует резидентские IP на стороне сервера и возвращает готовый HTML, избавляя вас от необходимости управлять headless-флотом и пулом прокси самостоятельно. Начните со страницы публичного поиска на бесплатном уровне.
Шаг 2: Изучение разметки и парсинг листингов
Прежде чем писать селекторы, откройте страницу результатов поиска Foursquare в браузере, щёлкните правой кнопкой мыши по листингу и выберите «Просмотр кода». Вы ищете элементы, которые оборачивают каждое заведение и содержат нужные поля. На странице результатов поиска каждое место находится внутри элемента списка, и публичные поля сопоставляются со следующими селекторами:
-
Venue name находится в теге
<a>внутриdiv.venueName. -
Address находится в
div.venueAddress. -
Category находится в
span.categoryName. -
Link является значением
hrefтого же якоряdiv.venueName a.
Имея готовый HTML, загрузите его в BeautifulSoup и пройдитесь по каждому листингу. Каждая строка результата соответствует ul.recommendationList > li.singleRecommendation. Проверка существования каждого поля не позволяет парсеру падать при отсутствии одного из них у заведения.
from bs4 import BeautifulSoup def scrape_foursquare_listings(html): soup = BeautifulSoup(html, "html.parser") venues = [] listings = soup.select("ul.recommendationList > li.singleRecommendation") for listing in listings: name_el = listing.select_one("div.venueName a") address_el = listing.select_one("div.venueAddress") category_el = listing.select_one("span.categoryName") rating_el = listing.select_one("span.venueScore") href = name_el["href"] if name_el and name_el.has_attr("href") else "" venues.append({ "name": name_el.text.strip() if name_el else "", "category": category_el.text.strip() if category_el else "", "address": address_el.text.strip() if address_el else "", "rating": rating_el.text.strip() if rating_el else "", "link": f"https://foursquare.com{href}" if href else "", }) return venues
Функция возвращает список словарей, по одному на заведение, с пятью публичными полями. Ссылка формируется путём добавления относительного href к источнику https://foursquare.com, чтобы каждая запись содержала рабочую постоянную ссылку. Рейтинг читается из span.venueScore; если Foursquare переименовал этот класс на проверяемой вами странице, замените его на тот, который оборачивает видимый счёт. Воспринимайте рейтинг как публичное агрегированное значение о месте, а не как сигнал об отдельном рецензенте.
Foursquare изменяет разметку и имена классов без предупреждения. Если поле возвращается пустым, заново проверьте живую страницу в инструментах разработчика браузера и обновите селектор. Периодическое обслуживание является нормальной практикой для любого промышленного скрапера, а не признаком неисправности. Защищённое извлечение, описанное выше, означает, что переименованный класс даёт пустую строку, а не ошибку.
Шаг 3: Обработка нескольких страниц результатов
Результаты поиска Foursquare используют пагинацию на основе кнопок: кнопка «See more results» подгружает следующую порцию заведений на месте, не переходя на новый URL. Crawling API может нажать эту кнопку за вас с помощью параметра css_click_selector, поэтому возвращаемый HTML уже содержит расширенный список. Укажите в селекторе кнопку, отвечающую за загрузку дополнительных результатов.
def make_request_with_pagination(url): options = { "ajax_wait": "true", "page_wait": "5000", "css_click_selector": "li.moreResults > button", } response = crawling_api.get(url, options) if response["headers"]["cb_status"] == "200": return response["body"].decode("utf-8") print(f"Failed to fetch the page. Crawlbase status: {response['headers']['cb_status']}") return None
Значение css_click_selector указывает на кнопку внутри li.moreResults. Если класс кнопки на проверяемой вами странице отличается, обновите селектор. Сохраняйте умеренный объём: исследование публичных данных не требует загрузки листингов целого города за один прогон. Извлекайте только то, что нужно, и останавливайтесь.
Шаг 4: Сборка полного скрапера и экспорт
Теперь объедините получение данных, парсинг и экспорт в один запускаемый скрипт. Скрипт получает страницу поиска с пагинацией, парсит все листинги и записывает результаты в форматы JSON и CSV. JSON сохраняет структуру для дальнейшей обработки; CSV напрямую открывается в таблице для быстрого исследования местного бизнеса.
import json import csv from crawlbase import CrawlingAPI from bs4 import BeautifulSoup crawling_api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"}) def make_request_with_pagination(url): options = { "ajax_wait": "true", "page_wait": "5000", "css_click_selector": "li.moreResults > button", } response = crawling_api.get(url, options) if response["headers"]["cb_status"] == "200": return response["body"].decode("utf-8") print(f"Failed to fetch the page. Crawlbase status: {response['headers']['cb_status']}") return None def scrape_foursquare_listings(html): soup = BeautifulSoup(html, "html.parser") venues = [] listings = soup.select("ul.recommendationList > li.singleRecommendation") for listing in listings: name_el = listing.select_one("div.venueName a") address_el = listing.select_one("div.venueAddress") category_el = listing.select_one("span.categoryName") rating_el = listing.select_one("span.venueScore") href = name_el["href"] if name_el and name_el.has_attr("href") else "" venues.append({ "name": name_el.text.strip() if name_el else "", "category": category_el.text.strip() if category_el else "", "address": address_el.text.strip() if address_el else "", "rating": rating_el.text.strip() if rating_el else "", "link": f"https://foursquare.com{href}" if href else "", }) return venues def save_to_json(data, filename="foursquare_data.json"): with open(filename, "w", encoding="utf-8") as f: json.dump(data, f, indent=4, ensure_ascii=False) print(f"Saved {len(data)} venues to {filename}") def save_to_csv(data, filename="foursquare_data.csv"): if not data: return fields = ["name", "category", "address", "rating", "link"] with open(filename, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fields) writer.writeheader() writer.writerows(data) print(f"Saved {len(data)} venues to {filename}") if __name__ == "__main__": url = "https://foursquare.com/explore?near=New%20York&q=Food" html = make_request_with_pagination(url) if html: venues = scrape_foursquare_listings(html) save_to_json(venues) save_to_csv(venues)
Это весь конвейер в одном файле: получение данных с пагинацией, парсинг листингов и экспорт в оба формата. Записчик CSV фиксирует порядок столбцов по пяти публичным полям, чтобы результат был стабильным между прогонами. Замените url на любой публичный поиск Foursquare, чтобы перенацелить скрапер на другой город или категорию.
Как выглядит результат
Запустите полный скрипт и получите чистый список публичных записей о заведениях. Ниже приведён сокращённый пример JSON-содержимого файла foursquare_data.json.
[ { "name": "Thai Diner", "category": "Thai", "address": "186 Mott St (at Kenmare), New York", "rating": "9.5", "link": "https://foursquare.com/v/thai-diner/5e46e2ec5791a10008c55728" }, { "name": "Mah-Ze-Dahr Bakery", "category": "Bakery", "address": "28 Greenwich Ave (Charles Street), New York", "rating": "9.1", "link": "https://foursquare.com/v/mahzedahr-bakery/568c0ce238fafac5f5ffe631" } ]
CSV-версия содержит те же поля в виде одной строки на заведение с заголовком, который напрямую открывается в любом табличном редакторе. Отсюда можно фильтровать по категории, группировать по районам или объединять адреса с другим набором данных для исследования местного бизнеса. Если ценовые сигналы являются частью вашего анализа, наше руководство по парсингу для ценовой аналитики охватывает, как агрегированные публичные данные обеспечивают такую работу.
Масштабирование до страниц отдельных заведений
Скрапер поиска даёт вам список и ссылку для каждого заведения. Чтобы обогатить каждую запись, передайте поле link обратно через ту же функцию получения данных и разберите собственную страницу заведения, где Foursquare предоставляет больше структурированных публичных деталей. На странице заведения публичные поля сопоставляются со следующими селекторами: название находится в h1.venueName, адрес в div.venueAddress, рейтинг в span[itemprop="ratingValue"], а публичное количество отзывов в div.numRatings. Повторно используйте защищённый паттерн извлечения из шага 2, задавайте паузы между запросами страниц заведений и ограничивайте прогон только теми заведениями, которые вам действительно нужны, вместо обхода всего, что возвращает поиск.
Foursquare является полезной отправной точкой, но данные о заведениях и местном бизнесе присутствуют на многих ресурсах. Для смежных техник см. наши руководства по парсингу Google Maps и парсингу листингов местного бизнеса, оба из которых применяют тот же подход с рендерингом и доверенным IP к другим картографическим и справочным ресурсам.
Сохранение работоспособности
Даже при отлаженном рендеринге через Crawling API Foursquare следит за трафиком, похожим на действия скрапера. Несколько привычек помогают поддерживать прогон в рабочем состоянии; они применимы к любому защищённому источнику.
- Задавайте темп запросов. Плотный цикл запросов, быстрейший способ получить ограничение скорости. Добавляйте реальные задержки между запросами и не стремитесь к агрессивному параллелизму.
- Используйте ротацию. Пул резидентских IP распределяет запросы по множеству реальных пользовательских адресов, чтобы ни один из них не превысил лимит. Crawling API делает это за вас; если вы создаёте собственный стек, именно эту часть нужно правильно реализовать.
- Следите за кодами статусов. Прогон, в котором начинают появляться блокировки или ошибки, сигнализирует о том, что текущая частота или уровень IP больше недостаточны. Снижайте активность, а не усиливайте её.
- Поддерживайте низкий объём и разнообразие целей. Исследование публичных данных не требует обхода целого города. Извлекайте только то, что нужно, и останавливайтесь.
Общую стратегию см. в нашем руководстве о том, как скрапить сайты без блокировок.
Законно ли скрапить Foursquare?
Это раздел, который нужно прочитать, прежде чем писать промышленный код. Сбор публичных данных о заведениях находится в серой зоне, которая во многом определяется тем, как вы это делаете и что именно собираете. Условия использования Foursquare ограничивают автоматизированный доступ, поэтому ознакомьтесь с ними вместе с файлом robots.txt сайта и считайте оба документа границей дозволенного в отношении того, что вы собираете и с какой скоростью. Приведённый выше код решает техническую часть; он не меняет то, что разрешено условиями.
Ограничивайтесь публичными, неперсональными данными о заведениях. Названия заведений, категории, публичные адреса и агрегированные рейтинги описывают места, а не людей, и это безопасная зона для такого рода исследований. Чего не следует касаться: всего, что находится за логином, профилей отдельных пользователей, историй чекинов или любых персональных данных об идентифицируемых пользователях. Агрегированный рейтинг заведения, это публичное число о месте; люди, оставившие отзывы или сделавшие чекин, не являются объектом вашего сбора. Когда вообще задействованы персональные данные, применяются законы о конфиденциальности, такие как GDPR и CCPA, что означает необходимость законного основания для их обработки и обязательного соблюдения запросов на удаление. Простейший способ избежать этого бремени, не собирать персональные данные в принципе, что именно и делает данное руководство.
Для любого реального, непрерывного или коммерческого использования правильным инструментом является официальный Foursquare Places API. Это санкционированный маршрут, предоставляющий структурированные данные о заведениях и категориях с чёткой лицензией на использование и позволяющий оставаться в рамках условий Foursquare. Эта статья является техническим руководством, строго ограниченным публичными данными о заведениях, а не одобрением крупномасштабного сбора данных или обработки персональных данных пользователей. Если для вашего проекта требуется больше, чем выборка публичных полей заведений, Places API или официальное соглашение о данных является правильным путём, а не более умный скрапер.
Ключевые выводы
- Foursquare рендерится на стороне клиента. Обычный запрос возвращает пустую оболочку, поэтому страницу нужно отрендерить перед парсингом, что обеспечивает JS-токен Crawling API.
-
Используйте правильные селекторы. Листинги находятся в
li.singleRecommendation, с названием, категорией, адресом и ссылкой вvenueName,categoryNameиvenueAddress. -
Обрабатывайте пагинацию на стороне сервера. Параметр
css_click_selectorнажимает кнопку «See more results», чтобы отрендеренный HTML уже содержал расширенный список. - Экспортируйте для исследований. Записывайте данные о заведениях в JSON для сохранения структуры и CSV для таблиц, затем фильтруйте или объединяйте для анализа местного бизнеса.
- Только публичные заведения, лучше используйте официальный API. Собирайте данные о местах, но никогда не персональные данные пользователей или чекины, и используйте Foursquare Places API для любого реального или коммерческого применения.
Часто задаваемые вопросы
Почему обычный запрос не возвращает данные с Foursquare?
Потому что Foursquare загружает листинги заведений на стороне клиента с помощью JavaScript. Исходный HTML представляет собой оболочку, которая заполняется только после выполнения скриптов страницы в браузере, поэтому обычный HTTP-запрос возвращает практически пустое тело. Чтобы получить реальные данные о заведениях, нужно сначала отрендерить страницу, что и обеспечивает JS-токен Crawling API.
Нужен ли обычный токен или JS-токен для Foursquare?
JS-токен. Обычный токен получает статичный HTML, который на Foursquare является той же пустой оболочкой, что и обычный запрос. JS-токен рендерит страницу в реальном браузере перед возвратом HTML, поэтому поля заведений присутствуют, когда BeautifulSoup их парсит.
Какие данные Foursquare безопасно скрапить?
Публичные, неперсональные данные о заведениях: названия заведений, категории, публичные адреса, агрегированные рейтинги и публичные ссылки на страницы заведений. Всё, что находится за логином, профили отдельных пользователей и истории чекинов недопустимы. Это персональные данные, и их сбор противоречит условиям Foursquare и, во многих юрисдикциях, законам о конфиденциальности.
Как обработать пагинацию при скрапинге Foursquare?
Поиск Foursquare использует кнопку «See more results» вместо отдельных URL. Передайте в Crawling API параметр css_click_selector, указывающий на эту кнопку (например, li.moreResults > button), и API нажмёт её во время рендеринга, чтобы получаемый HTML уже содержал расширенный список заведений.
Что лучше: использовать официальный Foursquare Places API или скрапить сайт?
Для любого реального, непрерывного или коммерческого использования используйте официальный Foursquare Places API. Это санкционированный маршрут, предоставляющий структурированные данные о заведениях и категориях с чёткой лицензией и позволяющий оставаться в рамках условий Foursquare. Скрапинг небольшой выборки публичных полей заведений с помощью описанного здесь подхода подходит для легковесных исследований без доступа к API, при условии соблюдения условий, robots.txt и ограничений по частоте запросов.
Как избежать блокировки при скрапинге Foursquare?
Поддерживайте низкую частоту запросов с одного IP, добавляйте реальные задержки между запросами, варьируйте цели вместо обхода целого города и маршрутизируйте через ротирующие резидентские IP, чтобы ни один адрес не превысил лимит. Crawling API управляет ротацией и пулом доверенных IP за вас. Следите за кодами статусов и снижайте активность при первых признаках блокировки трафика.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
