Regex для номера телефона. Поиск, проверка и нормализация

2023
Regex для номера телефона. Поиск, проверка и нормализация

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

^(?:+7|8)[ u00A0u202F-]?(?:([0-9]{3})|[0-9]{3})[ u00A0u202F-]?[0-9]{3}[ u00A0u202F-]?[0-9]{2}[ u00A0u202F-]?[0-9]{2}$

Регулярка принимает +7 999 123-45-67, 8 (999) 123 45 67, +7(999)1234567 и 89991234567. Строки с пропущенными цифрами, непарными скобками или посторонними буквами проверку не пройдут.

Но regex отвечает только на вопрос, похожа ли строка на разрешенный формат. Шаблон не доказывает, что номер существует, выделен оператором, принадлежит России или доступен пользователю. Я разделяю проверку на четыре этапа. Сначала ограничиваю форму записи, затем разбираю номер по правилам страны, привожу к каноническому виду и при необходимости подтверждаю кодом из SMS или звонка.

Один regex не может одновременно проверить оформление, национальный план нумерации, существование абонента и личность владельца.

Сначала определяем задачу

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

Задача Подход Что получаем
Найти телефон в тексте Мягкий regex и последующий разбор кандидатов Фрагменты, похожие на номера
Проверить поле формы Regex под форматы, которые разрешает интерфейс Строку без явных синтаксических ошибок
Проверить номер страны Библиотека с актуальными правилами нумерации Номер допустимой длины и диапазона
Подтвердить доступ Одноразовый код или контрольный звонок Признак доступа к номеру в данный момент

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

В HTML я использую input type="tel", а не input type="number". Тип tel помогает браузеру показать телефонную клавиатуру, но не задает универсальную проверку. Браузер намеренно не знает, какой формат считать правильным, поскольку правила разных стран несовместимы.

Регулярка для российского номера

Разберем базовый шаблон по частям.

^(?:+7|8)[ u00A0u202F-]?(?:([0-9]{3})|[0-9]{3})[ u00A0u202F-]?[0-9]{3}[ u00A0u202F-]?[0-9]{2}[ u00A0u202F-]?[0-9]{2}$
  • ^ и $ требуют совпадения со всей строкой.
  • (?:+7|8) разрешает международную запись с +7 или национальную запись с префиксом 8.
  • [ u00A0u202F-]? разрешает один обычный пробел, неразрывный пробел, узкий неразрывный пробел или дефис.
  • (?:([0-9]{3})|[0-9]{3}) принимает трехзначный код в скобках или без скобок.
  • [0-9]{3}, [0-9]{2} и [0-9]{2} описывают оставшиеся семь цифр.

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

