Regex: от нуля до профи
Извлечение и парсинг URL-подобных строк одним регулярным выражением
Содержание
- 🚀 Введение
- 🔍 Извлечение URL из текста
- 🛳️ Регулярное выражение на 120+ байт
- 🧩 Пошаговый разбор
- 🛠️ Пример парсинга
- ☑️ Следующие шаги
- 📝 Итоги
- 📚 Дополнительные материалы
TL;DR: Переходите сразу к регулярному выражению на 120+ байт.
🚀 Введение
Извлечение URL из неструктурированного текста иногда напоминает утомительную игру в «сбей крота». Знаки препинания, скобки и неоднозначное форматирование только усложняют задачу. Независимо от того, пишете ли вы веб-скрапер, анализатор данных или чат-приложение, точное извлечение URL — критически важная задача.
В этом материале мы подойдём к задаче напрямую, используя гибкий двухэтапный подход. Наша цель — сначала захватить все потенциальные строки, выглядящие как URL, а последующей валидацией заняться отдельно.
💡 Примечание: Этот паттерн не предназначен для валидации URL! Он намеренно допускает лишнюю пунктуацию и опечатки.
🔍 Цель: Извлечение URL из текста
При извлечении URL из исходного текста двухэтапный подход работает надёжнее:
- Захватить всё, что выглядит как URL: Закинуть широкую сеть и отловить все строки, которые могут оказаться URL. Именно здесь вступает в игру наше регулярное выражение на 120+ байт.
- Валидация: После отлова кандидатов примените вторичные проверки (например, разрешение DNS или сверку с известными доменами), чтобы отсечь ложные срабатывания.
Визуализация проблемы
Термины extract и parse часто используют как синонимы, хотя они описывают разные процессы. Извлечение (extract) — это поиск и захват строк, которые могут оказаться URL, из большего объёма текста. Разбор (parse), в свою очередь, предполагает декомпозицию этих URL на составные части.
Под «разбором» или «частями URL» я подразумеваю следующие компоненты:
Нажмите, чтобы увидеть скриншот подстрокового совпадения в RegEx101.
Прежде чем углубляться в синтаксис регулярных выражений, используем визуальный инструмент, чтобы оценить, как наш шаблон захватывает множество совпадений:

