在Python中解析非结构化文本

时间:2009-09-14 04:15:32

标签: python parsing text

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

. 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”的行是性别,DOB是“F”后面的行,DOB后面的名字,名称后面的ID,和没有。 ID下面的12是索引/记录号。

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

我想将文本重写为以下格式:

name, ID, address, sex, DOB

5 个答案:

答案 0 :(得分:15)

这是对pyparsing解决方案(easy-to-copy code at the pyparsing pastebin)的第一次尝试。根据交错的评论,浏览各个部分。

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

答案 1 :(得分:4)

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

我建议你一次读一行并将其与正则表达式匹配以确定其类型,填写人物对象中的相应字段。每当你得到一个已填写的字段时,写出该对象并开始一个新对象。

答案 2 :(得分:3)

这可能有些过分,但针对此类问题的前沿机器学习算法基于conditional random fields。例如,Accurate Information Extraction from Research Papers using Conditional Random Fields

有软件可以让这些模型相对容易。请参阅MalletCRF++

答案 3 :(得分:2)

您可以使用正则表达式执行此操作而不会有太多困难。如果您以前从未使用过它们,请查看python文档,然后启动redemo.py(在我的计算机上,它位于c:\ python26 \ Tools \ scripts中)。

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

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中,并尝试创建匹配所需的模式。你马上就可以解析了。一旦掌握了这一点,就可以使用符号组名称(请参阅文档)快速,干净地选择单个元素。

答案 4 :(得分:1)

这是一个快速的黑客工作。

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)