Как использовать re.findall() в Python для поиска всех совпадающих подстрок

PythonBeginner
Практиковаться сейчас

Введение

В этом руководстве мы рассмотрим функцию re.findall() в Python, мощный инструмент для извлечения совпадающих подстрок из текста. Эта функция является частью встроенного модуля регулярных выражений (regex) в Python и является важной для задач обработки текста.

К концу этого практического занятия (lab) вы сможете использовать re.findall() для извлечения различных шаблонов из текста, таких как адреса электронной почты, номера телефонов и URL-адреса. Эти навыки ценны в области анализа данных, веб-скрапинга и приложений для обработки текста.

Будь вы новичком в Python или желающим улучшить свои способности в обработке текста, это пошаговое руководство предоставит вам практические знания для эффективного использования регулярных выражений в своих Python-проектах.

Начало работы с re.findall()

На этом первом этапе мы узнаем о функции re.findall() и о том, как использовать ее для базового сопоставления шаблонов.

Понимание регулярных выражений

Регулярные выражения (regex) - это специальные текстовые строки, используемые для описания шаблонов поиска. Они особенно полезны, когда вам нужно:

  • Найти определенные шаблоны символов в тексте
  • Проверить формат текста (например, адреса электронной почты)
  • Извлечь информацию из текста
  • Заменить текст

Модуль re в Python

Python предоставляет встроенный модуль re для работы с регулярными выражениями. Одна из самых полезных функций этого модуля - re.findall().

Начнем с создания простого Python-скрипта, чтобы увидеть, как работает re.findall().

  1. Сначала откройте терминал и перейдите в наш проектный каталог:
cd ~/project
  1. Создайте новый Python-файл с именем basic_findall.py с помощью редактора кода. В VSCode вы можете нажать на иконку "Explorer" (обычно это первая иконка в боковой панели), затем нажать кнопку "New File" и назвать файл basic_findall.py.

  2. В файле basic_findall.py напишите следующий код:

import re

## Sample text
text = "Python is amazing. Python is versatile. I love learning Python programming."

## Using re.findall() to find all occurrences of "Python"
matches = re.findall(r"Python", text)

## Print the results
print("Original text:")
print(text)
print("\nMatches found:", len(matches))
print("Matching substrings:", matches)
  1. Сохраните файл и запустите его из терминала:
python3 ~/project/basic_findall.py

Вы должны увидеть вывод, похожий на следующий:

Original text:
Python is amazing. Python is versatile. I love learning Python programming.

Matches found: 3
Matching substrings: ['Python', 'Python', 'Python']

Анализ кода

Понять, что происходит в нашем коде:

  • Мы импортировали модуль re с помощью import re
  • Мы определили пример текста с несколькими вхождениями слова "Python"
  • Мы использовали re.findall(r"Python", text) для поиска всех вхождений "Python" в тексте
  • Символ r перед строкой обозначает сырую строку, что рекомендуется при работе с регулярными выражениями
  • Функция вернула список всех совпадающих подстрок
  • Мы вывели результаты, показывая, что "Python" встречается 3 раза в нашем тексте

Поиск различных шаблонов

Теперь давайте попробуем найти другой шаблон. Создайте новый файл с именем findall_words.py:

import re

text = "The rain in Spain falls mainly on the plain."

## Find all words ending with 'ain'
matches = re.findall(r"\w+ain\b", text)

print("Original text:")
print(text)
print("\nWords ending with 'ain':", matches)

Запустите этот скрипт:

python3 ~/project/findall_words.py

Вывод должен быть следующим:

Original text:
The rain in Spain falls mainly on the plain.

Words ending with 'ain': ['rain', 'Spain', 'plain']

В этом примере:

  • \w+ соответствует одному или нескольким символам слова (буквам, цифрам или подчеркиваниям)
  • ain соответствует буквальным символам "ain"
  • \b представляет границу слова, обеспечивая, что мы ищем целые слова, оканчивающиеся на "ain"

Попробуйте эти примеры, чтобы понять, как работает re.findall() с базовыми шаблонами.

Работа с более сложными шаблонами

На этом этапе мы рассмотрим более сложные шаблоны с использованием re.findall() и узнаем, как использовать классы символов и квантификаторы для создания гибких шаблонов поиска.

Поиск чисел в тексте

Сначала напишем скрипт для извлечения всех чисел из текста. Создайте новый файл с именем extract_numbers.py:

import re

text = "There are 42 apples, 15 oranges, and 123 bananas in the basket. The price is $9.99."

