Собрать базу телефонов / email для холодных контактов можно несколькими способами:

1) купить готовую базу

2) написать и запустить парсер сайтов по списку

3) собирать в режиме ручного поиска в интернете

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


Откуда берутся данные

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

Если совсем просто, OSM — это картографический проект, где любой зарегистрированный пользователь может указать точку на карте мира и наполнить ее различной информацией. Если эта точка, например, магазин или фитнес-клуб, то указать можно: 

  • контактный телефон;

  • адрес электронной почты;

  • адрес сайта;

  • часы работы.

Многие владельцы бизнеса так и делают. OSM — это бесплатный способ улучшить поисковую видимость компании в интернете. Тем более, что карты OSM используются во многих мобильных приложениях, например, «Organic maps», «Go map!». А значит, добавленная компания будет отображаться в поиске этих приложений.

Как получить данные из OSM

Вариантов получить необходимые данные несколько. Опишу наиболее простой.

Сообщество OSM разработало язык запросов overpass для получения информации об объектах, нанесенных на карту. На мой взгляд, синтаксис языка запутанный, поэтому разбирать его не будем. Да и понадобится для нашей задачи всего один запрос. Вот он:

[out:json];
nw[~"^contact:.*|email|phone|.*site$"~"."]
({{bbox}});
(._;>;);out;

Перевожу на человеческий язык — "нужно получить в формате json все точки и области видимой части карты, у которых заполнено хотя бы одно из полей с телефоном / почтой / адресом сайта"

Выполнить запрос можно из скрипта на python (есть удобная библиотека overpy), а можно воспользоваться одним из онлайн-инструментов, например, этим. Итак, алгоритм действий:

1. Выбираем на карте регион нашего интереса. Пусть это будет город Париж,  или, например, Ростов-на-Дону с окрестными деревнями.

2. Жмем “Manual Query”, вставляем и запускаем наш запрос.

3. Все! Через несколько секунд выбранные данные отобразятся на карте, скачать их можно будет в geojson-формате. 

Использовать данный формат для нашей задачи неудобно, поэтому для извлечения нужной информации напишем python-скрипт, который:

  • извлекает из указанного файла название геообъекта, его координаты и контактные данные (телефон, email, адрес сайта, ссылки на социальные профили);

  • сохраняет полученную информацию в файле табличного формата (csv) для последующей обработки и использования.

Код скрипта:

import json
from dataclasses import dataclass, asdict
import csv

@dataclass
class Record:
    type_obj: str
    lat: float
    lon: float
    name: str
    email: str
    phone: str
    site: str
    social: str

# абсолютный путь к файлу geojson OSM
GEOJSON_FILE = '<path>/<filename>'
# путь и имя csv-файла
CSV_FILE = '<path>/<filename>'
# имена ключей, в которых могут храниться данные нужных типов
TYPE_OBJ = ['leisure', 'amenity']
ALT_NAME = ['addr:city', 'addr:street', 'addr:housenumber', 'id']
EMAIL = ['email', 'contact:email']
PHONE = ['phone', 'contact:phone']
SITE = ['site', 'website', 'contact:website']
SOCIAL = ['contact:facebook', 'contact:instagram', 'contact:ok', 'contact:vk', 'contact:telegram']

def _get_data(jsn:dict, domain_name:str):
    return ', '.join([jsn[_] for _ in jsn if _ in domain_name])

def get_object(rec:dict):
    type_obj = _get_data(rec['properties'], TYPE_OBJ)            
    email = _get_data(rec['properties'], EMAIL)
    phone = _get_data(rec['properties'], PHONE)            
    site = _get_data(rec['properties'], SITE)            
    social = _get_data(rec['properties'], SOCIAL)            
    name = rec['properties'].get('name', _get_data(rec['properties'], ALT_NAME))
    match rec['geometry']['type']:
        case 'Point':
           lat, lon = rec['geometry']['coordinates']
        case 'Polygon':
           lat, lon = rec['geometry']['coordinates'][0][0]
        case _:
            return
    record = Record(
        type_obj=type_obj, lat=lat, lon=lon, name=name,
        email=email, phone=phone, site=site, social=social
        )
    return record

if __name__ == '__main__':
    count_parser = 0
    data = json.load(open(GEOJSON_FILE, 'r', encoding='utf-8'))
    print (f'Всего объектов в файле: {len(data["features"])}')
    f = open(CSV_FILE, 'a', newline='', encoding='utf-8')
    csv_writer = None

    for rec in data['features']:
        record = get_object(rec)
        if not record:
            continue
        if not csv_writer:
            csv_writer = csv.DictWriter(f, fieldnames=asdict(record).keys(), delimiter=';')
            csv_writer.writeheader()
        csv_writer.writerow(asdict(record))
        count_parser += 1
    f.close()
    print (f'Сохранено: {count_parser}')

