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.