Значение Результат
+7 999 123-45-67 Принято
8 (999) 123 45 67 Принято
89991234567 Принято
+7-999-123-45-67 Принято
+7 (99) 123-45-67 Отклонено
+7 (999 123-45-67 Отклонено
+7 999 123-45 Отклонено

Разделители в выражении независимы друг от друга. Поэтому строка +7 999-123 45-67 тоже пройдет проверку. Запретить смешанное оформление можно через группы и обратные ссылки, но такая строгость редко улучшает данные. Пользователь может вставить реальный номер из адресной книги, письма или мессенджера. Проще принять несколько понятных вариантов и отформатировать номер после разбора.

Если форма разрешает ввод без +7 и 8, всю начальную группу можно сделать необязательной.

^(?:(?:+7|8)[ u00A0u202F-]?)?(?:([0-9]{3})|[0-9]{3})[ u00A0u202F-]?[0-9]{3}[ u00A0u202F-]?[0-9]{2}[ u00A0u202F-]?[0-9]{2}$

Такой вариант принимает и 9991234567. Я применяю его только там, где страна уже выбрана отдельно или продукт работает исключительно с российскими номерами. Без контекста десять цифр остаются неоднозначной строкой.

Для мобильных номеров трехзначную часть можно ограничить выражением 9[0-9]{2}. Ограничение проверяет только внешний признак мобильного диапазона. Regex не знает, выделена ли конкретная комбинация, перенесен ли номер между операторами и активна ли SIM-карта.

Код +7 сам по себе не доказывает принадлежность России. Нумерационную зону совместно используют Россия и Казахстан. Поэтому проверка ^+7 не подходит для правила «принимать только российские номера». После разбора нужно отдельно проверить определенную библиотекой страну.

Я также предпочитаю [0-9] вместо d в переносимых шаблонах. В JavaScript d означает цифры ASCII от нуля до девяти. Python и .NET по умолчанию могут считать цифрами другие символы Unicode. Явный диапазон дает одинаковый результат в разных движках.

Поиск телефона в тексте и проверка формы

Для поиска номера внутри письма, страницы или журнала якоря ^ и $ мешают, поскольку требуют совпадения со всем текстом. Нужны границы, которые не позволят вырезать телефон из длинного идентификатора.

(?:^|[^0-9])((?:+7|8)[ u00A0u202F-]?(?:([0-9]{3})|[0-9]{3})[ u00A0u202F-]?[0-9]{3}[ u00A0u202F-]?[0-9]{2}[ u00A0u202F-]?[0-9]{2})(?![0-9])

Сам номер окажется в первой захватывающей группе. Левая часть (?:^|[^0-9]) требует начало текста или символ, который не является цифрой. Правая проверка (?![0-9]) запрещает дополнительную цифру после номера.

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

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

HTML-форма может использовать тот же шаблон без якорей. Атрибут pattern уже требует, чтобы регулярному выражению соответствовало все непустое значение поля. Атрибут required отдельно запрещает пустую строку.

<input
   type="tel"
   name="phone"
   autocomplete="tel"
   placeholder="+7 999 123-45-67"
   pattern="(?:+7|8)[ u00A0u202F-]?(?:([0-9]{3})|[0-9]{3})[ u00A0u202F-]?[0-9]{3}[ u00A0u202F-]?[0-9]{2}[ u00A0u202F-]?[0-9]{2}"
   required
 >

Проверка в браузере улучшает интерфейс, но не защищает сервер. Клиент может отключить ограничения, изменить запрос или обратиться к API напрямую. Сервер должен повторить разбор независимо от HTML и JavaScript страницы.

Для строгой проверки и нормализации я использую libphonenumber-js с полным набором метаданных.

import parsePhoneNumber from "libphonenumber-js/max";
 
 const RU_PHONE_RE =
   /^(?:+7|8)[ u00A0u202F-]?(?:([0-9]{3})|[0-9]{3})[ u00A0u202F-]?[0-9]{3}[ u00A0u202F-]?[0-9]{2}[ u00A0u202F-]?[0-9]{2}$/u;
 
 export function normalizeRuPhone(raw) {
   const value = raw.trim();
 
   if (!RU_PHONE_RE.test(value)) {
     return null;
   }
 
   const phone = parsePhoneNumber(value, {
     defaultCountry: "RU",
     extract: false
   });
 
   if (!phone || phone.country !== "RU" || !phone.isValid()) {
     return null;
   }
 
   return phone.number;
 }

Параметр extract: false запрещает библиотеке вырезать телефон из строки с посторонним текстом. Проверка phone.country !== "RU" отсекает казахстанские номера из общей зоны +7. Результат phone.number возвращается в каноническом международном виде, например +79991234567.

Метод isValid() проверяет длину и известные диапазоны, но не наличие конкретного абонента. Более мягкий isPossible() проверяет главным образом допустимую длину. Строгий режим полезен для очищенной базы, однако устаревшие метаданные могут временно отклонить новый диапазон. Зависимость нужно обновлять, а требования к строгости выбирать с учетом продукта.

Сначала удалять все символы, кроме цифр, опасно. Строка +7abc9991234567 после такой очистки превратится в внешне правильный номер. Я сначала проверяю допустимые символы и структуру, затем нормализую результат.

Добавочный номер лучше хранить в отдельном поле. Для телефонной ссылки можно использовать форму tel:+74951234567;ext=204, но поддержку добавочного параметра конкретным браузером, телефоном или корпоративным софтфоном придется проверить.

Почему международная проверка не помещается в один regex

В прикладных системах международный номер часто хранят как знак + и последовательность цифр. Грубая проверка такого представления выглядит так.

^+[1-9][0-9]{1,14}$

Шаблон запрещает ноль в начале кода страны и ограничивает запись пятнадцатью цифрами. Такой предел задает рекомендация E.164. Знак + служит обозначением международной записи и не входит в число пятнадцати цифр.

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

Универсальная регулярка ломается по нескольким причинам.

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

Даже результат библиотеки нельзя трактовать как доказательство существования абонента. «Валидный» обычно означает, что номер соответствует известному диапазону, из которого оператор вправе выдавать номера. Конкретная комбинация может оставаться свободной или уже не обслуживаться. Бывает и обратная ситуация, когда сеть принимает устаревший формат или игнорирует лишние цифры, хотя стандартный парсер считает строку неправильной.

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

Для формы одной страны я использую короткий regex, библиотеку с актуальными метаданными и сохранение в каноническом виде. Международная форма получает отдельный выбор страны и специализированный парсер. При поиске в тексте регулярка лишь собирает кандидатов. Такой подход проще длинного «идеального» шаблона и значительно реже блокирует реальные номера или принимает случайные последовательности цифр.

Alt text
Обращаем внимание, что все материалы в этом блоге представляют личное мнение их авторов. Редакция SecurityLab.ru не несет ответственности за точность, полноту и достоверность опубликованных данных. Вся информация предоставлена «как есть» и может не соответствовать официальной позиции компании.
QUANTUM
Сегодня в канале
В Пекине бар наливает пиво и бесплатный ИИ
IBM собирает квантовый компьютер из «холодильников»
Обычный день в «Изобретая будущее»
Подписаться
Реклама

Рекламодатель
ООО «СерчИнформ»
ИНН: 7704306397
searchinform.ru↗
ИИ-ассистент СерчИнформ