SuperPages является одним из крупнейших онлайн-каталогов предприятий в США с миллионами компаний, индексированных по отраслям и местоположению. Каждый листинг содержит именно тот публичный, структурированный набор данных, который нужен отделам продаж и маркетинга для списков потенциальных клиентов: название компании, её категория, почтовый адрес, публичный номер телефона и зачастую ссылка на собственный сайт компании. Для создания регионального датасета поставщиков услуг или формирования списков для B2B-рассылки эти публичные данные каталога являются именно тем сырьём, которое вам нужно.

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

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

Небольшой Python-парсер, который принимает поисковый запрос и местоположение, получает отрисованную страницу результатов поиска SuperPages через Crawling API и извлекает структурированную запись для каждого предприятия на странице. В качестве рабочего примера используются предприятия категории "Home Services" в "Los Angeles, CA". Для каждого листинга извлекаются следующие поля:

  • Название компании основной идентификатор, по которому группируются лиды.
  • Категория отрасль, к которой относится листинг, используется для сегментации лидов.
  • Адрес публичный почтовый адрес, включая город, штат и индекс.
  • Телефон публичный контактный номер, отображённый на карточке листинга.
  • Сайт ссылка на собственный сайт компании, если он указан.
  • Ссылка на страницу компании URL отдельной страницы компании на SuperPages.

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

Можно обратиться к URL поиска SuperPages через библиотеку requests и в удачный день получить HTML-ответ. Проблема появляется при больших объёмах. SuperPages применяет защиту от парсинга: ограничивает по IP-адресу, показывает CAPTCHA трафику с автоматизированным поведением и блокирует дата-центрные адреса, делающие запросы в плотном, машиноподобном паттерне. Единичный запрос с ноутбука может пройти; несколько сотен с одного и того же IP уже нет.

Поэтому парсер, который реально завершает задачу, нуждается в запросах, которые выглядят как обычный посетитель с доверенного IP. Можно создать это самостоятельно с пулом ротирующих резидентных прокси и инфраструктурой для их поддержки, но сопровождение этого стека составляет большую часть работы. Crawling API объединяет всё в один вызов: вы отправляете ему URL, он маршрутизирует запрос через резидентные IP на стороне сервера и обрабатывает антибот-слой, а вам возвращает HTML для парсинга.

Какой токен использовать

Crawlbase предлагает два типа токенов. Обычный токен получает статический HTML; JavaScript (JS) токен сначала отрисовывает страницу в реальном браузере. SuperPages отдаёт данные листингов в исходном HTML, поэтому здесь подходит обычный токен, и каждый запрос обходится дешевле. Берите JS-токен только в том случае, если целевой сайт начинает отображать листинги на клиентской стороне.

Что нужно подготовить

Несколько вещей, которые нужно иметь под рукой. Ни одна не займёт много времени.

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

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

Аккаунт Crawlbase и токен. Зарегистрируйтесь, откройте панель управления и скопируйте обычный токен со страницы документации аккаунта. Вы получаете до 20 000 бесплатных запросов, карта не требуется. Храните токен как пароль и не добавляйте его в систему контроля версий.

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

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

bash
python --version

python -m venv superpages_env
source superpages_env/bin/activate

pip install crawlbase beautifulsoup4

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

Шаг 1: Получение отрисованной страницы результатов поиска

Начните с получения одной страницы результатов. Сформируйте URL поиска из запроса и местоположения, импортируйте класс CrawlingAPI, инициализируйте его с вашим токеном и запросите URL. Проверка статуса перед парсингом гарантирует, что ошибки будут явными, а не молчаливыми.

python
from urllib.parse import urlencode
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

def build_url(query, location, page=1):
    base = "https://www.superpages.com/search?"
    params = {"search_terms": query, "geo_location_terms": location, "page": page}
    return base + urlencode(params)

def crawl(page_url):
    response = api.get(page_url)
    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__":
    url = build_url("Home Services", "Los Angeles, CA")
    html = crawl(url)
    print(html[:500] if html else "No HTML returned")

Обратите внимание: проверка статуса читает cb_status (legacy pc_status) из заголовков ответа, что является статусом Crawlbase для запроса, отличным от вышестоящего HTTP-кода. Запустите скрипт командой python scraper.py, и вы должны увидеть реальную разметку результатов, а не страницу с проверкой. Это подтверждает работу пути получения данных ещё до написания единого селектора.

Crawlbase Crawling API

