💻
Разработка в IT
Опубликовано:
16.04.2026
Обновлено:
16.04.2026

Парсинг строк в Python: split, find, partition, re — разбираем текст

Иван Смирнов

Когда я впервые открыл файл почтового лога в Python, задача звучала просто: вытащить email-адрес отправителя из каждой строки, начинающейся с "From". Строка выглядела так: From stephen.marquard@uct.ac.za Sat Jan 5 09:14:16 2008. Пять минут я ковырялся с индексами вручную, пока не обнаружил, что split() делает это за одну строку. Парсинг строк (parsing) это процесс разбора текста на составные части для извлечения нужных данных.

В этой статье разбираю пять подходов к парсингу: split(), find() со срезами, partition(), регулярные выражения и комбинированные методы. Каждый подход показан на реальных задачах с разбором ошибок.

Что значит "парсить строку"

Парсинг (от англ. parsing) это анализ строки с целью извлечь из неё структурированные данные. Входные данные это текст без чёткой структуры: строка лог-файла, HTML-тег, CSV-запись, email-заголовок. Результат это отдельные значения: дата, адрес, число, имя.

В Python парсинг строк строится на трёх базовых инструментах: поиск позиции (find), разбиение на части (split), извлечение подстроки (срез). Все остальные подходы это комбинации и расширения этих трёх операций.

Подход 1: split() — разбиение по разделителю

split() разбивает строку на список подстрок по заданному разделителю. Без аргумента разделяет по пробельным символам.

Базовый парсинг строки лога

line = "From stephen.marquard@uct.ac.za Sat Jan  5 09:14:16 2008"
words = line.split()
print(words[0])  # From
print(words[1])  # stephen.marquard@uct.ac.za
print(words[2])  # Sat
print(words[5])  # 09:14:16

split() разбил строку по пробелам и вернул список из 7 элементов. Доступ по индексу извлекает нужное поле.

Двойной split: вложенный разбор

Иногда одного split() недостаточно. Для извлечения домена из email-адреса нужен второй split по символу @.

line = "From stephen.marquard@uct.ac.za Sat Jan  5 09:14:16 2008"
words = line.split()
email = words[1]  # stephen.marquard@uct.ac.za
domain = email.split("@")[1]  # uct.ac.za
print(domain)

Первый split() разбивает строку по пробелам, второй split("@") разбивает email-адрес по @.

split() с разделителем

csv_line = "Иванов,Пётр,28,Москва"
fields = csv_line.split(",")
print(fields)  # ['Иванов', 'Пётр', '28', 'Москва']

surname = fields[0]  # Иванов
age = int(fields[2])  # 28

split() с аргументом-разделителем разбивает строку по указанному символу.

split() с ограничением количества разбиений

Второй аргумент maxsplit останавливает разбиение после заданного количества.

log = "ERROR: 2026-04-07: Disk space critical"
parts = log.split(": ", 1)
print(parts[0])  # ERROR
print(parts[1])  # 2026-04-07: Disk space critical

При maxsplit=1 строка разделена только по первому двоеточию с пробелом. Остальной текст попал во второй элемент целиком.

Разница между split() и split(" ")

text = "  hello   world  "
print(text.split())  # ['hello', 'world']
print(text.split(" "))  # ['', '', 'hello', '', '', 'world', '', '']

Без аргумента split() игнорирует множественные пробелы и пустые элементы. С аргументом " " каждый пробел создаёт отдельное разделение. Для парсинга текста с непредсказуемыми пробелами используйте split() без аргумента.

Подход 2: find() + срез — поиск и извлечение

Когда данные не разделены пробелами, а находятся между маркерами, используйте find() для поиска позиции и срез для извлечения.

Извлечение хоста из email-заголовка

data = "From stephen.marquard@uct.ac.za Sat Jan  5 09:14:16 2008"
at_pos = data.find("@")
sp_pos = data.find(" ", at_pos)
host = data[at_pos + 1 : sp_pos]
print(host)  # uct.ac.za

find("@") находит позицию 21. find(" ", at_pos) ищет первый пробел после позиции 21 и находит позицию 31. Срез data[22:31] извлекает "uct.ac.za".

Извлечение значения из заголовка

line = "X-DSPAM-Confidence: 0.8475"
colon_pos = line.find(": ")
value = float(line[colon_pos + 2 :])
print(value)  # 0.8475

find(": ") находит позицию двоеточия с пробелом. Срез от colon_pos + 2 до конца строки извлекает числовое значение. float() преобразует строку в число.

Извлечение текста между тегами

