Как я могу ускорить выборку страниц с помощью urllib2 в python?

StackOverflow https://stackoverflow.com/questions/3490173

Вопрос

У меня есть скрипт, который извлекает несколько веб-страниц и анализирует информацию.

(Пример можно посмотреть по адресу http://bluedevilbooks.com/search/?DEPT=MATH&CLASS=103&SEC=01 )

Я запустил cProfile на нем, и, как я и предполагал, urlopen отнимает много времени.Есть ли способ быстрее извлекать страницы?Или способ получить сразу несколько страниц?Я сделаю все, что будет проще всего, поскольку я новичок в python и веб-разработке.

Заранее спасибо!:)

Обновить:У меня есть функция, которая называется fetchURLs(), который я использую для создания массива нужных мне URL-адресов итак, что-то вроде urls = fetchURLS().Все URL-адреса представляют собой XML-файлы из API Amazon и eBay (что меня смущает относительно того, почему загрузка занимает так много времени, может быть, мой веб-хостинг работает медленно?)

Что мне нужно сделать, это загрузить каждый 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)
Это было полезно?

Решение

РЕДАКТИРОВАТЬ: Я расширяю ответ, чтобы включить более полированный пример. Я нашел много враждебности и дезинформации в этом посте относительно резьбы VS Async ввода / вывода. Поэтому я также добавляю больше аргументов, чтобы опровергнуть определенные неверные претензии. Я надеюсь, что это поможет людям выбрать правильный инструмент для правильной работы.

Это дуб к вопросу 3 дня назад.

Python Urllib2.open медленно, нужен лучший способ прочитать несколько 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() Лучший способ синхронизировать код. Я думаю, что этот код до сих пор улучшил все примеры резьбы.

Соединение

Предложение Вольфа об использовании CONTE-ALIVE CONNECTION может быть очень полезно, если все ваши URL-адреса указывают на тот же сервер.

скрученный

Аарон Галлахер - фанаты twisted Рамки и он враждебны любым людям, которые предлагают нить. К сожалению, много его претензий дезинформация. Например, он сказал «-1 для предлагающих потоки. Это IO-связанные; здесь нити бесполезны здесь». Это противоречит доказательствам как ник, и я продемонстрировал усиление скорости от используемого потока. На самом деле, приложение, связанное с I / O, больше всего усиления от использования потока Python (VS без усиления в приложении CPU CR). Бюзорская критика Аарона на нить показывает, что он скорее путают о параллельном программировании в целом.

Правильный инструмент для правильной работы

Я хорошо знаю, что проблемы относятся к параллельному программированию с использованием нитей, Python, Async ввода / вывода и т. Д. Каждый инструмент имеет свои плюсы и минусы. Для каждой ситуации есть соответствующий инструмент. Я не против скрученных (хотя я сам не развернул себя). Но я не верю, что мы можем прелюбнее сказать, что нить плохая и скручена, хорошая во всех ситуациях.

Например, если требование ОП состоит в том, чтобы получить 10 000 веб-сайта параллельно, Async ввод / вывод будет предпочтительным. Резьба не будет упрощена (если, может быть, с помощью Staterless Python).

Оппозиция Аарона нитям в основном обобщена. Он не сможет признать, что это тривиальное распараллелирование задачей. Каждая задача независима и не разделяет ресурсы. Поэтому большинство его атаки не применяются.

Учитывая мой код не имеет внешней зависимости, я позвоню правильному инструменту для правильной работы.

Спектакль

Я думаю, что большинство людей согласится с тем, что производительность этой задачи в значительной степени зависит от сетевого кода и внешнего сервера, где производительность кода платформы должна иметь незначительный эффект. Однако ориентир Aaron показать увеличение скорости 50% над резьбовым кодом. Я думаю, что необходимо ответить на эту очевидное усиление скорости.

В кодексе Ника есть очевидный недостаток, вызванный неэффективностью. Но как вы объясните скорость скорости 233 мс по моему коду? Я думаю, что даже скрученные фанаты воздержится от прыжки в заключение, чтобы приписать это эффективность скрученных. В конце концов, в конце концов, огромное количество переменной за пределами системного кода, например, производительность, сеть, кэширование, кэширование и различие между Urllib2 и Verveded Web Clience.

Просто чтобы повлиять на резьбу Python не понесет огромное количество неэффективности, я делаю быстрый ориентир для создания 5 ниток, а затем 500 потоков. Мне вполне удобно сказать, что накладные расходы нереста 5 нить незначительны и не могут объяснить разницу скорости 233 мс.

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

