Python разбивает текст на предложения


У меня есть текстовый файл. Мне нужен список предложений.

Как это можно реализовать? Есть много тонкостей, таких как точка используется в аббревиатурах.

мой старый и работает плохо.

re.compile('(. |^|!|?)([A-Z][^;↑.<>@^&/[]]*(.|!|?) )',re.M)
9 64

9 ответов:

инструментарий естественного языка (nltk.org) имеет то, что вам нужно. Это групповое сообщение указывает, что это не так:

import nltk.data

tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
fp = open("test.txt")
data = fp.read()
print '\n-----\n'.join(tokenizer.tokenize(data))

(Я еще не пробовал!)

эта функция может разделить весь текст Huckleberry Finn на предложения примерно за 0,1 секунды и обрабатывает многие из более болезненных краевых случаев, которые делают разбор предложений нетривиальным, например"г-н Джон Джонсон-младший родился в США, но получил степень доктора философии в Израиле, прежде чем присоединиться к Nike Inc. в качестве инженера. Он также работал в craigslist.org как бизнес-аналитик."

# -*- coding: utf-8 -*-
import re
caps = "([A-Z])"
prefixes = "(Mr|St|Mrs|Ms|Dr)[.]"
suffixes = "(Inc|Ltd|Jr|Sr|Co)"
starters = "(Mr|Mrs|Ms|Dr|He\s|She\s|It\s|They\s|Their\s|Our\s|We\s|But\s|However\s|That\s|This\s|Wherever)"
acronyms = "([A-Z][.][A-Z][.](?:[A-Z][.])?)"
websites = "[.](com|net|org|io|gov)"

def split_into_sentences(text):
    text = " " + text + "  "
    text = text.replace("\n"," ")
    text = re.sub(prefixes,"\1<prd>",text)
    text = re.sub(websites,"<prd>\1",text)
    if "Ph.D" in text: text = text.replace("Ph.D.","Ph<prd>D<prd>")
    text = re.sub("\s" + caps + "[.] "," \1<prd> ",text)
    text = re.sub(acronyms+" "+starters,"\1<stop> \2",text)
    text = re.sub(caps + "[.]" + caps + "[.]" + caps + "[.]","\1<prd>\2<prd>\3<prd>",text)
    text = re.sub(caps + "[.]" + caps + "[.]","\1<prd>\2<prd>",text)
    text = re.sub(" "+suffixes+"[.] "+starters," \1<stop> \2",text)
    text = re.sub(" "+suffixes+"[.]"," \1<prd>",text)
    text = re.sub(" " + caps + "[.]"," \1<prd>",text)
    if "”" in text: text = text.replace(".”","”.")
    if "\"" in text: text = text.replace(".\"","\".")
    if "!" in text: text = text.replace("!\"","\"!")
    if "?" in text: text = text.replace("?\"","\"?")
    text = text.replace(".",".<stop>")
    text = text.replace("?","?<stop>")
    text = text.replace("!","!<stop>")
    text = text.replace("<prd>",".")
    sentences = text.split("<stop>")
    sentences = sentences[:-1]
    sentences = [s.strip() for s in sentences]
    return sentences

вот середина пути подхода, который не зависит от каких-либо внешних библиотек. Я использую понимание списка, чтобы исключить перекрытия между аббревиатурами и Терминаторами, а также исключить перекрытия между вариациями окончаний, например: '."против"."'

abbreviations = {'dr.': 'doctor', 'mr.': 'mister', 'bro.': 'brother', 'bro': 'brother', 'mrs.': 'mistress', 'ms.': 'miss', 'jr.': 'junior', 'sr.': 'senior',
                 'i.e.': 'for example', 'e.g.': 'for example', 'vs.': 'versus'}
terminators = ['.', '!', '?']
wrappers = ['"', "'", ')', ']', '}']


def find_sentences(paragraph):
   end = True
   sentences = []
   while end > -1:
       end = find_sentence_end(paragraph)
       if end > -1:
           sentences.append(paragraph[end:].strip())
           paragraph = paragraph[:end]
   sentences.append(paragraph)
   sentences.reverse()
   return sentences


def find_sentence_end(paragraph):
    [possible_endings, contraction_locations] = [[], []]
    contractions = abbreviations.keys()
    sentence_terminators = terminators + [terminator + wrapper for wrapper in wrappers for terminator in terminators]
    for sentence_terminator in sentence_terminators:
        t_indices = list(find_all(paragraph, sentence_terminator))
        possible_endings.extend(([] if not len(t_indices) else [[i, len(sentence_terminator)] for i in t_indices]))
    for contraction in contractions:
        c_indices = list(find_all(paragraph, contraction))
        contraction_locations.extend(([] if not len(c_indices) else [i + len(contraction) for i in c_indices]))
    possible_endings = [pe for pe in possible_endings if pe[0] + pe[1] not in contraction_locations]
    if len(paragraph) in [pe[0] + pe[1] for pe in possible_endings]:
        max_end_start = max([pe[0] for pe in possible_endings])
        possible_endings = [pe for pe in possible_endings if pe[0] != max_end_start]
    possible_endings = [pe[0] + pe[1] for pe in possible_endings if sum(pe) > len(paragraph) or (sum(pe) < len(paragraph) and paragraph[sum(pe)] == ' ')]
    end = (-1 if not len(possible_endings) else max(possible_endings))
    return end


