Skip to main content

Extracting Data with findall() & Groups

While re.search() stops at the very first match, real-world data engineering and web scraping require finding all occurrences of a pattern across whole web pages, invoices, or log files.

Python's re.findall(), re.finditer(), and Capturing Groups () are designed for high-speed batch text extraction.


1. Extracting All Matches with re.findall()

re.findall() scans the text from left to right and returns a plain Python list of strings containing every matching item:

import re

text = "User Rahul (Phone: 9876543210) transferred funds to Priya (Phone: 8765432109)."

# Find all 10-digit phone numbers
phone_numbers = re.findall(r"\d{10}", text)
print(phone_numbers) # Output: ['9876543210', '8765432109']

2. Capturing Groups with Parentheses ()

Parentheses () allow you to isolate specific parts of a pattern while keeping the surrounding context for matching:

import re

log_data = """
ERROR: 2026-08-08 Database connection timeout
WARNING: 2026-08-09 High CPU usage
ERROR: 2026-08-10 Out of memory
"""

# Extract the log LEVEL and the DATE into tuples
pattern = r"(ERROR|WARNING):\s(\d{4}-\d{2}-\d{2})"
matches = re.findall(pattern, log_data)

for level, log_date in matches:
print(f"Severity: {level} on Date: {log_date}")
# Output:
# Severity: ERROR on Date: 2026-08-08
# Severity: WARNING on Date: 2026-08-09
# Severity: ERROR on Date: 2026-08-10

3. Named Groups (?P<name>...) (Self-Documenting Code)

Instead of relying on index numbers (group(1), group(2)), you can give human-readable names to your regex capture groups using (?P<group_name>...):

import re

flight_info = "Flight: AI-302 From: HYD To: BLR"
pattern = r"Flight:\s(?P<flight_id>[A-Z0-9-]+)\sFrom:\s(?P<origin>[A-Z]{3})\sTo:\s(?P<dest>[A-Z]{3})"

match = re.search(pattern, flight_info)
if match:
# Access by dictionary or group name
data = match.groupdict()
print("Flight ID:", data["flight_id"]) # Output: Flight ID: AI-302
print("Origin Airport:", data["origin"]) # Output: Origin Airport: HYD
print("Destination:", data["dest"]) # Output: Destination: BLR

4. Text Redaction & Cleaning with re.sub()

re.sub() (substitute) replaces every pattern match with new text. It is widely used to mask sensitive data (PII) before logging:

import re

chat_message = "My secret PIN is 4912 and account password is 9871"

# Mask all 4-digit numbers with ****
redacted_message = re.sub(r"\b\d{4}\b", "****", chat_message)
print(redacted_message)
# Output: My secret PIN is **** and account password is ****

Quick Summary

  • re.findall(pattern, text): Extracts all occurrences matching a pattern as a list of strings (or tuples of capture groups).
  • Capture Groups ((...)): Isolates specific subsets of matched text (e.g., username and domain from email).
  • Named Groups ((?P<name>...)): Creates self-documenting dictionary extractions via match.groupdict().
  • re.sub(pattern, replacement, text): Replaces matched patterns, ideal for data cleaning and PII redaction.

What's Next?

Let's build real-world input validators for emails, Indian mobile numbers, and PAN cards in Real-World RegEx Validations!