## Find all numbers (integers and decimals)
numbers = re.findall(r'\d+\.?\d*', text)

print("Original text:")
print(text)
print("\nNumbers found:", numbers)

## Finding only whole numbers
whole_numbers = re.findall(r'\b\d+\b', text)
print("Whole numbers only:", whole_numbers)

Запустите скрипт:

python3 ~/project/extract_numbers.py

Вы должны увидеть вывод, похожий на следующий:

Original text:
There are 42 apples, 15 oranges, and 123 bananas in the basket. The price is $9.99.

Numbers found: ['42', '15', '123', '9.99']
Whole numbers only: ['42', '15', '123', '9']

Разберем использованные шаблоны:

  • \d+\.?\d* соответствует:

    • \d+: Одному или нескольким цифрам
    • \.?: Необязательной десятичной точке
    • \d*: Нулям или более цифрам после десятичной точки
  • \b\d+\b соответствует:

    • \b: Границе слова
    • \d+: Одному или нескольким цифрам
    • \b: Еще одной границе слова (чтобы найти только отдельные числа)

Поиск слов определенной длины

Создадим скрипт для поиска всех четырехбуквенных слов в тексте. Создайте файл find_word_length.py:

import re

text = "The quick brown fox jumps over the lazy dog. A good day to code."

## Find all 4-letter words
four_letter_words = re.findall(r'\b\w{4}\b', text)

print("Original text:")
print(text)
print("\nFour-letter words:", four_letter_words)

## Find all words between 3 and 5 letters
words_3_to_5 = re.findall(r'\b\w{3,5}\b', text)
print("Words with 3 to 5 letters:", words_3_to_5)

Запустите этот скрипт:

python3 ~/project/find_word_length.py

Вывод должен быть следующим:

Original text:
The quick brown fox jumps over the lazy dog. A good day to code.

Four-letter words: ['over', 'lazy', 'good', 'code']
Words with 3 to 5 letters: ['The', 'over', 'the', 'lazy', 'dog', 'good', 'day', 'code']

В этих шаблонах:

  • \b\w{4}\b соответствует ровно 4 символам слова, окруженным границами слова
  • \b\w{3,5}\b соответствует от 3 до 5 символам слова, окруженным границами слова

Использование классов символов

Классы символов позволяют нам искать определенные наборы символов. Создайте файл character_classes.py:

import re

text = "The temperature is 72°F or 22°C. Contact us at: info@example.com"

## Find words containing both letters and digits
mixed_words = re.findall(r'\b[a-z0-9]+\b', text.lower())

print("Original text:")
print(text)
print("\nWords with letters and digits:", mixed_words)

## Find all email addresses
emails = re.findall(r'\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b', text)
print("Email addresses:", emails)

Запустите скрипт:

python3 ~/project/character_classes.py

Вывод должен быть похожим на следующий:

Original text:
The temperature is 72°F or 22°C. Contact us at: info@example.com

Words with letters and digits: ['72°f', '22°c', 'info@example.com']
Email addresses: ['info@example.com']

Эти шаблоны демонстрируют:

  • \b[a-z0-9]+\b: Слова, содержащие строчные буквы и цифры
  • Шаблон для поиска электронных адресов соответствует стандартному формату адресов электронной почты

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

Использование флагов и захватывающих групп

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

Понимание флагов в регулярных выражениях

Флаги изменяют способ, которым движок регулярных выражений выполняет поиск. Модуль re в Python предоставляет несколько флагов, которые можно передать как необязательный параметр в функцию re.findall(). Исследуем некоторые распространенные флаги.

Создайте новый файл с именем regex_flags.py:

import re

text = """
Python is a great language.
PYTHON is versatile.
python is easy to learn.
"""

## Case-sensitive search (default)
matches_case_sensitive = re.findall(r"python", text)

## Case-insensitive search using re.IGNORECASE flag
matches_case_insensitive = re.findall(r"python", text, re.IGNORECASE)

print("Original text:")
print(text)
print("\nCase-sensitive matches:", matches_case_sensitive)
print("Case-insensitive matches:", matches_case_insensitive)

## Using the multiline flag
multiline_text = "First line\nSecond line\nThird line"
## Find lines starting with 'S'
starts_with_s = re.findall(r"^S.*", multiline_text, re.MULTILINE)
print("\nMultiline text:")
print(multiline_text)
print("\nLines starting with 'S':", starts_with_s)

Запустите скрипт:

python3 ~/project/regex_flags.py

Вывод должен быть похожим на следующий:

Original text:

