Когда мы пытаемся определить какому оператору принадлежит номер телефона, то обычно смотрим на его DEF код. Например, если номер начинается на 916, то это МТС, на 968 – Билайн, 926 – Мегафон (все зависит от вашего региона). Но этот метод очень условный и совершенно не подходит когда нужны точные данные. В реальности все сложнее: DEF коды часто делят между собой несколько операторов, и совсем необязательно, что искомый номер относится к операторам большой четверки. Ну и наконец, номер можно просто портировать.

В статье я расскажу о том, как по номеру телефона достоверно определить мобильного оператора который его обслуживает, а также получить дополнительную, «бесплатную» информацию – домашний регион абонента. Использовать эти данные можно как угодно, начиная от предзаполнения адреса в анкете пользователя и перенаправления на региональную версию вашего сервиса, до использования этих данных в процессинге и статистике. В конце статьи будет ссылка на github с исходниками.

Сразу оговорюсь, что домашний регион абонента, по большому счету, никак не связан с текущим месторасположением пользователя, т.е. определяемый регион отвечает на вопрос «Откуда номер?», а не «Где пользователь?».

Источники данных


Россвязь


Свой номер телефона мы получаем когда заключаем договор на обслуживание с оператором связи. В свою очередь, распределением диапазонов номеров между операторами связи, а также стандартизацией и общим контролем за услугами связи занимаются соответствующие государственные и международные организации. В России такой организацией является Федеральное агентство связи (Россвязь).

Таким образом, самым надежным источником информации кто обслуживает российский номер телефона является Россвязь, причем это открытые данные, которые агентство публикует у себя на сайте: www.rossvyaz.ru/opendata. Свежий список диапазонов мобильных номеров находится в CSV по ссылке. Каждая строка в файле выглядит как:

DEF-код, начало диапазона, конец диапазона, название оператора, название региона

Однако, с 2013 года появилась возможность переносить номер от оператора к оператору. Значит, руководствуясь только реестрами Россвязи, нельзя однозначно сказать, что номер обслуживается определенным оператором. Зато это вполне можно сказать про регион, ведь переносимость номера работает только в рамках домашнего региона и перенести номер из МТС Новосибирск в Tele2 Санкт-Петербург не получится в принципе.

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

База Данных Перенесенных Номеров


Если нужно точно определять оператора, тогда не обойтись без Базы Данных Перенесенных Номеров, оператором которой является ЦНИИC. Процедуру подключения к базе можно найти у них на сайте: zniis.ru. Но, к сожалению, насколько я знаю, напрямую подключиться к ним не просто, а получив подключение, делиться базой ни с кем нельзя.

Структура этой базы крайне проста: это три CSV файла в которых в формате «номер, название оператора» перечислены:

  • все перенесенные номера на текущий день (обновляется раз в день);
  • все перенесенные номера за последний час (обновляется раз в час);
  • все номера возращенные родному оператору обратно за последний час (обновляется раз в час).

На момент написания статьи, в БДПН находится около 6 миллионов записей.

Суммируя: у нас есть некие диапазоны номеров, которые соответствуют определенным операторам и регионам (Россвязь), и список номеров-исключений из этих диапазонов (БДПН), который распространяется только на название оператора.

Как определять абонентов


Самое очевидное решение этой задачи: посмотреть на слово «диапазон» и использовать перечисленные емкости буквально. Т.е. для определения номера, сортируем всех операторов по их диапазонам и ищем запись, которая относится к минимальному диапазону, в который попадает конкретный номер. Сложность этого алгоритма будет как у бинарного поиска, что довольно неплохо.

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

Маска номера


Маска номера – это строка состоящая из цифр и спецсимвола со значением «wildcard одиночного символа» ("?"), который говорит, что на его месте может быть любая цифра. Причем после знака вопроса может стоять только знак вопроса.

Таким образом, один из диапазонов Билайн в Москве «79031000000 – 79031999999», в виде маски будет записан как «79031??????».

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

Хеш-таблица


Например, один из таких алгоритмов это хранение соответствий «маска-оператор» в хеш-таблице (или любом другом key-value хранилище). Суть алгоритма в следующем: все подобные маски складываются в хеш-таблицу, где они являются ключами. Значениями в таблице являются объекты-операторы с регионами.

Работу поиска нагляднее всего объяснить на примере. Скажем, мы ищем информацию по номеру: 7(903)100-1234, и у нас есть маска 79031?????? – Билайн, Москва.

Сперва ищем в таблице запись по ключу в точности как исходный номер: 79031001234.
Если не найдено, то меняем последнюю цифру номера на "?" и ищем по ключу 7903100123?.

Если опять ничего не нашли, то снова меняем последнюю цифру на "?" и ищем по 790310012??, и так далее.

