曾经有人和我说过,Perl在对正则表达式的识别方面效率非常高,甚至超过了一般程序员自己用C或C++写的识别某特定模式正则表达式的程序。对此,我还是有点震惊的。恰巧,本人最近打算实现一个有意思的程序,而此程序的第一步,就是对抓取的大量的html文件做文本识别以获取有用信息。因此,我不由得想到同时用Perl和Python来实现这第一步,由此可以比较二者的效率。其实,这里所比较的并不仅仅是对正则表达式识别的效率,还有通篇程序的效率,以及开发本身的效率。这些后面会一一赘述。但就程序核心而言,还是主要在做正则表达式的识别。
首先,介绍一下程序所要完成的事情:当下有一个叫做web_pages的文件夹,该文件夹下所有的子文件夹都是以日期命名,比如2016-01-09. 而每一个这样的子文件夹下都有12个html文件。每一个html文件的大小在530kB左右,行数在4000行左右。因为日期子文件夹的个数是36个。所以:
1. 所有文件数目是:36 * 12 = 432
2. 总行数大约是:432 * 4000 = 1728000
3. 总大小为:212 MB
而每一个html文件的内容,除了一些头尾之外,主要是100个如下的<tr>数据部分:
<tr>
<td>199</td>
<td class="hide-col">11-17</td>
<td class="hide-col">160212</td>
<td><a href="http://huobijijin.com/jijin/160212" title="国泰估值优势混合(LOF)的历史收益情况" target="_blank">国泰估值优势混合(LOF)</a></td>
<td class="thd-text-right">1.9230</td>
<td class="thd-text-right"><span class='green'>-1.99%</span></td>
<td class="thd-text-right"><span class='green'>-0.67%</span></td>
<td class="thd-text-right"><span class='red'>14.53%</span></td>
<td class="thd-text-right"><span class='red'>10.2%</span></td>
<td class="thd-text-right"><span class='red'>5.43%</span></td>
<td class="thd-text-right"><span class='red'>92.3%</span></td>
<td class="thd-text-right"><span class='red'>109.48%</span></td>
<td class="thd-text-right"><span class='red'>167.08%</span></td>
<td class="thd-text-right"><span class='red'>80.56%</span></td>
<td class="thd-text-right"><span class='red'>92.3%</span></td>
<td class="thd-text-right">2010-02-10</td>
<td class="thd-text-right"><a href="http://huobijijin.com/jijin/160212" title="国泰估值优势混合(LOF)的历史收益情况" target="_blank">详情</a></td>
</tr>
1. 11-17: 这是日期,至于年份,可以从文件名得出
2. 160212: 这是基金代码
3. 1.9230:这是基金净值
4. 国泰估值优势混合(LOF):这是基金名称
所以,程序的目的,就是利用正则表达式识别出以上这4项,并存入一个数据结构中(其实就是Perl中的hash表或为Python中的字典)。这个数据结构的设计如下:
{
160212: {
'title': '国泰估值优势混合(LOF)',
'2015-11-17': 1.923,
'2015-11-18': xxx,
...
},
...
}
既然搞清了需求,就开始写代码吧。
Perl程序的代码如下:
our @dir_set = ();
our %total_hash = ();
sub get_files($) {
($dir) = @_;
opendir(DIR,$dir) || die "Can not open $dir. $!\n";
my @all_entries = grep(!/^\.\.?$/,readdir(DIR));
closedir(DIR);
foreach my $entry (@all_entries) {
$entry=join('/',($dir,$entry));
if(-d $entry) {
push(@dir_set, $entry);
}
elsif(-f $entry) {
read_file($entry);
}
}
my $size = @dir_set;
if ($size != 0) {
my $dir_entry = pop(@dir_set);
get_files($dir_entry);
}
}
sub read_file($) {
($PathName) = @_;
my $year = '????';
if ($PathName =~ /.+?\/(\d{4})-\d{2}-\d{2}_.+/) {
$year = $1;
}
open(FILEHANDLE, $PathName) || die "Can't open $PathName: $@.";
my @content = <FILEHANDLE>;
close(FILEHANDLE);
my $size = @content;
my $hit_flag = 0;
my $i = 0;
while ($i < $size) {
my $line = $content[$i];
chomp($line);
my ($date, $id, $title, $value);
if ($line =~ /^\s*\<tr\>\s*/) { # <tr>
if ($content[$i + 2] =~ /\s*\<td.*?\>(.+)\<\/td\>/) {
$date = "${year}-$1";
} else {
$i += 2;
next;
}
if ($content[$i + 3] =~ /\s*\<td.*?\>(\d+)\<\/td\>/) {
$id = $1;
} else {
$i += 3;
next;
}
if ($content[$i + 4] =~ /\s*\<td.*?\>.*?title=\"(.+?)的历史收益情况\".+?\<\/td\>/) {
$title = $1;
} else {
$i += 4;
next;
}
if ($content[$i + 5] =~ /\s*\<td.*?\>(.+)\<\/td\>/) {
$value = $1;
} else {
$i += 5;
next;
}
$hit_flag = 1;
$i += 6;
} else {
$hit_flag = 0;
$i++;
}
if ($hit_flag) {
if (exists $total_hash{$id}) {
$total_hash{$id}{$date} = $value;
} else {
my %temp_hash = ();
$total_hash{$id} = \%temp_hash;
$total_hash{$id}{'title'} = $title;
$total_hash{$id}{$date} = $value;
}
}
}
}
sub print_total_hash() {
foreach my $key (sort keys %total_hash) {
print $key, "\n";
if (exists $total_hash{$key}{'title'}) {
print $total_hash{$key}{'title'}, "\n";
}
foreach my $date (sort keys $total_hash{$key}) {
if ($date != 'title') {
print " ${date}: $total_hash{$key}{$date}\n";
}
}
print "\n";
}
}
##### Main Body #####
get_files("./web_pages");
# print_total_hash();
自然,在Python代码中也注释了最后打印的功能。Python代码如下:
# coding=utf-8
import re
import os
file_path_mode = re.compile(r'.*?(\d{4})-\d{2}-\d{2}')
tr_mode = re.compile(r'\s*<tr>\s*')
date_mode = re.compile(r'\s*<td.*?>(.+)\</td>')
id_mode = re.compile(r'\s*<td.*?>(\d+)</td>')
title_mode = re.compile(r'\s*<td.*?>.*title="(.+?)的历史收益情况".+</td>')
value_mode = re.compile(r'\s*<td.*?>(.+)</td>')
total_hash = {}
def analyze_file(file_path):
try:
year = '????'
if file_path_mode.match(file_path):
res = file_path_mode.search(file_path).groups()
if res and res[0]:
year = res[0]
except Exception as ex:
print "Error happened when parsing file path: %s" % str(ex)
try:
with open(file_path, "r") as infile:
lines = infile.read().split('\n')
except Exception as ex:
print "Error happened when open file: " + str(ex)
raise
hit_flag = 0
i = 0
while i < len(lines):
date, id, title, value = '????', 'id', 'title', 0
try:
if tr_mode.match(lines[i]):
res = date_mode.search(lines[i+2]).groups()
if res and res[0]:
date = "%s-%s" % (year, res[0])
else:
i += 2
continue
res = id_mode.search(lines[i+3]).groups()
if res and res[0]:
id = res[0]
else:
i += 3
continue
res = title_mode.search(lines[i+4]).groups()
if res and res[0]:
title = res[0]
else:
i += 4
continue
res = value_mode.search(lines[i+5]).groups()
if res and res[0]:
value = res[0]
else:
i += 5
continue
hit_flag = 1
i += 6
else:
i += 1
hit_flag = 0
except Exception as ex:
# print "Error happened in one loop: " + str(ex)
i += 1
if hit_flag:
if total_hash.get(id) is None:
total_hash[id] = {}
total_hash[id]['title'] = title
if total_hash[id].get(date) is None:
total_hash[id][date] = value
def list_files(target_dir):
for root, dirs, files in os.walk(target_dir):
for file in files:
file_path = os.path.join(root, file)
analyze_file(file_path)
for dir in dirs:
list_files(dir)
def print_total_hash():
for id in sorted(total_hash.keys()):
print id
print total_hash[id]['title']
for date in sorted(total_hash[id].keys()):
if date != 'title':
print " %s: %s" % (date, total_hash[id][date])
if __name__ == "__main__":
list_files('./web_pages')
# print_total_hash()
那么,这2个程序对于这212MB,170多万行html文件的分析的速度如何呢?
本来,进行performance测试,应该用比较专业的工具。对于Perl,应该用诸如DProf、SmallProf或NYTProf这样的专业module;而对于Python,应该使用cProfile这样的library。但是,本文只是要简单比较一下到底谁执行的更快,而不是要搞清楚程序慢在何处,所以直接使用time命令就够了。
另外,当然,Perl程序和Python程序的最终打印结果是完全一致的。
1. 硬件:8核,16GB 内存的强机 - HP的ZBook15 (售价2.5万-3万哦,当然,这是公司电脑 :P)
2. 操作系统: Windows 7
3. 执行环境:Cygwin
4. Perl 版本: 5.22
5. Python版本:2.7.10
1. 从real time来看,Python程序比Perl程序快了 (18.539 - 17.317) / 18.539 * 100% = 6.6%
2. 从user time来看,Python程序比Perl程序快了 (15.487-13.739)/15.487 * 100% = 11.3%
1. 开发效率:Python 胜 Perl
在写Perl程序的时候,其实我已经2年没写Perl的程序了,再捡起来的时候真是头大啊。虽然有以前的笔记做参考,还是吭哧吭哧搞了半天才搞定一些语法问题,比如hash的各种情况下的表示方法;相比之下,虽然Python也有半年没写了,但是捡起来很快。所以,从开发效率来说,Python是远快于Perl的。
2. 普通代码可读性: Python 胜 Perl
从代码来看,Perl写一个递归遍历文件夹真是麻烦,而python一个os.walk就搞定了,很简单;
3. 正则表达式写法: Python 负 Perl
从正则表达式的写法来看,Perl比Python简单很多!并且还很好理解很好写。这是Perl的优点!
4. 综合执行效率: Python 胜 Perl
从整个程序执行的效率来看,Python竟然比Perl快!虽然快的很有限,综合来看只是6.6%,但毕竟打破了我之前的“perl的正则表达式是无敌”的认知。
5. 代码行数: Python 胜 Perl
代码行数来看,Python竟然没有占据什么优势,只是比Perl少了10行代码而已!(我想一个原因是Python的正则表达式写起来比较麻烦;第二个原因是我写了许多try...except在Python程序中。)
总比分:
Python 4:1 Perl
Python胜出!-- 嗯,我决定以后只写Python不写Perl了。所谓“人生苦短,我用Python”,还真是有道理的!
本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。