Python is a great language.
PYTHON is versatile.
python is easy to learn.


Case-sensitive matches: ['python']
Case-insensitive matches: ['Python', 'PYTHON', 'python']

Multiline text:
First line
Second line
Third line

Lines starting with 'S': ['Second line']

Общие флаги включают:

  • re.IGNORECASE (или re.I): делает шаблон нечувствительным к регистру букв
  • re.MULTILINE (или re.M): делает символы ^ и $ соответствовать началу/концу каждой строки
  • re.DOTALL (или re.S): делает символ . соответствовать любому символу, включая символы новой строки

Использование захватывающих групп

Захватывающие группы позволяют извлекать определенные части совпавшего текста. Они создаются путем помещения части регулярного выражения в круглые скобки.

Создайте файл с именем capturing_groups.py:

import re

## Sample text with dates in various formats
text = "Important dates: 2023-11-15, 12/25/2023, and Jan 1, 2024."

## Extract dates in YYYY-MM-DD format
iso_dates = re.findall(r'(\d{4})-(\d{1,2})-(\d{1,2})', text)

## Extract dates in MM/DD/YYYY format
us_dates = re.findall(r'(\d{1,2})/(\d{1,2})/(\d{4})', text)

print("Original text:")
print(text)
print("\nISO dates (Year, Month, Day):", iso_dates)
print("US dates (Month, Day, Year):", us_dates)

## Extract month names with capturing groups
month_dates = re.findall(r'(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+(\d{1,2}),\s+(\d{4})', text)
print("Month name dates (Month, Day, Year):", month_dates)

Запустите скрипт:

python3 ~/project/capturing_groups.py

Вывод должен быть следующим:

Original text:
Important dates: 2023-11-15, 12/25/2023, and Jan 1, 2024.

ISO dates (Year, Month, Day): [('2023', '11', '15')]
US dates (Month, Day, Year): [('12', '25', '2023')]
Month name dates (Month, Day, Year): [('Jan', '1', '2024')]

В этом примере:

  • Каждая пара круглых скобок () создает захватывающую группу
  • Функция возвращает список кортежей, где каждый кортеж содержит захваченные группы
  • Это позволяет извлекать и структурировать данные из текста

Практический пример: разбор лог-файлов

Теперь применим наши знания на практическом примере. Предположим, у нас есть лог-файл с записями, которые мы хотим разобрать. Создайте файл с именем log_parser.py:

import re

## Sample log entries
logs = """
[2023-11-15 08:30:45] INFO: System started
[2023-11-15 08:35:12] WARNING: High memory usage (85%)
[2023-11-15 08:42:11] ERROR: Connection timeout
[2023-11-15 09:15:27] INFO: Backup completed
"""

## Extract timestamp, level, and message from log entries
log_pattern = r'\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (\w+): (.+)'
log_entries = re.findall(log_pattern, logs)

print("Original logs:")
print(logs)
print("\nParsed log entries (timestamp, level, message):")
for entry in log_entries:
    timestamp, level, message = entry
    print(f"Time: {timestamp} | Level: {level} | Message: {message}")

## Find all ERROR logs
error_logs = re.findall(r'\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] ERROR: (.+)', logs)
print("\nError messages:", error_logs)

Запустите скрипт:

python3 ~/project/log_parser.py

Вывод должен быть похожим на следующий:

Original logs:

[2023-11-15 08:30:45] INFO: System started
[2023-11-15 08:35:12] WARNING: High memory usage (85%)
[2023-11-15 08:42:11] ERROR: Connection timeout
[2023-11-15 09:15:27] INFO: Backup completed


Parsed log entries (timestamp, level, message):
Time: 2023-11-15 08:30:45 | Level: INFO | Message: System started
Time: 2023-11-15 08:35:12 | Level: WARNING | Message: High memory usage (85%)
Time: 2023-11-15 08:42:11 | Level: ERROR | Message: Connection timeout
Time: 2023-11-15 09:15:27 | Level: INFO | Message: Backup completed

Error messages: ['Connection timeout']

Этот пример демонстрирует:

  • Использование захватывающих групп для извлечения структурированной информации
  • Обработку и отображение захваченной информации
  • Фильтрацию определенных типов записей в логах

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

Практические применения re.findall()

На этом последнем этапе мы рассмотрим практические, реальные применения функции re.findall(). Мы напишем код для извлечения адресов электронной почты, URL-адресов и выполнения задач по очистке данных.

Извлечение адресов электронной почты

