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

3 投票
3 回答
9893 浏览
提问于 2025-04-17 08:09

我正在尝试用Python中的正则表达式从一段文字中提取一句话。
通常我测试的代码能正确提取句子,但在下面这段文字中,句子没有被正确提取出来。

这段文字是:

"但在疟疾感染和脓毒症的情况下,树突状细胞在全身集中精力警告免疫系统,这使得它们无法检测和应对任何新的感染。" 一种新型疫苗?

代码如下:

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

在用上面的段落测试时,输出结果和输入的段落完全一样,但输出应该是这样的:

但在疟疾感染和脓毒症的情况下,树突状细胞在全身集中精力警告免疫系统,这使得它们无法检测和应对任何新的感染

一种新型疫苗

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

3 个回答

0

是的,这里确实有问题。你只在分隔符后面跟着一个或两个空格,然后再跟一个大写字母时才考虑这个分隔符,所以像“A new type of vaccine?”这样的句子结尾就不会被匹配到。

我觉得对于空格的处理也不需要太严格,除非这是你的意图(因为文本可能格式不太好),比如“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 ]
7

Riccardo Murri的回答是对的,不过我想再多说一点。

之前有个类似的问题是关于PHP的:php句子边界检测。我对那个问题的回答中提到了如何处理一些特殊情况,比如“Mr.”、“Mrs.”和“Jr.”。我把那个正则表达式调整了一下,让它可以在Python中使用(因为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 自己出现了..."

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

撰写回答