سؤال

لدي السيناريو الذي جلب العديد من صفحات الويب و يوزع المعلومات.

(على سبيل المثال يمكن أن ينظر إليه في http://bluedevilbooks.com/search/?DEPT=MATH&CLASS=103&SEC=01 )

ركضت cProfile على ذلك ، كما يفترض ، urlopen يستغرق الكثير من الوقت.هل هناك طريقة لجلب الصفحات بشكل أسرع ؟ أو وسيلة لجلب عدة صفحات في وقت واحد ؟ سأفعل كل ما هو أبسط ، أنا جديدة على الثعبان و تطوير الويب.

شكرا مقدما!:)

تحديث:لدي وظيفة تسمى fetchURLs(), التي يمكنني استخدامها لجعل مجموعة من عناوين url أريد حتى شيء مثل urls = fetchURLS().عناوين url جميع ملفات XML من أمازون و eBay واجهات برمجة التطبيقات (الذي يحيرني لماذا يستغرق وقتا طويلا لتحميل, ربما الإستضافة هو بطيء؟)

ما تحتاج إلى القيام به هو تحميل كل رابط ، قراءة كل صفحة وإرسال تلك البيانات إلى آخر جزء من السيناريو الذي سيتم تحليل و عرض البيانات.

علما بأنني لا أستطيع فعل الجزء الأخير حتى كل الصفحات تم المنال ، المسألة.

أيضا ، مضيفي حدود لي إلى 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)
هل كانت مفيدة؟

المحلول

تعديل: أقوم بتوسيع الإجابة لتشمل مثالًا أكثر تلميعًا. لقد وجدت الكثير من العداء والتضليل في هذا المنشور فيما يتعلق بالخيوط مقابل I/O ASYNC. لذلك أقوم أيضًا بإضافة المزيد من الحجة لدحض مطالبة غير صالحة معينة. آمل أن يساعد هذا الأشخاص على اختيار الأداة المناسبة للوظيفة المناسبة.

هذا هو DUP لسؤال منذ 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 أسطر فقط ، والتي يمكن أن تنهار في سطر واحد إذا كنت عدوانيًا. لا أعتقد أن أي شخص يمكن أن يبرر ادعائه بأن هذا معقد ولا يمكن صيانته.

لسوء الحظ ، تحتوي معظم رمز الخيوط الأخرى المنشورة هنا على بعض العيوب. يقوم العديد منهم بالاقتراع النشط لانتظار الكود. join() هي طريقة أفضل لمزامنة الكود. أعتقد أن هذا الرمز قد تحسن على جميع أمثلة الخيوط حتى الآن.

اتصال الحفاظ

قد يكون اقتراح Wolph حول استخدام اتصال Keep-Alive مفيدًا جدًا إذا كانت كل عناوين URL تشير إلى نفس الخادم.

ملتوية

آرون غالاغر هو عشاق twisted الإطار وهو معاد أي أشخاص يقترحون الخيط. للأسف الكثير من ادعاءاته هي معلومات خاطئة. على سبيل المثال ، قال "-1 لاقتراح المواضيع. هذا هو IO مرتبط ؛ الخيوط عديمة الفائدة هنا." هذا خلافًا للأدلة حيث أظهرت كلا من Nick T مكسبًا للسرعة من الخيط باستخدام. في الواقع ، يتمتع تطبيق I/O Bound بأكبر قدر من مكسبه من استخدام مؤشر ترابط Python (مقابل عدم وجود مكسب في تطبيق CPU Bound). يظهر انتقادات هارون المضللة على الموضوع أنه مرتبك إلى حد ما بشأن البرمجة المتوازية بشكل عام.

الأداة المناسبة للوظيفة المناسبة

إنني أدرك جيدًا المشكلات المتعلقة بالبرمجة المتوازية باستخدام مؤشرات الترابط ، والبيثون ، و I/O Async وما إلى ذلك. كل أداة لديها إيجابيات وسلبيات. لكل موقف هناك أداة مناسبة. أنا لست ضد ملتوية (على الرغم من أنني لم أنشر بنفسي). لكنني لا أعتقد أننا نستطيع أن نقول أن الخيط سيء وأن الملتوية جيدة في جميع المواقف.

على سبيل المثال ، إذا كان متطلبات البروتوكول الاختياري هو إحضار 10،000 موقع على شبكة الإنترنت بالتوازي ، فسيكون من الأفضل. لن يكون الخيوط ملائمة (ما لم يكن مع بايثون بدون تكديس).

معارضة هارون للخيوط هي في الغالب تعميمات. فشل في إدراك أن هذه مهمة موازاة تافهة. كل مهمة مستقلة ولا تشارك الموارد. لذلك معظم هجومه لا ينطبق.