SuperPages ограничивает по IP и проверяет трафик с паттернами парсера именно то трение, которое только что мотивировало шаг получения данных. Crawling API маршрутизирует каждый запрос через ротирующие резидентные IP на стороне сервера, обрабатывает CAPTCHA и блокировки и отдаёт готовый к парсингу HTML, избавляя вас от необходимости запускать флот headless-браузеров и пул прокси. Начните с публичной страницы поиска на бесплатном тарифе.

Шаг 2: Парсинг листингов с BeautifulSoup

Имея страницу результатов, загрузите её в BeautifulSoup и переберите карточки результатов. Каждая компания представляет собой самодостаточную карточку под предсказуемым контейнером, и внутри неё название, адрес, телефон, сайт и ссылка на страницу компании соответствуют собственным селекторам. Защитное чтение каждого поля с возвратом пустой строки при отсутствии элемента предотвращает сбой запуска из-за одного отсутствующего значения.

python
from bs4 import BeautifulSoup

BASE = "https://www.superpages.com"

def extract_listings(html):
    soup = BeautifulSoup(html, "html.parser")
    listings = []

    for business in soup.select("div.search-results > div.result"):
        name_el = business.select_one("a.business-name span")
        category_el = business.select_one("div.categories")
        address_el = business.select_one("span.street-address")
        phone_el = business.select_one("a.phone.primary")
        website_el = business.select_one("a.weblink-button")
        link_el = business.select_one("a.business-name")

        listings.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 "",
            "phone": phone_el.text.strip() if phone_el else "",
            "website": website_el["href"] if website_el else "",
            "detail_page_link": BASE + link_el["href"] if link_el else "",
        })

    return listings

Селекторы берутся прямо из разметки карточек SuperPages: название компании находится в span внутри якоря a.business-name, адрес в span.street-address, телефон в a.phone.primary, а внешний сайт в a.weblink-button. Ссылка на страницу компании повторно использует тот же якорь a.business-name и является относительным путём, поэтому к нему добавляется префикс хоста BASE для формирования полного URL. Каждое поле защищено конструкцией if ... else "", чтобы отсутствующий элемент оставлял в записи пустую строку, а не вызывал исключение.

Дрейф селекторов

Имена классов выше (result, business-name, street-address, phone primary, weblink-button) отражают текущую разметку SuperPages, и эта разметка меняется без предупреждения. Относитесь к селекторам как к стартовому шаблону, а не контракту. Когда поле возвращается пустым во всех листингах, повторно проверьте живую страницу результатов в инструментах разработчика браузера и обновите селектор. Периодическое обслуживание селекторов является нормой для любого производственного парсера.

Шаг 3: Обработка пагинации через страницы результатов

Одна страница является демонстрацией; реальный список лидов охватывает полный набор результатов. SuperPages отображает страницу результатов через параметр URL page, поэтому обход страниц представляет собой цикл по целочисленному диапазону. Функции build_url и extract_listings переносятся без изменений, так что пагинация является лишь внешним циклом с паузами между запросами.

python
import time

def scrape_all_pages(query, location, max_pages):
    all_listings = []
    for page in range(1, max_pages + 1):
        print(f"Scraping page {page}...")
        url = build_url(query, location, page)
        html = crawl(url)
        if not html:
            print(f"Stopping at page {page}: no HTML")
            break
        listings = extract_listings(html)
        if not listings:
            print(f"No results on page {page}; reached the end")
            break
        all_listings.extend(listings)
        time.sleep(2)
    return all_listings

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

Шаг 4: Сборка и экспорт

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

python
import csv
import json
import time
from urllib.parse import urlencode
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})
BASE = "https://www.superpages.com"
FIELDS = ["name", "category", "address", "phone", "website", "detail_page_link"]

def build_url(query, location, page=1):
    base = "https://www.superpages.com/search?"
    params = {"search_terms": query, "geo_location_terms": location, "page": page}
    return base + urlencode(params)

def crawl(page_url):
    response = api.get(page_url)
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['headers']['cb_status']}")
    return None

def extract_listings(html):
    soup = BeautifulSoup(html, "html.parser")
    listings = []
    for business in soup.select("div.search-results > div.result"):
        name_el = business.select_one("a.business-name span")
        category_el = business.select_one("div.categories")
        address_el = business.select_one("span.street-address")
        phone_el = business.select_one("a.phone.primary")
        website_el = business.select_one("a.weblink-button")
        link_el = business.select_one("a.business-name")
        listings.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 "",
            "phone": phone_el.text.strip() if phone_el else "",
            "website": website_el["href"] if website_el else "",
            "detail_page_link": BASE + link_el["href"] if link_el else "",
        })
    return listings