Мое тестирование не поддерживает вывод Aaron, что резьба последовательно медленнее, чем async ввод / вывод путем измеримого поля. Учитывая количество вовлеченных переменных, я должен сказать, что это не является действительным тестом для измерения систематической разности производительности между Async I / O и Threading.

Другие советы

Использование скрученный!Это делает подобные вещи абсурдно простыми по сравнению, скажем, с использованием потоков.

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, и будет сильно будет зависеть от скорости вашего интернет-соединения и задержки на сервер. Будучи в Австралии, мой пинг> 300 мс

С 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 способа ускорения этого.

  1. Держите соединение живым (см. Этот вопрос о том, как это сделать: Python Urllib2 с сохранением живым)
  2. Используйте MultiLLELED-соединения, вы можете использовать потоки или асинхронный подход, поскольку предлагается Aaron Gallagher. Для этого просто используйте любой пример резьбы, и вы должны делать хорошо :) Вы также можете использовать multiprocessing lib, чтобы сделать все довольно легко.

Большинство ответов были сосредоточены на получении нескольких страниц с разных серверов одновременно (резьба), но не на повторном использовании уже открытого HTTP-соединения. Если OP делает несколько запросов на тот же сервер / сайт.

В URLIB2 отдельное соединение создается с каждым запросом, который влияет на производительность и и в результате более медленнее скорость получения страниц. URLLIB3 решает эту проблему, используя соединительный пул. Можно прочитать здесь здесь Urllib3. Также безрезультатно

Есть также Запросы HTTP-библиотека, которая использует Urllib3

Это в сочетании с резьбой следует увеличить скорость получения страниц

В наше время есть отличный Python lib, которые сделают это для вас Запросы.

Используйте стандартные API запросов, если хотите решения на основе потоков или ASYNC API (используя Gevent под капотом), если вы хотите, чтобы решения на основе неблокирующегося IO.

Так как этот вопрос был опубликован, похоже, что есть доступная абстракция более высокого уровня, ThreadPoolExecutor:

https://docs.cython.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.cython.org/3/Library/concurrent.futures.html#concurrent.futures.executor.map.map.map.map.map.map.map.

Рентген Предлагает элегантный способ сделать это (как в Python 2, так и в Python 3). Ray - это библиотека для написания параллельного и распределенного Python.

Просто определить fetch функция с @ray.remote декоратор. Тогда вы можете получить URL на заднем плане, позвонив fetch.remote(url).

import ray
import sys

ray.init()

@ray.remote
def fetch(url):
    if sys.version_info >= (3, 0):
        import urllib.request
        return urllib.request.urlopen(url).read()
    else:
        import urllib2
        return urllib2.urlopen(url).read()

urls = ['https://en.wikipedia.org/wiki/Donald_Trump',
        'https://en.wikipedia.org/wiki/Barack_Obama',
        'https://en.wikipedia.org/wiki/George_W._Bush',
        'https://en.wikipedia.org/wiki/Bill_Clinton',
        'https://en.wikipedia.org/wiki/George_H._W._Bush']

# Fetch the webpages in parallel.
results = ray.get([fetch.remote(url) for url in urls])

Если вы также хотите обработать веб-страницы параллельно, вы можете либо поставить код обработки непосредственно в fetch, или вы можете определить новую удаленную функцию и составить их вместе.

@ray.remote
def process(html):
    tokens = html.split()
    return set(tokens)

# Fetch and process the pages in parallel.
results = []
for url in urls:
    results.append(process.remote(fetch.remote(url)))
results = ray.get(results)

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

urls = 100 * urls  # Pretend we have a long list of URLs.
results = []

in_progress_ids = []

# Start pulling 10 URLs in parallel.
for _ in range(10):
    url = urls.pop()
    in_progress_ids.append(fetch.remote(url))

# Whenever one finishes, start fetching a new one.
while len(in_progress_ids) > 0:
    # Get a result that has finished.
    [ready_id], in_progress_ids = ray.wait(in_progress_ids)
    results.append(ray.get(ready_id))
    # Start a new task.
    if len(urls) > 0:
        in_progress_ids.append(fetch.remote(urls.pop()))

Просмотреть Рэй Документация.

Получение веб-страниц, очевидно, займет некоторое время, так как вы не доступа к чему-либо местным. Если у вас есть несколько доступа, вы можете использовать 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-соединения, вероятно, поможет больше, чем делать вещи параллельно.

Пожалуйста, найдите сценарий Benchmark Network Python для одиночной идентификации Slowess соединения:

"""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 Slowness легко идентифицируются.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top