Как ускорить получение страниц с помощью urllib2 в python?
у меня есть скрипт, который выбирает из нескольких веб-страниц и анализирует информацию.
(пример можно увидеть на http://bluedevilbooks.com/search/?DEPT=MATH&CLASS=103&SEC=01)
я запустил cProfile на нем, и, как я предполагал, urlopen занимает много времени. Есть ли способ получить страницы быстрее? Или способ принести сразу несколько страниц? Я буду делать все, что проще, так как я новичок в python и веб-разработке.
спасибо заранее! :)
UPDATE: у меня есть функция под названием fetchURLs()
, который я использую для создания массива URL-адресов, которые мне нужны
что-то вроде urls = fetchURLS()
.URL-адреса-это все XML-файлы из Amazon и eBay APIs (что смущает меня, почему загрузка занимает так много времени, может быть, мой веб-сайт медленный?)
что мне нужно сделать, это загрузить каждый URL, прочитать каждую страницу и отправить эти данные в другую часть скрипта, который будет анализировать и отображать данные.
обратите внимание, что я не могу сделать последнюю часть, пока все страницы были принесены, вот в чем моя проблема.
кроме того, мой хост ограничивает меня 25 процессами за раз, я считаю, поэтому все, что проще всего на сервере, было бы неплохо:)
вот он для времени:
Sun Aug 15 20:51:22 2010 prof
211352 function calls (209292 primitive calls) in 22.254 CPU seconds
Ordered by: internal time
List reduced from 404 to 10 due to restriction <10>
ncalls tottime percall cumtime percall filename:lineno(function)
10 18.056 1.806 18.056 1.806 {_socket.getaddrinfo}
4991 2.730 0.001 2.730 0.001 {method 'recv' of '_socket.socket' objects}
10 0.490 0.049 0.490 0.049 {method 'connect' of '_socket.socket' objects}
2415 0.079 0.000 0.079 0.000 {method 'translate' of 'unicode' objects}
12 0.061 0.005 0.745 0.062 /usr/local/lib/python2.6/HTMLParser.py:132(goahead)
3428 0.060 0.000 0.202 0.000 /usr/local/lib/python2.6/site-packages/BeautifulSoup.py:1306(endData)
1698 0.055 0.000 0.068 0.000 /usr/local/lib/python2.6/site-packages/BeautifulSoup.py:1351(_smartPop)
4125 0.053 0.000 0.056 0.000 /usr/local/lib/python2.6/site-packages/BeautifulSoup.py:118(setup)
1698 0.042 0.000 0.358 0.000 /usr/local/lib/python2.6/HTMLParser.py:224(parse_starttag)
1698 0.042 0.000 0.275 0.000 /usr/local/lib/python2.6/site-packages/BeautifulSoup.py:1397(unknown_starttag)
10 ответов
редактировать: я расширяю ответ, чтобы включить более полированный пример. Я нашел много враждебности и дезинформации в этом посте относительно потоков v. s. асинхронный ввод-вывод Поэтому я также добавляю больше аргументов для опровержения определенного недопустимого утверждения. Я надеюсь, что это поможет людям выбрать правильный инструмент для правильной работы.
Это dup на вопрос 3 дней назад.
Python urllib2.открыть медленно, нужен лучший способ прочитать несколько URL - адресов-переполнение стека Python urllib2.urlopen () медленный, нужен лучший способ прочитать несколько URL
я полирую код, чтобы показать, как получить несколько веб-страниц параллельно с помощью потоков.
import time
import threading
import Queue
# utility - spawn a thread to execute target for each args
def run_parallel_in_threads(target, args_list):
result = Queue.Queue()
# wrapper to collect return value in a Queue
def task_wrapper(*args):
result.put(target(*args))
threads = [threading.Thread(target=task_wrapper, args=args) for args in args_list]
for t in threads:
t.start()
for t in threads:
t.join()
return result
def dummy_task(n):
for i in xrange(n):
time.sleep(0.1)
return n
# below is the application code
urls = [
('http://www.google.com/',),
('http://www.lycos.com/',),
('http://www.bing.com/',),
('http://www.altavista.com/',),
('http://achewood.com/',),
]
def fetch(url):
return urllib2.urlopen(url).read()
run_parallel_in_threads(fetch, urls)
как вы можете видеть, код конкретного приложения имеет только 3 строки, которые могут быть свернуты в 1 строку, если вы агрессивны. Я не думаю, что кто-то может оправдать свое утверждение о том, что это сложно и недостижимо.
к сожалению, большинство других потоковых кодов опубликовано здесь есть некоторые недостатки. Многие из них делают активный опрос, чтобы дождаться завершения кода. join()
- Это лучший способ для синхронизации кода. Я думаю, что этот код улучшился по всем примерам потоков до сих пор.
keep-alive соединение
предложение WoLpH об использовании keep-alive connection может быть очень полезно, если все ваши URL-адреса указывают на один и тот же сервер.
twisted
Аарон Галлахер является поклонником twisted
фреймворк и он враждебны любому человеку, который предлагает нить. К сожалению, многие его заявления-дезинформация. Например, он сказал: "-1 за предложение темы. Это IO-граница; потоки здесь бесполезны."Это противоречит доказательствам, поскольку и Ник Т, и я продемонстрировали увеличение скорости от использования нити. На самом деле связанное приложение ввода-вывода имеет больше всего преимуществ от использования потока Python (v. s. нет усиления в привязанном к CPU приложении). Ошибочная критика Аарона в thread показывает, что он довольно смущен параллельное программирование в целом.
правильный инструмент для правильной работы
Я хорошо знаю о проблемах, связанных с параллельным программированием с использованием потоков, python, асинхронного ввода-вывода и так далее. Каждый инструмент имеет свои плюсы и минусы. Для каждой ситуации есть соответствующий инструмент. Я не против twisted (хотя я сам не развернул его). Но я не верю, что мы можем сказать, что нить плохая, а скрученная-хорошая во всех ситуациях.
например, если требование ФП является извлечение 10,000 сайт в параллельных, асинхронных операций ввода-вывода будет prefereable. Threading не будет подходящим (если, возможно, с stackless Python).
оппозиция Аарона потокам-это в основном обобщения. Он не понимает, что это тривиальная задача распараллеливания. Каждая задача является независимой и не требует совместного использования ресурсов. Поэтому большая часть его атаки не применима.
учитывая, что мой код не имеет внешней зависимости, я назову его правильным инструментом для правильного работа.
производительность
Я думаю, большинство людей согласится с тем, что выполнение этой задачи во многом зависит от сетевого кода и внешнего сервера, где производительность кода платформы должны иметь незначительный эффект. Однако тест Аарона показывает увеличение скорости 50% над потоковым кодом. Я думаю, что необходимо реагировать на это кажущееся увеличение скорости.
в коде Ника есть очевидный недостаток, который вызвал неэффективность. Но как вы объясните увеличение скорости 233ms над моим кодом? Я думаю, что даже скрученные поклонники воздержатся от прыжков в заключение, чтобы приписать это эффективности twisted. В конце концов, существует огромное количество переменных вне системного кода, таких как производительность удаленного сервера, сеть, кэширование и реализация различий между urllib2 и twisted web client и так далее.
просто чтобы убедиться, что резьба Python не приведет к огромной неэффективности, я быстро бенчмарк для создания 5 потоков, а затем 500 потоков. Мне довольно удобно сказать, что накладные расходы на нерест 5 нитей незначительны и не могут объяснить разницу в скорости 233ms.
In [274]: %time run_parallel_in_threads(dummy_task, [(0,)]*5)
CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
Wall time: 0.00 s
Out[275]: <Queue.Queue instance at 0x038B2878>
In [276]: %time run_parallel_in_threads(dummy_task, [(0,)]*500)
CPU times: user 0.16 s, sys: 0.00 s, total: 0.16 s
Wall time: 0.16 s
In [278]: %time run_parallel_in_threads(dummy_task, [(10,)]*500)
CPU times: user 1.13 s, sys: 0.00 s, total: 1.13 s
Wall time: 1.13 s <<<<<<<< This means 0.13s of overhead
дальнейшее тестирование моей параллельной выборки показывает огромную изменчивость времени отклика в 17 запусках. (К сожалению, я не скрутил, чтобы проверить код Аарона).
0.75 s
0.38 s
0.59 s
0.38 s
0.62 s
1.50 s
0.49 s
0.36 s
0.95 s
0.43 s
0.61 s
0.81 s
0.46 s
1.21 s
2.87 s
1.04 s
1.72 s
мое тестирование не поддерживает вывод Аарона о том, что поток последовательно медленнее, чем асинхронный ввод-вывод измеримая маржа. Учитывая количество задействованных переменных, я должен сказать, что это недопустимый тест для измерения систематической разницы в производительности между асинхронным вводом-выводом и потоком.
использовать twisted! Это делает такие вещи абсурдно легкими по сравнению, скажем, с использованием нитей.
from twisted.internet import defer, reactor
from twisted.web.client import getPage
import time
def processPage(page, url):
# do somewthing here.
return url, len(page)
def printResults(result):
for success, value in result:
if success:
print 'Success:', value
else:
print 'Failure:', value.getErrorMessage()
def printDelta(_, start):
delta = time.time() - start
print 'ran in %0.3fs' % (delta,)
return delta
urls = [
'http://www.google.com/',
'http://www.lycos.com/',
'http://www.bing.com/',
'http://www.altavista.com/',
'http://achewood.com/',
]
def fetchURLs():
callbacks = []
for url in urls:
d = getPage(url)
d.addCallback(processPage, url)
callbacks.append(d)
callbacks = defer.DeferredList(callbacks)
callbacks.addCallback(printResults)
return callbacks
@defer.inlineCallbacks
def main():
times = []
for x in xrange(5):
d = fetchURLs()
d.addCallback(printDelta, time.time())
times.append((yield d))
print 'avg time: %0.3fs' % (sum(times) / len(times),)
reactor.callWhenRunning(main)
reactor.run()
этот код также работает лучше, чем любое другое опубликованное решение (отредактировано после того, как я закрыл некоторые вещи, которые использовали большую пропускную способность):
Success: ('http://www.google.com/', 8135)
Success: ('http://www.lycos.com/', 29996)
Success: ('http://www.bing.com/', 28611)
Success: ('http://www.altavista.com/', 8378)
Success: ('http://achewood.com/', 15043)
ran in 0.518s
Success: ('http://www.google.com/', 8135)
Success: ('http://www.lycos.com/', 30349)
Success: ('http://www.bing.com/', 28611)
Success: ('http://www.altavista.com/', 8378)
Success: ('http://achewood.com/', 15043)
ran in 0.461s
Success: ('http://www.google.com/', 8135)
Success: ('http://www.lycos.com/', 30033)
Success: ('http://www.bing.com/', 28611)
Success: ('http://www.altavista.com/', 8378)
Success: ('http://achewood.com/', 15043)
ran in 0.435s
Success: ('http://www.google.com/', 8117)
Success: ('http://www.lycos.com/', 30349)
Success: ('http://www.bing.com/', 28611)
Success: ('http://www.altavista.com/', 8378)
Success: ('http://achewood.com/', 15043)
ran in 0.449s
Success: ('http://www.google.com/', 8135)
Success: ('http://www.lycos.com/', 30349)
Success: ('http://www.bing.com/', 28611)
Success: ('http://www.altavista.com/', 8378)
Success: ('http://achewood.com/', 15043)
ran in 0.547s
avg time: 0.482s
и используя код Ника Т, сфальсифицированный, чтобы также дать в среднем пять и показать результат лучше:
Starting threaded reads:
...took 1.921520 seconds ([8117, 30070, 15043, 8386, 28611])
Starting threaded reads:
...took 1.779461 seconds ([8135, 15043, 8386, 30349, 28611])
Starting threaded reads:
...took 1.756968 seconds ([8135, 8386, 15043, 30349, 28611])
Starting threaded reads:
...took 1.762956 seconds ([8386, 8135, 15043, 29996, 28611])
Starting threaded reads:
...took 1.654377 seconds ([8117, 30349, 15043, 8386, 28611])
avg time: 1.775s
Starting sequential reads:
...took 1.389803 seconds ([8135, 30147, 28611, 8386, 15043])
Starting sequential reads:
...took 1.457451 seconds ([8135, 30051, 28611, 8386, 15043])
Starting sequential reads:
...took 1.432214 seconds ([8135, 29996, 28611, 8386, 15043])
Starting sequential reads:
...took 1.447866 seconds ([8117, 30028, 28611, 8386, 15043])
Starting sequential reads:
...took 1.468946 seconds ([8153, 30051, 28611, 8386, 15043])
avg time: 1.439s
и используя код Вай ИП Тунга:
Fetched 8117 from http://www.google.com/
Fetched 28611 from http://www.bing.com/
Fetched 8386 from http://www.altavista.com/
Fetched 30051 from http://www.lycos.com/
Fetched 15043 from http://achewood.com/
done in 0.704s
Fetched 8117 from http://www.google.com/
Fetched 28611 from http://www.bing.com/
Fetched 8386 from http://www.altavista.com/
Fetched 30114 from http://www.lycos.com/
Fetched 15043 from http://achewood.com/
done in 0.845s
Fetched 8153 from http://www.google.com/
Fetched 28611 from http://www.bing.com/
Fetched 8386 from http://www.altavista.com/
Fetched 30070 from http://www.lycos.com/
Fetched 15043 from http://achewood.com/
done in 0.689s
Fetched 8117 from http://www.google.com/
Fetched 28611 from http://www.bing.com/
Fetched 8386 from http://www.altavista.com/
Fetched 30114 from http://www.lycos.com/
Fetched 15043 from http://achewood.com/
done in 0.647s
Fetched 8135 from http://www.google.com/
Fetched 28611 from http://www.bing.com/
Fetched 8386 from http://www.altavista.com/
Fetched 30349 from http://www.lycos.com/
Fetched 15043 from http://achewood.com/
done in 0.693s
avg time: 0.715s
Я должен сказать, мне нравится, что последовательные выборки выполнены лучше для меня.
вот пример использования python Threads
. Другие потоковые примеры здесь запускают поток на url-адрес, который не очень дружелюбен, если он вызывает слишком много обращений для сервера (например, обычно для пауков много URL-адресов на одном хосте)
from threading import Thread
from urllib2 import urlopen
from time import time, sleep
WORKERS=1
urls = ['http://docs.python.org/library/threading.html',
'http://docs.python.org/library/thread.html',
'http://docs.python.org/library/multiprocessing.html',
'http://docs.python.org/howto/urllib2.html']*10
results = []
class Worker(Thread):
def run(self):
while urls:
url = urls.pop()
results.append((url, urlopen(url).read()))
start = time()
threads = [Worker() for i in range(WORKERS)]
any(t.start() for t in threads)
while len(results)<40:
sleep(0.1)
print time()-start
ПРИМЕЧАНИЕ: время, указанное здесь, для 40 URL-адресов и будет зависеть от скорости вашего интернет-соединения и задержки на сервере. Находясь в Австралии, мой пинг > 300ms
С WORKERS=1
потребовалось 86 секунд, чтобы запустить
С WORKERS=4
потребовалось 23 секунды, чтобы запустить
с WORKERS=10
потребовалось 10 секунд, чтобы запустить
таким образом, загрузка 10 потоков в 8,6 раза быстрее, чем один поток.
вот обновленная версия, которая использует очередь. Есть, по крайней мере, пара преимуществ.
1. URL-адреса запрашиваются в том порядке, в котором они отображаются в списке
2. Может использовать q.join()
чтобы определить, когда запросы будут завершены
3. Результаты хранятся в том же порядке, что и список url
from threading import Thread
from urllib2 import urlopen
from time import time, sleep
from Queue import Queue
WORKERS=10
urls = ['http://docs.python.org/library/threading.html',
'http://docs.python.org/library/thread.html',
'http://docs.python.org/library/multiprocessing.html',
'http://docs.python.org/howto/urllib2.html']*10
results = [None]*len(urls)
def worker():
while True:
i, url = q.get()
# print "requesting ", i, url # if you want to see what's going on
results[i]=urlopen(url).read()
q.task_done()
start = time()
q = Queue()
for i in range(WORKERS):
t=Thread(target=worker)
t.daemon = True
t.start()
for i,url in enumerate(urls):
q.put((i,url))
q.join()
print time()-start
фактическое ожидание, вероятно, не в urllib2
но на сервере и / или в вашем сетевом подключении к серверу.
есть 2 способа ускорить это.
- сохранить соединение (см. Этот вопрос о том, как это сделать:Python urllib2 с keep alive)
- использовать соединения multiplle, вы можете использовать потоки или асинхронный подход, как Аарон Галлахер предложил. Для этого просто используйте любой пример резьбы, и вы должны делать все хорошо :) вы также можно использовать
multiprocessing
lib, чтобы сделать вещи довольно легко.
большинство ответов сосредоточены на получении нескольких страниц с разных серверов одновременно (threading), но не при повторном использовании уже открытого HTTP-соединения. Если OP делает несколько запросов к одному и тому же серверу/сайту.
в urlib2 создается отдельное соединение с каждым запросом, которое влияет на производительность и, как следствие, более медленную скорость выборки страниц. urllib3 решает эту проблему с помощью пула соединений. Подробнее здесь urllib3 [также thread-safe]
есть еще запросы библиотека HTTP, использующая urllib3
Это в сочетании с резьбой должно увеличить скорость извлечения страниц
В настоящее время есть отличный Python lib, который делает это для вас называется запросы.
используйте стандартный api запросов, если вы хотите решение на основе потоков или асинхронного api (используя gevent под капотом), если вы хотите решение на основе неблокирующего ввода-вывода.
поскольку этот вопрос был опубликован, похоже, что доступна абстракция более высокого уровня,ThreadPoolExecutor
:
https://docs.python.org/3/library/concurrent.futures.html#threadpoolexecutor-example
пример оттуда вставлен здесь для удобства:
import concurrent.futures
import urllib.request
URLS = ['http://www.foxnews.com/',
'http://www.cnn.com/',
'http://europe.wsj.com/',
'http://www.bbc.co.uk/',
'http://some-made-up-domain.com/']
# Retrieve a single page and report the url and contents
def load_url(url, timeout):
with urllib.request.urlopen(url, timeout=timeout) as conn:
return conn.read()
# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# Start the load operations and mark each future with its URL
future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
except Exception as exc:
print('%r generated an exception: %s' % (url, exc))
else:
print('%r page is %d bytes' % (url, len(data)))
там же map
что, я думаю, упрощает код: https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Executor.map
извлечение веб-страниц, очевидно, займет некоторое время, поскольку вы не получаете доступа ни к чему локальному. Если у вас есть несколько для доступа, вы можете использовать threading
модуль для запуска пары сразу.
вот очень грубый пример
import threading
import urllib2
import time
urls = ['http://docs.python.org/library/threading.html',
'http://docs.python.org/library/thread.html',
'http://docs.python.org/library/multiprocessing.html',
'http://docs.python.org/howto/urllib2.html']
data1 = []
data2 = []
class PageFetch(threading.Thread):
def __init__(self, url, datadump):
self.url = url
self.datadump = datadump
threading.Thread.__init__(self)
def run(self):
page = urllib2.urlopen(self.url)
self.datadump.append(page.read()) # don't do it like this.
print "Starting threaded reads:"
start = time.clock()
for url in urls:
PageFetch(url, data2).start()
while len(data2) < len(urls): pass # don't do this either.
print "...took %f seconds" % (time.clock() - start)
print "Starting sequential reads:"
start = time.clock()
for url in urls:
page = urllib2.urlopen(url)
data1.append(page.read())
print "...took %f seconds" % (time.clock() - start)
for i,x in enumerate(data1):
print len(data1[i]), len(data2[i])
Это был выход, когда я запустил его:
Starting threaded reads:
...took 2.035579 seconds
Starting sequential reads:
...took 4.307102 seconds
73127 19923
19923 59366
361483 73127
59366 361483
захват данных из потока путем добавления в список, вероятно, не рекомендуется (очередь была бы лучше), но это иллюстрирует, что есть разница.
вот стандартное библиотечное решение. Это не так быстро, но он использует меньше памяти, чем резьбовые решения.
try:
from http.client import HTTPConnection, HTTPSConnection
except ImportError:
from httplib import HTTPConnection, HTTPSConnection
connections = []
results = []
for url in urls:
scheme, _, host, path = url.split('/', 3)
h = (HTTPConnection if scheme == 'http:' else HTTPSConnection)(host)
h.request('GET', '/' + path)
connections.append(h)
for h in connections:
results.append(h.getresponse().read())
кроме того, если большинство ваших запросов к одному и тому же хосту, то повторное использование одного и того же http-соединения, вероятно, поможет больше, чем делать вещи параллельно.
пожалуйста, найдите Python network benchmark script для идентификации медленности одиночного соединения:
"""Python network test."""
from socket import create_connection
from time import time
try:
from urllib2 import urlopen
except ImportError:
from urllib.request import urlopen
TIC = time()
create_connection(('216.58.194.174', 80))
print('Duration socket IP connection (s): {:.2f}'.format(time() - TIC))
TIC = time()
create_connection(('google.com', 80))
print('Duration socket DNS connection (s): {:.2f}'.format(time() - TIC))
TIC = time()
urlopen('http://216.58.194.174')
print('Duration urlopen IP connection (s): {:.2f}'.format(time() - TIC))
TIC = time()
urlopen('http://google.com')
print('Duration urlopen DNS connection (s): {:.2f}'.format(time() - TIC))
и пример результатов с Python 3.6:
Duration socket IP connection (s): 0.02
Duration socket DNS connection (s): 75.51
Duration urlopen IP connection (s): 75.88
Duration urlopen DNS connection (s): 151.42
Python 2.7.13 имеет очень похожие результаты.
в этом случае DNS и urlopen медлительность легко идентифицируются.