2010-07-15 21 views
4

Я очень новичок в Python и хочу использовать его для разбора текстового файла. Файл имеет между 250-300 строки следующего формата:Разбор текстовых файлов с использованием Python

---- Mark Grey ([email protected]) changed status from Busy to Available @ 14/07/2010 16:32:36 ---- 
---- Silvia Pablo ([email protected]) became Available @ 14/07/2010 16:32:39 ---- 

Мне нужно хранить следующую информацию в другой файл (Excel или текст) для всех записей из этого файла

UserName/ID Previous Status New Status Date Time 

Так мой результат файл должен выглядеть следующим образом для выше entried

Mark Grey/[email protected] Busy Available 14/07/2010 16:32:36 
Silvia Pablo/[email protected] NaN Available 14/07/2010 16:32:39 

заранее спасибо,

Любая помощь была бы действительно оценена

+1

NaN ........... –

+0

Ну, это не число хорошо :) –

+1

EDIT Примечание: Марсело и Тим дал вам довольно хороший ответ на то, что вы хотите сделать. Вот документация для библиотеки регулярных выражений, включенная в Python, которая может помочь вам расширить код: http://docs.python.org/library/re.html –

ответ

1

Ну, если бы я подошел к этой проблеме, вероятно, я бы начал с разбивки каждой записи на свою отдельную строку. Похоже, что это может быть ориентировано на линию, поэтому inputfile.split('\n'), вероятно, адекватен. Оттуда я, вероятно, создаю регулярное выражение для соответствия каждому из возможных изменений состояния, причем подгруппы будут обертывать каждое из важных полей.

6
import re 

pat = re.compile(r"----\s+(.*?) \((.*?)\) (?:changed status from (\w+) to|became) (\w+) @ (.*?) ----\s*") 
with open("data.txt") as f: 
    for line in f: 
     (name, email, prev, curr, date) = pat.match(line).groups() 
     print "{0}/{1} {2} {3} {4}".format(name, email, prev or "NaN", curr, date) 

Это делает предположения о пробеле, а также предполагает, что каждая строка соответствует шаблону. Возможно, вы захотите добавить проверку ошибок (например, проверить, что pat.match() не возвращает None), если вы хотите грамотно обрабатывать грязный вход.

15

Для начала:

result = [] 
regex = re.compile(
    r"""^-*\s+ 
    (?P<name>.*?)\s+ 
    \((?P<email>.*?)\)\s+ 
    (?:changed\s+status\s+from\s+(?P<previous>.*?)\s+to|became)\s+ 
    (?P<new>.*?)\[email protected]\s+ 
    (?P<date>\S+)\s+ 
    (?P<time>\S+)\s+ 
    -*$""", re.VERBOSE) 
with open("inputfile") as f: 
    for line in f: 
     match = regex.match(line) 
     if match: 
      result.append([ 
       match.group("name"), 
       match.group("email"), 
       match.group("previous") 
       # etc. 
      ]) 
     else: 
      # Match attempt failed 

получит вам множество частей матча. Я бы предложил вам использовать csv module для хранения результатов в стандартном формате.

6

Два RE модели интереса, как представляется, ...:

p1 = r'^---- ([^(]+) \(([^)]+)\) changed status from (\w+) to (\w+) (\S+) (\S+) ----$' 
p2 = r'^---- ([^(]+) \(([^)]+)\) became (\w+) (\S+) (\S+) ----$' 

, так что я хотел бы сделать:

import csv, re, sys 

# assign p1, p2 as above (or enhance them, etc etc) 

r1 = re.compile(p1) 
r2 = re.compile(p2) 
data = [] 

with open('somefile.txt') as f: 
    for line in f: 
     m = p1.match(line) 
     if m: 
      data.append(m.groups()) 
      continue 
     m = p2.match(line) 
     if not m: 
      print>>sys.stderr, "No match for line: %r" % line 
      continue 
     listofgroups = m.groups() 
     listofgroups.insert(2, 'NaN') 
     data.append(listofgroups) 

with open('result.csv', 'w') as f: 
    w = csv.writer(f) 
    w.writerow('UserName/ID Previous Status New Status Date Time'.split()) 
    w.writerows(data) 

Если две моделей я описал, не достаточно общие, они могут нужно, конечно, изменить, но я думаю, что этот общий подход будет полезен. Хотя многие пользователи Python в Stack Overflow сильно не любят REs, я нахожу их очень полезными для такого рода прагматичной специальной обработки текста.

Может быть, нелюбовь объясняется другими желающими использовать УЭ для нелепых применений, таких как специальный разбор CSV, HTML, XML, ... - и многих других видов структурированных текстовых форматов, для которых совершенно исправных парсеров существовать! А также другие задачи, выходящие за пределы зоны комфорта REES, и требующие вместо этого надежных общих парсерных систем, таких как pyparsing. Или в других экстремальных суперпростых задачах, выполненных отлично с простыми строками (например, я помню недавний вопрос SO, который использовал if re.search('something', s): вместо if 'something' in s:! -).

