PHP: извлекаем из текста номера сотовых с разными разделителями
Номера сотовых - хороший пример данных, которые люди записывают "разными способами": 89530011204 или +7 953 001 12 04 или +7 953-001-1204 и т.д. обозначают совершенно одно и то же, хотя для машинного анализа это далеко не очевидно.
Самый общий подход к решению таких задач - написать километровый Regexp, которых много в Интернете.
А можно подойти к задаче по-другому, применив немного программирования.
Попробуем в учебных целях извлечь из произвольного текста российские номера сотовых, записанные со всеми цифрами.
Задачи "вытащить" номер из строки вроде "8 953 ноль ноль 1 двенадцать 04" мы не ставим, хотя её можно решать на том же принципе, просто усложнив метод phone_filter
и увеличив константу TEST_LIMIT
.
Общий подход, получившийся у меня после часа экспериментов, представляется таким:
1. Получить массив лексем (слов) из исходной строки $data
:
1.1. Убрать лишние разделители через preg_replace
;
1.2. Получить массив лексем через preg_split
;
1.3. Можно дополнительно отфильтровать "лишние" лексемы через array_filter
, хотя это лишний проход по массиву и шаг необязателен.
2. Искомым номером может быть как всё очередное слово (89530011204), так и несколько подряд идущих слов (+7 953 001 12 04), при этом между группами цифр могут быть всякие посторонние символы (+7 953, 001,1202). Поэтому мы разобьём задачу на 2 этапа:
2.1. Проверить, не соответствует ли сама лексема (фильтрованная от лишних символов) нужному шаблону, если да, занести её в массив результатов.
2.2. Если нет, прицепить несколько следующих лексем, фильтруя получающуюся строку и проверяя её на соответствие шаблону. Остановиться, если длина такой "составной" и отфильтрованной лексемы превысила некий разумный лимит, в нашем случае, значение 12.
Требуются 2 дополнительных метода:
phone_filter
- фильтрует лексему или цепочку соединённых соседних лексем, убирая символы, которых не должно быть в целевой строке. В нашем случае мы убираем дефисы и всё, что не является цифрой или "плюсом" (шаблон[^\d\+]
);is_phone
- проверяет отфильтрованную строку на жёсткое соответствие нужному шаблону, в нашем случае^(\+7|8)(\d{10})$
, то есть, номер распознаётся в виде +79530011204.
Зачем два метода, а не один? А чтобы проверять, не превышена ли максимально возможная длина целевой строки для уже отфильтрованных данных. Если сунуть функционал phone_filter
в is_phone
, можно потерять "замусоренные" цепочки нужных символов.
Алгоритм не оптимален по производительности, но и не слишком страшен для разовой практической цели, для которой он мне понадобился.
Вот полный исходник на PHP+HTML, файл сохранять в Юникоде (UTF-8):
<!DOCTYPE html> <html dir="ltr" lang="ru-RU"> <head> <meta charset="utf-8"> <title>Номера сотовых из текста</title> </head><body> <?php $data = "Я текст+++, в котором могут быть номера 8 953 001 12 01 и такие тоже +7 953, 001,1202 не номер 234564 89139992203 89530011204 и еще такие + 7 953-001-12-05 номер может даже +7 953-001-12- 06 переноситься на другую строку, +7 953-001-1207, содержать лищние пробелы, и так далее +7 953-001-1208 в общем, +79530011209 , будем считать что они в любой записи 8-9530011210 и такой тоже 89530 011 211! +7-961-222-33-12"; define ('TEST_LIMIT','12'); //лимит проверяемой длины строк $data = preg_replace ("/\s+/u"," ",$data); //убрали лишние разделители $tokens = preg_split("/\s/u",$data); //получили массив лексем $tokens = array_filter ($tokens, function ($item) {return !empty($item);} ); //отфильтровали пустые лексемы $len = count($tokens); $result = array (); $i = 0; for ($i=0; $i<$len; $i++) { //цикл по лексемам if (is_phone(phone_filter($tokens[$i]))) $result[] = $tokens[$i]; //сама фильтрованная лексема есть номер else { //или пробуем сливать лексему с несколькими последующими $test = $tokens[$i]; $j = $i+1; while (1) { $test .= $tokens[$j]; $test = phone_filter ($test); if (is_phone($test)) { $result[] = $test; $i=$j; break; } else if ($j>=$len or strlen($test)>TEST_LIMIT) break; $j++; } } } print_r($result); //вывод результатов function phone_filter ($s) { //фильтр символов проверяемой лексемы return preg_replace (array("/\-/u","/[^\d\+]/u"),"",$s); } function is_phone ($s) { //проверка лексемы на соответствие шаблону return preg_match("/^(\+7|8)(\d{10})$/u",$s) ? 1 : 0; } ?> </body></html>
Результат прогона:
Array ( [0] => 89530011201 [1] => +79530011202 [2] => 89139992203 [3] => 89530011204 [4] => +79530011205 [5] => +79530011206 [6] => +79530011207 [7] => +79530011208 [8] => +79530011209 [9] => 89530011210 [10] => 89530011211 [11] => +7-961-222-33-12 )
28.10.2016, 13:24 [5838 просмотров]