IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Réseau/Web Python Discussion :

Threads & Cie - BeautifulSoup & urllib2 : couple sympathique mais lent !


Sujet :

Réseau/Web Python

  1. #1
    Candidat au Club
    Inscrit en
    Octobre 2010
    Messages
    9
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 9
    Points : 4
    Points
    4
    Par défaut Threads & Cie - BeautifulSoup & urllib2 : couple sympathique mais lent !
    Bonjour à tous et à toutes,


    Je suis actuellement quelque peu bloqué dans le développement d'un script qui a pour but de m'éviter de longues séances de copier-coller au bureau. Évidemment, c'est aussi une superbe occasion d'approfondir mes compétences (trop maigres à mon goût) en python.

    J'espère donc que quelques bonnes âmes charitables seront prêtes à me donner un léger coup de main sur ce coup ! Si bien évidemment j'aimerai beaucoup résoudre les problèmes exposés ci-dessous, si vous avez des remarques conseils quant à l'approche abordée par mon script ... je serais ravis de les lire

    Le concept du script:

    1°) Récupérer la source de beaucoup de pages web d'un même site (vraiment beaucoup)

    2°) Parser cette source HTML avec BeautifulSoup

    3°) Rentrer les données dans un classeur excel

    Tout fonctionne à merveille mais je me suis rendu compte que le tout était HORRIBLEMENT lent ! Dès lors, dans un but d'approfondissement de mes compétences en python, je me suis penché sur le concept de threads, et un peu aussi sur urrlib3.

    Voici donc le script que j'essaie d'optimiser:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
     
    import Queue
    import threading
    import urllib2
    from urllib2 import urlopen
    import time
    from bs4 import BeautifulSoup as BeautifulSoup
    from urllib3 import HTTPConnectionPool
    #from urllib3 import urlopen
    import xlwt
    import time
    import socket
     
    socket.setdefaulttimeout(5.0)
     
     
    class Retry(object):
        default_exceptions = (Exception,)
        def __init__(self, tries, exceptions=None, delay=0):
            """
            Decorator for retrying a function if exception occurs
            
            tries -- num tries 
            exceptions -- exceptions to catch
            delay -- wait between retries
            """
            self.tries = tries
            if exceptions is None:
                exceptions = Retry.default_exceptions
            self.exceptions =  exceptions
            self.delay = delay
     
        def __call__(self, f):
            def fn(*args, **kwargs):
                exception = None
                for _ in range(self.tries):
                    try:
                        return f(*args, **kwargs)
                    except self.exceptions, e:
                        print "Retry, exception: "+str(e)
                        time.sleep(self.delay)
                        exception = e
                #if no success after tries, raise last exception
                raise exception
            return fn
     
    @Retry(5)
    def open_url(source):
        print("Retrying to open and read the page")
        resp = urlopen(source).read()
        #resp = urllib3.urlopen(source)
        return resp
     
     
    hosts = ["http://www.bulats.org/agents/find-an-agent", "http://bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=1", "http://bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=2", "http://bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=3", "http://bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=4", "http://bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=5", "http://bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=6", "http://bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=7", "http://bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=8", "http://bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=9", "http://bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=10", "http://bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=11", "http://bulats.org//agents/find-an-agent?field_continent_tid=All&field_country_tid=All&page=12"]
     
    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
                chunk = open_url(host)
                #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):
            global x
            while True:
                #grabs host from queue
                chunk = self.out_queue.get()
     
                #parse the chunk
                soup = BeautifulSoup(chunk)
     
                tableau = soup.find('table')
    	    rows = tableau.findAll('tr')
    	    for tr in rows:
    	        cols = tr.findAll('td')
                    y = 0
                    x = x + 1
    	        for td in cols:
                        texte_bu = td.text
                        texte_bu = texte_bu.encode('utf-8')
                        print("1")
                        ws.write(x,y,td.text)
                        y = y + 1
                wb.save("BULATS_TTTTT.xls")
     
     
     
                #signals to queue job is done
                self.out_queue.task_done()
                break
     
    start = time.time()
    def main():
     
        #spawn a pool of threads, and pass them queue instance
        for i in range(13):
            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(13):
            dt = DatamineThread(out_queue)
            dt.setDaemon(True)
            dt.start()
     
     
        #wait on the queue until everything has been processed
        queue.join()
        out_queue.join()
     
    global x
    x = 0
    wb = xlwt.Workbook(encoding='utf-8')
    ws = wb.add_sheet("BULATS_IA_PARSED")
     
    main()
    print "Elapsed Time: %s" % (time.time() - start)
    Le tout me renvoie souvent deux types d'erreurs :

    Une erreur : RuntimeError: dictionary changed size during iteration
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    Exception in thread Thread-18:
    Traceback (most recent call last):
      File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 530, in __bootstrap_inner
      File "parsing_threads.py", line 109, in run
        wb.save("BULATS_TTTTT.xls")
      File "/Users/*/Desktop/PYTHON PROJECT/SCRIPTS/xlwt/Workbook.py", line 643, in save
        doc.save(filename, self.get_biff_data())
      File "/Users/*/Desktop/PYTHON PROJECT/SCRIPTS/xlwt/Workbook.py", line 628, in get_biff_data
        data = sheet.get_biff_data()
      File "/Users/*/Desktop/PYTHON PROJECT/SCRIPTS/xlwt/Worksheet.py", line 1309, in get_biff_data
        self.__guts_rec(),
      File "/Users/*/Desktop/PYTHON PROJECT/SCRIPTS/xlwt/Worksheet.py", line 1113, in __guts_rec
        self.__update_row_visible_levels()
      File "/Users/*/Desktop/PYTHON PROJECT/SCRIPTS/xlwt/Worksheet.py", line 1109, in __update_row_visible_levels
        temp = max([self.__rows[r].level for r in self.__rows]) + 1
    RuntimeError: dictionary changed size during iteration
    Ainsi qu'une erreur : Exception: Attempt to overwrite cell: sheetname=u'BULATS_IA_PARSED' rowx=107 colx=3


    Cela me semble logique dans la mesure ou les threads s'exécute simultanément, mais je ne vois pas trop comment résoudre ce problème !

    Par ailleurs, dans le script ci-dessus je ne cherche qu'à récupérer une douzaine de page mais dans un autre script du même acabit, je cherche à récupérer plus de 300 pages différentes sur le même site. Pourriez-vous, dans un accès de générosité, m'indiquer si urllib3 est une bonne idée et comment je pourrais le mettre en place (pour réutiliser la connexion au site afin d'optimiser le chargement) dans tous ces threads :-)

    J'espère avoir été assez clair, et que vous serez disposé à me donner un petit coup de main :-) !

    Carto_

    PS: Ce script est un exemple, dans un autre du même type ce n'est plus douze page qu'il faut récupérer mais 122 (pays) * 50 (pages) donc l'optimisation est réellement indispensable.

  2. #2
    Expert éminent

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    4 298
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2008
    Messages : 4 298
    Points : 6 778
    Points
    6 778
    Par défaut
    Salut,

    Il pourrait être utile que tu vérifies aussi les temps de travail de beautifulSoup.

    Par expérience, je le suspecte d'être la cause des lenteurs d'exécution dont tu parles.

  3. #3
    Candidat au Club
    Inscrit en
    Octobre 2010
    Messages
    9
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 9
    Points : 4
    Points
    4
    Par défaut
    J'ai effectué un petit benchmark de tout le process sans les threads et BeautifulSoup est VRAIMENT moins lent que le rest. C'est à dire que je peux lui envoyer 4 pages à parser le temps que l'une s'ouvre. Et si j'arrive à quadrupler la vitesse d'exécution du script, ce sera déjà bien assez suffisant !

    Pas de solutions en vue pour mon soucis ?



    EDIT: Apparemment il n'est pas utile d'utiliser 5 threads pour BeautifulSoup. Le problem vient du chargement des pages et de leur lecture qui est 4 fois plus long que Bsoup.

    Par ailleurs, lorsque je lance le script ... il ne s'arrête pas ... ?

  4. #4
    Candidat au Club
    Inscrit en
    Octobre 2010
    Messages
    9
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 9
    Points : 4
    Points
    4
    Par défaut RÉSOLU !
    Problem résolu avec Twisted .. Tellement plus rapide, c'est incroyable !

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    from twisted.web import client
    from twisted.internet import reactor, defer
    from bs4 import BeautifulSoup as BeautifulSoup
     
    Countries_List = ['Afghanistan','Armenia','Brazil','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']
    urls = ["http://www.cambridgeesol.org/institutions/results.php?region=%s&type=&BULATS=on" % Countries for Countries in Countries_List]
     
     
    def finish(results):
        for result in results:
            print 'GOT PAGE', len(result), 'bytes'
            soup = BeautifulSoup(result)
            tableau = soup.findAll('table')
    	try:
    	    rows = tableau[3].findAll('tr')
    	    print("DONE")
    	    for tr in rows:
    		cols = tr.findAll('td')
    		#y = 0
    		#x = x + 1
    		for td in cols:
    		    texte_bu = td.text
    		    texte_bu = texte_bu.encode('utf-8')
    		    print texte_bu
    		    #ws.write(x,y,td.text)
    		    #y = y + 1
    		#wb.save("IA.xls")
    	except(IndexError):
    	    print("Index ERROR")
    	    pass
     
     
     
        reactor.stop()
     
    waiting = [client.getPage(url) for url in urls]
    defer.gatherResults(waiting).addCallback(finish)
     
    reactor.run()

  5. #5
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 241
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 241
    Points : 36 698
    Points
    36 698
    Par défaut
    Salut,

    Scrapy mérite peut être un détour.
    C'est un "web crawler framework" écrit en Python (et utilisant Twisted).

    Cordialement,
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  6. #6
    Candidat au Club
    Inscrit en
    Octobre 2010
    Messages
    9
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 9
    Points : 4
    Points
    4
    Par défaut
    Scrapy, faut que j'aille y jeter un oeil !

    D'ailleurs j'ai une autre question: comment faire la même chose (même type de script) mais cette fois-ci pour aller poster plein de requêtes POST différentes dans une même page (plutôt qu'aller chercher plein de pages différentes, ce que je fais ici).

    En gros est-il possible de faire des requêtes POSTS en utilisant le script que j'ai précédemment posté ?

    Merci d'avance !

  7. #7
    Membre habitué
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2010
    Messages
    140
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Décembre 2010
    Messages : 140
    Points : 182
    Points
    182
    Par défaut
    Hello,

    voici un script simple que j'ai fait il y a peu, si ca peut te servir...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    import sys, urllib, time
    from threading import Thread 
     
    try:
        f = open(sys.argv[1])
        iterations = int(sys.argv[2])
    except:
        print 'Args : \n - XML filepath to post\n - number of simultaneous posts'
        print 'an error occured while parsing parameters'
        sys.exit()
     
    data = f.read()
    f.close()
     
     
    def runPost():
        time.sleep(1)
        urllib.urlopen('monurl',data=urllib.urlencode({'DATA': data}))
     
    for i in range(iterations):
        for j in xrange(3):
            t = Thread(target=runPost, args=())
            t.start()
        time.sleep(1)
        urllib.urlopen('monurl',data=urllib.urlencode({'DATA': data}))
        time.sleep(1)
    il faut enlever les sleep pour que ca dépote.
    Pour ton problème de lenteur avec Beautiful Soup , c'est un peu normal, car tu fais du XML en dessous, c'est normal que ca prenne du temps. si tu as besoin de performances tu peux peut être utiliser BS pour formater correctement ton HTML puis faire du XPATH dedans (si ce n'est pas déjà ce que fait BS)

  8. #8
    Candidat au Club
    Inscrit en
    Octobre 2010
    Messages
    9
    Détails du profil
    Informations forums :
    Inscription : Octobre 2010
    Messages : 9
    Points : 4
    Points
    4
    Par défaut
    utopman, un énorme merci pour prendre le temps de partager ton code avec moi. Il m'a tout l'air très intéressant !

    L'idée serait donc de faire quelque chose du genre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    data_liste=['France', 'Angleterre', 'Brazil', '...']
    def runPost():
        for data in data_liste:
             urllib.urlopen('http://bandscore.ielts.org/',data=urllib.urlencode({'DATA': data}))
    Mais au moment ou j'écris ça je réalise que ça ne va pas fonctionner ...
    Je ne comprends donc pas tout (désolé je suis encore un peu mauvais) !

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. BeautifulSoup + urllib2(3?) + threading ou twisted ?!
    Par Carto_ dans le forum Réseau/Web
    Réponses: 2
    Dernier message: 26/04/2012, 05h15
  2. [TopLink] couplé à JPA très lent
    Par willoi dans le forum JPA
    Réponses: 11
    Dernier message: 03/03/2008, 11h35

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo