Это третий материал цикла. В «Парсере 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 |
граница слова |
Чтобы использовать сам метасимвол как обычный (например, точку в числе), его экранируют обратным слешем: \., \$, \(.
Классы символов
Квадратные скобки задают набор допустимых символов в одной позиции:
[аеёиоуыэюя] один из перечисленных гласных
[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 и больше |
\d+ одна или больше цифр: «12990»
\d{1,3} от одной до трёх цифр (группа разряда)
colou?r «color» и «colour»
Жадность и лень
По умолчанию квантификаторы жадные — захватывают как можно больше. Добавленный ? делает их ленивыми — захватывают минимум:
".*" жадно: от первой кавычки до ПОСЛЕДНЕЙ в строке
".*?" лениво: от первой кавычки до БЛИЖАЙШЕЙ
Это одна из самых частых ловушек: жадный шаблон «съедает» лишнее. На практике для извлечения коротких значений почти всегда нужен ленивый вариант или более узкий класс символов вместо ..
Якоря и границы
Якоря не совпадают с символами — они привязывают шаблон к позиции:
^началó ^ — начало строки
конец$ $ — конец строки
^\d+$ строка целиком состоит из цифр
\bSKU\b «SKU» как отдельное слово, не часть другого
Якоря защищают от ложных срабатываний: ^\d+$ гарантирует, что в ячейке только число, а не «12 шт».
Группы
Круглые скобки ( ) группируют часть шаблона и захватывают совпавший фрагмент, чтобы потом его достать.
(\d+)[.,](\d+) целая и дробная часть числа по отдельности
Группы нумеруются слева направо: группа 1 — (\d+) до разделителя, группа 2 — после.
Незахватывающие группы
Если скобки нужны только для группировки (например, под квантификатор), но результат не нужен — используют (?: ):
(?:https?://)?(\S+) «http://» или «https://» опционально, не сохраняя
Именованные группы
Чтобы не считать номера, группе дают имя (?P<name>...) (в Python) или (?<name>...):
(?P<rub>\d+)[.,](?P<kop>\d{2})
Потом обращаются по match.group("rub") — читаемо и устойчиво к перестановке групп.
Обратные ссылки
Внутри шаблона можно сослаться на уже захваченную группу через \1, \2:
<(\w+)>.*?</\1> парный тег: открывающий и закрывающий совпадают по имени
(["']).*?\1 строка в одинарных или двойных кавычках
\1 требует, чтобы во второй позиции стоял тот же символ, что совпал в первой группе.
Опережающие и ретроспективные проверки
Lookaround-конструкции проверяют контекст, не включая его в результат — очень полезно, чтобы «зацепиться» за метку, но вернуть только значение.
| Конструкция | Значение |
|---|---|
(?=...) |
впереди есть... (positive lookahead) |
(?!...) |
впереди НЕТ... (negative lookahead) |
(?<=...) |
позади есть... (positive lookbehind) |
(?<!...) |
позади НЕТ... (negative lookbehind) |
\d+(?=\s*₽) число, ЗА которым следует знак рубля — вернётся только число
(?<=Цена:\s)\d+ число, ПЕРЕД которым стоит «Цена: » — вернётся только число
\d+(?!\d) последняя цифра числа
Lookbehind особенно удобен, когда нужно достать значение после фиксированной подписи, не таская саму подпись в результат.
Флаги
Флаги меняют поведение всего шаблона:
| Флаг (Python) | Действие |
|---|---|
re.I / (?i) |
без учёта регистра |
re.S / (?s) |
. совпадает и с переводом строки |
re.M / (?m) |
^ и $ работают на каждой строке |
re.X / (?x) |
«многословный» режим — можно комментировать шаблон |
re.findall(r"цена", text, re.I) # «Цена», «ЦЕНА», «цена»
Практические примеры для парсинга
Соберём типичные шаблоны на Python (модуль re). Полезные функции: re.search (первое совпадение), re.findall (все, как список), re.sub (замена), re.finditer (итератор с группами).
Цена из «грязной» строки:
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
Все цены в тексте (новая и старая):
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:
sku = re.search(r"\bАртикул[:\s]+([A-Z0-9\-]+)", text)
Телефон (нормализация к цифрам):
digits = re.sub(r"\D", "", "+7 (495) 123-45-67") # «74951234567»
Email:
emails = re.findall(r"[\w.+-]+@[\w-]+\.[\w.-]+", text)
Извлечение JSON-значения из inline-скрипта:
# На странице товара цена нередко лежит в <script> как window.__DATA__ = {...}
m = re.search(r'"price"\s*:\s*"?(\d+(?:\.\d+)?)"?', script_text)
Регулярки в связке с DOM и CSS
Сильнее всего regex работает не вместо, а вместе с инструментами из прошлых статей:
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-селекторы — адресацию, регулярные выражения — финальную очистку данных.