2012-04-22 15 views
6

Estaba buscando una manera de optimizar mi código cuando escuché algunas cosas buenas sobre los hilos y urllib3. Aparentemente, las personas no están de acuerdo sobre qué solución es la mejor.Urllib2 & BeautifulSoup: Buena pareja pero demasiado lento - urllib3 & threads?

El problema con mi script a continuación es el tiempo de ejecución: ¡tan lento!

Paso 1: I buscar a esta página http://www.cambridgeesol.org/institutions/results.php?region=Afghanistan&type=&BULATS=on

Paso 2: I analizar la página con BeautifulSoup

Paso 3: puse los datos en un doc excel

Paso 4: Lo vuelvo a hacer, una y otra vez para todos los países de mi lista (lista grande) (Estoy cambiando "Afganistán" en la url a otro país)

Aquí está mi código:

ws = wb.add_sheet("BULATS_IA") #We add a new tab in the excel doc 
    x = 0 # We need x and y for pulling the data into the excel doc 
    y = 0 
    Countries_List = ['Afghanistan','Albania','Andorra','Argentina','Armenia','Australia','Austria','Azerbaijan','Bahrain','Bangladesh','Belgium','Belize','Bolivia','Bosnia and Herzegovina','Brazil','Brunei Darussalam','Bulgaria','Cameroon','Canada','Central African Republic','Chile','China','Colombia','Costa Rica','Croatia','Cuba','Cyprus','Czech Republic','Denmark','Dominican Republic','Ecuador','Egypt','Eritrea','Estonia','Ethiopia','Faroe Islands','Fiji','Finland','France','French Polynesia','Georgia','Germany','Gibraltar','Greece','Grenada','Hong Kong','Hungary','Iceland','India','Indonesia','Iran','Iraq','Ireland','Israel','Italy','Jamaica','Japan','Jordan','Kazakhstan','Kenya','Kuwait','Latvia','Lebanon','Libya','Liechtenstein','Lithuania','Luxembourg','Macau','Macedonia','Malaysia','Maldives','Malta','Mexico','Monaco','Montenegro','Morocco','Mozambique','Myanmar (Burma)','Nepal','Netherlands','New Caledonia','New Zealand','Nigeria','Norway','Oman','Pakistan','Palestine','Papua New Guinea','Paraguay','Peru','Philippines','Poland','Portugal','Qatar','Romania','Russia','Saudi Arabia','Serbia','Singapore','Slovakia','Slovenia','South Africa','South Korea','Spain','Sri Lanka','Sweden','Switzerland','Syria','Taiwan','Thailand','Trinadad and Tobago','Tunisia','Turkey','Ukraine','United Arab Emirates','United Kingdom','United States','Uruguay','Uzbekistan','Venezuela','Vietnam'] 
    Longueur = len(Countries_List) 



    for Countries in Countries_List: 
     y = 0 

     htmlSource = urllib.urlopen("http://www.cambridgeesol.org/institutions/results.php?region=%s&type=&BULATS=on" % (Countries)).read() # I am opening the page with the name of the correspondant country in the url 
     s = soup(htmlSource) 
     tableGood = s.findAll('table') 
     try: 
      rows = tableGood[3].findAll('tr') 
      for tr in rows: 
       cols = tr.findAll('td') 
       y = 0 
       x = x + 1 
       for td in cols: 
        hum = td.text 
        ws.write(x,y,hum) 
        y = y + 1 
        wb.save("%s.xls" % name_excel) 

     except (IndexError): 
      pass 

así que sé que no todo es perfecto, pero estoy deseando volver a aprender cosas nuevas en Python! El script es muy lento porque urllib2 no es tan rápido y BeautifulSoup. Para la sopa, creo que realmente no puedo hacerlo mejor, pero para urllib2, no lo hago.

EDITAR 1: Multiprocessing useless with urllib2? Parece ser interesante en mi caso. ¿Qué piensan ustedes acerca de esta posible solución?

# Make sure that the queue is thread-safe!! 

def producer(self): 
    # Only need one producer, although you could have multiple 
    with fh = open('urllist.txt', 'r'): 
     for line in fh: 
      self.queue.enqueue(line.strip()) 

def consumer(self): 
    # Fire up N of these babies for some speed 
    while True: 
     url = self.queue.dequeue() 
     dh = urllib2.urlopen(url) 
     with fh = open('/dev/null', 'w'): # gotta put it somewhere 
      fh.write(dh.read()) 

EDIT 2: URLLIB3 Puede alguien decirme más cosas acerca de eso?

Re-use the same socket connection for multiple requests (HTTPConnectionPool and HTTPSConnectionPool) (with optional client-side certificate verification). https://github.com/shazow/urllib3