В конце концов мы сделаем поиск по ключу 79031?????? и обнаружим, что номер относится к оператору Билайн, Москва.

Видно, что в данном случае сложность алгоритма равна сложности нескольких взятий из хеш-таблицы, что при правильной реализации обычно равно константе. Сложность поиска в таком дереве зависит от длины телефонных номеров, которая по рекомендации ITU-T E.164 не превышает 15 символов.

Этот же алгоритм можно применить и к портированным номерам – их можно просто добавить в ту же самую хеш-таблицу.

Префиксное дерево


Гораздо более эффективный по производительности метод – построение префиксного дерева из масок, который будет опираться на то, что номера состоят из цифр. Каждая нода этого дерева сможет иметь до 10 цифровых нод-потомков (0-9) и одной wildcard-ноды. Wildcard-нода может иметь только wildcard-потомков. При добавлении очередной маски в дерево, каждый символ маски последовательно превратится в ноду. Таким образом, фактически мы представляем все имеющиеся у нас маски в виде одного дерева.
Например, дерево состоящее из масок:
7913? – Mno1
791?? – Mno3
7952 – Mno2
7953 – Mno3
795? – Mno1
будет иметь вид, как на картинке (перечисленные маски в дереве идут слева направо).


Алгоритм поиска в дереве, думаю, уже понятен: берем по порядку каждую цифру из искомого номера и последовательно спускаемся по дереву начиная с корня. В первую очередь спускаемся по цифровым нодам, если цифровых нод нет, то смотрим есть ли "?"-нода. Если есть, то в конечном итоге проверяем длину маски, и если она соответствует номеру, то оператор найден.

Заключение


В зависимости от ограничений, можно комбинировать эти подходы и разделять хранилища перенесенных номеров и масок Россвязи. Например, по памяти выгоднее для портированных номеров использовать подход с хеш-таблицей, а для реестров Россвязи всегда выгоднее использовать дерево масок. При поиске сначала смотреть в таблице, а если в ней ничего не найдено, то искать в дереве. Разделение хранилищ в первую очередь удобно для их автообновления, т.е. если изменилась БДПН (а она меняется постоянно), то совсем необязательно перечитывать диапазоны Россвязи.

Для максимальной производительности можно хранить всю информацию прямо в оперативной памяти. В моей реализации на Java, дерево масок Россвязи занимает не более 20-30Мб, хеш-таблица с масками портированных номеров: около 500-600 Мб. Если же портированные номера хранить в префиксном дереве, то из-за того, что ноды дерева получаются очень разреженные, памяти потребуется примерно в 1.5 раза больше. Но зато, это дает достаточно весомый прирост производительности.

Спасибо за внимание!