Извлечение адресов электронной почты - это распространенная задача в области дата-майнинга, веб-скрапинга и текстового анализа. Создайте файл с именем email_extractor.py:

import re

## Sample text with email addresses
text = """
Contact information:
- Support: support@example.com
- Sales: sales@example.com, international.sales@example.co.uk
- Technical team: tech.team@subdomain.example.org
Personal email: john.doe123@gmail.com
"""

## Extract all email addresses
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
emails = re.findall(email_pattern, text)

print("Original text:")
print(text)
print("\nExtracted email addresses:")
for i, email in enumerate(emails, 1):
    print(f"{i}. {email}")

## Extract specific domain emails
gmail_emails = re.findall(r'\b[A-Za-z0-9._%+-]+@gmail\.com\b', text)
print("\nGmail addresses:", gmail_emails)

Запустите скрипт:

python3 ~/project/email_extractor.py

Вывод должен быть похожим на следующий:

Original text:

Contact information:
- Support: support@example.com
- Sales: sales@example.com, international.sales@example.co.uk
- Technical team: tech.team@subdomain.example.org
Personal email: john.doe123@gmail.com


Extracted email addresses:
1. support@example.com
2. sales@example.com
3. international.sales@example.co.uk
4. tech.team@subdomain.example.org
5. john.doe123@gmail.com

Gmail addresses: ['john.doe123@gmail.com']

Извлечение URL-адресов

Извлечение URL-адресов полезно для веб-скрапинга, проверки ссылок и анализа контента. Создайте файл с именем url_extractor.py:

import re

## Sample text with various URLs
text = """
Visit our website at https://www.example.com
Documentation: http://docs.example.org/guide
Repository: https://github.com/user/project
Forum: https://community.example.net/forum
Image: https://images.example.com/logo.png
"""

## Extract all URLs
url_pattern = r'https?://[^\s]+'
urls = re.findall(url_pattern, text)

print("Original text:")
print(text)
print("\nExtracted URLs:")
for i, url in enumerate(urls, 1):
    print(f"{i}. {url}")

## Extract specific domain URLs
github_urls = re.findall(r'https?://github\.com/[^\s]+', text)
print("\nGitHub URLs:", github_urls)

## Extract image URLs
image_urls = re.findall(r'https?://[^\s]+\.(jpg|jpeg|png|gif)', text)
print("\nImage URLs:", image_urls)

Запустите скрипт:

python3 ~/project/url_extractor.py

Вывод должен быть похожим на следующий:

Original text:

Visit our website at https://www.example.com
Documentation: http://docs.example.org/guide
Repository: https://github.com/user/project
Forum: https://community.example.net/forum
Image: https://images.example.com/logo.png


Extracted URLs:
1. https://www.example.com
2. http://docs.example.org/guide
3. https://github.com/user/project
4. https://community.example.net/forum
5. https://images.example.com/logo.png

GitHub URLs: ['https://github.com/user/project']

Image URLs: ['https://images.example.com/logo.png']

Очистка данных с использованием re.findall()

Создадим скрипт для очистки и извлечения информации из запутанного набора данных. Создайте файл с именем data_cleaning.py:

import re

## Sample messy data
data = """
Product: Laptop X200, Price: $899.99, SKU: LP-X200-2023
Product: Smartphone S10+, Price: $699.50, SKU: SP-S10P-2023
Product: Tablet T7, Price: $299.99, SKU: TB-T7-2023
Product: Wireless Earbuds, Price: $129.95, SKU: WE-PRO-2023
"""

## Extract product information
product_pattern = r'Product: (.*?), Price: \$([\d.]+), SKU: ([A-Z0-9-]+)'
products = re.findall(product_pattern, data)

print("Original data:")
print(data)
print("\nExtracted and structured product information:")
print("Name\t\tPrice\t\tSKU")
print("-" * 50)
for product in products:
    name, price, sku = product
    print(f"{name}\t${price}\t{sku}")

## Calculate total price
total_price = sum(float(price) for _, price, _ in products)
print(f"\nTotal price of all products: ${total_price:.2f}")

## Extract only products above $500
expensive_products = [name for name, price, _ in products if float(price) > 500]
print("\nExpensive products (>$500):", expensive_products)

Запустите скрипт:

python3 ~/project/data_cleaning.py

Вывод должен быть похожим на следующий:

Original data:

Product: Laptop X200, Price: $899.99, SKU: LP-X200-2023
Product: Smartphone S10+, Price: $699.50, SKU: SP-S10P-2023
Product: Tablet T7, Price: $299.99, SKU: TB-T7-2023
Product: Wireless Earbuds, Price: $129.95, SKU: WE-PRO-2023