def scrape_all_pages(query, location, max_pages):
    all_listings = []
    for page in range(1, max_pages + 1):
        print(f"Scraping page {page}...")
        html = crawl(build_url(query, location, page))
        if not html:
            break
        listings = extract_listings(html)
        if not listings:
            break
        all_listings.extend(listings)
        time.sleep(2)
    return all_listings

def save_json(data, filename="superpages_listings.json"):
    with open(filename, "w") as f:
        json.dump(data, f, indent=4)

def save_csv(data, filename="superpages_listings.csv"):
    with open(filename, "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        writer.writeheader()
        writer.writerows(data)

def main():
    rows = scrape_all_pages("Home Services", "Los Angeles, CA", max_pages=5)
    save_json(rows)
    save_csv(rows)
    print(f"Saved {len(rows)} listings")

if __name__ == "__main__":
    main()

Поскольку каждая запись имеет одни и те же шесть ключей, столбцы CSV выравниваются чисто, и csv.DictWriter записывает их без дополнительного маппинга. Замените запрос и местоположение внизу, чтобы нацелиться на другую отрасль или город, и увеличьте max_pages, когда нужен более глубокий охват одного поиска.

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

Запустите полный скрипт командой python scraper.py, и получите чистый список структурированных записей, готовых для записи в JSON, CSV или базу данных. JSON-файл выглядит следующим образом:

json
[
  {
    "name": "Evergreen Cleaning Systems",
    "category": "House Cleaning",
    "address": "3325 Wilshire Blvd Ste 622, Los Angeles, CA 90010",
    "phone": "213-375-1597",
    "website": "https://www.evergreencleaningsystems.com",
    "detail_page_link": "https://www.superpages.com/los-angeles-ca/bpp/evergreen-cleaning-systems-540709574"
  },
  {
    "name": "Any Day Anytime Cleaning Service",
    "category": "House Cleaning",
    "address": "27612 Cherry Creek Dr, Santa Clarita, CA 91354",
    "phone": "661-297-2702",
    "website": "",
    "detail_page_link": "https://www.superpages.com/santa-clarita-ca/bpp/any-day-anytime-cleaning-service-513720439"
  }
]

Листинги без указанного сайта возвращаются с "website": "", что ожидаемо и именно поэтому парсер читает каждое поле защитно, а не предполагает наличие каждого ключа. Отсюда данные готовы к дедупликации, обогащению или импорту в инструменты для рассылки. Для общего рабочего процесса по превращению этих записей в кампанию см. руководство о парсинге для лидогенерации.

Масштабирование на несколько запросов и местоположений

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

python
searches = [
    ("Home Services", "Los Angeles, CA"),
    ("Plumbers", "San Diego, CA"),
    ("Electricians", "Phoenix, AZ"),
]

all_rows = []
for query, location in searches:
    all_rows.extend(scrape_all_pages(query, location, max_pages=3))

save_json(all_rows)
save_csv(all_rows)

Вызов extend добавляет все результаты поиска в один плоский список, так что шаг экспорта остаётся неизменным. Когда матрица запросов и местоположений становится большой, переносите работу с единого синхронного цикла на очередь. Асинхронный Crawler принимает URL пакетами и возвращает результаты по мере готовности, что лучше подходит, чем блокировка на каждой странице при парсинге тысяч поисков.

Как оставаться незаблокированным

Даже при том, что Crawling API обрабатывает ротацию IP и антибот-слой, несколько привычек помогают запуску оставаться стабильным, и они применимы к любой цели-каталогу.

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

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

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

Допустимость парсинга SuperPages зависит от условий обслуживания сайта, вашей юрисдикции и того, что вы делаете с данными. Ни один из приведённых здесь кодов не меняет этого: он лишь обеспечивает работу технической части. Прочитайте Условия обслуживания SuperPages и его robots.txt и относитесь к обоим как к границе того, что и как быстро вы собираете. Всё в этом руководстве ограничено публичными B2B-данными бизнес-каталога: название компании, её категория, публичный почтовый адрес, публичный номер телефона и ссылка на собственный сайт. Это информация, которую любой посетитель может увидеть без регистрации, и она описывает предприятия, а не частных лиц.

Правовая ответственность возрастает, когда вы действуете на основе этих данных. Контактные данные предприятий по-прежнему подпадают под законодательство о конфиденциальности и защите от спама во многих регионах. По GDPR именной контакт в малом бизнесе может считаться персональными данными, поэтому вам нужно законное основание для их хранения и обработки, и люди сохраняют право на возражение и удаление. В США Закон CAN-SPAM регулирует коммерческую электронную почту: вы должны честно представляться, избегать вводящих в заблуждение строк темы и оперативно выполнять запросы об отказе. Правила холодных звонков и реестры "не звонить" применяются к телефонным контактам в том же духе. Сбор данных является одним делом; их использование для рассылки именно там возникают эти обязательства, поэтому встраивайте обработку отказов и списки подавления с самого начала, а не добавляйте их позже.

Что этот подход не охватывает, не менее важно. Он не затрагивает ничего за логином и не обходит аутентификацию или контроль доступа для достижения закрытого контента; это выходит за рамки данного руководства и противоречит условиям сайта. Если SuperPages предлагает официальный API или лицензированный канал данных для нужного вам объёма, отдайте предпочтение ему: санкционированный источник полностью устраняет неоднозначность. Когда сомневаетесь в коммерческом использовании агрегированного датасета контактов, проверьте применимые к вам правила, а не предполагайте, что публичное означает неограниченное.

Итоги

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

  • SuperPages является структурированным B2B-каталогом. Каждый результат поиска является карточкой с названием компании, категорией, публичным адресом, публичным телефоном, необязательным сайтом и ссылкой на страницу компании, управляемой параметрами URL search_terms и geo_location_terms.
  • Обычный запрос не справляется при больших объёмах. Ограничения по частоте, CAPTCHA и блокировки по IP останавливают наивный цикл; Crawling API маршрутизирует через резидентные IP и возвращает готовый к парсингу HTML в одном вызове.
  • BeautifulSoup выполняет извлечение данных. Соотнесите название, категорию, адрес, телефон, сайт и ссылку с текущими селекторами, читайте каждое поле защитно и ожидайте дрейфа этих селекторов.
  • Пагинация является циклом по параметру page. Повторно используйте тот же парсер для всех страниц, останавливайтесь досрочно на пустой странице, делайте паузы между запросами и экспортируйте в JSON и CSV.
  • Законная рассылка является вашей ответственностью. Данные являются публичными, но GDPR и CAN-SPAM по-прежнему применяются к тому, как вы контактируете с этими предприятиями, поэтому вам нужно законное основание и необходимо соблюдать отказы.

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

Нужен обычный токен или JS-токен для SuperPages?

Обычный токен. SuperPages отдаёт данные листингов в исходном HTML, поэтому запрос с обычным токеном возвращает парсируемую разметку и обходится дешевле. JS-токен сначала отрисовывает страницу в реальном браузере, что нужно только когда цель загружает листинги на клиентской стороне после получения страницы. Начните с обычного токена и переключайтесь только если поля возвращаются пустыми повсюду.

Как обработать пагинацию на SuperPages?

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

Мои селекторы возвращают пустые значения. Что изменилось?

Почти наверняка разметка SuperPages. Имена классов типа result, business-name, street-address и weblink-button меняются без предупреждения, поэтому селекторы, работавшие в прошлом месяце, могут сломаться. Повторно проверьте живую страницу результатов в инструментах разработчика браузера и обновите селекторы. Периодическое обслуживание селекторов является нормой для любого производственного парсера, а не признаком поломки.

Как экспортировать лиды в CSV или Excel?

Парсер уже записывает CSV через csv.DictWriter, поскольку все записи имеют одинаковые ключи. Для Excel pandas преобразует тот же список словарей в таблицу за две строки: pd.DataFrame(rows).to_excel("superpages_listings.xlsx", index=False). Столбцы выравниваются чисто, поскольку набор полей фиксирован.

Можно ли также парсить отдельные страницы компаний?

Да. Каждая запись содержит detail_page_link, поэтому можно передать эти URL обратно через ту же функцию crawl и парсить отдельную страницу для дополнительных полей, таких как часы работы или расширенный блок контактов. Делайте тот же второй проход с паузами, поскольку это удваивает количество запросов, и ограничивайтесь публичной деловой информацией на странице.

Как сделать рассылку соответствующей требованиям?

Относитесь к собранному списку как к отправной точке, а не зелёному свету. Убедитесь, что у вас есть законное основание для контакта с каждой компанией в соответствии с правилами вашего региона, честно представляйтесь в каждом сообщении и встраивайте обработку отказов и список подавления в ваш конвейер рассылки с первого дня. Обязательства по GDPR и CAN-SPAM прикреплены к рассылке, а не к сбору данных, поэтому работа по соответствию требованиям касается того, как вы используете данные.

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

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

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

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