> Весь исходный код доступен на github.

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


  1. BigD
    06.09.2017 23:40
    +1

    1. set610 Автор
      07.09.2017 08:43

      Да, API Мегафон тоже отличное и легкое решение задачи.
      Но моя статья о том, как делать определение очень быстро, локально у себя, без лишних зависимостей. А также, на какие данные и алгоритмы это определение опирается.
      При необходимости, кстати, можно самому поднять у себя подобный API (готовая реализация есть репозитории).


  1. antirek
    07.09.2017 06:17

    Серьезный подход, маски, деревья, java.


  1. ubx7b8
    07.09.2017 10:17

    Знание домашнего региона — это хорошо, но в чём практическая польза от знания оператора?


    1. Dmitry_5
      07.09.2017 10:54

      Стоимость звонка разная.


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


    1. bopoh13
      07.09.2017 11:16

      С кодом 800 была история; а сколько ещё будет.


    1. strib
      07.09.2017 11:23

      Пополнение счета, sms шлюзы, и т.д.


    1. set610 Автор
      07.09.2017 11:23

      Из тех, что сейчас приходят в голову:


      • главным образом это может пригодиться, если вы используете какие-то операторские услуги, которые отличаются по цене от оператора к оператору. Такие как: мобильная коммерция (оплата услуг со счета абонента), или, например, рассылка сообщений;
      • как сказали в комментарии выше, цена звонка абоненту может отличаться;
      • можно для UI анкеты пользователя рядом с полем ввода телефона рисовать иконку оператора и страны (красивости и доп. валидация);
      • возможно будет полезна статистика, пользователей какого оператора у вас больше.


  1. dzmat
    07.09.2017 11:24

    600 Мбайт на 6 миллионов записей это по 100 байт на одну запись.
    Если применить нехитрое наблюдение, что все номера представляют собой вид 79xxx-???-???, то можно завести одну таблицу первичного поиска на 1000 записей для поиска по xxx части. Содержать эта таблица будет указатели на таблицы вторичного поиска, в которых содержится ???-??? часть и 1 байт для обозначения оператора.
    ???-??? можно хранить как BCD.
    Получаем расход памяти в первичной таблице 1000*sizeof(pointer) = 8000 байт для 64 битного приложения.
    По вторичному поиску получаем 1000*sizeof(пустой vector) + (3 байта для номера+ 1 байт на обозначение оператора)*количество номеров всего = 1000*20+4*6*10^6= 24020000 байт.
    Добавка 8кбайт таблицы первичного поиска на результат почти не влияет и получаем 4,003 (чуть больше 4) байт на запись.
    Поиск будет двоичным по вторичным таблицам, но за счет заметно меньшего объема памяти будет заметно быстрее, имхо. т.к. узким местом будет обмен с памятью, поскольку в кеш это все не влазит.

    Можно разбить как 79xxx-xx?-???, и получить меньше 4 байт на запись, но тогда записи в таблицах вторичного поиска будут по 3 байта и не будут выровнены. Это может негативно сказаться на производительности.


  1. kilgur
    07.09.2017 13:55

    В конце концов мы сделаем поиск по ключу 79031?????? и обнаружим, что номер относится к оператору Билайн, Москва.

    Или обнаружим несколько записей, п.ч. диапазоны могут быть разделены некратно 10^n. Там есть диапазоны и по 2, и по 5, и по 242 номера, к сожалению.


    1. kilgur
      07.09.2017 14:06

      И, кстати, регионы диапазонов заданы у Россвязи не кодами, а наименованиями, которые могут обозначать один регион разными способами. Например, «Свердловская обл» и «Свердловская область». Какие-то работы по приведению названий в порядок явно ведутся, судя по обновлениям, но пока не закончены. Также, названия некоторых регионов (навскидку, к сожалению, не вспомню) и операторов не бьются с БДПН.


      1. set610 Автор
        07.09.2017 14:14

        Для этого, я в библиотеке сделал систему фильтров, которые по регулярным выражениям из конфигов преобразуют названия операторов и регионов.
        На данный момент, я навел порядок с названиями крупных операторов. Особо сильное разнообразие операторов у того же Tele2.
        С регионами тоже надо будет как-нибудь разобраться и заполнить их конфигурацию.


    1. set610 Автор
      07.09.2017 14:07

      Уверяю вас, тут будет все будет хорошо.


      Действительно, у Россвязи очень много таких диапазонов (особенно почему-то у Tele2).
      В таких случаях диапазон будет записан в виде нескольких масок.
      Например, диапазон из 2-5 номеров моя библиотека пребразует к 2-5 маскам которые в точности будут повторять эти номера.
      А, скажем, диапазон 888-999 будет записан как набор масок [888, 889, 89?, 9??], каждая из которых будет поставлена в соответствие одному объекту-оператору.


      Код который это делает можно посмотреть тут.


      1. kilgur
        07.09.2017 14:26

        Интересный вариант. Спасибо


  1. Shakhmin
    07.09.2017 18:30

    Ранее оператор и регион определялись по первым 5 цифрам федерального номера.
    В связи с возможностью сменить оператора теперь так можно достоверно определить только регион
    Остальные 5 цифрам в данном контексте нужны только, чтобы точно определить оператора


    1. set610 Автор
      08.09.2017 07:48

      Сейчас, к сожалению, этот метод уже не сработает.
      Например, самые первые в списке маски Россвязи:
      90000????? – Tele2 Краснодарский край
      90001????? – Tele2 Тверская обл.


      1. Shakhmin
        08.09.2017 08:00

        Не вижу противоречия
        Берём первые 5 цифр из вашего примера и получаем регион
        PS под регионом я подразумеваю то, что мобильные операторы называют домашней сетью — по сути область, в которой зарегистрирован абонент, она же является отчётной точкой для тарификации


        1. set610 Автор
          08.09.2017 08:15

          Первые пять цифр первого номера «90000» — Краснодарский край.
          Первые пять цифр второго номера «90001» — Тверская обл.
          Это совершенно разные области.
          И эти первые цифры больше нигде больше не могут повториться у других операторов в реестре Россвязи.

          И если бы ваше правило работало, то у операторов в конкретных областях не было бы емкостей больше 100.000 номеров. А это не так, т.к. есть емкости, например, по 1.000.000 номеров. Например Tele2 Москва: (901)-5??-????


  1. TimsTims
    07.09.2017 18:44

    Можно же определять через сервисы, предоставляющие пополнение счёта, например Qiwi. Они недавно тут писали, что еще API теперь для всех ФЛ теперь открытое, не знаю, может через API тоже можно определять оператора?


    1. set610 Автор
      08.09.2017 08:34

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


      1. TimsTims
        08.09.2017 09:27
        +1

        Да, я видел. Но ведь лучше больше API, чем меньше?