Extracted and structured product information:
Name		Price		SKU
--------------------------------------------------
Laptop X200	$899.99	LP-X200-2023
Smartphone S10+	$699.50	SP-S10P-2023
Tablet T7	$299.99	TB-T7-2023
Wireless Earbuds	$129.95	WE-PRO-2023

Total price of all products: $2029.43

Expensive products (>$500): ['Laptop X200', 'Smartphone S10+']

Комбинирование re.findall() с другими строковыми функциями

Наконец, посмотрим, как можно комбинировать функцию re.findall() с другими строковыми функциями для продвинутой обработки текста. Создайте файл с именем combined_processing.py:

import re

## Sample text with mixed content
text = """
Temperature readings:
- New York: 72°F (22.2°C)
- London: 59°F (15.0°C)
- Tokyo: 80°F (26.7°C)
- Sydney: 68°F (20.0°C)
"""

## Extract all temperature readings in Fahrenheit
fahrenheit_pattern = r'(\d+)°F'
fahrenheit_temps = re.findall(fahrenheit_pattern, text)

## Convert to integers
fahrenheit_temps = [int(temp) for temp in fahrenheit_temps]

print("Original text:")
print(text)
print("\nFahrenheit temperatures:", fahrenheit_temps)

## Calculate average temperature
avg_temp = sum(fahrenheit_temps) / len(fahrenheit_temps)
print(f"Average temperature: {avg_temp:.1f}°F")

## Extract city and temperature pairs
city_temp_pattern = r'- ([A-Za-z\s]+): (\d+)°F'
city_temps = re.findall(city_temp_pattern, text)

print("\nCity and temperature pairs:")
for city, temp in city_temps:
    print(f"{city}: {temp}°F")

## Find the hottest and coldest cities
hottest_city = max(city_temps, key=lambda x: int(x[1]))
coldest_city = min(city_temps, key=lambda x: int(x[1]))

print(f"\nHottest city: {hottest_city[0]} ({hottest_city[1]}°F)")
print(f"Coldest city: {coldest_city[0]} ({coldest_city[1]}°F)")

Запустите скрипт:

python3 ~/project/combined_processing.py

Вывод должен быть похожим на следующий:

Original text:

Temperature readings:
- New York: 72°F (22.2°C)
- London: 59°F (15.0°C)
- Tokyo: 80°F (26.7°C)
- Sydney: 68°F (20.0°C)


Fahrenheit temperatures: [72, 59, 80, 68]
Average temperature: 69.8°F

City and temperature pairs:
New York: 72°F
London: 59°F
Tokyo: 80°F
Sydney: 68°F

Hottest city: Tokyo (80°F)
Coldest city: London (59°F)

Эти примеры демонстрируют, как функцию re.findall() можно комбинировать с другими возможностями Python для решения реальных задач по обработке текста. Возможность извлекать структурированные данные из неструктурированного текста - это важный навык для анализа данных, веб-скрапинга и многих других программистских задач.

Резюме

В этом руководстве вы узнали, как использовать мощную функцию re.findall() в Python для поиска и извлечения текстовых шаблонов. Вы приобрели практические знания в нескольких ключевых областях:

  1. Базовое сопоставление шаблонов — вы научились находить простые подстроки и использовать базовые шаблоны регулярных выражений для сопоставления определенных текстовых шаблонов.

  2. Сложные шаблоны — вы исследовали более сложные шаблоны, включая классы символов, границы слов и квантификаторы, чтобы создавать гибкие шаблоны поиска.

  3. Флаги и захватывающие группы — вы узнали, как изменить поведение поиска с помощью флагов, таких как re.IGNORECASE, и как извлекать структурированные данные с использованием захватывающих групп.

  4. Практические применения — вы применили свои знания в практических сценариях, таких как извлечение адресов электронной почты и URL-адресов, разбор лог-файлов и очистка данных.

Навыки, которые вы получили в этом LabEx, ценны для широкого спектра задач по обработке текста, включая:

  • Извлечение и очистка данных
  • Анализ контента
  • Веб-скрапинг
  • Разбор лог-файлов
  • Валидация данных

С помощью регулярных выражений и функции re.findall() у вас теперь есть мощный инструмент для обработки текстовых данных в ваших Python-проектах. По мере того, как вы будете продолжать практиковаться и применять эти техники, вы станете более компетентными в создании эффективных шаблонов для своих конкретных задач по обработке текста.