Но для разумно широкого круга задач (исключая самые простейшие на одном конце и синтаксический анализ структурированных или несколько сложных грамматик в другом), для которых подходят RE, нет ничего плохого в их использовании, и я рекомендую всем программистам изучить основы REs.

4

Алекс упоминал Pyparsing и так вот Pyparsing подход к вашей же проблема:

from pyparsing import Word, Suppress, Regex, oneOf, SkipTo 
import datetime 

DASHES = Word('-').suppress() 
LPAR,RPAR,AT = map(Suppress,"()@") 
date = Regex(r'\d{2}/\d{2}/\d{4}') 
time = Regex(r'\d{2}:\d{2}:\d{2}') 
status = oneOf("Busy Available Idle Offline Unavailable") 

statechange1 = 'changed status from' + status('fromstate') + 'to' + status('tostate') 
statechange2 = 'became' + status('tostate') 
linefmt = (DASHES + SkipTo('(')('name') + LPAR + SkipTo(RPAR)('email') + RPAR + 
      (statechange1 | statechange2) + 
      AT + date('date') + time('time') + DASHES) 

def convertFields(tokens): 
    if 'fromstate' not in tokens: 
     tokens['fromstate'] = 'NULL' 
    tokens['name'] = tokens.name.strip() 
    tokens['email'] = tokens.email.strip() 
    d,mon,yr = map(int, tokens.date.split('/')) 
    h,m,s = map(int, tokens.time.split(':')) 
    tokens['datetime'] = datetime.datetime(yr, mon, d, h, m, s) 
linefmt.setParseAction(convertFields) 

for line in text.splitlines(): 
    fields = linefmt.parseString(line) 
    print "%(name)s/%(email)s %(fromstate)-10.10s %(tostate)-10.10s %(datetime)s" % fields 

печатает:

Mark Grey/[email protected] Busy  Available 2010-07-14 16:32:36 
Silvia Pablo/[email protected] NULL  Available 2010-07-14 16:32:39 

Pyparsing позволяет присоединять имена полей результатов (так же, как по имени группы в ответе RE-style от Tom Pietzcker), а также действия разбора во времени для действий или обработки анализируемых действий - обратите внимание на преобразование отдельных полей даты и времени в настоящий объект datetime, уже преобразованный и готовый к обработке после синтаксического анализа без дополнительный muss n или суета.

Вот модифицированный цикл, который только дампы из разобранных жетоны и именованные поля для каждой строки:

for line in text.splitlines(): 
    fields = linefmt.parseString(line) 
    print fields.dump() 

печатает:

['Mark Grey ', '[email protected]', 'changed status from', 'Busy', 'to', 'Available', '14/07/2010', '16:32:36'] 
- date: 14/07/2010 
- datetime: 2010-07-14 16:32:36 
- email: [email protected] 
- fromstate: Busy 
- name: Mark Grey 
- time: 16:32:36 
- tostate: Available 
['Silvia Pablo ', '[email protected]', 'became', 'Available', '14/07/2010', '16:32:39'] 
- date: 14/07/2010 
- datetime: 2010-07-14 16:32:39 
- email: [email protected] 
- fromstate: NULL 
- name: Silvia Pablo 
- time: 16:32:39 
- tostate: Available 

Я подозреваю, что, как вы будете продолжать работать над этим проблема, вы найдете другие варианты формата входного текста, определяющие изменение состояния пользователя. В этом случае вы просто добавите другое определение, например statechange1 или statechange2, и вставьте его в linefmt с остальными. Я чувствую, что структурирование pyparsing определения парсера помогает разработчикам вернуться к парсеру после того, как все изменилось, и легко расширить свою программу синтаксического анализа.

1

спасибо за ваши комментарии. Они были очень полезны. Я написал свой код, используя функциональность каталога. Что он делает, так это чтение файла и создание выходного файла для каждого пользователя со всеми его обновлениями статуса. Вот код, вставленный ниже. ?

#Script to extract info from individual data files and print out a data file combining info from these files 

import os 
import commands 

dataFileDir="data/"; 

#Dictionary linking names to email ids 
#For the time being, assume no 2 people have the same name 
usrName2Id={}; 

#User id to user name mapping to check for duplicate names 
usrId2Name={}; 

#Store info: key: user ids and values a dictionary with time stamp keys and status messages values 
infoDict={}; 

#Given an array of space tokenized inputs, extract user name 
def getUserName(info,mailInd): 

    userName=""; 
    for i in range(mailInd-1,0,-1): 

     if info[i].endswith("-") or info[i].endswith("+"): 
      break; 

     userName=info[i]+" "+userName; 

    userName=userName.strip(); 
    userName=userName.replace(" "," "); 
    userName=userName.replace(" ","_"); 

    return userName; 

#Given an array of space tokenized inputs, extract time stamp 
def getTimeStamp(info,timeStartInd): 
    timeStamp=""; 
    for i in range(timeStartInd+1,len(info)): 
     timeStamp=timeStamp+" "+info[i]; 

    timeStamp=timeStamp.replace("-",""); 
    timeStamp=timeStamp.strip(); 
    return timeStamp; 

