Парсинг логов, HTML, JSON, CSV, извлечение структурированных данных
Регулярные выражения мощны для извлечения структурированных данных из текста: логи, HTML, JSON, CSV. В этой теме изучим практические приёмы парсинга.
import re
log_line = '192.168.1.1 - - [09/Mar/2024:14:30:15 +0000] "GET /index.html HTTP/1.1" 200 1234'
# Извлечение IP, даты, метода, пути, статуса, размера
pattern = r'''
^(?P<ip>\d+\.\d+\.\d+\.\d+) # IP адрес
\s+-\s+-\s+ # идентификаторы
\[(?P<timestamp>[^\]]+)\] # timestamp
\s+"(?P<method>\w+)\s+ # метод
(?P<path>\S+)\s+ # путь
(?P<protocol>[^"]+)" # протокол
\s+(?P<status>\d+) # статус
\s+(?P<size>\d+) # размер
'''
match = re.search(pattern, log_line, re.VERBOSE)
if match:
data = match.groupdict()
# {'ip': '192.168.1.1', 'timestamp': '09/Mar/2024:14:30:15 +0000',
# 'method': 'GET', 'path': '/index.html', 'protocol': 'HTTP/1.1',
# 'status': '200', 'size': '1234'}log_text = """
192.168.1.1 - - [09/Mar/2024:14:30:15 +0000] "GET /index.html HTTP/1.1" 200 1234
192.168.1.2 - - [09/Mar/2024:14:30:16 +0000] "POST /api/data HTTP/1.1" 201 567
192.168.1.1 - - [09/Mar/2024:14:30:17 +0000] "GET /style.css HTTP/1.1" 200 890
"""
pattern = r'^(?P<ip>\d+\.\d+\.\d+\.\d+).*"(?P<method>\w+)\s+(?P<path>\S+).*"\s+(?P<status>\d+)'
requests = []
for match in re.finditer(pattern, log_text, re.MULTILINE):
requests.append(match.groupdict())
# requests = [
# {'ip': '192.168.1.1', 'method': 'GET', 'path': '/index.html', 'status': '200'},
# {'ip': '192.168.1.2', 'method': 'POST', 'path': '/api/data', 'status': '201'},
# {'ip': '192.168.1.1', 'method': 'GET', 'path': '/style.css', 'status': '200'}
# ]# Найти все 4xx и 5xx ошибки
errors = [r for r in requests if int(r['status']) >= 400]
# Найти все POST запросы
posts = [r for r in requests if r['method'] == 'POST']Предупреждение: Для сложного HTML используйте BeautifulSoup. Regex подходит только для простых, предсказуемых структур.
html = '''
<a href="https://example.com">Example</a>
<a href="/page1">Page 1</a>
<a href='page2.html'>Page 2</a>
'''
# Извлечение URL из ссылок
pattern = r'<a\s+href=["\']([^"\']+)["\']'
urls = re.findall(pattern, html)
# ['https://example.com', '/page1', 'page2.html']
# Извлечение текста ссылки
pattern = r'<a\s+href=["\'][^"\']+["\']>([^<]+)</a>'
texts = re.findall(pattern, html)
# ['Example', 'Page 1', 'Page 2']html = '''
<div class="price">199.99</div>
<div class="price">299.50</div>
<span class="name">Product 1</span>
'''
# Цены
prices = re.findall(r'<div\s+class="price">([\d.]+)</div>', html)
# ['199.99', '299.50']
# Названия
names = re.findall(r'<span\s+class="name">([^<]+)</span>', html)
# ['Product 1']html = '<div data-product=\'{"id": 123, "name": "Product"}\'></div>'
# Извлечение JSON
pattern = r'data-product=\'({[^}]+})\''
match = re.search(pattern, html)
if match:
import json
data = json.loads(match.group(1))
# {'id': 123, 'name': 'Product'}Предупреждение: Для JSON используйте модуль
json. Regex только для извлечения частей.
json_text = '{"name": "John", "age": 30, "city": "Moscow"}'
# Извлечение значения по ключу
pattern = r'"name":\s*"([^"]+)"'
match = re.search(pattern, json_text)
if match:
name = match.group(1) # 'John'
# Для числовых значений
pattern = r'"age":\s*(\d+)'
match = re.search(pattern, json_text)
if match:
age = int(match.group(1)) # 30json_text = '''
{
"user": {
"name": "John",
"contacts": {
"email": "john@example.com",
"phone": "+79991234567"
}
}
}
'''
# Извлечение email
pattern = r'"email":\s*"([^"]+)"'
match = re.search(pattern, json_text)
email = match.group(1) # 'john@example.com'csv_text = """name,age,city
John,30,Moscow
Jane,25,Paris
Bob,35,London"""
# Разбиение на строки
lines = csv_text.strip().split('\n')
# Парсинг каждой строки
pattern = r'^([^,]+),([^,]+),([^,]+)$'
for line in lines[1:]: # Пропуск заголовка
match = re.match(pattern, line)
if match:
name, age, city = match.groups()csv_text = '''name,description,price
"Product 1","Description with, comma",199.99
"Product 2","Simple description",299.50'''
# Извлечение полей с кавычками
pattern = r'"([^"]+)"|"([^"]+)"|([^,]+)'
# Лучше использовать csv модуль для надёжностиtext = 'Заказы: #12345, #67890, #11111'
# Все номера заказов
orders = re.findall(r'#(\d+)', text)
# ['12345', '67890', '11111']text = 'Цены: 199.99 руб., 299.50 руб., 1000 руб.'
# Все цены
prices = re.findall(r'(\d+(?:\.\d+)?)\s*руб', text)
# ['199.99', '299.50', '1000']text = 'Даты: 2024-03-09, 09.03.2024, 03/09/2024'
# YYYY-MM-DD
dates1 = re.findall(r'\d{4}-\d{2}-\d{2}', text)
# ['2024-03-09']
# DD.MM.YYYY
dates2 = re.findall(r'\d{2}\.\d{2}\.\d{4}', text)
# ['09.03.2024']
# MM/DD/YYYY
dates3 = re.findall(r'\d{2}/\d{2}/\d{4}', text)
# ['03/09/2024']text = """
Контакты:
support@example.com
sales@test.org
admin@company.co.uk
"""
pattern = r'[\w.-]+@[\w.-]+\.[a-zA-Z]{2,}'
emails = re.findall(pattern, text)
# ['support@example.com', 'sales@test.org', 'admin@company.co.uk']text = '#Python #regex #programming и #100days'
# Все хэштеги
hashtags = re.findall(r'#(\w+)', text)
# ['Python', 'regex', 'programming', '100days']text = '@john привет! @jane как дела?'
# Все упоминания
mentions = re.findall(r'@(\w+)', text)
# ['john', 'jane']text = 'Файлы: document.pdf (1.5 MB), image.jpg (2.3 KB), video.mp4 (1.2 GB)'
# Все размеры с единицами
sizes = re.findall(r'\(([\d.]+)\s*(KB|MB|GB)\)', text)
# [('1.5', 'MB'), ('2.3', 'KB'), ('1.2', 'GB')]code = """
# Это комментарий
def hello(): # Ещё комментарий
pass
"""
# Все комментарии
comments = re.findall(r'#\s*(.+)$', code, re.MULTILINE)
# ['Это комментарий', 'Ещё комментарий']markdown = """
# Заголовок 1
## Заголовок 2
[Ссылка](https://example.com)

"""
# Заголовки первого уровня
h1 = re.findall(r'^#\s+(.+)$', markdown, re.MULTILINE)
# ['Заголовок 1']
# Заголовки второго уровня
h2 = re.findall(r'^##\s+(.+)$', markdown, re.MULTILINE)
# ['Заголовок 2']
# Ссылки
links = re.findall(r'\[([^\]]+)\]\(([^)]+)\)', markdown)
# [('Ссылка', 'https://example.com')]def parse_log_file(filename):
pattern = re.compile(r'^(?P<ip>\d+\.\d+\.\d+\.\d+).*"(?P<method>\w+)')
with open(filename, 'r') as f:
for line in f:
match = pattern.match(line)
if match:
yield match.groupdict()
# Использование
for request in parse_log_file('access.log'):
process(request)# Вместо findall, который создаёт весь список в памяти
text = large_text # Очень большой текст
# Плохо: создаёт список всех совпадений
matches = re.findall(r'\d+', text)
# Хорошо: итератор
for match in re.finditer(r'\d+', text):
process(match.group())html = '<div>content1</div><div>content2</div>'
# Ошибка: жадный захват
re.findall(r'<div>.*</div>', html) # ['<div>content1</div><div>content2</div>']
# Правильно: нежадный захват
re.findall(r'<div>.*?</div>', html) # ['<div>content1</div>', '<div>content2</div>']html = '<div><span>nested</span></div>'
# Ошибка: regex не может парсить вложенные структуры
re.findall(r'<div>.*</div>', html) #соответствует весь HTML
# Правильно: использовать BeautifulSoup
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, 'html.parser')
div = soup.find('div')text = '1-2 3-4'
# Ошибка: ожидали полные совпадения
re.findall(r'(\d+)-(\d+)', text) # [('1', '2'), ('3', '4')]
# Правильно: non-capturing группы
re.findall(r'(?:\d+)-(?:\d+)', text) # ['1-2', '3-4']json, regex только для извлечения частейfinditer() экономит память при обработке больших файлов.*? для множественных элементовВопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.