html = '<a href="https://example.com">Ссылка</a>'
start = html.find('">') + 2
end = html.find("</a>")
text = html[start:end]
print(text)  # Ссылка

Цепочка find() для многоуровневого разбора

line = "Received: from mail.example.com (192.168.1.1) by server.local"

# Извлечь имя хоста
from_pos = line.find("from ") + 5
paren_pos = line.find(" (", from_pos)
hostname = line[from_pos:paren_pos]
print(hostname)  # mail.example.com

# Извлечь IP
ip_start = paren_pos + 2
ip_end = line.find(")", ip_start)
ip = line[ip_start:ip_end]
print(ip)  # 192.168.1.1

Каждый следующий find() использует позицию от предыдущего как стартовую точку. Это гарантирует, что мы ищем в правильном участке строки.

Подход 3: partition() — разбиение на три части

partition() разделяет строку по первому вхождению разделителя и возвращает кортеж из трёх элементов: часть до, сам разделитель, часть после.

email = "user@example.com"
name, sep, domain = email.partition("@")
print(name)  # user
print(domain)  # example.com

partition() vs split()

text = "key=value=extra"

# split — всё разделяет
print(text.split("="))  # ['key', 'value', 'extra']

# split с maxsplit — два элемента
print(text.split("=", 1))  # ['key', 'value=extra']

# partition — три элемента, включая разделитель
print(text.partition("="))  # ('key', '=', 'value=extra')

partition() удобнее split(x, 1), когда нужно знать, нашёлся ли разделитель. Если разделитель не найден, partition() возвращает (строка, "", ""), а split(x, 1) возвращает список с одним элементом.

rpartition() — разбиение по последнему вхождению

path = "/home/user/documents/report.txt"
directory, sep, filename = path.rpartition("/")
print(directory)  # /home/user/documents
print(filename)  # report.txt

rpartition() ищет последнее вхождение разделителя. Для путей к файлам это удобнее, чем find() + rfind().

Подход 4: регулярные выражения (re)

Когда структура текста сложная или разделители непредсказуемы, строковых методов недостаточно. Модуль re позволяет описать шаблон и извлечь данные по нему.

Базовый поиск по шаблону

import re

line = "From stephen.marquard@uct.ac.za Sat Jan  5 09:14:16 2008"
match = re.search(r"\S+@\S+", line)
if match:
    print(match.group())  # stephen.marquard@uct.ac.za

Шаблон \S+@\S+ означает: один или более непробельных символов, затем @, затем снова один или более непробельных символов.

findall() — все вхождения

import re

text = "Контакты: alice@example.com и bob@company.org"
emails = re.findall(r"\S+@\S+", text)
print(emails)  # ['alice@example.com', 'bob@company.org']

findall() возвращает список всех совпадений. find() нашёл бы только позицию первого.

Именованные группы для структурированного парсинга

import re

log = "2026-04-07T12:25:00 ERROR Database connection failed"
pattern = r"(?P<timestamp>\S+)\s+(?P<level>\w+)\s+(?P<message>.*)"
match = re.match(pattern, log)

if match:
    print(match.group("timestamp"))  # 2026-04-07T12:25:00
    print(match.group("level"))  # ERROR
    print(match.group("message"))  # Database connection failed

Именованные группы (?P<name>...) позволяют обращаться к частям совпадения по имени. Код становится читаемым без комментариев.

re.split() — разбиение по шаблону

import re

text = "слово1; слово2, слово3  слово4"
words = re.split(r"[;,\s]+", text)
print(words)  # ['слово1', 'слово2', 'слово3', 'слово4']

re.split() разбивает строку по регулярному выражению. Строковый split() так не умеет.

Подход 5: комбинированный — строковые методы + логика

Реальный парсинг редко укладывается в один метод. Чаще всего нужна комбинация: отфильтровать строки, разбить на части, очистить, преобразовать тип.

Парсинг лог-файла: дни недели отправки

fhand = open("mbox-short.txt")
for line in fhand:
    line = line.rstrip()
    if not line.startswith("From "):
        continue
    words = line.split()
    print(words[2])

Алгоритм: rstrip() убирает перенос строки, startswith() фильтрует нужные строки, split() разбивает на слова, индекс извлекает день недели.

Парсинг с защитой от ошибок

fhand = open("mbox-short.txt")
for line in fhand:
    words = line.split()
    # Защита от пустых строк
    if len(words) == 0:
        continue
    if words[0] != "From":
        continue
    print(words[2])

Пустая строка после split() даёт пустой список. Обращение к words[0] вызовет IndexError. Проверка len(words) == 0 предотвращает это.

Подсчёт частоты слов в файле

