Python analiza el texto de varios archivos txt
parsing dictionary (2)
¿Podría confirmar que solo necesita las secciones "Presentación" y "Preguntas y respuestas"? Además, con respecto a la salida, está bien volcar un formato CSV similar al que ha "transformado manualmente".
Solución actualizada para que funcione con cada archivo de muestra que proporcionó.
La salida es de la celda "D: H" según el archivo "Transcripción analizada" compartido.
#state = ["other", "head", "present", "qa", "speaker", "data"]
# codes : 0, 1, 2, 3, 4, 5
def writecell(out, data):
out.write(data)
out.write(",")
def readfile(fname, outname):
initstate = 0
f = open(fname, "r")
out = open(outname, "w")
head = ""
head_written = 0
quotes = 0
had_speaker = 0
for line in f:
line = line.strip()
if not line: continue
if initstate in [0,5] and not any([s for s in line if "=" != s]):
if initstate == 5:
out.write(''"'')
quotes = 0
out.write("/n")
initstate = 1
elif initstate in [0,5] and not any([s for s in line if "-" != s]):
if initstate == 5:
out.write(''"'')
quotes = 0
out.write("/n")
initstate = 4
elif initstate == 1 and line == "Presentation":
initstate = 2
head = "Presentation"
head_written = 0
elif initstate == 1 and line == "Questions and Answers":
initstate = 3
head = "Questions and Answers"
head_written = 0
elif initstate == 1 and not any([s for s in line if "=" != s]):
initstate = 0
elif initstate in [2, 3] and not any([s for s in line if ("=" != s and "-" != s)]):
initstate = 4
elif initstate == 4 and ''['' in line and '']'' in line:
comma = line.find('','')
speech_st = line.find(''['')
speech_end = line.find('']'')
if speech_st == -1:
initstate = 0
continue
if comma == -1:
firm = ""
speaker = line[:speech_st].strip()
else:
speaker = line[:comma].strip()
firm = line[comma+1:speech_st].strip()
head_written = 1
if head_written:
writecell(out, head)
head_written = 0
order = line[speech_st+1:speech_end]
writecell(out, speaker)
writecell(out, firm)
writecell(out, order)
had_speaker = 1
elif initstate == 4 and not any([s for s in line if ("=" != s and "-" != s)]):
if had_speaker:
initstate = 5
out.write(''"'')
quotes = 1
had_speaker = 0
elif initstate == 5:
line = line.replace(''"'', ''""'')
out.write(line)
elif initstate == 0:
continue
else:
continue
f.close()
if quotes:
out.write(''"'')
out.close()
readfile("Sample1.txt", "out1.csv")
readfile("Sample2.txt", "out2.csv")
readfile("Sample3.txt", "out3.csv")
Detalles
en esta solución hay una máquina de estados que funciona de la siguiente manera: 1. detecta si el encabezado está presente, si es así, escríbalo 2. detecta los altavoces después de escribir el encabezado 3. escribe notas para ese hablante 4. cambia al siguiente orador y así sucesivamente ...
Más tarde puede procesar los archivos csv que desee. También puede llenar los datos en cualquier formato que desee una vez que se realiza el procesamiento básico.
Editar:
Por favor reemplaza la función "writecell"
def writecell(out, data):
data = data.replace(''"'', ''""'')
out.write(''"'')
out.write(data)
out.write(''"'')
out.write(",")
Buscando consejos sobre cómo extraer elementos de varios archivos de texto para crear un diccionario.
Este archivo de texto: https://pastebin.com/Npcp3HCM
Se transformó manualmente en esta estructura de datos requerida: https://drive.google.com/file/d/0B2AJ7rliSQubV0J2Z0d0eXF3bW8/view
Hay miles de estos archivos de texto y pueden tener diferentes encabezados de sección como se muestra en estos ejemplos:
Comencé leyendo los archivos
from glob import glob
txtPth = ''../tr-txt/*.txt''
txtFiles = glob(txtPth)
with open(txtFiles[0],''r'') as tf:
allLines = [line.rstrip() for line in tf]
sectionHeading = [''Corporate Participants'',
''Conference Call Participiants'',
''Presentation'',
''Questions and Answers'']
for lineNum, line in enumerate(allLines):
if line in sectionHeading:
print(lineNum,allLines[lineNum])
Mi idea fue buscar los números de línea donde existían los encabezados de las secciones e intentar extraer el contenido entre esos números de línea, luego eliminar los separadores como guiones. Eso no funcionó y me quedé atascado tratando de crear un diccionario de este tipo para poder luego ejecutar varios algoritmos de procesamiento de lenguaje natural en artículos de cantera.
{file-name1:{
{date-time:[string]},
{corporate-name:[string]},
{corporate-participants:[name1,name2,name3]},
{call-participants:[name4,name5]},
{section-headings:{
{heading1:[
{name1:[speechOrderNum, text-content]},
{name2:[speechOrderNum, text-content]},
{name3:[speechOrderNum, text-content]}],
{heading2:[
{name1:[speechOrderNum, text-content]},
{name2:[speechOrderNum, text-content]},
{name3:[speechOrderNum, text-content]},
{name2:[speechOrderNum, text-content]},
{name1:[speechOrderNum, text-content]},
{name4:[speechOrderNum, text-content]}],
{heading3:[text-content]},
{heading4:[text-content]}
}
}
}
El desafío es que los diferentes archivos pueden tener diferentes encabezados y número de encabezados. Pero siempre habrá una sección llamada "Presentación" y es muy probable que tenga una sección de "Preguntas y respuestas". Estos encabezados de sección siempre están separados por una cadena de signos igual a. Y el contenido de los diferentes oradores siempre está separado por una cadena de guiones. El "orden de voz" para la sección de Preguntas y Respuestas se indica con un número entre corchetes. Los participantes siempre se indican al principio del documento con un asterisco antes de su nombre y su mosaico siempre está en la línea siguiente.
Cualquier sugerencia sobre cómo analizar los archivos de texto es apreciada. La ayuda ideal sería proporcionar orientación sobre cómo producir dicho diccionario (u otra estructura de datos adecuada) para cada archivo que luego se puede escribir en una base de datos.
Gracias
--EDITAR--
Uno de los archivos tiene este aspecto: https://pastebin.com/MSvmHb2e
En la que la sección "Preguntas y respuestas" está etiquetada incorrectamente como "Presentación" y no hay otra sección "Preguntas y respuestas".
Y el texto final de la muestra: https://pastebin.com/jr9WfpV8
Los comentarios en el código deben explicarlo todo. Déjame saber si algo está por debajo de, y necesita más comentarios.
En resumen, aprovecho la expresión regular para encontrar las líneas delimitadoras ''='' para subdividir todo el texto en subsecciones y luego manejar cada tipo de secciones por separado (para que pueda saber cómo estoy manejando cada caso).
Nota al margen: estoy usando la palabra "asistente" y "autor" indistintamente.
EDITAR: Se actualizó el código para clasificar según el patrón ''[x]'' que se encuentra justo al lado del asistente / autor en la sección de presentación / control de calidad. También cambió la parte de impresión bonita ya que pprint no maneja OrderedDict muy bien.
Para eliminar cualquier espacio en blanco adicional, incluido /n
en cualquier lugar de la cadena, simplemente haga str.strip()
. Si específicamente necesita quitar solo /n
, entonces simplemente haga str.strip(''/n'')
.
He modificado el código para eliminar cualquier espacio en blanco en las conversaciones.
import json
import re
from collections import OrderedDict
from pprint import pprint
# Subdivides a collection of lines based on the delimiting regular expression.
# >>> example_string ='' =============================
# asdfasdfasdf
# sdfasdfdfsdfsdf
# =============================
# asdfsdfasdfasd
# =============================
# >>> subdivide(example_string, "^=+")
# >>> [''asdfasdfasdf/nsdfasdfdfsdfsdf/n'', ''asdfsdfasdfasd/n'']
def subdivide(lines, regex):
equ_pattern = re.compile(regex, re.MULTILINE)
sections = equ_pattern.split(lines)
sections = [section.strip(''/n'') for section in sections]
return sections
# for processing sections with dashes in them, returns the heading of the section along with
# a dictionary where each key is the subsection''s header, and each value is the text in the subsection.
def process_dashed_sections(section):
subsections = subdivide(section, "^-+")
heading = subsections[0] # header of the section.
d = {key: value for key, value in zip(subsections[1::2], subsections[2::2])}
index_pattern = re.compile("/[(.+)/]", re.MULTILINE)
# sort the dictionary by first capturing the pattern ''[x]'' and extracting ''x'' number.
# Then this is passed as a compare function to ''sorted'' to sort based on ''x''.
def cmp(d):
mat = index_pattern.findall(d[0])
if mat:
print(mat[0])
return int(mat[0])
# There are issues when dealing with subsections containing ''-''s but not containing ''[x]'' pattern.
# This is just to deal with that small issue.
else:
return 0
o_d = OrderedDict(sorted(d.items(), key=cmp))
return heading, o_d
# this is to rename the keys of ''d'' dictionary to the proper names present in the attendees.
# it searches for the best match for the key in the ''attendees'' list, and replaces the corresponding key.
# >>> d = {''mr. man ceo of company [1]'' : '' This is talk a'' ,
# ... ''ms. woman ceo of company [2]'' : '' This is talk b''}
# >>> l = [''mr. man'', ''ms. woman'']
# >>> new_d = assign_attendee(d, l)
# new_d = {''mr. man'': ''This is talk a'', ''ms. woman'': ''This is talk b''}
def assign_attendee(d, attendees):
new_d = OrderedDict()
for key, value in d.items():
a = [a for a in attendees if a in key]
if len(a) == 1:
# to strip out any additional whitespace anywhere in the text including ''/n''.
new_d[a[0]] = value.strip()
elif len(a) == 0:
# to strip out any additional whitespace anywhere in the text including ''/n''.
new_d[key] = value.strip()
return new_d
if __name__ == ''__main__'':
with open(''input.txt'', ''r'') as input:
lines = input.read()
# regex pattern for matching headers of each section
header_pattern = re.compile("^.*[^/n]", re.MULTILINE)
# regex pattern for matching the sections that contains
# the list of attendee''s (those that start with asterisks )
ppl_pattern = re.compile("^(/s+/*)(.+)(/s.*)", re.MULTILINE)
# regex pattern for matching sections with subsections in them.
dash_pattern = re.compile("^-+", re.MULTILINE)
ppl_d = dict()
talks_d = dict()
# Step1. Divide the the entire document into sections using the ''='' divider
sections = subdivide(lines, "^=+")
header = []
print(sections)
# Step2. Handle each section like a switch case
for section in sections:
# Handle headers
if len(section.split(''/n'')) == 1: # likely to match only a header (assuming )
header = header_pattern.match(section).string
# Handle attendees/authors
elif ppl_pattern.match(section):
ppls = ppl_pattern.findall(section)
d = {key.strip(): value.strip() for (_, key, value) in ppls}
ppl_d.update(d)
# assuming that if the previous section was detected as a header, then this section will relate
# to that header
if header:
talks_d.update({header: ppl_d})
# Handle subsections
elif dash_pattern.findall(section):
heading, d = process_dashed_sections(section)
talks_d.update({heading: d})
# Else its just some random text.
else:
# assuming that if the previous section was detected as a header, then this section will relate
# to that header
if header:
talks_d.update({header: section})
#pprint(talks_d)
# To assign the talks material to the appropriate attendee/author. Still works if no match found.
for key, value in talks_d.items():
talks_d[key] = assign_attendee(value, ppl_d.keys())
# ordered dict does not pretty print using ''pprint''. So a small hack to make use of json output to pretty print.
print(json.dumps(talks_d, indent=4))