2017-04-24 63 views
16

Szuka porady, jak kopać przedmioty z wielu plików tekstowych, aby zbudować słownik.Python przetwarza tekst z wielu plików txt

Ten plik tekstowy: https://pastebin.com/Npcp3HCM

został ręcznie przekształcona to wymagane struktury danych: https://drive.google.com/file/d/0B2AJ7rliSQubV0J2Z0d0eXF3bW8/view

Istnieją tysiące takich plików tekstowych i mogą mieć różne nagłówki sekcji, jak pokazano na poniższych przykładach:

  1. https://pastebin.com/wWSPGaLX
  2. https://pastebin.com/9Up4RWHu

Zacząłem poprzez czytanie plików

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]) 

Mój pomysł był szukać numerów linii, gdzie istniały Nagłówki sekcji i spróbować wyodrębnić zawartość między tymi numerami linii, a następnie rozebrać się separatory jak myślniki . To nie zadziałało i utknąłem w próbach stworzenia tego rodzaju słownika, aby później móc uruchomić różne algorytmy przetwarzania języka naturalnego na przedmiotach wydobywanych.

{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]} 
     } 
    } 
} 

Wyzwaniem jest to, że różne pliki mogą mieć różne nagłówki i liczbę nagłówków. Ale zawsze będzie sekcja "Prezentacja" i najprawdopodobniej sekcja "Pytanie i odpowiedź". Te nagłówki sekcji są zawsze oddzielone ciągiem równych znaków. Treść innego głośnika jest zawsze oddzielona ciągiem kresek. "Kolejność mowy" dla Q & Sekcja jest oznaczona liczbą w nawiasach kwadratowych. Uczestnicy są zawsze wskazani na początku dokumentu gwiazdkami przed ich nazwiskiem, a ich kafelek zawsze znajduje się w następnej linii.

Wszelkie sugestie dotyczące przetwarzania plików tekstowych są mile widziane. Idealną pomocą byłoby dostarczenie wskazówek, jak stworzyć taki słownik (lub inną odpowiednią strukturę danych) dla każdego pliku, który można następnie zapisać w bazie danych.

Dzięki

--EDIT--

Jeden z plików wygląda następująco: https://pastebin.com/MSvmHb2e

w którym sekcja „Pytanie & Answer” jest błędnie jako „prezentacja” i tam nie jest inne "Pytanie & Odpowiedź" sekcja.

A końcowy przykładowy tekst: https://pastebin.com/jr9WfpV8

+3

Nie proponujemy, aby przechowywać wszystkie dane tekstowe w jednym 'dict' obiektu, jak już wspomniano, że może istnieć duża liczba plików tekstowych do przeanalizowania, więc w czasie wykonywania proces Pythona wymagałby więcej czasu na aktualizację obiektu 'dict' w miarę wzrostu rozmiaru obiektu' dict' i mógłby uzyskać OutOfMemory, jeśli masz kilka naprawdę dużych plików do przetworzenia, chciałbym postawić na "DBMS" do przechowywania tego rodzaju danych. – ZdaR

+0

@ZdaR dziękuję za poradę. po przeczytaniu twojego komentarza zdecydowałem się pracować z bazą danych. Obecnie zajmuję się sqlalchemy – samkhan13

+0

Mylące oznakowanie nie będzie tak łatwe do rozwiązania. Będziesz musiał zbudować klasyfikator, używając technik ML, które klasyfikują sekcję jako sekcję "Prezentacja" lub "Pytanie i odpowiedź", ponieważ nie ma gwarantowanych wskazówek (żadna ilość rozpoznawania wzorca przy użyciu ręcznie wykonywanych reguł nie spowoduje tego prawo 100%) obecne w tekście. – entrophy

Odpowiedz

8

Komentarze w kodzie powinno wszystko wyjaśnić. Daj mi znać, jeśli coś jest pod określone i potrzebuje więcej komentarzy.

W skrócie używam wyrażenia regularnego, aby znaleźć linie ograniczające "=", aby podzielić cały tekst na podsekcje, a następnie oddzielnie dla każdego rodzaju sekcji, aby uzyskać jasność (dzięki czemu można określić, w jaki sposób obsługuję każdy przypadek).

Uwaga boczna: Używam zamiennie słowa "uczestnik" i "autor".

EDYCJA: Zaktualizowano kod do sortowania na podstawie wzoru "[x]" znalezionego tuż obok uczestnika/autora w sekcji prezentacja/kontrola jakości. Zmieniłem także ładną część druku, ponieważ pprint nie obsługuje bardzo dobrze OrderedDict.

Aby usunąć dodatkowe białe znaki, w tym \n w dowolnym miejscu ciągu, po prostu wykonaj str.strip(). jeśli musisz specjalnie usunąć tylko \n, to po prostu wykonaj str.strip('\n').

Zmodyfikowałem kod, aby usunąć wszelkie spacje w rozmowach.

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)) 
+0

Mogę przyjąć tę odpowiedź, jeśli możesz dołączyć kolejność mowy wraz z mową w 'talks_d'. kolejność słów jest oznaczona nawiasami kwadratowymi. byłoby użyteczne, gdyby talk_d był uporządkowanym słownikiem. – samkhan13

+0

jak usunąć "\ n" z tekstu w talks_d? – samkhan13

+0

zaktualizował odpowiedź, wprowadzając wymagane zmiany. – entrophy

3

Czy możesz potwierdzić, że potrzebujesz tylko sekcji "Prezentacja" i "Pytanie i odpowiedź"? Ponadto, jeśli chodzi o wynik, można zrzucić format CSV podobny do tego, co "ręcznie przekształcono".

Zaktualizowane rozwiązanie do pracy dla każdego dostarczonego pliku próbki.

Dane wyjściowe pochodzą z komórki "D: H" zgodnie z udostępnionym plikiem "przeanalizowanych transkryptów".

#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") 

Szczegóły

w tym roztworze jest stan maszyny, która działa w następujący sposób: 1. wykrywa czy nagłówek jest obecny, jeśli tak, to pisać 2. wykrywa głośniki po nagłówek napisany 3 pisze notatki dla tego głośnika 4. przełącza na następny głośnik i tak dalej ...

Możesz później przetwarzać pliki csv, jak chcesz. Można również wypełnić dane w dowolnym formacie po zakończeniu przetwarzania podstawowego.

Edit:

Proszę zastąpić funkcję „writecell”

def writecell(out, data): 
    data = data.replace('"', '""') 
    out.write('"') 
    out.write(data) 
    out.write('"') 
    out.write(",") 
+0

Twoje podejście jest najbliższe moim wymaganiom. ściśle obsługuje wszystkie przykładowe pliki. ale czasami po nazwie firmy pojawia się śpiączka, która zakłóca strukturę wyjściową. Potrafię zaakceptować odpowiedź, która najlepiej rozwiązuje próbki w sekcji --EDIT-- pytania. – samkhan13

+0

można napisać bezpośrednio do pliku csv lub do dyktatury lub do bazy danych – samkhan13

+0

Witam Mam zaktualizowaną odpowiedź na podstawie opinii użytkowników. Dzięki za opinie. – mangupt