Разумеется, код можно доработать под свои нужды.

В итоге, за несколько секунд для выбранного региона мы получили набор данных:

  • название объекта;

  • тип объекта;

  • его гео координаты;

  • номера телефонов;

  • адреса электронной почты;

  • адреса сайтов;

  • ссылки на социальные профили.

Для получения окончательного результата нужно совсем немного поработать с полученными данными: удалить дубликаты (например, сетевые магазины, как правило, указывают одни и те же контакты) и выбрать только интересующие нас типы объектов. 

Например, нам нужны потенциальные клиенты в сегменте HORECA, выбираем типы объектов:

  • bar;

  • cafe;

  • fast_food;

  • pub;

  • restaurant.

В Ростове-на-Дону, по данным OSM, объектов типа, у которых указаны контактные данные, нашлось 241, в Париже — 7570.

Узнать, как обозначаются типы компаний для вашей задачи, можно на этом сервисе

Бонус

В минимальной постановке задача решена: данные для холодных контактов собраны. 

Но зачастую для большой базы контактов нужно решить сопутствующую задачу — разбить базу на несколько географических секторов и распределить ее между менеджерами. Сделать это по географическим координатам не совсем удобно (при указании данных объекта на карте почтовый адрес далеко не всегда указывается корректно или вообще пропускается). 

Но есть выход. У OSM есть собственный сервис геокодирования и обратного геокодирования — Nominatim. Получить почтовый адрес по геокоординатам можно, например, с помощью такого кода на python:

import requests
def get_address(lat:float, lon:float)-> str:
    url = f'https://nominatim.openstreetmap.org/reverse?format=json&lat={lat}&lon={lon}'
    result = requests.get(url)
    return result.json().get('display_name')

Пара слов в защиту кейса

Объективно слабое место разобранного кейса — достоверность данных. Но, во-первых, к любой базе, которую вы купили / скачали, следует относиться критически, потому как неизвестен источник этих данных.

А во-вторых, существуют различные методики валидации и кросс-проверок полученных данных. Возможно, их разбор станет темой следующей публикации


НЛО прилетело и оставило здесь промокод для читателей нашего блога:
-15% на заказ любого VDS (кроме тарифа Прогрев) — HABRFIRSTVDS

Комментарии (5)


  1. roman_deev
    13.08.2024 10:19
    +4

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

    В интерфейсе Overpass Turbo есть кнопка Помощник (Wizard), которому вы можете перечислить теги, а в ответ он составит вам запрос.

    Ваш запрос в более приятном виде:

    [out:json][bbox:{{bbox}}];
    (
      nw[email];
      nw[phone];
      nw[website];
      nw[~"^contact:"~".*"];
    );
    out geom;
    


    1. osp2003 Автор
      13.08.2024 10:19

      Согласен, Ваш запрос выглядит приятней и читается проще

      гпт не использовал, честное слово, подсматривал в документацию. Применил в своем запросе вот эту регулярку

      nw[~"^contact:.*|email|phone|.*site$"~"."]

      потому что интересующая инфа может присутствовать в различных тегах, например информация по сайту может быть в тегах 'site', 'website', 'contact:website'

      Ваш пример запроса этот кейс не учитывает. если не прав - поправьте меня


      1. roman_deev
        13.08.2024 10:19

        Тег site используется не для указания веб-сайта :) https://taginfo.openstreetmap.org/keys/site#values


        1. osp2003 Автор
          13.08.2024 10:19

          Наверное, да. Но вы не представляете чего только люди не придумают

          пока готовил статью - выполнил выборочный парсинг по отдельным регионам. Вот несколько примеров "не по документации"

          {"name": "Кырсай", "site": "kyr-altay.ru", "tourism": "chalet"}
          {"name": "Мазаев-Моторс", "shop": "car_repair", "site": "http://mazaev-club.ru/"}
          {"name": "Flamingo Bar (Bandara Hotel)", "site": "https://www.phuketflamingo.com", "amenity": "bar"}
          ну и т.д.

          возможно это не самый популярный тег для указания сайта, но в запросе я его всет-таки учел


          1. roman_deev
            13.08.2024 10:19
            +1

            Это совсем крупицы сомнительных данных. Если уж на то пошло, то нужная информация может оказаться в любом теге :) Но не в site= теперь — от ссылок его подчистил.