2025, Oct 04 19:16

Извлечение маркеров из логов: буфер look-behind на Python

Как найти beta и delta в логах: при втором токене truck извлекаем третий токен из строки двумя выше. Python, deque, скользящее окно за один проход. Быстро.

Разбор полуструктурированных логов часто требует смотреть назад или вперед относительно найденного совпадения. Здесь задача конкретная: найти каждую строку, где второй токен равен truck, затем извлечь третий токен из строки, которая находится на две строки выше. Вход — это простой текстовый файл с блоками значений, разделенными строками из плюсов, а ожидаемый результат — список маркеров, расположенных двумя строками выше совпавших строк.

Постановка задачи

Для такого входа нужно вернуть beta и delta, потому что оба вхождения truck встречаются на две строки ниже соответствующих маркеров:

apples    grapes    alpha   pears
chicago paris london 
yellow    blue      red
+++++++++++++++++++++
apples    grapes    beta   pears
chicago paris london 
car   truck  van
+++++++++++++++++++
apples    grapes    gamma   pears
chicago paris london 
white  purple   black
+++++++++++++++++++
apples    grapes    delta   pears
chicago paris london 
car   truck  van

Базовая попытка (и почему она не срабатывает)

Логично начать со сканирования файла и сбора строк, где второй токен равен truck, а затем отправить их в pandas DataFrame. Однако такой подход собирает лишь совпавшие строки, а не значения, находящиеся двумя строками выше. Он не сохраняет нужное окно контекста.

import pandas as pd

rows_buffer = []

with open('input.txt', 'r') as fh:
    for record in fh:
        tokens = record.split()
        if len(tokens) > 1 and tokens[1] == "truck":
            rows_buffer.append(tokens)

frame = pd.DataFrame(rows_buffer)
print(frame.to_string)

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

Что тут на самом деле важно

Требование позиционное и относительное. Когда у текущей строки второй токен равен truck, нужное значение находится в третьем токене строки, которая была двумя строками ранее. Значит, по мере итерации нужно хранить скользящее окно последних строк. Простое выделение строк в DataFrame постфактум не поможет, если не сохранять окружающий контекст.

Точный подход с окном «смотрящим назад»

Компактный и надежный способ — поддерживать скользящий буфер фиксированного размера, в котором всегда лежат последние три строки. Как только встречаем строку, у которой второй токен равен truck, обращаемся к самой старой записи в буфере и извлекаем из нее третий токен, если он есть. Строки с недостаточным числом токенов (например, разделители из плюсов) пропускаются при сопоставлении, чтобы избежать ложных срабатываний.

#!/usr/bin/env python
import sys
from collections import deque

def run():
    if len(sys.argv) < 2:
        print("Usage: script_runner.py inputPath", file=sys.stderr)
        sys.exit(1)

    LOOKBACK = 3
    ring = deque(maxlen=LOOKBACK)
    hits = []

    with open(sys.argv[1], 'r') as src:
        for entry in src:
            parts = entry.strip().split()
            if len(parts) < 2:
                # полей недостаточно, чтобы строка могла участвовать в сопоставлении
                ring.append(entry)
                continue

            ring.append(entry)

            if len(ring) == LOOKBACK and parts[1] == "truck":
                target_line = ring[0]
                target_parts = target_line.split()
                if len(target_parts) >= 3:
                    hits.append(target_parts[2])

    if hits:
        print(hits)
        # Необязательно: преобразовать в DataFrame
        # import pandas as pd
        # df_out = pd.DataFrame(hits, columns=['lookbehind'])
        # print(df_out)

if __name__ == "__main__":
    run()

На приведенных данных скрипт выводит ['beta', 'delta'], что и требуется. Кольцевой буфер сохраняет ровно тот контекст, который нужен, и делает поиск «на две строки выше» простым и безопасным.

Почему это важно

В построчной обработке часто важны относительные позиции, а не абсолютные индексы: заголовки перед содержимым, хвостовые строки, подводящие итоги предыдущих секций, или сигнальные строки вроде truck в нашем примере. Простой и детерминированный механизм «взгляда назад» предотвращает рассинхронизацию и избавляет от хрупкой индексации постфактум. Он также естественным образом отбрасывает некорректные или несущественные строки, проверяя число токенов до сопоставления.

Выводы

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

Статья основана на вопросе на StackOverflow от yodish и ответе от ticktalk.