我将Verizon帐单的谈话活动内容提取到一个文本文件中。我想对数据进行一些分析。我更喜欢使用powershell,python或bash。关于如何将以下内容转换为csv的任何想法:
1月6日下午12:30不可用拉斯维加斯,内华达州传入,CL 2
1月7日,上午11:06 697.732.5672,内华达州内华达州维克多,里诺30
1月4日下午3:26 702.792.2189拉斯维加斯,内华达州VM VM沉积室,CL 1
1月24日下午4:24 213.302.2581加利福尼亚州萨克拉门托市,来袭,CL 105
结果应为以下示例:
“ 1月6日下午12:30”,“不可用”,“内华达州拉斯维加斯”,“来话,CL”,“ 2”
“ 1月7日上午11:06”,“ 697.732.5672”,“内华达州里诺”,“加利福尼亚维克托维尔”,“ 30”
“ 1月4日下午3:26”,“ 702.792.2189”,“内华达州拉斯维加斯”,“ VM存款,CL”,“ 1”
“ 1月24日下午4:24”,“ 213.302.2581”,“加利福尼亚萨克拉门托”,“ CL传入”,“ 105”
谢谢您的建议。
答案 0 :(得分:1)
由于问题空间未明确定义,我不愿在此处发布答案,也没有提供任何工作来显示卡住的地方,或者您是否尝试过任何方法。
另一方面,这是一个机会来突出显示Python中的一些基本的字符串解析策略,因此我将把它视为一种带注释的演练,这可能对那些从事此文章的人员有所帮助。
我采用的方法是将每个内容行拆分为以空格分隔的元素,然后组合适当的片段。
让我们假设输入data
看起来与发布的样子完全一样,用空格分隔内容行。然后data.split("\n")
将产生一个由7个元素组成的列表:4行内容和3个空字符串(''
)行:
data.split("\n")
['Jan 6 12:30 PM Unavailable Las Vegas, NV Incoming, CL 2',
'',
'Jan 7 11:06 AM 697.732.5672 Reno, NV Victorvl, CA 30',
'',
'Jan 4 3:26 PM 702.792.2189 Las Vegas, NV VM Deposit, CL 1',
'',
'Jan 24 4:24 PM 213.302.2581 Sacramento, CA Incoming, CL 105']
我们可以通过检查len(x)
(如果为False
则为len == 0
)来删除空的字符串行,然后检查split()
其余的行以单个空格。
lines = [x.split() for x in data.split("\n") if len(x)]
lines
[['Jan', '6', '12:30', 'PM', 'Unavailable', 'Las', 'Vegas,', 'NV', 'Incoming,', 'CL', '2'],
['Jan', '7', '11:06', 'AM', '697.732.5672', 'Reno,', 'NV', 'Victorvl,', 'CA', '30'],
['Jan', '4', '3:26', 'PM', '702.792.2189', 'Las', 'Vegas,', 'NV', 'VM', 'Deposit,', 'CL', '1'],
['Jan', '24', '4:24', 'PM', '213.302.2581', 'Sacramento,', 'CA', 'Incoming,', 'CL', '105']]
我将假设每个记录中的三个字段始终具有相同数量的元素:日期/时间,IP地址和最终编号(通话时间?)。这样可以很容易地硬编码我们应该join()
用于那些字段的元素数量。
有问题的字段是位置字段,因为每个位置的字符串块数量可能有所不同。例如,经过上述拆分操作后,"Reno, NV"
成为2元素列表(["Reno,", "NV"]
)。但是"Las Vegas, NV"
在["Las", "Vegas,", "NV"]
之后变成split
,具有3个元素。这意味着我们不能仅仅硬编码要在join
中使用的每组位置字符串的开始和结束索引。
我们可以在此处使用的一种技巧是在一组位置字符串的最后一个字符串中添加一个特殊字符。然后,我们可以将join
最初的两个位置字段全部放在一起,然后将split
放到特殊字符上。这有点不雅致,但可以完成工作。
如何确定一组位置字符串中的最后一个字符串是哪个?可以肯定地假设,如果一个字符串块以逗号结尾(例如'Vegas,'
或'Reno,'
),则下一个块将是状态缩写,即该位置集中的最后一个。我们可以在“逗号块”之后的块中添加*
标记,如下所示:
for i, elem in enumerate(line):
if elem[-1] == ",":
line[i+1] += "*"
然后,在两个位置字符串集(在原始数据中相邻)上执行join
之后,我们可以对*
进行另一次拆分以将它们分开。
这是完整的解决方案:
lines = [x.split() for x in data.split("\n") if len(x)]
grouped = []
for line in lines:
for i, elem in enumerate(line):
if elem[-1] == ",":
line[i+1] += "*"
grp = [' '.join([str(x) for x in line[:4]]),
str(line[4]),
' '.join([str(x) for x in line[5:]])]
grouped.append(grp[:2] + grp[2].split("* "))
输出:
grouped
[['Jan 6 12:30 PM', 'Unavailable', 'Las Vegas, NV', 'Incoming, CL', '2'],
['Jan 7 11:06 AM', '697.732.5672', 'Reno, NV', 'Victorvl, CA', '30'],
['Jan 4 3:26 PM', '702.792.2189', 'Las Vegas, NV', 'VM Deposit, CL', '1'],
['Jan 24 4:24 PM', '213.302.2581', 'Sacramento, CA', 'Incoming, CL', '105']]
您可以使用自己喜欢的任何I / O方法将其存储为CSV。
(IMO,Pandas使其易于使用:pd.DataFrame(grouped).to_csv("records.csv", index=False)
)
答案 1 :(得分:1)
此代码使用正则表达式分解记录,创建新对象,然后将其导出到CSV文件。
[regex]$rx = '(?<ts>\S+\s\S+\s\S+\s\S+)\s+(?<number>\S+)\s+(?<citystate>[^,]*,\s\S{2})\s+(?<direction>[^,]*, \S{2})\s+(?<minutes>\d*)'
Get-Content -Path '.\phonebill.txt' |
ForEach-Object {
$m = $rx.Match($_)
$record = [PSCustomObject][ordered]@{
Timestamp = $m.groups['ts'].Value
Number = $m.groups['number'].Value
CityState = $m.groups['citystate'].Value
Direction = $m.groups['direction'].Value
Minutes = $m.groups['minutes'].Value
}
$record | Export-Csv -Path '.\phonebill.csv' -Append -Encoding ascii -NoTypeInformation
}
它产生以下输出。
"Timestamp","Number","CityState","Direction","Minutes"
"Jan 6 12:30 PM","Unavailable","Las Vegas, NV","Incoming, CL","2"
"Jan 7 11:06 AM","697.732.5672","Reno, NV","Victorvl, CA","30"
"Jan 4 3:26 PM","702.792.2189","Las Vegas, NV","VM Deposit, CL","1"
"Jan 24 4:24 PM","213.302.2581","Sacramento, CA","Incoming, CL","105"
根据@TheMadTechnician和@ mklement0的好建议进行修订。
[regex]$rx = '(?<ts>\S+\s\S+\s\S+\s\S+)\s+(?<number>\S+)\s+(?<citystate>[^,]*,\s\S{2})\s+(?<direction>[^,]*, \S{2})\s+(?<minutes>\d*)'
Get-Content -Path '.\phonebill.txt' |
ForEach-Object {
if ($_ -match $rx) {
[PSCustomObject]@{
Timestamp = $Matches.ts
Number = $Matches.number
CityState = $Matches.citystate
Direction = $Matches.direction
Minutes = $Matches.minutes
}
}
} |
Export-Csv -Path '.\phonebill.csv' -Encoding ascii -NoTypeInformation
答案 2 :(得分:1)
这里有一些python可以做到,RE应该可以转移到其他几种语言:
import re
with open('gash.txt') as f:
for line in f:
m = re.match(r"(.+[AP]M) ((?:Unavailable)|(?:[0-9\.]+)) ([\w ]+?, [A-Z]{2}) ([\w ]+?, [A-Z]{2}) (\d+)" ,line)
if m:
val = '"'+'","'.join(m.groups())+'"'
print(val)
礼物:
"Jan 6 12:30 PM","Unavailable","Las Vegas, NV","Incoming, CL","2"
"Jan 7 11:06 AM","697.732.5672","Reno, NV","Victorvl, CA","30"
"Jan 4 3:26 PM","702.792.2189","Las Vegas, NV","VM Deposit, CL","1"
"Jan 24 4:24 PM","213.302.2581","Sacramento, CA","Incoming, CL","105"
请询问您是否需要任何解释。
答案 3 :(得分:0)
您的数据存在的问题是,输入行在位置字段中具有标记的可变个变量(例如Reno, NV
与Las Vegas, NV
),其中缺少字段定界符排除了仅通过索引将行分成字段的问题。
这是一个实用的PowerShell解决方案,
从基于空格的拆分开始,并提取其令牌计数和位置 不变的字段。
通过正则表达式通过它们各自具有的, <state-abbreviation>
后缀将其余令牌分为两个位置字段:
注意:我假设数据线(以及输出线)之间的空行是发布问题的产物。如果它们在实际输入中,请在下面的if (-not $_) { return }
块的第一行添加Foreach-Object
,以忽略它们。
& {
# Output the CSV header row.
'"Date","Number","Loc1","Loc2","Duration"'
# Process the input lines and generate the output CSV rows.
Get-Content call-log.txt | ForEach-Object {
$tokens = -split $_ # split line into tokens by whitespace
$date = $tokens[0..3] -join ' ' # first 4 tokens
$number = $tokens[4] # 5th token
$duration = $tokens[-1] # last token
# split the remaining tokens into thw two locations by inserting
# a '|' char. after the first ', <state-abbrev>' and then splitting by it.
$loc1, $loc2 =
$tokens[5..($tokens.Count-2)] -join ' ' -replace '(, [A-Z]{2}) ', '$1|' -split '\|'
# Synthesize and output the CSV data row.
'"{0}","{1}","{2}","{3}","{4}"' -f $date, $number, $loc1, $loc2, $duration
}
} | Set-Content out.csv
请注意,在Windows PowerShell中,输出文件的字符编码将为“ ANSI”(与您的系统区域设置相关联的旧版代码页所隐含的编码)(在PowerShell Core中,它将是没有BOM的UTF-8);使用Set-Content
的{{1}}参数进行更改。