Perl在抓取HTML页面上胜过Python?
我有一个用Perl写的脚本,它可以抓取网页。我尝试把它用Python重写(因为我想学Python),结果发现速度真的很慢!
这是用Perl写的测试脚本
#!/usr/bin/perl
use LWP::Simple;
$url = "http://majorgeeks.com/page.php?id=";
open(WEB,">>"."perldata.txt");
for ($column = 1 ; $column <= 20 ; $column ++)
{
$temp = $url.$column;
print "val = $temp\n\n";
$response=get($temp)or die("[-] Failed!!\n");
print WEB "$response\n\n";
}
这是用Python写的等效代码
import urllib2
url = "http://majorgeeks.com/page.php?id="
f = open("pydata.txt", 'w')
for i in range(20):
tempurl = url + str(i+1)
print "Val : " + tempurl + "\n\n"
#req = urllib2.Request(tempurl)
res = urllib2.urlopen(tempurl)
f.write(res.read())
f.close()
我发现两者之间的差别非常大!
Perl脚本大约花了30秒完成,而Python脚本却花了大约7分钟(420秒)!!
我使用的是Ubuntu 11.10,64位,Core i7,测试时网络速度是12MBPS。我试了好几次,每次的差别都是一样的。
我是不是做错了什么?还是说我需要做些什么?或者这个差别是合理的?(我希望不是)
非常感谢你的帮助。
更新3:我刚回到家,启动了我的笔记本,重新运行了代码,结果只花了11秒!!! :/ 是不是因为我“重启”了电脑?
这是性能分析器的输出注意 - Perl在做同样的事情时仍然花了31秒!! :/
更新2:按照@Makoto的建议,这是我做的性能分析数据。确实很慢!我知道某些Python的配置可能与此有关,但我不知道是什么。对于一个简单的请求,怎么会花20秒呢!!!
更新:把网址改成了tempurl。按照这里的建议注释掉了urllib2.Request。结果几乎没有什么变化。
3 个回答
我不太清楚你为什么会得到这些奇怪的结果。不过我可以给你一个快速的解决办法。你可以使用一些异步库。我个人喜欢gevent,它在请求库中有一个非常好用的接口
代码:
from requests import async
import time
begin = time.time()
url = "http://majorgeeks.com/page.php?id=%s"
rs = [async.get(url % i) for i in xrange(1, 21)]
responses = async.map(rs, size=10)
with open("pydata.txt", 'w') as f:
for response in responses:
print response.url
f.write(response.content)
print 'Elapsed:', (time.time()-begin)
这只需要2.45秒。
编辑
导致urllib2.urlopen
慢的可能原因:
系统环境中的
http_proxy
网站可能以某种方式减慢了
urllib2
的速度,以限制自动抓取
我还是得挠挠头,想想为什么这段代码在 @mayjune 和 @Tadeck 那里运行得这么慢。我有机会用一个性能分析工具来正式测试这两段代码,结果如下。我强烈建议你在自己的机器上也跑一遍这些测试,因为我的机器会产生不同的结果(AMD Athlon II X4 @3GHz,8GB内存,Ubuntu 11.04 x64,7Mbit网络)。
运行方法:
python -m cProfile -o profile.dat <path/to/code.py>; python -m pstats profile.dat
(在性能分析工具里,你可以输入help
来查看命令。)
原始代码:
Fri Jan 6 17:49:29 2012 profile.dat
20966 function calls (20665 primitive calls) in 13.566 CPU seconds
Ordered by: cumulative time
List reduced from 306 to 15 due to restriction <15>
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.001 0.001 13.567 13.567 websiteretrieval.py:1(<module>)
20 0.000 0.000 7.874 0.394 /usr/lib/python2.7/urllib2.py:122(urlopen)
20 0.000 0.000 7.874 0.394 /usr/lib/python2.7/urllib2.py:373(open)
20 0.000 0.000 7.870 0.394 /usr/lib/python2.7/urllib2.py:401(_open)
40 0.000 0.000 7.870 0.197 /usr/lib/python2.7/urllib2.py:361(_call_chain)
20 0.000 0.000 7.870 0.393 /usr/lib/python2.7/urllib2.py:1184(http_open)
20 0.001 0.000 7.870 0.393 /usr/lib/python2.7/urllib2.py:1112(do_open)
1178 7.596 0.006 7.596 0.006 {method 'recv' of '_socket.socket' objects}
20 0.000 0.000 5.911 0.296 /usr/lib/python2.7/httplib.py:953(request)
20 0.000 0.000 5.911 0.296 /usr/lib/python2.7/httplib.py:974(_send_request)
20 0.000 0.000 5.911 0.296 /usr/lib/python2.7/httplib.py:938(endheaders)
20 0.000 0.000 5.911 0.296 /usr/lib/python2.7/httplib.py:796(_send_output)
20 0.000 0.000 5.910 0.296 /usr/lib/python2.7/httplib.py:769(send)
20 0.000 0.000 5.909 0.295 /usr/lib/python2.7/httplib.py:751(connect)
20 0.001 0.000 5.909 0.295 /usr/lib/python2.7/socket.py:537(create_connection)
...根据观察,唯一可能让你慢下来的就是...urlopen
和open
。输入输出(I/O)是比较慢的,所以这也算是可以理解的。
修改后的代码
Fri Jan 6 17:52:36 2012 profileTadeck.dat
21008 function calls (20707 primitive calls) in 13.249 CPU seconds
Ordered by: cumulative time
List reduced from 305 to 15 due to restriction <15>
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.002 0.002 13.249 13.249 websiteretrievalTadeck.py:1(<module>)
20 0.000 0.000 7.706 0.385 /usr/lib/python2.7/urllib2.py:122(urlopen)
20 0.000 0.000 7.706 0.385 /usr/lib/python2.7/urllib2.py:373(open)
20 0.000 0.000 7.702 0.385 /usr/lib/python2.7/urllib2.py:401(_open)
40 0.000 0.000 7.702 0.193 /usr/lib/python2.7/urllib2.py:361(_call_chain)
20 0.000 0.000 7.702 0.385 /usr/lib/python2.7/urllib2.py:1184(http_open)
20 0.001 0.000 7.702 0.385 /usr/lib/python2.7/urllib2.py:1112(do_open)
1178 7.348 0.006 7.348 0.006 {method 'recv' of '_socket.socket' objects}
20 0.000 0.000 5.841 0.292 /usr/lib/python2.7/httplib.py:953(request)
20 0.000 0.000 5.841 0.292 /usr/lib/python2.7/httplib.py:974(_send_request)
20 0.000 0.000 5.840 0.292 /usr/lib/python2.7/httplib.py:938(endheaders)
20 0.000 0.000 5.840 0.292 /usr/lib/python2.7/httplib.py:796(_send_output)
20 0.000 0.000 5.840 0.292 /usr/lib/python2.7/httplib.py:769(send)
20 0.000 0.000 5.839 0.292 /usr/lib/python2.7/httplib.py:751(connect)
20 0.001 0.000 5.839 0.292 /usr/lib/python2.7/socket.py:537(create_connection)
再次强调,耗时最多的两个原因还是urlopen
和open
。这让我相信,输入输出在拖慢你的代码上起了很大作用。不过,在我测试的机器上,差别并不大——Perl的脚本大约也花了同样的时间。
real 0m11.129s
user 0m0.230s
sys 0m0.070s
我不太相信是软件的问题导致你的代码慢,尽管你的机器配置相当不错。我强烈建议你运行性能分析工具(上面有代码)来看看是否能找到我遗漏的瓶颈。
你的代码可以改进一下,虽然我不确定这样做能否解决所有的性能问题:
from urllib2 import urlopen
url = "http://majorgeeks.com/page.php?id={}"
with open("pydata.txt", 'w') as f:
for i in xrange(1, 21):
tempurl = url.format(i)
print "Val : {}\n\n".format(tempurl)
f.write(urlopen(tempurl).read())
我在逻辑上也做了一些改动——现在它请求不同的网址(由 tempurl
定义),之前是请求同一个网址20次(由 url
定义)。我还用了字符串格式化,虽然我不太确定这对效率有什么影响。
我在我的系统上测试了一下(Windows 7 64位,Python 2.7.2,在IDLE中,网络连接一般),总共花了40秒(40.262秒)才完成。