Python re.findall: извлечение всех совпадений из текста шаблоном
Для кого эта статья:
- Программисты и разработчики, работающие с Python
- Специалисты по обработке данных и аналитике
Студенты и учащиеся, изучающие основы Python и регулярные выражения
Регулярные выражения — это мощный инструмент программиста, позволяющий манипулировать текстом с хирургической точностью. Среди всего арсенала модуля re в Python функция
findall()занимает особое место 🔍 — она позволяет извлечь все совпадения шаблона в строке одним элегантным вызовом. Если ваш код пестрит циклами для обработки текста или вы тратите часы на парсинг данных вручную, пора освоить этот метод и автоматизировать рутину. Поиск email-адресов в документе, извлечение дат из текста, сбор всех URL со страницы — всё это задачи дляre.findall().
Погрузившись в мир Python-разработки с курсом Python от Skypro, вы освоите не только базовые основы языка, но и продвинутые техники обработки данных, включая мастерство работы с регулярными выражениями. Этот навык критически важен для анализа данных, веб-скрейпинга и создания парсеров. Наши студенты экономят до 70% времени на задачах обработки текста, применяя элегантные решения с
re.findall()вместо громоздких алгоритмов.
Функция re.findall() в Python: базовый синтаксис
Функция re.findall() — это один из фундаментальных методов модуля re в Python, предназначенный для поиска всех непересекающихся совпадений шаблона в строке. В отличие от re.search() или re.match(), которые останавливаются на первом найденном совпадении, findall() извлекает все вхождения шаблона и возвращает их в виде списка.
Базовый синтаксис функции выглядит следующим образом:
re.findall(pattern, string, flags=0)
Где:
- pattern — регулярное выражение, определяющее шаблон поиска
- string — строка, в которой производится поиск
- flags — необязательные флаги, модифицирующие поведение поиска
Рассмотрим простой пример поиска всех цифр в строке:
import re
text = "В 2023 году выпущена Python 3.11, а в 2024 ожидается Python 3.12"
digits = re.findall(r'\d', text)
print(digits) # ['2', '0', '2', '3', '3', '1', '1', '2', '0', '2', '4', '3', '1', '2']
Для поиска последовательностей цифр (целых чисел) нужно модифицировать шаблон:
numbers = re.findall(r'\d+', text)
print(numbers) # ['2023', '3', '11', '2024', '3', '12']
Наиболее часто используемые флаги при работе с re.findall():
| Флаг | Константа | Описание | Пример |
|---|---|---|---|
re.IGNORECASE | re.I | Игнорирование регистра при сопоставлении | re.findall(r'python', text, re.I) |
re.MULTILINE | re.M | Метасимволы ^ и $ соответствуют началу и концу каждой строки | re.findall(r'^Python', text, re.M) |
re.DOTALL | re.S | Символ "." соответствует любому символу, включая новую строку | re.findall(r'Python.*версия', text, re.S) |
re.VERBOSE | re.X | Позволяет добавлять комментарии и пробелы в регулярное выражение | re.findall(r"""\n\d+ # Числа\n\s+ # Пробелы\n""", text, re.X) |
Можно скомбинировать несколько флагов с помощью оператора побитового ИЛИ (|):
result = re.findall(r'python \d+', text, re.IGNORECASE | re.MULTILINE)
Помимо вызова через функцию re.findall(), можно также использовать объектно-ориентированный подход с предварительной компиляцией регулярного выражения:
pattern = re.compile(r'\d+')
numbers = pattern.findall(text)
Этот подход особенно полезен, когда одно и то же регулярное выражение используется многократно, так как повышает производительность за счёт предварительной компиляции шаблона.