counts = {}
for line in fhand:
    words = line.split()
    for word in words:
        counts[word] = counts.get(word, 0) + 1

print(counts)

Вложенный цикл: внешний перебирает строки файла, внутренний перебирает слова в строке. Метод dict.get() возвращает текущий счётчик или 0, если слово встретилось впервые.

Очистка текста перед парсингом

Грязные данные ломают парсинг. Перед разбором текст нужно очистить: убрать лишние пробелы, привести регистр, удалить пунктуацию.

Удаление пунктуации через translate()

import string

text = "But, soft! what light through yonder window breaks?"
clean = text.translate(str.maketrans("", "", string.punctuation))
print(clean)  # But soft what light through yonder window breaks
words = clean.lower().split()
print(words)
# ['but', 'soft', 'what', 'light', 'through', 'yonder', 'window', 'breaks']

str.maketrans("", "", string.punctuation) создаёт таблицу перевода, которая удаляет все знаки пунктуации. После этого split() разбивает чистый текст на слова.

Какой подход выбрать

Ситуация Подход Пример
Данные разделены пробелами/запятыми split() CSV, лог-строки
Фрагмент между двумя маркерами find() + срез Заголовки email, HTML-теги
Нужно разделить на "до" и "после" partition() key=value, email по @
Сложный или непредсказуемый формат re (регулярные выражения) Логи с вариативным форматом
Многошаговый разбор с фильтрацией Комбинация методов Реальные файлы данных

split() справляется с 70% задач парсинга текста. find() + срез помогает, когда позиция данных определяется маркерами, а не разделителями. Регулярные выражения нужны, когда формат строк непредсказуем или содержит несколько вариантов структуры.

Неочевидные детали

Первый факт: split() с аргументом и без него ведёт себя по-разному с пустыми строками. "".split() возвращает [], а "".split(",") возвращает [""].

print("".split())  # []
print("".split(","))  # ['']

Второй факт: find() возвращает -1 при неудаче, и срез с -1 может дать неожиданный результат. Код data[data.find("@"):] при отсутствии @ вернёт последний символ строки, потому что data[-1:] это корректный срез. Всегда проверяйте результат find() перед использованием в срезе.

data = "no at sign here"
pos = data.find("@")
print(pos)  # -1
print(data[pos:])  # e (последний символ!)

Третий факт: partition() работает быстрее, чем split(x, 1), потому что всегда возвращает ровно три элемента без создания списка переменной длины.

Четвёртый факт: re.findall() с группами возвращает не полные совпадения, а только содержимое групп.

import re

text = "Цена: 100 руб, Скидка: 20 руб"
print(re.findall(r"(\d+) руб", text))  # ['100', '20']

Без скобок findall() вернул бы ['100 руб', '20 руб']. С одной группой возвращает только содержимое группы.

Пятый факт: для парсинга CSV используйте модуль csv, а не split(","): он корректно обрабатывает кавычки и переносы строк внутри полей.

import csv, io

line = '"Иванов, Пётр",28,"Москва"'
reader = csv.reader(io.StringIO(line))
fields = next(reader)
print(fields)  # ['Иванов, Пётр', '28', 'Москва']

FAQ

Когда использовать find(), а когда in?

Оператор in проверяет наличие подстроки и возвращает True/False. find() возвращает позицию. Используйте in для проверки, find() когда нужна позиция.

Как парсить строку с несколькими разделителями?

Используйте re.split() с шаблоном, перечисляющим все разделители.

import re

text = "яблоко; груша, слива | банан"
fruits = re.split(r"[;,|]\s*", text)
print(fruits)  # ['яблоко', 'груша', 'слива', 'банан']

Что быстрее: split() или re.split()?

split() быстрее, потому что реализован на C и не требует компиляции шаблона. Используйте re.split() только когда разделитель описывается шаблоном, а не фиксированной строкой.

Как парсить JSON-строку?

Не разбирайте JSON вручную через split() или find(). Используйте модуль json:

import json

data = '{"name": "Alice", "age": 30}'
parsed = json.loads(data)
print(parsed["name"])  # Alice

Как безопасно парсить строку, если формат может отличаться?

Используйте try/except или проверяйте длину списка после split().

line = "incomplete data"
parts = line.split(":")
if len(parts) >= 2:
    key, value = parts[0], parts[1]
else:
    print("Неожиданный формат")

Мой совет: начинайте парсинг с split(). Если split() не справляется, добавьте find() и срезы. Если формат данных нестабильный или содержит вложенные структуры, переходите на регулярные выражения. Для стандартных форматов (CSV, JSON, XML) всегда используйте специализированные модули, а не ручной разбор.

Читайте также