2009-09-29 46 views
2

我想用Python语言编写一个脚本,可以从数据库中抓取网址,并下载网页的同时,加快而不是等待每个页面下载一前一后的事情。同时下载多个页面?

根据this thread,Python不允许这样做,因为一些名为Global Interpreter Lock的内容会阻止多次激活相同的脚本。

在投入时间学习Twisted框架之前,我想确保没有更简单的方法来完成我需要做的事情。

谢谢你的任何提示。

+0

非并发的,但很扎实,细致(老!)工具带有Python安装(或http://svn.python.org/view/python/trunk/Tools/webchecker在线浏览/) - 确保在你的并发实现中,你应用了所有的礼貌和预防措施(robots.txt解析等等;-)。 – 2009-09-29 15:01:12

+1

@Alex,是否有与网络爬行相关的所有礼节和预防措施的集中位置? – tgray 2009-09-29 20:41:11

回答

9

不要担心GIL。在你的情况下,这并不重要。

最简单的做法是创建线程池,使用线程模块和ASPN的线程池实现之一。从池中的每个线程可以使用httplib的下载网页。

另一种选择是使用PyCURL模块 - 它本身支持并行下坡,因此您不必亲自实施它。

+1

对于PyCURL建议+1 – drAlberT 2009-09-29 16:02:02

0

下载是IO,可以使用非阻塞套接字或扭曲来异步完成。这两种解决方案都比线程或多处理更有效率。

7

GIL阻止您切实做好处理器负载均衡的线程。由于这不是处理器负载均衡,但防止一个IO等待从停止整个下载后,GIL是不是与此有关。 *)

所以你需要做的就是创建几个同时下载的进程。您可以使用线程模块或多处理模块执行此操作。

*)嗯......除非你有千兆连接,并且你的问题确实是你的处理器在你的网络之前变得超载了。但这显然不是这种情况。

2

最近,我解决了这个同样的问题。有一点需要考虑的是,有些人不愿意让他们的服务器陷入困境,并会阻止这样做的IP地址。我听到的标准礼貌是在页面请求之间大约3秒钟,但这是灵活的。

如果您从多个网站下载,则可以按域对网址进行分组,并为每个网站创建一个线程。然后,在你的线程,你可以做这样的事情:

for url in urls: 
    timer = time.time() 
    # ... get your content ... 
    # perhaps put content in a queue to be written back to 
    # your database if it doesn't allow concurrent writes. 
    while time.time() - timer < 3.0: 
     time.sleep(0.5) 

有时候,只是让你的反应会采取3秒的完整,你不必担心。

当然,如果您只从一个站点下载,这完全不会对您有任何帮助,但它可能会阻止您被阻止。

我的机器在管理它们的开销之前处理了大约200个线程,从而减慢了进程速度。我以每秒40-50页的速度结束。

1

urllib & threading(或multiprocessing)软件包有所有你需要做的“蜘蛛”你需要。

你必须做的是从数据库获取网址,并为每个网址启动一个线程或进程 抓取网址。

就像例子(错过数据库的URL检索):

#!/usr/bin/env python 
import Queue 
import threading 
import urllib2 
import time 

hosts = ["http://yahoo.com", "http://google.com", "http://amazon.com", 
    "http://ibm.com", "http://apple.com"] 

queue = Queue.Queue() 

class ThreadUrl(threading.Thread): 
    """Threaded Url Grab""" 
    def __init__(self, queue): 
     threading.Thread.__init__(self) 
     self.queue = queue 

    def run(self): 
     while True: 
      #grabs host from queue 
      host = self.queue.get() 

      #grabs urls of hosts and prints first 1024 bytes of page 
      url = urllib2.urlopen(host) 
      print url.read(1024) 

      #signals to queue job is done 
      self.queue.task_done() 

start = time.time() 
def main(): 

    #spawn a pool of threads, and pass them queue instance 
    for i in range(5): 
     t = ThreadUrl(queue) 
     t.setDaemon(True) 
     t.start() 

    #populate queue with data 
    for host in hosts: 
     queue.put(host) 

    #wait on the queue until everything has been processed 
    queue.join() 

main() 
print "Elapsed Time: %s" % (time.time() - start) 
1

现在有优秀的Python库为你做到这一点 - 今天urllib3requests

0

我得到的帮助来自一些专家,如他发布了python 2.X代码以供参考。 http://paste.ubuntu.com/24529360/

from multiprocessing.dummy import Pool 
import urllib 
def download(_id): 
    success = False 
    fail_count = 0 
    url = 'fuck_url/%s'%_id 
    while not success: 
     if fail_count>10: 
      print url,'download faild' 
      return 
     try: 
      urllib.urlretrieve(url,'%s.html'%_id) 
      success = True 
     except Exception: 
      fail_count+=1 
      pass 

if __name__ == '__main__': 
    pool = Pool(processes=100) # 100 thread 
    pool.map(download,range(30000)) 
    pool.close() 
    pool.join() 

我更喜欢使用requests因为它有header,并可以很容易地添加proxy

import requests 
hdrs = {'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6'} 
r = requests.get(url, headers=hdrs, verify=False)#, proxies=proxyDict) 
      with open(fullFileName, 'wb') as code: 
       code.write(r.content)