Одновременно загружать несколько страниц?

Я хотел бы написать script в Python, который может захватить URL-адрес из базы данных и одновременно загружать веб-страницы, чтобы ускорить работу, а не ждать, пока каждая страница будет загружаться одна за другой.

Согласно этой теме, Python не позволяет этого из-за чего-то называемого Global Interpreter Lock, который предотвращает многократное повторение одного и того же script.

Прежде чем инвестировать время в изучение структуры Twisted, я хотел бы убедиться, что нет более простого способа сделать то, что мне нужно сделать выше.

Спасибо за любой совет.

Ответ 1

Не беспокойтесь о GIL. В вашем случае это не имеет значения.

Самый простой способ сделать то, что вы хотите, - создать пул потоков, используя модуль потоковой передачи и одну из реализаций пула потоков из ASPN. Каждый поток из этого пула может использовать httplib для загрузки ваших веб-страниц.

Другим вариантом является использование PyCURL module - он поддерживает параллельные downlaods изначально, поэтому вам не нужно его реализовывать самостоятельно.

Ответ 2

GIL не позволяет эффективно выполнять балансировку нагрузки процессора с помощью потоков. Поскольку это не балансировка нагрузки процессора, но предотвращение одного ожидания ввода-вывода от остановки всей загрузки, GIL здесь не имеет отношения. *)

Итак, все, что вам нужно сделать, это создать несколько процессов, загружаемых одновременно. Вы можете сделать это с помощью модуля потоковой передачи или модуля многопроцессорности.

*) Хорошо... если у вас нет Gigabit-соединений, и ваша проблема в том, что ваш процессор перегружен до вашей сети. Но это явно не так.

Ответ 3

Недавно я решил эту же проблему. Следует учитывать, что некоторые люди не любезно относятся к тому, что их серверы увязли и блокируют IP-адрес, который делает это. Стандартная любезность, которую я слышал, составляет около 3 секунд между запросами страниц, но это гибко.

Если вы загружаете с нескольких веб-сайтов, вы можете группировать свои URL-адреса по домену и создавать по одному потоку. Затем в вашем потоке вы можете сделать что-то вроде этого:

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 страниц в секунду.

Ответ 4

urllib и threading (или multiprocessing) в пакетах есть все, что вам нужно для "паука", в котором вы нуждаетесь.

Что вам нужно сделать, так это получить URL-адреса от БД, и для каждого URL-адреса запустите поток или процесс, который захватывает 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)

Ответ 5

В настоящее время есть отличные библиотеки Python, которые делают это за вас - urllib3 и requests

Ответ 7

Загрузка - это IO, которая может выполняться асинхронно с использованием неблокирующих сокетов или скрученных. Оба этих решения будут намного эффективнее, чем потоки или многопроцессорность.

Ответ 8

Я получаю помощь от какого-то эксперта сегодня, поскольку он публикует код 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)