Технологии и защита 8 мин чтения

Регулярные выражения для парсинга

Регулярные выражения в парсинге: синтаксис, полезные шаблоны, границы применимости и почему для HTML нужен парсер, а не regex.

КP
Команда Parsing.agency
Сбор данных под задачи бизнеса
Опубликовано: 27 апреля 2025

Это третий материал цикла. В «Парсере DOM» мы научились превращать страницу в дерево узлов, а в статье о CSS-селекторах — точно адресовать в этом дереве нужные элементы. Но когда узел найден, внутри него часто оказывается «грязный» текст: «Цена: 12 990 ₽ (старая 15 990)», телефон с лишними символами, артикул вперемешку с описанием. Вытащить из такой строки именно число или код помогают регулярные выражения.

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

Когда нужны регулярки — и когда нет

Сначала важное предостережение. Регулярными выражениями не парсят HTML целиком. HTML — вложенная, нерегулярная структура; попытка разобрать её одним шаблоном приводит к хрупкому и нечитаемому коду. Для навигации по разметке есть DOM-парсеры и CSS-селекторы из предыдущих статей.

Регулярки хороши в другой роли — обработать уже извлечённый короткий текст:

  • достать число цены из строки с валютой и пробелами;
  • нормализовать телефон или email;
  • вытащить артикул, SKU, дату, размер скидки;
  • разобрать параметры из URL или из текста скрипта <script>.

Правило простое: структуру берём селекторами, значение внутри узла — регуляркой.

Базовый синтаксис

Большинство символов в шаблоне обозначают сами себя: шаблон цена найдёт подстроку «цена». Сила появляется со спецсимволами (метасимволами).

Символ Значение
. любой символ (кроме перевода строки)
\d цифра 0–9
\D не-цифра
\w буква, цифра или _
\W не-\w
\s пробельный символ (пробел, таб, перенос)
\S не-пробельный
\b граница слова