def find_all(a_str, sub):
    start = 0
    while True:
        start = a_str.find(sub, start)
        if start == -1:
            return
        yield start
        start += len(sub)

я использовал функцию find_all Карла из этой записи: найти все вхождения подстроки в Python

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

>>> from nltk import tokenize
>>> p = "Good morning Dr. Adams. The patient is waiting for you in room number 3."

>>> tokenize.sent_tokenize(p)
['Good morning Dr. Adams.', 'The patient is waiting for you in room number 3.']

ref:https://stackoverflow.com/a/9474645/2877052

для простых случаев (когда предложения обычно заканчиваются) это должно работать:

import re
text = ''.join(open('somefile.txt').readlines())
sentences = re.split(r' *[\.\?!][\'"\)\]]* *', text)

регулярное выражение *\. +, который соответствует периоду, окруженному 0 или более пробелами слева и 1 или более справа (чтобы предотвратить что-то вроде периода в re.сплит засчитывается как изменение в предложении).

очевидно, что это не самое надежное решение, но в большинстве случаев все будет хорошо. Единственный случай, когда это не будет охватывать аббревиатуры (возможно, запустить через список предложения и проверьте, что каждая строка в sentences начинается с большой буквы?)

вы можете попробовать использовать Spacy вместо регулярных выражений. Я использую его, и он делает свою работу.

import spacy
nlp = spacy.load('en')

text = '''Your text here'''
tokens = nlp(text)

for sent in tokens.sents:
    print(sent.string.strip())

@Artyom,

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

def russianTokenizer(text):
    result = text
    result = result.replace('.', ' . ')
    result = result.replace(' .  .  . ', ' ... ')
    result = result.replace(',', ' , ')
    result = result.replace(':', ' : ')
    result = result.replace(';', ' ; ')
    result = result.replace('!', ' ! ')
    result = result.replace('?', ' ? ')
    result = result.replace('\"', ' \" ')
    result = result.replace('\'', ' \' ')
    result = result.replace('(', ' ( ')
    result = result.replace(')', ' ) ') 
    result = result.replace('  ', ' ')
    result = result.replace('  ', ' ')
    result = result.replace('  ', ' ')
    result = result.replace('  ', ' ')
    result = result.strip()
    result = result.split(' ')
    return result

а потом назовем это так:

text = 'вы выполняете поиск, используя Google SSL;'
tokens = russianTokenizer(text)

удачи, Марилена.

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

Итак, вот простой re-код, доступный по адресу http://pythonicprose.blogspot.com/2009/09/python-split-paragraph-into-sentences.html

# split up a paragraph into sentences
# using regular expressions


def splitParagraphIntoSentences(paragraph):
    ''' break a paragraph into sentences
        and return a list '''
    import re
    # to split by multile characters

    #   regular expressions are easiest (and fastest)
    sentenceEnders = re.compile('[.!?]')
    sentenceList = sentenceEnders.split(paragraph)
    return sentenceList


if __name__ == '__main__':
    p = """This is a sentence.  This is an excited sentence! And do you think this is a question?"""

    sentences = splitParagraphIntoSentences(p)
    for s in sentences:
        print s.strip()

#output:
#   This is a sentence
#   This is an excited sentence

#   And do you think this is a question 

мне пришлось читать файлы субтитров и разбивать их на предложения. После предварительной обработки (например, удаление информации о времени и т. д.srt files), переменная fullFile содержала полный текст файла субтитров. Нижеприведенный грубый способ аккуратно разделил их на предложения. Наверное, мне повезло, что предложения всегда заканчивались (правильно) пробелом. Сначала попробуйте это, и если у него есть какие-либо исключения, добавьте больше сдержек и противовесов.

# Very approximate way to split the text into sentences - Break after ? . and !
fullFile = re.sub("(\!|\?|\.) ","\1<BRK>",fullFile)
sentences = fullFile.split("<BRK>");
sentFile = open("./sentences.out", "w+");
for line in sentences:
    sentFile.write (line);
    sentFile.write ("\n");
sentFile.close;

о! что ж. Теперь я понимаю, что с моего контента был испанцем, у меня не было проблем с "мистером Смитом" и т. д. Тем не менее, если кто-то хочет быстрый и грязный парсер...