Используйте Python для поиска в одном файле .txt списка слов или фраз (и отображения контекста).

В основном, как говорится в вопросе. Я новичок в Python и люблю учиться, видя и делая.

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

При получении результатов было бы здорово получить контекст результатов. Так что, возможно, он мог бы распечатать 50 символов в текстовом файле до и после каждого найденного условия поиска. Было бы здорово, если бы он также показывал, в какой строке был найден поисковый запрос.

Любые указатели на то, как кодировать это, или даже примеры кода будут высоко оценены.


person prupert    schedule 09.06.2010    source источник


Ответы (2)


arrow_upward
7
arrow_downward

Несмотря на часто выражаемую антипатию к регулярным выражениям со стороны многих в сообществе Python, они действительно являются ценным инструментом для соответствующих случаев использования, которые определенно включают в себя идентификацию слов и фраз (благодаря элементу \b «граница слова» в шаблоны регулярных выражений - альтернативы, основанные на обработке строк, представляют гораздо большую проблему, например, .split() использует пробел в качестве разделителя и, таким образом, досадно оставляет знаки препинания прикрепленными к соседним словам и т. д. и т. д.).

Если RE в порядке, я бы рекомендовал что-то вроде:

import re
import sys

def main():
  if len(sys.argv) != 3:
    print("Usage: %s fileofstufftofind filetofinditin" % sys.argv[0])
    sys.exit(1)

  with open(sys.argv[1]) as f:
    patterns = [r'\b%s\b' % re.escape(s.strip()) for s in f]
  there = re.compile('|'.join(patterns))

  with open(sys.argv[2]) as f:
    for i, s in enumerate(f):
      if there.search(s):
        print("Line %s: %r" % (i, s))

main()

первый аргумент — это (путь) текстовый файл со словами или фразами для поиска, по одному в строке, а второй аргумент (путь) — текстовый файл, в котором их нужно найти. При желании легко сделать случай нечувствительным к поиску (возможно, просто опционально на основе переключателя параметров командной строки) и т. д. и т. д.

Некоторое пояснение для читателей, не знакомых с RE...:

Элемент \b в patterns элементах гарантирует, что случайных совпадений не будет (если вы ищете «кошка» или «собака», вы не увидите случайного совпадения с «каталогом» или «аутсайдером»; и вы выиграли не пропустите попадание в "Кошка, улыбнувшись, убежала" по некоторому расщеплению, думая, что слово там "кошка", включая запятую ;-).

Элемент | означает or, поэтому, например. из текстового файла с содержимым (две строки)

cat
dog

это сформирует шаблон '\bcat\b|\bdog\b', который найдет либо «кошку», либо «собаку» (как отдельные слова, игнорируя пунктуацию, но отклоняя совпадения в более длинных словах).

re.escape избегает знаков препинания, поэтому он соответствует буквально, а не со специальным значением, как это обычно имеет место в шаблоне RE.

person Alex Martelli    schedule 09.06.2010
comment
Еще раз спасибо за первоклассный ответ - код с объяснением очень полезен. Я задавался вопросом о RE, но не был уверен, что это уместно в этом случае - приятно видеть, что это так! - person prupert; 10.06.2010

arrow_upward
3
arrow_downward

Начните с чего-то вроде этого. Этот код не является точным решением для имеющейся у вас спецификации, но является хорошей отправной точкой.

import sys

words = "foo bar baz frob"

word_set = set(words.split())
for line_number, line in enumerate(open(sys.argv[1])):
    if words_set.intersection(line.split()):
        print "%d:%s" % (line_number, line.strip())

Некоторые пояснения ниже:

  • Искомые слова изначально хранятся в строке (в строке 3). Я разбиваю этот список слов по пробелам и создаю из него набор, чтобы было легче проверить, есть ли какие-либо слова в текущей строке в списке слов. (Проверка членства в наборе — O(1), а в списке — O(n)).

  • В основном цикле for я открываю входной файл (который передается как аргумент командной строки) и использую встроенный метод enumerate для получения счетчика номера строки, а также фактической строки. sys.argv — это массив, в котором хранятся аргументы командной строки; sys.argv[0] — это всегда имя скрипта Python.

  • В самом цикле я беру текущую строку, разбиваю ее на отдельные слова и снова создаю набор из слов. Затем я могу быстро найти пересечение набора слов в текущей строке с набором слов, которые я ищу. Если пересечение имеет логическое значение True (т. е. если оно не пусто), я печатаю номер строки, а также строку.

Вещи, которые еще не решены (и оставлены на ваше усмотрение):

  • Список слов теперь жестко закодирован в исходнике, но не должно быть слишком сложно открыть дополнительный файл (чье имя передается, скажем, sys.argv[2]), прочитать его слова одно за другим и сохранить их в наборе. Обратите внимание, что вы можете расширять наборы с помощью их методов add и update (вместо append и extend, которые работают для списков).

  • Очевидно, что описанный выше метод не работает, если вместо слов у вас фразы (как указано в одном из комментариев). Поскольку я предполагаю, что вы хотите учиться и вам не нужно точное решение, я скажу только, что если у вас есть фразы в наборе, вы можете проверить, находится ли какой-либо из элементов набора в строке, сказав any(phrase in line for phrase in set_of_phrases). Это можно использовать вместо заданного пересечения (и, конечно, в этом случае не разбивайте строку на слова).

  • Если вы хотите распечатать контекст совпадений, вы можете использовать две дополнительные переменные (скажем, prev_line и next_line), в которых хранится предыдущая и следующая строки. В цикле for вы фактически будете читать next_line вместо line, а в конце цикла вам следует позаботиться о копировании line в prev_line и next_line в line.

  • Еще более питоновский способ отслеживать предыдущую и следующую строки — создать функцию генератора Python, которая выдает кортеж, состоящий из элемента i-1, элемента i и элемент i+1 для каждого i заданного итерируемого объекта (например, файла). Однако это более сложный материал, и, поскольку вы довольно плохо знакомы с Python, я думаю, что лучше оставить его на потом. Однако, если вам любопытно, функция генератора, выполняющая эту задачу, может выглядеть так:

    def context_generator(iterable):
        prev, current, next = None, None, None
        for element in iterable:
            prev, current, next = current, next, element
            if current is not None:
                yield prev, current, next
        if next is not None:
            yield current, next, None
    
person Tamás    schedule 09.06.2010
comment
чтобы открыть входной файл, вам нужно использовать open. - person SilentGhost; 09.06.2010
comment
также вам не нужно преобразовывать слова в строке в набор, это может быть сделано внутри word_set.intersection(line.split()) - person SilentGhost; 09.06.2010
comment
@FogleBird @SilentGhost: спасибо за комментарии. Я использовал какой-то итеративный подход и улучшал свой ответ после его отправки. Ваши предложения были включены в мой ответ. Что касается фраз, я не хочу давать точное готовое решение, так как считаю, что будет лучше, если исходный постер догадается сам, используя мой ответ только в качестве ориентира. Я упомянул случай фраз в одном из пунктов списка. - person Tamás; 09.06.2010
comment
Спасибо за подробный ответ, именно то, что мне было нужно - с интересом прочитаю. - person prupert; 10.06.2010