【问题标题】:Parsing unstructured text in Python在 Python 中解析非结构化文本
【发布时间】:2009-09-14 04:15:31
【问题描述】:

我想解析一个包含非结构化文本的文本文件。我需要获取地址、出生日期、姓名、性别和身份证。

. 55 MORILLO ZONE VIII,
BARANGAY ZONE VIII
(POB.), LUISIANA, LAGROS
F
01/16/1952
ALOMO, TERESITA CABALLES
3412-00000-A1652TCA2
12    
. 22 FABRICANTE ST. ZONE
VIII LUISIANA LAGROS,
BARANGAY ZONE VIII
(POB.), LUISIANA, LAGROS
M
10/14/1967
AMURAO, CALIXTO MANALO13

在上面的例子中,前 3 行是地址,只有一个“F”的行是性别,出生日期是“F”后面的行,出生日期后面的名字,名字后面的 ID,和没有。 ID下的12是索引/记录号。

但是,格式不一致。在第二组中,地址是 4 行而不是 3 行,索引/记录号。附加在姓名之后(如果此人没有 ID 字段)。

我想把文字改写成以下格式:

name, ID, address, sex, DOB

【问题讨论】:

    标签: python parsing text


    【解决方案1】:

    这是 pyparsing 解决方案的第一次尝试 (easy-to-copy code at the pyparsing pastebin)。根据交错的 cmets 遍历各个部分。

    data = """\
    . 55 MORILLO ZONE VIII,
    BARANGAY ZONE VIII
    (POB.), LUISIANA, LAGROS
    F
    01/16/1952
    ALOMO, TERESITA CABALLES
    3412-00000-A1652TCA2
    12
    . 22 FABRICANTE ST. ZONE
    VIII LUISIANA LAGROS,
    BARANGAY ZONE VIII
    (POB.), LUISIANA, LAGROS
    M
    10/14/1967
    AMURAO, CALIXTO MANALO13
    """
    
    from pyparsing import LineEnd, oneOf, Word, nums, Combine, restOfLine, \
        alphanums, Suppress, empty, originalTextFor, OneOrMore, alphas, \
        Group, ZeroOrMore
    
    NL = LineEnd().suppress()
    gender = oneOf("M F")
    integer = Word(nums)
    date = Combine(integer + '/' + integer + '/' + integer)
    
    # define the simple line definitions
    gender_line = gender("sex") + NL
    dob_line = date("DOB") + NL
    name_line = restOfLine("name") + NL
    id_line = Word(alphanums+"-")("ID") + NL
    recnum_line = integer("recnum") + NL
    
    # define forms of address lines
    first_addr_line = Suppress('.') + empty + restOfLine + NL
    # a subsequent address line is any line that is not a gender definition
    subsq_addr_line = ~(gender_line) + restOfLine + NL
    
    # a line with a name and a recnum combined, if there is no ID
    name_recnum_line = originalTextFor(OneOrMore(Word(alphas+',')))("name") + \
        integer("recnum") + NL
    
    # defining the form of an overall record, either with or without an ID
    record = Group((first_addr_line + ZeroOrMore(subsq_addr_line))("address") + 
        gender_line + 
        dob_line +
        ((name_line +
            id_line + 
            recnum_line) |
          name_recnum_line))
    
    # parse data
    records = OneOrMore(record).parseString(data)
    
    # output the desired results (note that address is actually a list of lines)
    for rec in records:
        if rec.ID:
            print "%(name)s, %(ID)s, %(address)s, %(sex)s, %(DOB)s" % rec
        else:
            print "%(name)s, , %(address)s, %(sex)s, %(DOB)s" % rec
    print
    
    # how to access the individual fields of the parsed record
    for rec in records:
        print rec.dump()
        print rec.name, 'is', rec.sex
        print
    

    打印:

    ALOMO, TERESITA CABALLES, 3412-00000-A1652TCA2, ['55 MORILLO ZONE VIII,', 'BARANGAY ZONE VIII', '(POB.), LUISIANA, LAGROS'], F, 01/16/1952
    AMURAO, CALIXTO MANALO, , ['22 FABRICANTE ST. ZONE', 'VIII LUISIANA LAGROS,', 'BARANGAY ZONE VIII', '(POB.), LUISIANA, LAGROS'], M, 10/14/1967
    
    ['55 MORILLO ZONE VIII,', 'BARANGAY ZONE VIII', '(POB.), LUISIANA, LAGROS', 'F', '01/16/1952', 'ALOMO, TERESITA CABALLES', '3412-00000-A1652TCA2', '12']
    - DOB: 01/16/1952
    - ID: 3412-00000-A1652TCA2
    - address: ['55 MORILLO ZONE VIII,', 'BARANGAY ZONE VIII', '(POB.), LUISIANA, LAGROS']
    - name: ALOMO, TERESITA CABALLES
    - recnum: 12
    - sex: F
    ALOMO, TERESITA CABALLES is F
    
    ['22 FABRICANTE ST. ZONE', 'VIII LUISIANA LAGROS,', 'BARANGAY ZONE VIII', '(POB.), LUISIANA, LAGROS', 'M', '10/14/1967', 'AMURAO, CALIXTO MANALO', '13']
    - DOB: 10/14/1967
    - address: ['22 FABRICANTE ST. ZONE', 'VIII LUISIANA LAGROS,', 'BARANGAY ZONE VIII', '(POB.), LUISIANA, LAGROS']
    - name: AMURAO, CALIXTO MANALO
    - recnum: 13
    - sex: M
    AMURAO, CALIXTO MANALO is M
    

    【讨论】:

    • 嗨,保罗。感谢您的解决方案!我有一些以“# | *”开头的行,我想忽略/跳过。我该怎么做?
    • 您的意思是它们以字符串“# | *”开头吗?或者他们从这些字符中的任何一个开始?如果是第一个,将注释定义为 comment = "# | *" + restOfLine + NL;如果第二个将评论定义为 comment = oneOf("# | *") + restOfLine + NL。然后执行:record.ignore(comment) - easy-peasy!
    【解决方案2】:

    您必须利用文本所具有的任何规律性和结构。

    我建议您一次读取一行并将其与正则表达式匹配以确定其类型,并在 person 对象中填写适当的字段。写出该对象并在获得已填写的字段时开始一个新对象。

    【讨论】:

      【解决方案3】:

      这可能有点矫枉过正,但这类问题的前沿机器学习算法是基于conditional random fields。例如,Accurate Information Extraction from Research Papers using Conditional Random Fields

      有一些软件可以相对轻松地训练这些模型。请参阅MalletCRF++

      【讨论】:

        【解决方案4】:

        您可以使用正则表达式轻松做到这一点。如果您以前从未使用过它们,请查看 python 文档,然后启动 redemo.py(在我的计算机上,它位于 c:\python26\Tools\scripts 中)。

        第一个任务是将平面文件拆分为实体列表(每条记录一个文本块)。从您提供的文本的 sn-p 中,您可以使用与行首匹配的模式拆分文件,其中第一个字符是一个点:

        import re
        re_entity_splitter = re.compile(r'^\.')
        
        entities = re_entity_splitter.split(open(textfile).read())
        

        请注意,点必须被转义(默认为通配符)。还要注意模式前的 r。 r 表示“原始字符串”格式,这使您不必转义转义字符,从而导致所谓的“反斜杠瘟疫”。

        将文件拆分为个人后,选择性别和出生日期是一件轻而易举的事。使用这些:

        re_gender     = re.compile(r'^[MF]')
        re_birth_Date = re.compile(r'\d\d/\d\d/\d\d')
        

        你走了。您可以将平面文件粘贴到重新演示 GUI 中,并尝试创建匹配您需要的模式。您将立即对其进行解析。一旦熟练掌握了这一点,您就可以使用符号组名称(请参阅文档)快速、干净地挑选出单个元素。

        【讨论】:

        • 谢谢。我已经有一些使用正则表达式的经验。我如何处理地址部分?有些实体有 3 或 4 行。
        • 一旦您将文件拆分为人员列表,对于每个人,我会尝试以下操作:1. 将人员的文本拆分为行列表 2. 对于每个人员列表,而最后一项列表与 re_gender 不匹配,将项目从列表末尾弹出。 3. 剩下的列表项是地址。 lst = person.splitlines() 而不是 re_gender.search(lst[-1].strip()): lst.pop() lst.pop() address_list = lst
        【解决方案5】:

        这是一个快速的 hack 工作。

        f = open('data.txt')
        
        def process(file):
            address = ""
        
            for line in file:
                if line == '': raise StopIteration
                line = line.rstrip() # to ignore \n
                if line in ('M','F'):
                    sex = line
                    break
                else:
                    address += line
        
            DOB = file.readline().rstrip() # to ignore \n
            name = file.readline().rstrip()
        
            if name[-1].isdigit():
                name = re.match(r'^([^\d]+)\d+', name).group(1)
                ID = None
            else:
                ID = file.readline().rstrip()
                file.readline() # ignore the record #
        
            print (name, ID, address, sex, DOB)
        
        while True:
            process(f)
        

        【讨论】:

        • 在 DOB = file.readline().rstrip() 行失败并出现错误 ValueError:混合迭代和读取方法会丢失数据
        • @Francis:在这种情况下,只需将 for 循环转换为 while 循环并使用 file.readline().rstrip()。
        猜你喜欢
        • 1970-01-01
        • 2015-08-26
        • 1970-01-01
        • 1970-01-01
        • 2017-12-19
        • 1970-01-01
        • 1970-01-01
        • 2020-07-03
        • 2023-03-16
        相关资源
        最近更新 更多