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:
Le tout me renvoie souvent deux types d'erreurs :
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)
Une erreur : RuntimeError: dictionary changed size during iteration
Ainsi qu'une erreur : Exception: Attempt to overwrite cell: sheetname=u'BULATS_IA_PARSED' rowx=107 colx=3
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
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.
Partager