用Python提取段落中句子的正则表达式

3
我正在尝试使用Python中的正则表达式从段落中提取一句话。通常我测试的代码可以正确地提取句子,但在以下段落中,该句子未被正确提取。
段落内容:
“But in the case of malaria infections and sepsis, dendritic cells throughout the body are concentrated on alerting the immune system, which prevents them from detecting and responding to any new infections.” A new type of vaccine?
代码如下:
def splitParagraphIntoSentences(paragraph):

import re

sentenceEnders = re.compile('[.!?][\s]{1,2}(?=[A-Z])')
sentenceList = sentenceEnders.split(paragraph)
return sentenceList
if __name__ == '__main__':
    f = open("bs.txt", 'r')
    text = f.read()
    mylist = []
    sentences = splitParagraphIntoSentences(text)
    for s in sentences:
        mylist.append(s.strip())
        for i in mylist:
            print i

当使用上述段落进行测试时,输出结果与输入段落完全相同,但输出应该如下所示-

但在疟疾感染和败血症的情况下,全身的树突状细胞都集中在警觉免疫系统,这会使它们无法检测和应对任何新的感染

一种新型疫苗

正则表达式有什么问题吗?


2
至少正确缩进代码... - rubik
3个回答

7
Riccardo Murri的回答是正确的,但我想在这个问题上再多讲一点。
有一个类似的关于PHP的问题:php sentence boundaries detection。我对那个问题的回答包括处理诸如“Mr.”,“Mrs.”和“Jr.”等异常情况。我改编了那个正则表达式以适用于Python(其对后行限制更严格)。以下是使用此新正则表达式修改和测试过的脚本版本:
def splitParagraphIntoSentences(paragraph):
    import re
    sentenceEnders = re.compile(r"""
        # Split sentences on whitespace between them.
        (?:               # Group for two positive lookbehinds.
          (?<=[.!?])      # Either an end of sentence punct,
        | (?<=[.!?]['"])  # or end of sentence punct and quote.
        )                 # End group of two positive lookbehinds.
        (?<!  Mr\.   )    # Don't end sentence on "Mr."
        (?<!  Mrs\.  )    # Don't end sentence on "Mrs."
        (?<!  Jr\.   )    # Don't end sentence on "Jr."
        (?<!  Dr\.   )    # Don't end sentence on "Dr."
        (?<!  Prof\. )    # Don't end sentence on "Prof."
        (?<!  Sr\.   )    # Don't end sentence on "Sr."
        \s+               # Split on whitespace between sentences.
        """, 
        re.IGNORECASE | re.VERBOSE)
    sentenceList = sentenceEnders.split(paragraph)
    return sentenceList

if __name__ == '__main__':
    f = open("bs.txt", 'r')
    text = f.read()
    mylist = []
    sentences = splitParagraphIntoSentences(text)
    for s in sentences:
        mylist.append(s.strip())
    for i in mylist:
        print i

您可以看到它如何处理特殊情况,并且根据需要很容易添加或删除它们。它可以正确解析您的示例段落。它还可以正确解析以下测试段落(其中包括更多特殊情况):

这是第一句话。第二个句子!第三个句子?第四个句子。第五个句子!第六个句子?第七个句子。第八个句子!琼斯博士说:“史密斯夫人,您有一个可爱的女儿!”

但请注意,Riccardo Murri正确指出了可能会失败的其他异常情况。

3

您所发布的示例段落中,第一句话被双引号"包围,而结束引号紧接着句号出现:infections."

您的正则表达式[.!?]\s{1,2}是在查找一个句号后跟着一个或两个空格作为句子终止符,因此它无法捕获到这种情况。

可以通过允许可选的结束引号来调整它以处理这种情况:

sentenceEnders = re.compile(r'''[.!?]['"]?\s{1,2}(?=[A-Z])''')

然而,使用上述正则表达式会从句子中删除末尾的引号。保留它稍微有点棘手,可以使用后顾断言来实现:

sentenceEnders = re.compile(r'''(?<=[.!?]['"\s])\s*(?=[A-Z])''')

请注意,基于正则表达式的分割器存在许多失败的情况,例如:

  • 缩写:"在Dr. A. B. Givental的作品中..." -- 根据您的正则表达式,这将在"Dr.""A.""B."之后被错误地分割(您可以调整单个字母的情况,但除非硬编码,否则无法检测到缩写)。

  • 在句子中使用感叹号:"... 当然,M. Deshayes本人出现了!"

  • 使用多个引号和嵌套引号等。


谢谢。你能给我一点建议,关于处理你提到的特殊情况时使用什么方法或过程吗?一个小提示会很有帮助。 - martan
@martan 你可以看一下PERL模块Text::SentenceLingua::EN::Sentence的实现,但我的观点是,无论你的正则表达式变得多么复杂,总会有一些特殊情况需要考虑。 - Riccardo Murri

0

是的,有些问题。只有在分隔符后面跟着一个或两个空格和一个大写字母时,才会考虑分隔符,因此,“一种新型疫苗?”句子的结尾就不会被匹配。

我也不会对空格太严格要求,除非这是一种意图(文本可能格式不良),因为例如“Hello Lucky Boy!How are you today?”将不会被拆分。

我也不理解你的例子,为什么只有第一句话用引号括起来?

无论如何:

>>> Text="""But in the case of malaria infections, dendritic cells and stuff.
            A new type of vaccine? My uncle!
         """
>>> Sentences = re.split('[?!.][\s]*',Text)
>>> Sentences
    ['But in the case of malaria infections, dendritic cells and stuff',
     'A new type of vaccine',
     'My uncle',
     '']

你也可以过滤掉空的句子:

>>> NonemptyS = [ s for s in Senteces if s ]

无论实际的正则表达式是什么,段落的尾部部分将始终包含在re.split的返回值中。 - Riccardo Murri

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接