Однажды, когда мы работали над одним из проектов по сбору информации про риелторов и их активности на рынке, мой товарищ спросил меня: «Можем ли мы определять адрес квартиры по тексту автоматически?», тогда я не мог даже корректно обрабатывать номера телефонов в тексте, не то что адреса, на подобии: «Не далеко от кинотеатра ‘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
Данный способ позволяет искать номера всех актуальных стран из библиотеки номеров от 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