Perl在抓取HTML页面上胜过Python?

6 投票
3 回答
1453 浏览
提问于 2025-04-17 09:42

我有一个用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 个回答

0

我不太清楚你为什么会得到这些奇怪的结果。不过我可以给你一个快速的解决办法。你可以使用一些异步库。我个人喜欢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的速度,以限制自动抓取

1

我还是得挠挠头,想想为什么这段代码在 @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)

...根据观察,唯一可能让你慢下来的就是...urlopenopen。输入输出(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)

再次强调,耗时最多的两个原因还是urlopenopen。这让我相信,输入输出在拖慢你的代码上起了很大作用。不过,在我测试的机器上,差别并不大——Perl的脚本大约也花了同样的时间。

real    0m11.129s
user    0m0.230s
sys 0m0.070s

我不太相信是软件的问题导致你的代码慢,尽管你的机器配置相当不错。我强烈建议你运行性能分析工具(上面有代码)来看看是否能找到我遗漏的瓶颈。

2

你的代码可以改进一下,虽然我不确定这样做能否解决所有的性能问题:

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秒)才完成。

撰写回答