Однажды, когда мы работали над одним из проектов по сбору информации про риелторов и их активности на рынке, мой товарищ спросил меня: «Можем ли мы определять адрес квартиры по тексту автоматически?», тогда я не мог даже корректно обрабатывать номера телефонов в тексте, не то что адреса, на подобии: «Не далеко от кинотеатра ‘N’». Теперь, я наконец научился находить номера телефонов в тексте и приводить их в стандартный вид. Ниже я опишу как это сделать.

Скрипт работает на базе двух библиотек libphonenumber от Google и ReverseRegex. libphonenumber отдает регулярные выражения для парсинга телефона, а ReverseRegex генерирует строку, которая удовлетворит переданное на вход регулярное выражение.

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

Какую информацию мы можем вытащить из гугл библиотеки:

  • Код страны — международный код страны (Россия 7, Украина 380)
  • Национальный формат — первая цифра которая сообщает что мы хотим набрать локальный номер (Россия 8, Украина 0)
  • Национальный формат — первая цифра которая сообщает что мы хотим набрать локальный номер (Россия 8, Украина 0)
  • Длинна номера абонента

Для формирования регулярного выражения нам нужно объединить все эти данные в одно выражение. «Зачем тогда нам реверс регулярных выражений?»- спросите вы. Дело в том, что libphonenumber возвращает длину номера абонента с участием префикса оператора сотовой связи. Интерпретировать регулярное выражение самостоятельно не хочется и для избежания ошибок мы просто генерируем строки из шаблона префиксов и шаблона номера. В результате теперь мы можем получить длину префикса и длину номера. Для этого нам нужно из длины номера вычесть длину префикса.

Но так как люди в тексте пишут номера как хотят, в этом собственно и была причина периодических ошибок сборщика информации:

  • 066 12 345 67
  • 38 066 123 45 67
  • 38 ( 066 ) 12 — 34 — 567

Для выявления таких записей мы немного разбавим шаблон регулярного выражения разделителями между каждым выражением:

(?:\(|\)|\-|\_|\[|\]|\s+){0,3}

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

~(?<phone>(380(?:\(|\)|\-|\_|\[|\]|\s+){0,3}((([38]9|4(?:[45][0-5]|87)|5(?:0|6(?:3[14-7]|7)|7[37])|6[36-8]|73|9[1-9])(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d)|((3(?:[1-46-8]2[013-9]|52)|4(?:[1378]2|62[013-9])|5(?:[12457]2|6[24])|6(?:[49]2|[12][29]|5[24])|8[0-8]|90)(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d)|((3(?:5[013-9]|[1-46-8](?:22|[013-9]))|4(?:[137][013-9]|6(?:[013-9]|22)|[45][6-9]|8[4-6])|5(?:[1245][013-9]|6(?:3[02389]|[015689])|3|7[4-6])|6(?:[49][013-9]|5[0135-9]|[12][13-8]))(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d)))|((0(?:\(|\)|\-|\_|\[|\]|\s+){0,3}([38]9|4(?:[45][0-5]|87)|5(?:0|6(?:3[14-7]|7)|7[37])|6[36-8]|73|9[1-9])(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3})|(0(?:\(|\)|\-|\_|\[|\]|\s+){0,3}(3(?:[1-46-8]2[013-9]|52)|4(?:[1378]2|62[013-9])|5(?:[12457]2|6[24])|6(?:[49]2|[12][29]|5[24])|8[0-8]|90)(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3})|(0(?:\(|\)|\-|\_|\[|\]|\s+){0,3}(3(?:5[013-9]|[1-46-8](?:22|[013-9]))|4(?:[137][013-9]|6(?:[013-9]|22)|[45][6-9]|8[4-6])|5(?:[1245][013-9]|6(?:3[02389]|[015689])|3|7[4-6])|6(?:[49][013-9]|5[0135-9]|[12][13-8]))(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3}\d(?:\(|\)|\-|\_|\[|\]|\s+){0,3})))~Uu

Regexp test

Данный способ позволяет искать номера всех актуальных стран из библиотеки номеров от Google. Для этого достаточно добавить название страны при создании объекта или задать его после через сеттер.

<?php
require "vendor/autoload.php";
use bpteam\Parser\Phone\Phone;
$phone = new Phone('RU');
$text = 'Сдаются 1-,2- и 3-комнатные квартиры, на любой срок по часам .
Все удобства, кабельное ТВ, Wi-Fi, стиральная машина-автомат, посуда, 
постельное бельё. Командированным скидки. Отчетные 8 905 111-22-33 
документы предоставляем .';
$number = current($phone->find($text));
var_dump($number); //79051112233

Репозиторий с кодом можно скачать GitHub