بالنظر إلى عدم وجود تبعية خارجية ، سأسميها الأداة الصحيحة للمهمة الصحيحة.

أداء

أعتقد أن معظم الناس سيوافقون على أن أداء هذه المهمة يعتمد إلى حد كبير على رمز الشبكات والخادم الخارجي ، حيث يجب أن يكون لأداء رمز النظام الأساسي تأثير ضئيل. ومع ذلك ، فإن معيار هارون يظهر زيادة سرعة 50 ٪ على الرمز الخيوط. أعتقد أنه من الضروري الاستجابة لهذا المكاسب السرعة الظاهرة.

في رمز نيك ، هناك عيب واضح تسبب في عدم الكفاءة. ولكن كيف تشرح زيادة سرعة 233ms على الكود الخاص بي؟ أعتقد أن المشجعين الملتويين سوف يمتنعون عن القفز إلى الختام ليعزو هذا إلى كفاءة ملتوية. بعد كل شيء ، هناك قدر كبير من المتغير خارج رمز النظام ، مثل أداء الخادم البعيد والشبكة والتخزين المؤقت وتطبيق الفرق بين urllib2 وعميل الويب الملتوي وما إلى ذلك.

فقط للتأكد من أن خيوط 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

لا يدعم اختباري استنتاج هارون بأن الخيوط أبطأ باستمرار من I/O غير المتزامن بهامش قابل للقياس. بالنظر إلى عدد المتغيرات المعنية ، يجب أن أقول إن هذا ليس اختبارًا صحيحًا لقياس الفرق المنهجي للأداء بين I/O ASYNC وخيوطه.

نصائح أخرى

استخدام الملتوية!يجعل هذا النوع من الشيء سخيف سهلة بالمقارنة إلى استخدام مؤشرات الترابط.

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

واستخدام نيك T رمز بتزوير أيضا إعطاء المتوسط من خمسة وتظهر إخراج أفضل:

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 وستعتمد كثيرًا على سرعة اتصال الإنترنت الخاص بك والكمون للخادم. في أستراليا ، فإن ping الخاص بي هو 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 ولكن في الخادم و/أو اتصال الشبكة الخاص بك إلى الخادم.

هناك طريقتان لتسريع هذا الأمر.

  1. حافظ على الاتصال حيًا (انظر هذا السؤال حول كيفية القيام بذلك: Python urllib2 مع Keep على قيد الحياة)
  2. استخدم اتصالات مضاعفة ، يمكنك استخدام مؤشرات الترابط أو نهج Async كما اقترح Aaron Gallagher. لذلك ، ما عليك سوى استخدام أي مثال لخيوط الخيوط ويجب أن تفعل بشكل جيد :) يمكنك أيضًا استخدام multiprocessing ليب لجعل الأمور سهلة للغاية.

ركزت معظم الإجابات على جلب صفحات متعددة من خوادم مختلفة في نفس الوقت (الخيوط) ولكن ليس على إعادة استخدام اتصال HTTP المفتوح بالفعل. إذا كان OP يقوم بطلب متعددة لنفس الخادم/الموقع.

في urlib2 يتم إنشاء اتصال منفصل مع كل طلب يؤثر على الأداء ونتيجة لذلك معدل أبطأ من صفحات الجلب. URLLIB3 يحل هذه المشكلة باستخدام تجمع اتصال. يمكن قراءة المزيد هنا urllib3 أيضا آمن الخيط

يوجد ايضا الطلبات مكتبة HTTP التي تستخدم urllib3

هذا جنبا إلى جنب مع الخيوط يجب أن يزيد من سرعة جلب الصفحات

في الوقت الحاضر ، هناك Python lib ممتاز يقوم بذلك من أجلك يسمى الطلبات.

استخدم API القياسي للطلبات إذا كنت تريد حلًا استنادًا إلى مؤشرات الترابط أو واجهة برمجة تطبيقات ASYNC (باستخدام Gevent تحت الغطاء) إذا كنت تريد حلًا استنادًا إلى IO غير المحظورة.

منذ نشر هذا السؤال ، يبدو أن هناك تجريدًا أعلى من المستوى المتاح ، 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

شعاع يوفر طريقة أنيقة للقيام بذلك (في كل من Python 2 و Python 3). راي هي مكتبة لكتابة بيثون متوازية وموزعة.

ببساطة تحديد 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 نفسه أكثر من القيام بالأشياء بالتوازي.

يرجى الاطلاع على نص معايير شبكة Python للحصول على تحديد بطء الاتصال الفردي:

"""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

بيثون 2.7.13 له نتائج مماثلة للغاية.

في هذه الحالة ، يتم التعرف بسهولة على بطء DNS وعنوان URLOPEN.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top