2016-07-09 47 views
13

Próbowałem zbudować skrobak z funkcją wielowątkowości ostatnich dwóch dni. Jakoś wciąż nie mogłem sobie z tym poradzić. Najpierw próbowałem regularnego podejścia wielowątkowego z modułem wątków, ale nie był szybszy niż przy użyciu pojedynczego wątku. Później dowiedziałem się, że żądania są blokowane, a wielowątkowość nie działa tak naprawdę. Tak więc kontynuowałem badania i dowiedziałem się o podbojach i geenie. Teraz mam testy z geventem i nadal nie jest to szybsze niż użycie pojedynczego wątku. Czy moje kodowanie jest złe?Żądania python z wielowątkowością

Oto odpowiednia część mojej klasy:

import gevent.monkey 
from gevent.pool import Pool 
import requests 

gevent.monkey.patch_all() 

class Test: 
    def __init__(self): 
     self.session = requests.Session() 
     self.pool = Pool(20) 
     self.urls = [...urls...] 

    def fetch(self, url): 

     try: 
      response = self.session.get(url, headers=self.headers) 
     except: 
      self.logger.error('Problem: ', id, exc_info=True) 

     self.doSomething(response) 

    def async(self): 
     for url in self.urls: 
      self.pool.spawn(self.fetch, url) 

     self.pool.join() 

test = Test() 
test.async() 
+0

Gdzie jest twój import? Czy próbowałeś także modułu 'multiprocessing'? – Will

+0

Dodałem import. Przepraszam, nie sądziłem, że będzie to konieczne. Nie próbowałem wieloprocesowości, ale dlaczego gevent nie miałby działać? – krypt

+0

Nie ma problemu! Spróbuj zmienić 'gevent.monkey.patch_all()' na 'gevent.monkey.patch_all (httplib = True)'. Jeśli to pomoże, wyjaśnię to. – Will

Odpowiedz

17

Instalacja grequests module który współpracuje z gevent (requests nie jest przeznaczony dla asynchroniczny):

pip install grequests 

Następnie zmień kod na coś podobnego to:

import grequests 

class Test: 
    def __init__(self): 
     self.urls = [ 
      'http://www.example.com', 
      'http://www.google.com', 
      'http://www.yahoo.com', 
      'http://www.stackoverflow.com/', 
      'http://www.reddit.com/' 
     ] 

    def exception(self, request, exception): 
     print "Problem: {}: {}".format(request.url, exception) 

    def async(self): 
     results = grequests.map((grequests.get(u) for u in self.urls), exception_handler=self.exception, size=5) 
     print results 

test = Test() 
test.async() 

To jest officially recommended w projekcie requests:

Blocking Or Non-Blocking?

With the default Transport Adapter in place, Requests does not provide any kind of non-blocking IO. The Response.content property will block until the entire response has been downloaded. If you require more granularity, the streaming features of the library (see Streaming Requests) allow you to retrieve smaller quantities of the response at a time. However, these calls will still block.

If you are concerned about the use of blocking IO, there are lots of projects out there that combine Requests with one of Python's asynchronicity frameworks. Two excellent examples are grequests and requests-futures .

Korzystając z tej metody daje mi zauważalne zwiększenie wydajności z 10 adresami: 0.877s vs 3.852s z oryginalnej metody.

+0

W moim środowisku testowym wysyłam żądania do 88 adresów URL w tej samej domenie. Wykonanie jednego procesu trwa około 60 sekund. Korzystanie z geowizji trwa około 60 sekund. Niestety, przy użyciu podróbek jest to sittl ~ 60 sekund. Znacznie wolniej niż 0,8 s na 10 adresów URL. Czy może to być spowodowane ograniczeniem serwera docelowego? – krypt

+0

To całkowicie możliwe. Czy mógłbyś pokazać swój nowy kod "podróbki" w edycji pod swoim oryginalnym wpisem? Przetestuj serwer za pomocą 'ab -c 10 -n 100 ' (ApacheBench). Moje testowe adresy URL to wszystkie główne, wydajne witryny. Możesz również dodać 'size = 5' lub' size = 10' do 'map()', aby ograniczyć liczbę współbieżnych żądań, co może zwiększyć wydajność. – Will

+0

Zauważyłem, że uzyskuję znacznie lepszą wydajność przy niższych numerach "rozmiaru". – Will