Por lo que yo estoy solicitando 122 veces el mismo sitio web para diferentes páginas, supongo reutilizar la misma conexión de socket puede ser interesante, ¿me equivoco? ¿No puede ser más rápido? ...

http = urllib3.PoolManager() 
r = http.request('GET', 'http://www.bulats.org') 
for Pages in Pages_List: 
    r = http.request('GET', 'http://www.bulats.org/agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=%s' % (Pages)) 
    s = soup(r.data) 
+0

Sí, su problema aquí es que la búsqueda es de un solo hilo. Pero si usa el multihilo, tendrá que hacer que el proceso de escritura para sobresalir sea seguro. Recomiendo scrapy, un framework de raspado para Python, que hace todo por ti. – WooParadog

+1

Muchas gracias, veré lo que Scrapy puede hacer por mí. Y urllib3 tampoco es una solución válida? :) Pero si hay alguna posibilidad de hacerlo más rápido sin usar scrapy, será mejor que yo. ¡Estoy aprendiendo Python así que me gustaría entender todo! –

+0

No debe abrir más de una conexión al mismo sitio web, creo que es más como un "acuerdo de caballero" –

Respuesta

9

Considere el uso de algo como workerpool. Con referencia al ejemplo Mass Downloader, combinado con urllib3 sería algo como:

import workerpool 
import urllib3 

URL_LIST = [] # Fill this from somewhere 

NUM_SOCKETS = 3 
NUM_WORKERS = 5 

# We want a few more workers than sockets so that they have extra 
# time to parse things and such. 

http = urllib3.PoolManager(maxsize=NUM_SOCKETS) 
workers = workerpool.WorkerPool(size=NUM_WORKERS) 

class MyJob(workerpool.Job): 
    def __init__(self, url): 
     self.url = url 

    def run(self): 
     r = http.request('GET', self.url) 
     # ... do parsing stuff here 


for url in URL_LIST: 
    workers.put(MyJob(url)) 

# Send shutdown jobs to all threads, and wait until all the jobs have been completed 
# (If you don't do this, the script might hang due to a rogue undead thread.) 
workers.shutdown() 
workers.wait() 

Es posible observar en los ejemplos Mass Downloader que hay múltiples maneras de hacer esto. Elegí este ejemplo en particular solo porque es menos mágico, pero cualquiera de las otras estrategias también es válida.

Descargo de responsabilidad: Soy el autor de ambos, urllib3 y workerpool.

+1

¡Woaw, gracias por esta súper idea! Voy a intentar eso :-) También estoy mirando Twisted, que parece ser bastante rápido también. ¿Cuál crees que es el mejor? –

+0

¡Woaw, gracias por esta súper idea! Voy a intentar eso :-) También estoy mirando Twisted, que parece ser bastante rápido también. ¿Cuál crees que es el mejor? Hum, y también otra pregunta (lo siento): ¿cómo puedo hacer si quiero hacer lo mismo para una página, pero con muchos POSTS diferentes en un formulario? Su solución parece estar adaptada para eso (urllib3) así que siéntete, échame una mano;) Aquí está la parte difícil del guión: http://pastebin.com/m9TAs2cj –

+0

@Carto_ Twisted es una buena solución también. También puede ver el uso de Solicitudes en lugar de urllib3, que agrega muchas funciones encima de urllib3, como las sesiones de cookies. – shazow

2

No creo que urllib o BeautifulSoup sea lento. Ejecuto tu código en mi máquina local con una versión modificada (eliminé las cosas de Excel). Tomó alrededor de 100 ms abrir la conexión, descargar el contenido, analizarlo e imprimirlo en la consola de un país.

10 ms es la cantidad total de tiempo que BeautifulSoup pasó para analizar el contenido e imprimir en la consola por país. Eso es lo suficientemente rápido.

Tampoco creo que usar Scrappy o Threading resuelva el problema. Porque el problema es expectativa que va a ser rápido.

Bienvenido al mundo de HTTP. Va a ser lento a veces, a veces será muy rápido.Pareja de conexión lenta razona

  • debido a la manipulación de su solicitud (retorno 404 veces)
  • resolución DNS,
  • apretón de manos HTTP,
  • su estabilidad de la conexión de su ISP,
  • su tasa de ancho de banda del servidor,
  • paquete de velocidad de pérdida

etc ..

No olvide, está tratando de hacer 121 solicitudes HTTP a un servidor en consecuencia y no sabe qué tipo de servidores tienen. También pueden prohibir su dirección IP debido a las llamadas consiguientes.

Eche un vistazo a Solicitudes lib. Lee su documentación. Si está haciendo esto para aprender más sobre Python, no salte directamente a Scrapy.

+0

¡Muchas gracias por todas estas informaciones! Por supuesto, Scrapy parece ser interesante, pero mi objetivo es aprender Python, así que necesito permanecer en Python :-) Necesito eliminar wb.save ("% s.xls"% name_excel) para el "para" también, fue bastante estúpido Y voy a echar un vistazo a las solicitudes de lib, como consejo :) –

+0

"Considere usar urllib3. Es compatible con la agrupación de conexiones y múltiples solicitudes concurrentes a través de procesos (no hilos). Debería resolver este problema. Tenga cuidado con la recolección de basura. piscinas si se contacta con muchos sitios diferentes, ya que cada sitio tiene su propio grupo ". En mi caso, puede ser interesante, ¿no? –

+0

Todavía no he usado url3, pero lo que has escrito es muy prometedor. El mayor paso en el aprendizaje es probar diferentes opciones, comprender los problemas y las posibles soluciones. También puede decir "aprender más y más" y encontrar la solución adecuada a su problema. Pero primero entiende el "problema" muy bien. Las solicitudes tienen una buena API sobre URLLib, así que eche un vistazo al código fuente también. Sigue jugando ... –

0

Hola chicos,

algunas noticias del problema! ¡He encontrado este script, que puede ser útil! De hecho, lo estoy probando y es prometedor (6.03 ejecutar el script a continuación).

Mi idea es encontrar una manera de mezclar eso con urllib3. En effet, estoy solicitando el mismo host muchas veces.

The PoolManager will take care of reusing connections for you whenever you request the same host. this should cover most scenarios without significant loss of efficiency, but you can always drop down to a lower level component for more granular control. (urrlib3 doc site)

De todos modos, parece ser muy interesante y si no puedo ver todavía cómo mezclar estas dos funcionalidades (urllib3 y el guión enhebrado a continuación), que supongo que es factible! :-)

Muchas gracias por tomarse el tiempo para echarme una mano con eso, ¡huele bien!

import Queue 
import threading 
import urllib2 
import time 
from bs4 import BeautifulSoup as BeautifulSoup 



hosts = ["http://www.bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All", "http://www.bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=1", "http://www.bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=2", "http://www.bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=3", "http://www.bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=4", "http://www.bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=5", "http://www.bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=6"] 

queue = Queue.Queue() 
out_queue = Queue.Queue() 

class ThreadUrl(threading.Thread): 
    """Threaded Url Grab""" 
    def __init__(self, queue, out_queue): 
     threading.Thread.__init__(self) 
     self.queue = queue 
     self.out_queue = out_queue 

    def run(self): 
     while True: 
      #grabs host from queue 
      host = self.queue.get() 

      #grabs urls of hosts and then grabs chunk of webpage 
      url = urllib2.urlopen(host) 
      chunk = url.read() 

      #place chunk into out queue 
      self.out_queue.put(chunk) 

      #signals to queue job is done 
      self.queue.task_done() 

class DatamineThread(threading.Thread): 
    """Threaded Url Grab""" 
    def __init__(self, out_queue): 
     threading.Thread.__init__(self) 
     self.out_queue = out_queue 

    def run(self): 
     while True: 
      #grabs host from queue 
      chunk = self.out_queue.get() 

      #parse the chunk 
      soup = BeautifulSoup(chunk) 
      #print soup.findAll(['table']) 

      tableau = soup.find('table') 
     rows = tableau.findAll('tr') 
     for tr in rows: 
      cols = tr.findAll('td') 
      for td in cols: 
        texte_bu = td.text 
        texte_bu = texte_bu.encode('utf-8') 
        print texte_bu 

      #signals to queue job is done 
      self.out_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, out_queue) 
     t.setDaemon(True) 
     t.start() 

    #populate queue with data 
    for host in hosts: 
     queue.put(host) 

    for i in range(5): 
     dt = DatamineThread(out_queue) 
     dt.setDaemon(True) 
     dt.start() 


    #wait on the queue until everything has been processed 
    queue.join() 
    out_queue.join() 

main() 
print "Elapsed Time: %s" % (time.time() - start) 
+1

Hum, cuando agrego más páginas a [hosts] parece no para trabajar tan bien ... ¡No sé por qué! Por cierto, ¿cómo incrementar una var globalmente (quiero decir en todos los hilos, como global para las funciones)? –

+1

¡Hum, parece que no funciona a la perfección! Tengo algunos errors: RuntimeError: tamaño cambiado de diccionario durante la iteración Excepción: A intento de sobrescribir la celda: nombre de la hoja = u'BULATS_IA_PARSED 'rowx = 107 colx = 3 –

Cuestiones relacionadas