Pulling structured fields out of log lines, dates and URLs is the most common real use of regex.
Each pair of capturing parentheses records the text it matched. Groups are numbered by their opening parenthesis, left to right, starting at 1; group 0 is the whole match.
Named groups make patterns self-documenting and robust to edits:
- Python:
(?P<year>\d{4}), and since 3.12 also(?<year>\d{4}); read withm["year"]orm.groupdict(). - JavaScript, PCRE, .NET, Java:
(?<year>\d{4}); in JS readm.groups.year.
Details worth knowing:
- A group inside a quantifier keeps only its last iteration:
(\d)+on"123"captures3. - An optional group that didn't participate returns
Nonein Python andundefinedin JavaScript. findallreturns tuples of groups when there are several;finditergives full match objects, which is usually clearer.- Prefer non-capturing
(?:...)for groups you don't read, so numbering stays stable.
For repeated structured extraction, compile the pattern once (re.compile) and iterate with finditer. When the data is a real format (JSON, CSV, URLs, HTML), use its parser instead of groups; regex extraction is best for semi-structured text like log lines.
Example
import re
line = "2026-10-17 12:04:55 ERROR [db] connection lost"
pat = re.compile(r"(?P<date>\d{4}-\d{2}-\d{2}) \S+ (?P<level>[A-Z]+) \[(?P<src>\w+)\]")
m = pat.search(line)
print(m.group(0)) # 2026-10-17 12:04:55 ERROR [db]
print(m["level"], m[3]) # ERROR db
print(m.groupdict()) # {'date': '2026-10-17', 'level': 'ERROR', 'src': 'db'}
print(re.search(r"(\d)+", "123").group(1)) # 3 (last iteration)
print(re.search(r"(a)?b", "b").group(1)) # None
for m in re.finditer(r"(\w+)=(\d+)", "x=1 y=22"):
print(m.group(1), int(m.group(2))) # x 1 / y 22Remember
- Groups are numbered by their opening parenthesis; group 0 is the whole match
- Named groups: (?P<name>...) in Python, (?<name>...) almost everywhere
- A repeated group keeps only its last iteration
Watch out for
- Expecting (\d)+ to capture every digit; capture the whole run with (\d+) instead.