Maison >développement back-end >Tutoriel Python >Python utilise le multithreading pour explorer les informations des pages Web

Python utilise le multithreading pour explorer les informations des pages Web

巴扎黑
巴扎黑original
2017-08-09 11:03:411671parcourir

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 &#39;Total fetched :&#39;, statistics.fetched_url
&#39;&#39;&#39;threadpoll and GET method&#39;&#39;&#39;
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 &#39;Total url :&#39;,statistics.total_url
  print &#39;Total fetched :&#39;, statistics.fetched_url
  print &#39;Lost url :&#39;, statistics.total_url - statistics.fetched_url
  print &#39;Error 404 :&#39; ,statistics.failed_url
  print &#39;Error timeout :&#39;,statistics.timeout_url
  print &#39;Error Try too many times &#39; ,statistics.trytoomany_url
  print &#39;Error Other faults &#39;,statistics.other_url
  main.stop()
&#39;&#39;&#39;threadpool and urllib2 &#39;&#39;&#39;
def downPageUsingUrlib2(url):
  try:
    req = urllib2.Request(url)
    fd = urllib2.urlopen(req)
    f = open("pages3/"+str(abs(hash(url))),&#39;w&#39;)
    f.write(fd.read())
    f.flush()
    f.close()
    return url ,&#39;success&#39;
  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,&#39;\tfetched :&#39;, result[0],
  else:
    statistics.failed_url += 1
    print statistics.total_url,&#39;\tLost :&#39;,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 &#39;Total url :&#39;,statistics.total_url
  print &#39;Total fetched :&#39;, statistics.fetched_url
  print &#39;Lost url :&#39;, statistics.total_url - statistics.fetched_url
  main.stop()
if __name__ ==&#39;__main__&#39;:
  &#39;&#39;&#39;too slow&#39;&#39;&#39;
  #usingOneThread(100)
  &#39;&#39;&#39;use Get method&#39;&#39;&#39;
  #usingThreadpool(3000,50)
  &#39;&#39;&#39;use urllib2&#39;&#39;&#39;
  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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn