2010-07-15 8 views
4

Je suis très novice en Python et je cherche à l'utiliser pour analyser un fichier texte. Le fichier a entre 250-300 lignes du format suivant:Analyse de fichiers texte en utilisant 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 ---- 

je dois stocker les informations suivantes dans un autre fichier (Excel ou texte) pour toutes les entrées de ce fichier

UserName/ID Previous Status New Status Date Time 

Alors mon fichier résultat devrait ressembler à ceci pour les entried ci-dessus

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

Merci à l'avance,

Toute aide serait vraiment appréciée

+1

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

+0

Eh bien, ce n'est pas un nombre correct :) –

+1

EDIT NOTE: Marcelo et Tim vous a donné une assez bonne réponse pour ce que vous voulez faire. Voici la documentation de la bibliothèque d'expressions régulières, incluse dans Python, qui peut vous aider à étendre le code: http://docs.python.org/library/re.html –

Répondre

1

Eh bien, si je devais aborder ce problème, je commencerais probablement par séparer chaque entrée dans sa propre chaîne séparée. Cela semble être en ligne, donc inputfile.split('\n') est probablement suffisant. À partir de là, je créerais probablement une expression régulière pour correspondre à chacun des changements de statut possibles, avec des sous-groupes entourant chacun des champs importants.

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) 

Cela fait des hypothèses sur les espaces et suppose également que chaque ligne est conforme au modèle. Vous pouvez ajouter une vérification d'erreur (par exemple, vérifier que pat.match() ne renvoie pas None) si vous souhaitez gérer correctement les entrées incorrectes.

15

Pour vous aider à démarrer:

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 

vous obtiendrez un tableau des parties du match. Je vous suggère alors d'utiliser le csv module pour stocker les résultats dans un format standard.

6

Les deux modèles de RE d'intérêt semblent être ...:

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

donc je ferais:

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) 

Si les deux modèles que je décrits ne sont pas assez général, ils peuvent Bien sûr, il faut ajuster, mais je pense que cette approche générale sera utile. Alors que de nombreux utilisateurs Python sur Stack Overflow n'aiment pas les RE, je les trouve très utiles pour ce type de traitement de texte ad hoc pragmatique.

Peut-être que l'aversion est expliquée par d'autres qui veulent utiliser REs pour des utilisations telles que absurdes ad hoc de l'analyse syntaxique CSV, HTML, XML, ... - et bien d'autres types de structurés formats de texte pour lequel parfaitement bien parseurs exister! Et aussi, d'autres tâches bien au-delà de la "zone de confort" des RE, et nécessitant à la place des systèmes d'analyse syntaxique solides tels que pyparsing. Ou à l'autre extrême, des tâches super simples très bien faites avec des chaînes simples (par exemple, je me souviens d'une question SO récente qui a utilisé if re.search('something', s): au lieu de if 'something' in s:! -). Mais pour la gamme raisonnablement large de tâches (en excluant les plus simples à une extrémité, et l'analyse des grammaires structurées ou quelque peu compliquées à l'autre) pour lesquelles les ER sont appropriées, il n'y a vraiment rien de mal à les utiliser, et je recommande à tous les programmeurs d'apprendre au moins les bases des ER.

4

Alex mentionné pyparsing et voici donc une approche pyparsing à votre même problème:

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 

impressions:

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 vous permet de joindre des noms aux résultats des champs (comme le nom les groupes dans la réponse de style de Tom Pietzcker), plus les actions d'analyse pour agir ou manipuler les actions analysées - notez la conversion des champs date et heure séparés en un vrai objet datetime, déjà converti et prêt pour le traitement après analyse sans muss supplémentaire ou d'agitation.

Voici une boucle modifiée qui vient dumps les jetons analysés et les champs nommés pour chaque ligne:

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

imprime:

['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 

Je soupçonne que vous continuez à travailler sur ce problème, vous trouverez d'autres variations sur le format du texte de saisie en précisant comment l'état de l'utilisateur a changé. Dans ce cas, vous devez simplement ajouter une autre définition comme statechange1 ou statechange2 et l'insérer dans linefmt avec les autres. Je pense que la structuration de la définition de l'analyseur par pyparsing aide les développeurs à revenir à un analyseur après que les choses ont changé, et à étendre facilement leur programme d'analyse.

1

merci beaucoup pour tous vos commentaires. Ils étaient très utiles. J'ai écrit mon code en utilisant la fonctionnalité de répertoire. Ce qu'il fait est qu'il lit le fichier et crée un fichier de sortie pour chaque utilisateur avec toutes ses mises à jour de statut. Voici le code collé ci-dessous. ?

#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 Qu'avez-vous édité dans cet ancien post? Je me demande . Par ailleurs, l'affiche n'a pas été revu depuis août 2010 – eyquem

+0

@eyquem: C'était [criant sur moi] (http://stackoverflow.com/suggested-edits/32961) donc j'ai corrigé le formatage. ':)' – yhw42

+0

@ yhw42 C'était vraiment horrible. Merci pour l'explication – eyquem