#pdf #text-parsing
#PDF #синтаксический анализ текста
Вопрос:
У меня есть очень большой текстовый файл, который был создан путем преобразования PDF в текстовый формат с помощью утилиты XPDF ‘pdftotext’. Документ содержит данные о заброшенных свойствах, которые я хотел бы иметь возможность анализировать в формате, удобном для базы данных. Файл отформатирован следующим образом:
DOE JANE H STATE 1994 0002 SAVINGS 52 33.99 0
EMPLOYEES ACCOUNTS
CREDIT UNION
SOMECITY ZZ 12345
В общем, все записи выглядят так. Некоторые из них занимают одну дополнительную строку (с дополнительным пробелом перед строкой города / штата / почтового индекса.
Чтобы сделать это еще лучше, текст не был преобразован равномерно. Каждая страница PDF конвертируется в «страницу» текстового файла с разной шириной столбцов. Кроме того, данные иногда могут выглядеть следующим образом:
DOE JANE H OR SOME OTHER OWNER STATE 1994 0002 SAVINGS 52 33.99 0
EMPLOYEES ACCOUNTS
CREDIT UNION
SOMECITY ZZ 12345
Или, в качестве альтернативы, вот так:
DOE STATE 1994 0002 SAVINGS 52 33.99 0
JANE EMPLOYEES ACCOUNTS
H CREDIT UNION
SOMECITY ZZ 12345
Итак, могу ли я что-нибудь с этим сделать? Приветствуется любая помощь.
Обновить:
Для пояснения — я пытался разобрать это, используя регулярные выражения и Perl, но безуспешно (я не смог найти достойный способ работать с фрагментами по 5 строк за раз или с различными фрагментами, поскольку записи иногда занимают 6 строк). В моей лучшей попытке я смог разобрать только первую строку каждой записи, которая содержит только частичную информацию (часть или все имя, первая строка владельца, отчетный год, тип свойства и т.д.). В идеале я хотел бы иметь возможность получать всю информацию для записи, независимо от различных форматов. Язык программирования не имеет значения, поэтому для этой работы подойдет любой подходящий инструмент. Я надеялся получить информацию о различных вариантах разбора файла, подобного этому, возможно, о разных инструментах или даже приложениях, которые решают подобные проблемы.
Комментарии:
1. «Итак, я могу что-нибудь с этим сделать?» Да. Есть ли что-то более конкретное, что вы хотели бы знать? Вы ищете предложения? Подсказки по программированию? Подсказки по настройке? Что вы действительно хотите делать?
2. Извините, я хотел бы знать, есть ли хороший способ разбить данные на поля. Я пытался использовать Perl и множество регулярных выражений, но мне не везет. Если существуют другие методы или приложения, предназначенные для этой цели, мы были бы признательны за подсказку в правильном направлении.
3. Пожалуйста, обновите вопрос, чтобы точно указать, что вы хотите сделать в качестве выходных данных. Какой язык вы хотите использовать? Что вы пробовали (на каких языках)? Какую ошибку вы получаете? Это помогает быть очень, очень конкретным в том, какова ваша реальная цель. Вопрос «что я могу с этим поделать» настолько расплывчатый, что мы не можем толком ответить на него, кроме как догадываться или спрашивать подробности.
4. Я добавил небольшое обновление — дайте мне знать, если вы считаете, что требуется дополнительная информация.
Ответ №1:
Вот начало работы с Python. Что-то подобное, вероятно, можно сделать в perl.
col_start_pat = re.compile( r's s(?=S)' )
for row in ( r.rstrip() for r in data.splitlines() ):
if not row: continue
def matches( row ):
offset= 0
for m in col_start_pat.finditer( row ):
yield offset, row[offset:m.end(0)].rstrip()
offset= m.end(0)
yield offset, row[offset:].rstrip()
columns= list( matches( row ) )
if not columns: continue
print( columns )
Это не выполняет всю работу. Он просто идентифицирует «поля» в каждой строке как последовательность кортежей, которые выглядят следующим образом.
[(0, 'DOE JANE H'), (50, 'STATE'), (64, '1994'), (73, '0002'), (84, 'SAVINGS'), (98, '52'), (112, '33.99'), (120, '0')]
[(0, ''), (50, 'EMPLOYEES'), (84, 'ACCOUNTS')]
[(0, ''), (50, 'CREDIT UNION')]
[(0, ''), (17, 'SOMECITY'), (31, 'ZZ'), (39, '12345')]
[(0, 'DOE JANE H'), (16, 'OR SOME OTHER OWNER'), (46, 'STATE'), (60, '1994'), (69, '0002'), (80, 'SAVINGS'), (94, '52'), (108, '33.99'), (116, '0')]
[(0, ''), (46, 'EMPLOYEES'), (80, 'ACCOUNTS')]
[(0, ''), (46, 'CREDIT UNION')]
[(0, ''), (16, 'SOMECITY'), (30, 'ZZ'), (38, '12345')]
[(0, 'DOE'), (41, 'STATE'), (55, '1994'), (64, '0002'), (75, 'SAVINGS'), (89, '52'), (103, '33.99'), (111, '0')]
[(0, 'JANE'), (41, 'EMPLOYEES'), (75, 'ACCOUNTS')]
[(0, 'H'), (41, 'CREDIT UNION')]
[(0, ''), (12, 'SOMECITY'), (26, 'ZZ'), (34, '12345')]
Следующая часть заключается в использовании номеров столбцов заголовка для определения формата, а затем свертывании строк в единый объект.
Продолжая об этом…
pat1= [0, 50, 64, 73, 84, 98, 112, 120]
pat2= [0, 16, 46, 60, 69, 80, 94, 108, 116]
pat3= [0, 41, 55, 64, 75, 89, 103, 111]
def columns( data ):
col_start_pat = re.compile( r's s(?=S)' )
for row in ( r.rstrip() for r in data.splitlines() ):
if not row: continue
def matches( row ):
offset= 0
for m in col_start_pat.finditer( row ):
yield offset, row[offset:m.end(0)].rstrip()
offset= m.end(0)
yield offset, row[offset:].rstrip()
columns= list( matches( row ) )
yield columns
def row_groups( data ):
columns_iter= columns(data)
record= []
for parsed in columns_iter:
offsets= [ c[0] for c in parsed ]
if offsets == pat1:
if record: yield( format, record )
format= 1
record= []
elif offsets == pat2:
if record: yield( format, record )
format= 2
record= []
elif offsets == pat3:
if record: yield( format, record )
format= 3
record= []
record.extend( parsed )
yield( format, record )
for record in row_groups( data ):
print( record )
Позволяет получить следующее:
(1, [(0, 'DOE JANE H'), (50, 'STATE'), (64, '1994'), (73, '0002'), (84, 'SAVINGS'), (98, '52'), (112, '33.99'), (120, '0'), (0, ''), (50, 'EMPLOYEES'), (84, 'ACCOUNTS'), (0, ''), (50, 'CREDIT UNION'), (0, ''), (17, 'SOMECITY'), (31, 'ZZ'), (39, '12345')])
(2, [(0, 'DOE JANE H'), (16, 'OR SOME OTHER OWNER'), (46, 'STATE'), (60, '1994'), (69, '0002'), (80, 'SAVINGS'), (94, '52'), (108, '33.99'), (116, '0'), (0, ''), (46, 'EMPLOYEES'), (80, 'ACCOUNTS'), (0, ''), (46, 'CREDIT UNION'), (0, ''), (16, 'SOMECITY'), (30, 'ZZ'), (38, '12345')])
(3, [(0, 'DOE'), (41, 'STATE'), (55, '1994'), (64, '0002'), (75, 'SAVINGS'), (89, '52'), (103, '33.99'), (111, '0'), (0, 'JANE'), (41, 'EMPLOYEES'), (75, 'ACCOUNTS'), (0, 'H'), (41, 'CREDIT UNION'), (0, ''), (12, 'SOMECITY'), (26, 'ZZ'), (34, '12345')])
В котором есть вся информация; ему просто нужна какая-то умная сборка.
Комментарии:
1. Спасибо, мне придется немного повозиться с этим, чтобы разобраться с некоторыми другими странностями в файле (в основном без учета заголовков), но я думаю, что это будет работать просто отлично.