#Given an array of space tokenized inputs, extract status message 
def getStatusMsg(info,startInd,endInd): 
    msg=""; 
    for i in range(startInd,endInd): 
     msg=msg+" "+info[i]; 
    msg=msg.strip(); 
    msg=msg.replace(" ","_"); 
    return msg; 

#Extract and store info from each line in the datafile 
def extractLineInfo(line): 

    print line; 
    info=line.split(" "); 

    mailInd=-1;userId="-NONE-"; 
    timeStartInd=-1;timeStamp="-NONE-"; 
    becameInd="-1"; 
    statusMsg="-NONE-"; 

    #Find indices of email id and "@" char indicating start of timestamp 
    for i in range(0,len(info)): 
     #print (str(i)+" "+info[i]); 
     if(info[i].startswith("(") and info[i].endswith("@in.ibm.com)")): 
      mailInd=i; 
     if(info[i]=="@"): 
      timeStartInd=i; 

     if(info[i]=="became"): 
      becameInd=i; 

    #Debug print of mail and time stamp start inds 
    """print "\n"; 
    print "Index of mail id: "+str(mailInd); 
    print "Index of time start index: "+str(timeStartInd); 
    print "\n";""" 

    #Extract IBM user id and name for lines with ibm id 
    if(mailInd>=0): 
     userId=info[mailInd].replace("(",""); 
     userId=userId.replace(")",""); 
     userName=getUserName(info,mailInd); 
    #Lines with no ibm id are of the form "Suraj Godar Mr became idle @ 15/07/2010 16:30:18" 
    elif(becameInd>0): 
     userName=getUserName(info,becameInd); 

    #Time stamp info 
    if(timeStartInd>=0): 
     timeStamp=getTimeStamp(info,timeStartInd); 
     if(mailInd>=0): 
      statusMsg=getStatusMsg(info,mailInd+1,timeStartInd); 
     elif(becameInd>0): 
      statusMsg=getStatusMsg(info,becameInd,timeStartInd); 

    print userId; 
    print userName; 
    print timeStamp 
    print statusMsg+"\n"; 

    if not(userName in usrName2Id) and not(userName=="-NONE-") and not(userId=="-NONE-"): 
     usrName2Id[userName]=userId; 

    #Store status messages keyed by user email ids 
    timeDict={}; 

    #Retrieve user id corresponding to user name 
    if userName in usrName2Id: 
     userId=usrName2Id[userName]; 

    #For valid user ids, store status message in the dict within dict data str arrangement 
    if not(userId=="-NONE-"): 

     if not(userId in infoDict.keys()): 
      infoDict[userId]={}; 

     timeDict=infoDict[userId]; 
     if not(timeStamp in timeDict.keys()): 
      timeDict[timeStamp]=statusMsg; 
     else: 
      timeDict[timeStamp]=timeDict[timeStamp]+" "+statusMsg; 


#Print for each user a file containing status 
def printStatusFiles(dataFileDir): 


    volNum=0; 

    for userName in usrName2Id: 
     volNum=volNum+1; 

     filename=dataFileDir+"/"+"status-"+str(volNum)+".txt"; 
     file = open(filename,"w"); 

     print "Printing output file name: "+filename; 
     print volNum,userName,usrName2Id[userName]+"\n"; 
     file.write(userName+" "+usrName2Id[userName]+"\n"); 

     timeDict=infoDict[usrName2Id[userName]]; 
     for time in sorted(timeDict.keys()): 
      file.write(time+" "+timeDict[time]+"\n"); 


#Read and store data from individual data files 
def readDataFiles(dataFileDir): 

    #Process each datafile 
    files=os.listdir(dataFileDir) 
    files.sort(); 
    for i in range(0,len(files)): 
    #for i in range(0,1): 

     file=files[i]; 

     #Do not process other non-data files lying around in that dir 
     if not file.endswith(".txt"): 
      continue 

     print "Processing data file: "+file 
     dataFile=dataFileDir+str(file); 
     inpFile=open(dataFile,"r"); 
     lines=inpFile.readlines(); 

     #Process lines 
     for line in lines: 

      #Clean lines 
      line=line.strip(); 
      line=line.replace("/India/Contr/IBM",""); 
      line=line.strip(); 

      #Skip header line of the file and L's sign in sign out times 
      if(line.startswith("System log for account") or line.find("signed")>-1): 
       continue; 


      extractLineInfo(line); 


print "\n"; 
readDataFiles(dataFileDir); 
print "\n"; 
printStatusFiles("out/"); 
+0

@ yhw42 Что вы редактировали в этом древнем посте? Интересно . Кстати, плакат не был снова замечен с августа 2010 года. – eyquem

+0

@eyquem: Это [кричал на меня] (http://stackoverflow.com/spected-edits/32961), поэтому я исправил форматирование. ':)' – yhw42

+0

@ yhw42 Это было ужасно. Спасибо вам за разъяснение – eyquem

Смежные вопросы