Регулярное выражение весом 120+ байт
Ниже приведено компактное регулярное выражение, предназначенное для извлечения и разбора URL за один проход. Оно поддерживает различные протоколы, домены, пути, а также необязательные секции query и fragment.
Не переживайте — разберём всё по шагам!
const urlRegex = /([-.a-z0-9]+:\/{1,3})([^-\/\.[\](|)\s?][^`\/\s\]?]+)([-_a-z0-9!@$%^&*()=+;/~\.]*)[?]?([^#\s`?]*)[#]?([^#\s'"`\.,!]*)/gi;// Совместимость: ES5+
// Тот же паттерн, разбитый по строкам для удобства чтения:([-.a-z0-9]+:\/{1,3})([^-\/\.[\](|)\s?][^`\/\s\]?]+)([-_a-z0-9!@$%^&*()=+;/~\.]*)[?]?([^#\s`?]*)[#]?([^#\s'"`\.,!]*)Делитесь самыми безумными регулярными выражениями, с которыми вы сталкивались (или писали сами), в комментариях ниже! 🚀
🧩 Пошаговый разбор
Разберём регулярное выражение на компоненты, чтобы понять, как оно работает:
1. Протокол (Группа 1): ([-.a-z0-9]+:/{1,3})
- Назначение: Совпадает с частью URL, определяющей протокол (например,
http://,ftp://,custom-scheme://). Разбор:
[-.a-z0-9]+: Совпадает с одним или более символами: строчными буквами, цифрами, дефисами или точками (стандарт для схем протоколов).:/{1,3}: Совпадает с двоеточием, за которым следуют от одного до трёх слешей (:/,://или:///).
2. Домен (Группа 2): ([^-/.[](|)s?][^`/s]?]+)
- Назначение: Захватывает часть URL, отвечающую за домен или хост.
Разбор:
[^-/.[](|)\s?]: Совпадает с любым символом, кроме указанных спецсимволов и пробельных.[^`/\s]?]+: Совпадает с одним или более символами, исключая обратные кавычки, слеш, пробелы или закрывающие квадратные скобки.
3. Путь (Группа 3): ([-_a-z0-9!@$%^&*()=+;/~\.]*)
- Назначение: Совпадает с компонентом пути URL.
Разбор:
[-_a-z0-9!@$%^&()=+;/~.]: Совпадает с нулём или более символами, допустимыми в URL и часто встречающимися в путях.
4. Запрос (Группа 4): [?]?([^#\s`?]*)
- Назначение: Опционально совпадает со строкой запроса, начинающейся с любого символа
?. Разбор:
[?]?: Опционально совпадает с?. (Квадратные скобки не являются строго обязательными, однако они нагляднее, чем предельно сжатый двойной??. Кроме того, они создают визуальный параллелизм для следующей аналогичной группы[#]?.)([^#\s`?]*): Совпадает с нулём или более символами, которые не являются хэшем, пробельным символом, обратной кавычкой или знаком вопроса.
5. Фрагмент (Группа 5): [#]?([^#\s’”`.,!]*)
- Назначение: Опционально совпадает с идентификатором фрагмента, начинающимся с
#. Разбор:
[#]?: Опционально совпадает с#.([^#\s’”`.,!]*): Совпадает с нулём или более символами, которые не являются запрещённой пунктуацией или пробельными символами.
🛠️ Пример парсинга
Вот как применить этот сложный regex на практике с помощью JavaScript:
const text = `Check this out: https://example.com/path?query=123#sectionAnd also (ftp://files.server.org/index).Plus a weird one: custom-scheme://host/param;weird^stuff`;
const urlRegex = /([-.a-z0-9]+:\/{1,3})([^-\/\.[\](|)\s?][^`\/\s\]?]+)([-_a-z0-9!@$%^&*()=+;/~\.]*)[?]?([^#\s`?]*)[#]?([^#\s'"`\.,!]*)/gi;
const matches = [ ...text.matchAll(urlRegex),].map((match) => match[0]);console.log("Extracted URLs:", matches);
const parts = [ ...text.matchAll(urlRegex),].map((match) => match.slice(1));console.log("Extracted Parts:", parts);[ "https://example.com/path?query=123#section", "ftp://files.server.org/index", "custom-scheme://host/param;weird^stuff"][ [ "https://", // Protocol "example.com", // Domain "/path", // Path "query=123", // Query "section" // Fragment ], [ "ftp://", // Protocol "files.server.org", // Domain "/index", // Path "", // Query "" // Fragment ], [ "custom-scheme://", // Protocol "host", // Domain "/param;weird^stuff", // Path "", // Query "" // Fragment ]]☑️ Следующие шаги
В зависимости от конкретной задачи может потребоваться доработать это регулярное выражение или добавить дополнительные этапы валидации и постобработки.
Разные проекты — разные требования
У проектов разные требования и уровни рисков безопасности:
- Веб-скрейпинг: Валидировать URL-адреса, проверяя их доступность и надёжность.
- Обработка данных: Извлекать URL из пользовательского контента с учётом требований безопасности.
- Анализ данных: Отсеивать дубликаты и нерелевантные ссылки для исследовательских или маркетинговых задач.
- Клиентские приложения: Автоматически оборачивать URL в гиперссылки в чатах и на форумах.
Постобработка и валидация
После первичного извлечения потенциальных URL-адресов примените дополнительные проверки:
- DNS-запрос: Убедитесь, что доменные имена корректно разрешаются.
- Проверки безопасности: Используйте сторонние сервисы для оценки риска вредоносного или фишингового контента.
- Специфичные правила: Применяйте фильтры под конкретный проект (например, разрешённые домены верхнего уровня, лимит длины URL).
📝 Итоги
Извлечение полуструктурированных строк — это, пожалуй, самая удовлетворительная часть владения регулярными выражениями.
Кратко резюмирую ключевые моменты:
- Используйте визуальные инструменты для написания, тестирования и понимания ваших регулярных выражений.
- Декомпозируйте задачу: решайте каждую часть отдельно. Захватывающие группы (capture groups) выступают в роли условных «маркеров пути» при построении регулярного выражения.
- Используйте «мягкие» шаблоны совпадения, не стремитесь к строгому соответствию спецификации при сборе данных.
- Проводите валидацию после первичного извлечения — всегда учитывайте требования безопасности и специфику проекта.
Следуя этим шагам, вы сможете эффективно извлекать любые полуструктурированные строковые данные, закладывая основу для последующей обработки и валидации.
📚 Дополнительные материалы
- Не забудьте поэкспериментировать с интерактивным демо на RegEx101.com!
- Исходный вопрос на StackOverflow и ссылка на мой ответ здесь.
- Документация MDN по регулярным выражениям
- Продвинутые техники Regex: Изучите lookaheads, lookbehinds и другие продвинутые конструкции для более точного сопоставления.
- RFC 3986 — Общий синтаксис URI