Cet article présente principalement la fonction d'exploration de pages Web multithread de Python. Il analyse en détail les techniques de fonctionnement et les précautions associées à la programmation multithread Python sur la base d'exemples spécifiques. -méthode d'exploration de pages Web threadée. Pour la méthode d'implémentation, les amis qui en ont besoin peuvent se référer à
Cet article décrit l'exemple d'implémentation par Python de la fonction d'exploration de pages Web multithread. Partagez-le avec tout le monde pour votre référence, les détails sont les suivants :
Récemment, j'ai fait des choses liées aux robots d'exploration Web. J'ai jeté un œil au robot d'exploration larbin écrit en C++ open source et lu attentivement les idées de conception et la mise en œuvre de certaines technologies clés.
1. La réutilisation d'URL de Larbin est un algorithme de filtre de floraison très efficace ;
2. Le traitement DNS utilise le composant open source asynchrone
3. stratégie de mise en cache partielle en mémoire et d'écriture partielle dans des fichiers.
4. Larbin a fait beaucoup de travail sur les opérations liées aux fichiers
5 Il y a un pool de connexions dans larbin, il envoie la méthode GET dans le protocole HTTP au site cible, obtient. le contenu, puis analyse les éléments de classe
6 Un grand nombre de descripteurs, multiplexage d'E/S via la méthode de sondage
7 Larbin est très configurable
8. des structures de données utilisées par l'auteur sont les siennes. J'ai commencé par le bas et je n'ai pratiquement pas utilisé des choses comme STL
...
Il y en a bien d'autres, j'écrirai un article pour les résumer quand. J'ai du temps dans le futur.
Au cours des deux derniers jours, j'ai écrit un programme de téléchargement de pages multi-thread en python. Pour les applications gourmandes en E/S, le multi-thread est évidemment une bonne solution. Le pool de threads que je viens d'écrire peut également être utilisé. En fait, il est très simple d'utiliser Python pour explorer des pages. Il existe un module urllib2, qui est très pratique à utiliser et peut être réalisé en deux ou trois lignes de code. Bien que l'utilisation de modules tiers puisse résoudre les problèmes très facilement, cela ne présente aucun avantage pour l'accumulation technique personnelle, car les algorithmes clés sont implémentés par d'autres, pas par vous. De nombreux détails ne sont pas du tout implémentés par vous. Nous qui travaillons dans le domaine de la technologie ne pouvons pas simplement utiliser des modules ou des API écrits par d'autres. Nous devons les implémenter nous-mêmes afin de pouvoir en apprendre davantage.
J'ai décidé de partir du socket, qui encapsule également le protocole GET et analyse l'en-tête. Il peut également gérer le processus d'analyse DNS séparément, comme la mise en cache DNS, donc si je l'écris moi-même, ce sera le cas. plus contrôlable. Plus propice à l’expansion. Pour le traitement du délai d'attente, j'utilise un traitement de délai d'attente global de 5 secondes. Pour le traitement de la relocalisation (301 ou 302), la relocalisation maximale est de 3 fois, car lors du processus de test précédent, j'ai constaté que les relocalisations de nombreux sites étaient redirigées vers moi-même. boucle infinie, donc une limite supérieure est fixée. Le principe spécifique est relativement simple, il suffit de regarder le code.
Après avoir fini de l'écrire, j'ai comparé les performances avec urllib2, j'ai trouvé que l'efficacité de ma propre écriture était relativement élevée et que le taux d'erreur de urllib2 était légèrement plus élevé. Certaines personnes sur Internet disent que urllib2 a quelques problèmes mineurs dans un contexte multithread, mais je ne suis pas particulièrement clair sur les détails.
Publiez d'abord le code :
fetchPage.py Utilisez la méthode Get du protocole Http pour télécharger la page et stockez-le Pour le fichier
''' Created on 2012-3-13 Get Page using GET method Default using HTTP Protocol , http port 80 @author: xiaojay ''' import socket import statistics import datetime import threading socket.setdefaulttimeout(statistics.timeout) class Error404(Exception): '''Can not find the page.''' pass class ErrorOther(Exception): '''Some other exception''' def __init__(self,code): #print 'Code :',code pass class ErrorTryTooManyTimes(Exception): '''try too many times''' pass def downPage(hostname ,filename , trytimes=0): try : #To avoid too many tries .Try times can not be more than max_try_times if trytimes >= statistics.max_try_times : raise ErrorTryTooManyTimes except ErrorTryTooManyTimes : return statistics.RESULTTRYTOOMANY,hostname+filename try: s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #DNS cache if statistics.DNSCache.has_key(hostname): addr = statistics.DNSCache[hostname] else: addr = socket.gethostbyname(hostname) statistics.DNSCache[hostname] = addr #connect to http server ,default port 80 s.connect((addr,80)) msg = 'GET '+filename+' HTTP/1.0\r\n' msg += 'Host: '+hostname+'\r\n' msg += 'User-Agent:xiaojay\r\n\r\n' code = '' f = None s.sendall(msg) first = True while True: msg = s.recv(40960) if not len(msg): if f!=None: f.flush() f.close() break # Head information must be in the first recv buffer if first: first = False headpos = msg.index("\r\n\r\n") code,other = dealwithHead(msg[:headpos]) if code=='200': #statistics.fetched_url += 1 f = open('pages/'+str(abs(hash(hostname+filename))),'w') f.writelines(msg[headpos+4:]) elif code=='301' or code=='302': #if code is 301 or 302 , try down again using redirect location if other.startswith("http") : hname, fname = parse(other) downPage(hname,fname,trytimes+1)#try again else : downPage(hostname,other,trytimes+1) elif code=='404': raise Error404 else : raise ErrorOther(code) else: if f!=None :f.writelines(msg) s.shutdown(socket.SHUT_RDWR) s.close() return statistics.RESULTFETCHED,hostname+filename except Error404 : return statistics.RESULTCANNOTFIND,hostname+filename except ErrorOther: return statistics.RESULTOTHER,hostname+filename except socket.timeout: return statistics.RESULTTIMEOUT,hostname+filename except Exception, e: return statistics.RESULTOTHER,hostname+filename def dealwithHead(head): '''deal with HTTP HEAD''' lines = head.splitlines() fstline = lines[0] code =fstline.split()[1] if code == '404' : return (code,None) if code == '200' : return (code,None) if code == '301' or code == '302' : for line in lines[1:]: p = line.index(':') key = line[:p] if key=='Location' : return (code,line[p+2:]) return (code,None) def parse(url): '''Parse a url to hostname+filename''' try: u = url.strip().strip('\n').strip('\r').strip('\t') if u.startswith('http://') : u = u[7:] elif u.startswith('https://'): u = u[8:] if u.find(':80')>0 : p = u.index(':80') p2 = p + 3 else: if u.find('/')>0: p = u.index('/') p2 = p else: p = len(u) p2 = -1 hostname = u[:p] if p2>0 : filename = u[p2:] else : filename = '/' return hostname, filename except Exception ,e: print "Parse wrong : " , url print e def PrintDNSCache(): '''print DNS dict''' n = 1 for hostname in statistics.DNSCache.keys(): print n,'\t',hostname, '\t',statistics.DNSCache[hostname] n+=1 def dealwithResult(res,url): '''Deal with the result of downPage''' statistics.total_url+=1 if res==statistics.RESULTFETCHED : statistics.fetched_url+=1 print statistics.total_url , '\t fetched :', url if res==statistics.RESULTCANNOTFIND : statistics.failed_url+=1 print "Error 404 at : ", url if res==statistics.RESULTOTHER : statistics.other_url +=1 print "Error Undefined at : ", url if res==statistics.RESULTTIMEOUT : statistics.timeout_url +=1 print "Timeout ",url if res==statistics.RESULTTRYTOOMANY: statistics.trytoomany_url+=1 print e ,"Try too many times at", url if __name__=='__main__': print 'Get Page using GET method'
ci-dessous, j'utiliserai le pool de threads de l'article précédent comme auxiliaire pour implémenter l'exploration parallèle sous multi-threads , et écrivez-le moi-même en utilisant ce qui précède. Comparons les performances de la méthode de la page de téléchargement avec urllib2.
''' Created on 2012-3-16 @author: xiaojay ''' import fetchPage import threadpool import datetime import statistics import urllib2 '''one thread''' def usingOneThread(limit): urlset = open("input.txt","r") start = datetime.datetime.now() for u in urlset: if limit <= 0 : break limit-=1 hostname , filename = parse(u) res= fetchPage.downPage(hostname,filename,0) fetchPage.dealwithResult(res) end = datetime.datetime.now() print "Start at :\t" , start print "End at :\t" , end print "Total Cost :\t" , end - start print 'Total fetched :', statistics.fetched_url '''threadpoll and GET method''' def callbackfunc(request,result): fetchPage.dealwithResult(result[0],result[1]) def usingThreadpool(limit,num_thread): urlset = open("input.txt","r") start = datetime.datetime.now() main = threadpool.ThreadPool(num_thread) for url in urlset : try : hostname , filename = fetchPage.parse(url) req = threadpool.WorkRequest(fetchPage.downPage,args=[hostname,filename],kwds={},callback=callbackfunc) main.putRequest(req) except Exception: print Exception.message while True: try: main.poll() if statistics.total_url >= limit : break except threadpool.NoResultsPending: print "no pending results" break except Exception ,e: print e end = datetime.datetime.now() print "Start at :\t" , start print "End at :\t" , end print "Total Cost :\t" , end - start print 'Total url :',statistics.total_url print 'Total fetched :', statistics.fetched_url print 'Lost url :', statistics.total_url - statistics.fetched_url print 'Error 404 :' ,statistics.failed_url print 'Error timeout :',statistics.timeout_url print 'Error Try too many times ' ,statistics.trytoomany_url print 'Error Other faults ',statistics.other_url main.stop() '''threadpool and urllib2 ''' def downPageUsingUrlib2(url): try: req = urllib2.Request(url) fd = urllib2.urlopen(req) f = open("pages3/"+str(abs(hash(url))),'w') f.write(fd.read()) f.flush() f.close() return url ,'success' except Exception: return url , None def writeFile(request,result): statistics.total_url += 1 if result[1]!=None : statistics.fetched_url += 1 print statistics.total_url,'\tfetched :', result[0], else: statistics.failed_url += 1 print statistics.total_url,'\tLost :',result[0], def usingThreadpoolUrllib2(limit,num_thread): urlset = open("input.txt","r") start = datetime.datetime.now() main = threadpool.ThreadPool(num_thread) for url in urlset : try : req = threadpool.WorkRequest(downPageUsingUrlib2,args=[url],kwds={},callback=writeFile) main.putRequest(req) except Exception ,e: print e while True: try: main.poll() if statistics.total_url >= limit : break except threadpool.NoResultsPending: print "no pending results" break except Exception ,e: print e end = datetime.datetime.now() print "Start at :\t" , start print "End at :\t" , end print "Total Cost :\t" , end - start print 'Total url :',statistics.total_url print 'Total fetched :', statistics.fetched_url print 'Lost url :', statistics.total_url - statistics.fetched_url main.stop() if __name__ =='__main__': '''too slow''' #usingOneThread(100) '''use Get method''' #usingThreadpool(3000,50) '''use urllib2''' usingThreadpoolUrllib2(3000,50)
Analyse expérimentale :
Données expérimentales : Les 3000 URL capturées par larbin sont traitées par le modèle de file d'attente Mercator (je l'ai implémenté en C++, et je publierai un blog lorsque j'en aurai l'occasion dans le futur). La collection d'URL est aléatoire et représentative. Utilisez un pool de threads de 50 threads.
Environnement expérimental : ubuntu10.04, bon réseau, python2.6
Stockage : petits fichiers, chaque page, un fichier pour le stockage
PS : Puisque l'accès Internet de l'école est basé sur trafic L'exploration du Web est payante, ce qui représente un gaspillage de trafic régulier ! ! ! Dans quelques jours, nous pourrions mener une expérience de téléchargement d’URL à grande échelle et l’essayer avec des centaines de milliers d’URL.
Résultats expérimentaux :
Utilisationurllib2 , utilisationThreadpoolUrllib2(3000,50)
Début à : 2012-03-16 22:18:20.956054
Fin à : 2012-03-16 22:22:15.203018
Coût total : 0:03:54.246964
URL totale : 3001
Total récupéré : 2442
Lo st url : 559
Taille de stockage physique de la page de téléchargement : 84088 Ko
Utilisez votre propre getPageUsingGet, en utilisantThreadpool(3000,50)
Début à : 2012-03-16 22 : 23:40.206730
Fin à : 2012-03-16 22:26:26.843563
Coût total : 0:02:46.636833
URL totale : 3002
Total récupéré : 2484
URL perdue : 518
Erreur 404 : 94
Délai d'expiration de l'erreur : 312
Erreur Essayez trop de fois 0
Erreur Autres défauts 112
Taille de stockage physique de la page de téléchargement : 87168 Ko
Résumé : Le programme de page de téléchargement que j'ai écrit moi-même est très efficace et comporte moins de pages perdues. Mais en fait, si vous y réfléchissez, il existe encore de nombreux endroits qui peuvent être optimisés. Par exemple, la création et la publication d'un trop grand nombre de petits fichiers entraîneront certainement une surcharge de performances et du programme. utilise la dénomination de hachage, ce qui générera également de nombreux problèmes de calcul, si vous avez une bonne stratégie, ces coûts peuvent en fait être omis. En plus du DNS, vous n'avez pas besoin d'utiliser la résolution DNS fournie avec Python, car la résolution DNS par défaut est une opération synchrone et la résolution DNS prend généralement du temps, elle peut donc être effectuée de manière asynchrone multithread. manière, couplée à une mise en cache DNS appropriée, l’efficacité peut être améliorée dans une large mesure. De plus, pendant le processus d'exploration de la page, il y aura un grand nombre d'URL, et il est impossible de les stocker en mémoire à la fois. Au lieu de cela, elles doivent être raisonnablement allouées selon une certaine stratégie ou un certain algorithme. Bref, il y a encore beaucoup de choses à faire dans la page de collection et des choses qui peuvent être optimisées.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!