Чтобы использовать сам метасимвол как обычный (например, точку в числе), его экранируют обратным слешем: \., \$, \(.

Классы символов

Квадратные скобки задают набор допустимых символов в одной позиции:

code
[аеёиоуыэюя]      один из перечисленных гласных
[0-9]             любая цифра (то же, что \d)
[a-zA-Z]          любая латинская буква
[А-Яа-яЁё]        любая русская буква
[^0-9]            любой символ, КРОМЕ цифры (^ внутри скобок = отрицание)
[\d.,]            цифра, точка или запятая

Диапазоны (a-z, 0-9) и перечисления комбинируются. ^ в начале класса инвертирует набор.

Квантификаторы: сколько повторений

Квантификатор указывает, сколько раз повторяется предыдущий элемент.

Квантификатор Сколько раз
* 0 или больше
+ 1 или больше
? 0 или 1 (необязательный)
{3} ровно 3
{2,5} от 2 до 5
{2,} 2 и больше
code
\d+               одна или больше цифр: «12990»
\d{1,3}           от одной до трёх цифр (группа разряда)
colou?r           «color» и «colour»

Жадность и лень

По умолчанию квантификаторы жадные — захватывают как можно больше. Добавленный ? делает их ленивыми — захватывают минимум:

code
".*"              жадно: от первой кавычки до ПОСЛЕДНЕЙ в строке
".*?"             лениво: от первой кавычки до БЛИЖАЙШЕЙ

Это одна из самых частых ловушек: жадный шаблон «съедает» лишнее. На практике для извлечения коротких значений почти всегда нужен ленивый вариант или более узкий класс символов вместо ..

Якоря и границы

Якоря не совпадают с символами — они привязывают шаблон к позиции:

code
^началó            ^ — начало строки
конец$             $ — конец строки
^\d+$              строка целиком состоит из цифр
\bSKU\b            «SKU» как отдельное слово, не часть другого

Якоря защищают от ложных срабатываний: ^\d+$ гарантирует, что в ячейке только число, а не «12 шт».

Группы

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

code
(\d+)[.,](\d+)        целая и дробная часть числа по отдельности

Группы нумеруются слева направо: группа 1 — (\d+) до разделителя, группа 2 — после.

Незахватывающие группы

Если скобки нужны только для группировки (например, под квантификатор), но результат не нужен — используют (?: ):

code
(?:https?://)?(\S+)   «http://» или «https://» опционально, не сохраняя

Именованные группы

Чтобы не считать номера, группе дают имя (?P<name>...) (в Python) или (?<name>...):

code
(?P<rub>\d+)[.,](?P<kop>\d{2})

Потом обращаются по match.group("rub") — читаемо и устойчиво к перестановке групп.

Обратные ссылки

Внутри шаблона можно сослаться на уже захваченную группу через \1, \2:

code
<(\w+)>.*?</\1>       парный тег: открывающий и закрывающий совпадают по имени
(["']).*?\1           строка в одинарных или двойных кавычках

\1 требует, чтобы во второй позиции стоял тот же символ, что совпал в первой группе.

Опережающие и ретроспективные проверки

Lookaround-конструкции проверяют контекст, не включая его в результат — очень полезно, чтобы «зацепиться» за метку, но вернуть только значение.

Конструкция Значение
(?=...) впереди есть... (positive lookahead)
(?!...) впереди НЕТ... (negative lookahead)
(?<=...) позади есть... (positive lookbehind)
(?<!...) позади НЕТ... (negative lookbehind)
code
\d+(?=\s*₽)           число, ЗА которым следует знак рубля — вернётся только число
(?<=Цена:\s)\d+       число, ПЕРЕД которым стоит «Цена: » — вернётся только число
\d+(?!\d)             последняя цифра числа

Lookbehind особенно удобен, когда нужно достать значение после фиксированной подписи, не таская саму подпись в результат.

Флаги

Флаги меняют поведение всего шаблона:

Флаг (Python) Действие
re.I / (?i) без учёта регистра
re.S / (?s) . совпадает и с переводом строки
re.M / (?m) ^ и $ работают на каждой строке
re.X / (?x) «многословный» режим — можно комментировать шаблон
python
re.findall(r"цена", text, re.I)   # «Цена», «ЦЕНА», «цена»

Практические примеры для парсинга

Соберём типичные шаблоны на Python (модуль re). Полезные функции: re.search (первое совпадение), re.findall (все, как список), re.sub (замена), re.finditer (итератор с группами).

Цена из «грязной» строки:

python
import re

text = "Цена: 12 990,50 ₽  (старая 15 990 ₽)"

# Достаём первое число, игнорируя пробелы-разделители разрядов
m = re.search(r"(\d[\d\s]*\d|\d)(?:[.,](\d{2}))?", text)
rub = re.sub(r"\s", "", m.group(1))     # «12990»
kop = m.group(2) or "00"                # «50»
price = float(f"{rub}.{kop}")           # 12990.5

Все цены в тексте (новая и старая):

python
prices = re.findall(r"\d[\d\s]*\d(?=\s*₽)", text)
# ['12 990', '15 990']  → дальше очищаем от пробелов
clean = [int(re.sub(r"\D", "", p)) for p in prices]   # [12990, 15990]

Артикул / SKU:

python
sku = re.search(r"\bАртикул[:\s]+([A-Z0-9\-]+)", text)

Телефон (нормализация к цифрам):

python
digits = re.sub(r"\D", "", "+7 (495) 123-45-67")   # «74951234567»

Email:

python
emails = re.findall(r"[\w.+-]+@[\w-]+\.[\w.-]+", text)

Извлечение JSON-значения из inline-скрипта:

python
# На странице товара цена нередко лежит в <script> как window.__DATA__ = {...}
m = re.search(r'"price"\s*:\s*"?(\d+(?:\.\d+)?)"?', script_text)

Регулярки в связке с DOM и CSS

Сильнее всего regex работает не вместо, а вместе с инструментами из прошлых статей:

python
from bs4 import BeautifulSoup
import re

soup = BeautifulSoup(html, "html.parser")

# 1) CSS-селектор достаёт нужный узел
raw = soup.select_one("span.price").get_text(strip=True)

# 2) regex очищает текст до числа
price = int(re.sub(r"\D", "", raw))

Селектор отвечает за «где», регулярка — за «что именно из этого текста». Такое разделение делает парсер и точным, и читаемым.

Применение в мониторинге цен

В мониторинге цен интернет-магазинов регулярные выражения закрывают «последнюю милю» извлечения данных:

  • Нормализация цены к единому числовому формату: убрать пробелы-разделители, валюту, привести запятую к точке — иначе значения с разных площадок невозможно сравнивать.
  • Разбор скидки из строк вроде «−23 %» или «выгода 3 000 ₽».
  • Чистка наличия: из «осталось 4 шт.» вытащить количество \d+.
  • Идентификаторы товара — SKU, EAN/штрихкод (\b\d{13}\b), артикул — для сопоставления одного и того же товара у разных продавцов.
  • Данные из скриптов: многие магазины кладут цену в JSON внутри <script type="application/ld+json"> или в инлайн-переменные — regex достаёт нужное поле без полного парсинга JS.

Связка «селектор → регулярка» — рабочая основа движка парсинга сайтов: он по расписанию обходит карточки, вытаскивает узлы по CSS и приводит их содержимое к чистым числам регулярными выражениями.

Производительность и подводные камни

  • Компилируйте часто используемые шаблоныre.compile(...) один раз вместо повторного разбора в цикле по тысячам товаров.
  • Избегайте катастрофического бэктрекинга. Вложенные неоднозначные квантификаторы вроде (\d+)+ или (.*)* могут «зависнуть» на специально (или случайно) подобранной строке. Делайте классы символов узкими и предпочитайте ленивые квантификаторы жадным там, где это уместно.
  • Не усложняйте сверх меры. Если шаблон стал нечитаемым, разбейте задачу на два простых regex или вернитесь к селекторам — возможно, нужное значение лежит в отдельном data-атрибуте, который проще взять напрямую (см. статью о CSS-селекторах).
  • Тестируйте на реальных примерах. Цены пишут по-разному: 1 990, 1.990, 1990.00, от 1990. Прогоняйте шаблон на наборе живых строк с разных магазинов.

Итог

Регулярные выражения — инструмент извлечения значений из текста, а не разбора структуры. Метасимволы и классы описывают, какие символы ожидаются, квантификаторы — сколько их, группы и lookaround — как выделить нужный фрагмент в контексте. В паре с DOM-парсингом и CSS-селекторами они образуют полный конвейер парсинга сайтов: найти узел, достать текст, привести к чистым данным. Именно эта связка и обеспечивает надёжный мониторинг цен интернет-магазинов, где из пёстрой вёрстки конкурентов нужно регулярно получать сопоставимые числа.

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