Механизм работы re.findall() при поиске совпадений
Для эффективного использования re.findall() необходимо понимать механизм его работы. Функция сканирует строку слева направо и последовательно извлекает непересекающиеся фрагменты, соответствующие заданному шаблону.
Александр Петров, Lead Python Developer
Помню случай из практики, когда нам требовалось проанализировать логи сервера на предмет подозрительных IP-адресов. Файл содержал более миллиона строк, и изначально я попытался использовать цикл с
re.search()для каждой строки. Процесс занимал около 8 минут. Затем я переписал решение с использованиемre.findall()и получил все IP-адреса одним вызовом. Время выполнения сократилось до 45 секунд! Разница между "найти первое совпадение в каждой строке" и "найти все совпадения сразу" оказалась колоссальной. Особенно впечатляющим было то, что код сократился с 20 строк до буквально трёх.
Принцип работы re.findall() можно представить следующим образом:
- Функция ищет первое совпадение с шаблоном в строке
- Найденное совпадение добавляется в результирующий список
- Поиск продолжается после окончания найденного совпадения
- Процесс повторяется до тех пор, пока не будет достигнут конец строки
Важно понимать, как именно формируется результат в разных ситуациях:
| Шаблон | Тип результата | Пример |
|---|---|---|
| Без групп захвата | Список совпавших строк | re.findall(r'\d+', "a12b34c") → ['12', '34'] |
| С одной группой захвата | Список содержимого групп | re.findall(r'a(\d+)c', "a12c a34c") → ['12', '34'] |
| С несколькими группами | Список кортежей групп | re.findall(r'a(\d+)b(\d+)c', "a12b34c") → [('12', '34')] |
| С именованными группами | Список кортежей групп | re.findall(r'a(?P<first>\d+)b', "a12b") → ['12'] |
Одним из ключевых аспектов работы re.findall() является то, что он возвращает непересекающиеся совпадения. Это означает, что после нахождения совпадения, поиск продолжается с позиции после найденного фрагмента. Например:
text = "ababab"
re.findall(r'ab', text) # Результат: ['ab', 'ab', 'ab']
Однако, если использовать жадный квантификатор, который захватывает максимально возможную строку, результат может быть иным:
text = "ababab"
re.findall(r'a.*b', text) # Результат: ['ababab']
Важно также понимать разницу в поведении re.findall() по сравнению с другими функциями модуля re:
re.search()— находит первое совпадение шаблона в строкеre.match()— проверяет совпадение шаблона только с началом строкиre.finditer()— работает аналогичноfindall(), но возвращает итератор объектов matchre.findall()— находит все непересекающиеся совпадения и возвращает их в виде списка
При наличии групп в регулярном выражении re.findall() возвращает только содержимое этих групп, игнорируя полное совпадение. Если необходимо получить полные совпадения вместе с группами, можно использовать re.finditer() и извлекать нужную информацию из объектов match.
Практические сценарии применения re.findall()
Функция re.findall() находит широкое применение в различных задачах обработки текста и анализа данных. Рассмотрим несколько практических сценариев, где данная функция демонстрирует свою эффективность 🚀.
Мария Соколова, Data Scientist
В одном из проектов по анализу отзывов клиентов нам требовалось извлечь все упоминания продуктов и их характеристик из тысяч комментариев. Изначально я пыталась использовать сложные NLP-модели, но они работали медленно и требовали много ресурсов. Решение пришло неожиданно просто: я составила небольшой набор регулярных выражений и применила
re.findall(). За несколько минут удалось извлечь все нужные данные из корпуса текстов объемом в 10 МБ. Особенно полезной оказалась возможность группировки результатов, что позволило сразу структурировать извлечённую информацию. Когда коллеги увидели, как быстро работает это решение, они также начали применять этот подход в своих проектах.
Извлечение email-адресов из текста:
import re
text = """Свяжитесь с нашими специалистами:
support@example.com, sales.dept@company.co.uk
или с технической поддержкой: tech-help@service.org"""
emails = re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', text)
print(emails)
# ['support@example.com', 'sales.dept@company.co.uk', 'tech-help@service.org']
Извлечение URL-адресов из HTML-текста:
html = """
<a href="https://python.org">Python</a>
<img src="https://example.com/image.jpg">
Посетите <a href="http://docs.python.org/3/">документацию</a>
"""
urls = re.findall(r'https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?:/[a-zA-Z0-9._/-]*)?', html)
print(urls)
# ['https://python.org', 'https://example.com/image.jpg', 'http://docs.python.org/3/']
Парсинг дат в различных форматах:
text = "Событие пройдет 2023-11-15, затем 12/25/2023 и завершится 30.01.2024"
# Поиск дат в формате YYYY-MM-DD
dates_iso = re.findall(r'\d{4}-\d{2}-\d{2}', text)
# Поиск дат в формате MM/DD/YYYY
dates_us = re.findall(r'\d{2}/\d{2}/\d{4}', text)
# Поиск дат в формате DD.MM.YYYY
dates_eu = re.findall(r'\d{2}\.\d{2}\.\d{4}', text)
print(f"ISO: {dates_iso}, US: {dates_us}, EU: {dates_eu}")
# ISO: ['2023-11-15'], US: ['12/25/2023'], EU: ['30.01.2024']
Извлечение всех слов, начинающихся с заглавной буквы (потенциальные имена собственные):
text = "Иван Петров посетил Москву и встретился с Сергеем Сидоровым."
proper_nouns = re.findall(r'\b[А-Я][а-я]+\b', text)
print(proper_nouns) # ['Иван', 'Петров', 'Москву', 'Сергеем', 'Сидоровым']
Извлечение чисел с плавающей точкой:
text = "Значения температуры: 23.5°C, -12.7°C, +3.8°C и 0.0°C"
temperatures = re.findall(r'[+-]?\d+\.\d+', text)
print(temperatures) # ['23.5', '-12.7', '+3.8', '0.0']
Извлечение хештегов из текста социальных медиа:
tweet = "Изучаю #Python и #регулярные_выражения для #анализа_данных! #учеба"
hashtags = re.findall(r'#\w+', tweet)
print(hashtags) # ['#Python', '#регулярные_выражения', '#анализа_данных', '#учеба']
Парсинг HTML-тегов:
html = "<div class='container'><h1>Заголовок</h1><p>Параграф</p></div>"
tags = re.findall(r'<(\w+)[^>]*>', html)
print(tags) # ['div', 'h1', 'p']
Извлечение IP-адресов:
log = "Успешный доступ с IP 192.168.1.1, блокирован доступ с 10.0.0.123"
ips = re.findall(r'\b(?:\d{1,3}\.){3}\d{1,3}\b', log)
print(ips) # ['192.168.1.1', '10.0.0.123']
Разбор CSV-данных без использования специализированных библиотек:
csv_line = 'John,"Doe, Jr.",32,"New York, NY"'
fields = re.findall(r'(?:"([^"]*)")|([^,]+)', csv_line)
# Обработка результатов, поскольку findall возвращает кортежи для каждой группы
parsed = [group[0] if group[0] else group[1] for group in fields]
print(parsed) # ['John', 'Doe, Jr.', '32', 'New York, NY']
Эти примеры демонстрируют гибкость и мощь функции re.findall() для решения различных задач обработки текста. Комбинируя re.findall() с хорошо продуманными регулярными выражениями, можно эффективно извлекать структурированную информацию из неструктурированного текста.
Тонкости использования групп в регулярных выражениях
Группы в регулярных выражениях — это мощный механизм, который кардинально меняет поведение функции re.findall() 🧩. Понимание нюансов их использования критически важно для корректного извлечения данных.
Основное правило, которое следует запомнить: при наличии в регулярном выражении групп захвата re.findall() возвращает только содержимое этих групп, а не полные совпадения.
Рассмотрим базовый пример:
import re
# Без групп захвата
text = "prefix-123-suffix prefix-456-suffix"
re.findall(r'prefix-\d+-suffix', text) # ['prefix-123-suffix', 'prefix-456-suffix']
# С группой захвата
re.findall(r'prefix-(\d+)-suffix', text) # ['123', '456']
Во втором случае функция вернула только цифры из скобочных групп, игнорируя префикс и суффикс. Это поведение может быть как полезным, так и неожиданным, если его не учитывать.
При наличии нескольких групп захвата re.findall() возвращает список кортежей, где каждый кортеж содержит значения соответствующих групп:
text = "name:John age:30 name:Alice age:25"
result = re.findall(r'name:(\w+) age:(\d+)', text)
print(result) # [('John', '30'), ('Alice', '25')]
Различные типы групп в регулярных выражениях по-разному влияют на результат работы re.findall():
| Тип группы | Синтаксис | Влияние на re.findall() | Пример |
|---|---|---|---|
| Захватывающая группа | ( ... ) | Включается в результат | re.findall(r'a(\d+)c', 'a123c') → ['123'] |
| Незахватывающая группа | (?:...) | Не включается в результат | re.findall(r'a(?:\d+)c', 'a123c') → ['a123c'] |
| Именованная группа | (?P<name>...) | Включается в результат как обычная группа | re.findall(r'a(?P<num>\d+)c', 'a123c') → ['123'] |
| Позитивный просмотр вперёд | (?=...) | Не включается в результат | re.findall(r'\w+(?=\d)', 'abc1 def2') → ['abc', 'def'] |
| Негативный просмотр вперёд | (?!...) | Не включается в результат | re.findall(r'\w+(?!\d)', 'abc1 def') → ['ab', 'def'] |
Для получения полных совпадений вместе с содержимым групп можно использовать несколько подходов:
- Использование незахватывающих групп
(?:...) - Создание дополнительной внешней группы, охватывающей всё выражение
- Использование
re.finditer()вместоre.findall()
Рассмотрим эти подходы на примере извлечения данных из текста:
text = "name:John age:30 name:Alice age:25"
# Подход 1: Незахватывающие группы
re.findall(r'name:(?:\w+) age:(?:\d+)', text) # ['name:John age:30', 'name:Alice age:25']
# Подход 2: Внешняя группа
re.findall(r'(name:(\w+) age:(\d+))', text) # [('name:John age:30', 'John', '30'), ('name:Alice age:25', 'Alice', '25')]
# Подход 3: Использование re.finditer()
matches = re.finditer(r'name:(\w+) age:(\d+)', text)
result = [(match.group(0), match.group(1), match.group(2)) for match in matches]
# [('name:John age:30', 'John', '30'), ('name:Alice age:25', 'Alice', '25')]
При работе с вложенными группами результат становится ещё более сложным. Рассмотрим пример с вложенными группами:
text = "<div><p>Первый параграф</p><p>Второй параграф</p></div>"
re.findall(r'<p>(.*?)</p>', text) # ['Первый параграф', 'Второй параграф']
re.findall(r'<div>((?:<p>.*?</p>)+)</div>', text) # ['<p>Первый параграф</p><p>Второй параграф</p>']
re.findall(r'<div>(<p>(.*?)</p>)</div>', text) # [('<p>Первый параграф</p>', 'Первый параграф')]
При работе с группами в re.findall() важно помнить следующие моменты:
- Захватывающие группы (обычные скобки) меняют формат результата
- Незахватывающие группы
(?:...)полезны для группировки без изменения результата - Можно использовать комбинацию захватывающих и незахватывающих групп
- При сложных шаблонах может быть проще использовать
re.finditer() - Именованные группы
(?P<name>...)в контекстеre.findall()ведут себя как обычные группы
Правильное использование групп позволяет точно контролировать, какую информацию извлекать из текста, делая код более читаемым и эффективным.
Оптимизация производительности с re.findall()
При обработке больших объёмов текстовых данных производительность функции re.findall() становится критически важным фактором. Существует ряд методов оптимизации, которые могут значительно ускорить работу с регулярными выражениями.
Предварительная компиляция регулярных выражений с помощью re.compile() — первый и наиболее эффективный способ повысить производительность, особенно когда одно и то же выражение используется многократно:
import re
import time
text = "a" * 1000000 + "b" * 1000000 # Большая строка для тестирования
# Без предварительной компиляции
start = time.time()
for _ in range(100):
re.findall(r'a+b+', text)
print(f"Без компиляции: {time.time() – start:.4f} секунд")
# С предварительной компиляцией
pattern = re.compile(r'a+b+')
start = time.time()
for _ in range(100):
pattern.findall(text)
print(f"С компиляцией: {time.time() – start:.4f} секунд")
Оптимизация самих регулярных выражений также может существенно повлиять на производительность:
- Избегайте излишнего использования жадных квантификаторов (*+{n,}), особенно в начале шаблона
- Используйте привязки к началу/концу строки (^ и $), если это возможно
- Применяйте более конкретные шаблоны вместо общих
- Избегайте излишних групп захвата, используя незахватывающие группы
(?:...) - Используйте встроенные классы символов (\d, \w, \s) вместо пользовательских, где это возможно
Сравнение эффективности различных подходов к написанию регулярных выражений:
| Шаблон | Оптимизированный вариант | Преимущество |
|---|---|---|
.*\d{4}-\d{2}-\d{2}.* | \d{4}-\d{2}-\d{2} | Удаление излишних жадных квантификаторов |
[0-9]+ | \d+ | Использование встроенных классов символов |
([a-z]+)@([a-z]+).com | (?:[a-z]+)@(?:[a-z]+).com | Незахватывающие группы для структурирования |
.*?word.*? | \bword\b | Использование границ слова вместо нежадных квантификаторов |
В некоторых случаях re.findall() может быть не самым эффективным инструментом. Альтернативные подходы могут обеспечить лучшую производительность:
re.finditer()— возвращает итератор объектов match, что может быть более эффективно при работе с большими наборами данных, когда не требуется хранить все результаты в памяти одновременноstr.split()и другие строковые методы — для простых задач разбиения текста они могут работать быстрее, чем регулярные выражения- Специализированные парсеры — для разбора структурированных форматов (например, HTML, XML, JSON) специализированные библиотеки часто работают быстрее и надёжнее, чем регулярные выражения
Работа с большими текстами требует особого подхода:
# Обработка больших файлов построчно
import re
compiled_pattern = re.compile(r'\d+')
# Вместо загрузки всего файла в память
with open('large_file.txt', 'r') as file:
for line in file:
numbers = compiled_pattern.findall(line)
# Обработка найденных чисел в текущей строке
Также стоит учитывать, что начиная с Python 3.11 были внесены значительные оптимизации в модуль re, улучшившие производительность регулярных выражений в целом. Обновление до последней версии Python может само по себе дать заметный прирост производительности.
При работе с очень сложными регулярными выражениями или при необходимости обработки больших объёмов данных стоит рассмотреть альтернативные модули, такие как:
regex— расширенная версия модуля re с дополнительными возможностямиpyrsistent— для эффективной обработки повторяющихся шаблоновrapidfuzz— для приближённого поиска строк
Оптимизация работы с регулярными выражениями — это баланс между читаемостью кода, точностью соответствия шаблону и производительностью. В большинстве случаев предварительная компиляция и тщательное составление шаблонов обеспечивают наилучший результат.
Освоение
re.findall()открывает перед вами новый уровень работы с текстом в Python. Эта функция позволяет одним элегантным вызовом решать задачи, требующие ранее десятков строк кода и множества циклов. Помните, что ключ к успешному использованию — это понимание не только синтаксиса, но и механизмов работы регулярных выражений. Сочетая предварительную компиляцию, оптимальное конструирование шаблонов и правильное использование групп захвата, вы сможете обрабатывать даже объемные наборы данных с впечатляющей эффективностью. Теперь в вашем арсенале есть инструмент, способный превратить хаос текстовых данных в структурированную информацию.
Читайте также
- Модуль re в Python: эффективная обработка текста регулярками
- Модуль re в Python: мощный инструмент для обработки текста
- Регулярные выражения в Python: как использовать re.finditer эффективно
- Функции re.sub() и re.subn() в Python: мощные инструменты замены
- Python re.findall: извлечение всех совпадений из текста шаблоном


