Heim  >  Artikel  >  Backend-Entwicklung  >  Python verwendet Multithreading zum Crawlen von Webseiteninformationen

Python verwendet Multithreading zum Crawlen von Webseiteninformationen

巴扎黑
巴扎黑Original
2017-08-09 11:03:411633Durchsuche

In diesem Artikel wird hauptsächlich die Multithread-Webseiten-Crawling-Funktion von Python vorgestellt. Er analysiert die zugehörigen Betriebstechniken und Vorsichtsmaßnahmen der Python-Multithread-Programmierung im Detail und enthält außerdem ein Demo-Beispiel, das die Multithread-Funktion darstellt -Threaded-Webseiten-Crawling-Methode. Freunde, die sie benötigen, können sich auf

beziehen. Dieser Artikel beschreibt das Beispiel der Python-Implementierung der Multithread-Webseiten-Crawling-Funktion. Teilen Sie es als Referenz mit allen. Die Details lauten wie folgt:

In letzter Zeit habe ich Dinge getan, die mit Webcrawlern zu tun haben. Ich habe mir den in Open Source C++ geschriebenen Larbin-Crawler angesehen und die Designideen und die Implementierung einiger Schlüsseltechnologien sorgfältig gelesen.

1. Die URL-Wiederverwendung ist ein sehr effizienter Bloom-Filter-Algorithmus.
2. Für die URL-Warteschlangenverarbeitung wird Folgendes verwendet: Strategie des teilweisen Zwischenspeicherns im Speicher und teilweisen Schreibens in Dateien.
4. Larbin hat viel an dateibezogenen Vorgängen gearbeitet
5. Es gibt einen Verbindungspool, der die GET-Methode im HTTP-Protokoll an die Zielseite sendet den Inhalt und analysiert dann die Header.
Eine große Anzahl von Deskriptoren, E/A-Multiplexing durch die Poll-Methode, sehr effizient
Eine große Anzahl Die vom Autor verwendeten Datenstrukturen sind seine eigenen. Ich habe von unten angefangen und im Grunde keine Dinge wie STL verwendet.

Es gibt noch viele weitere. Ich werde einen Artikel schreiben und sie zusammenfassen Ich habe Zeit in der Zukunft.

In den letzten zwei Tagen habe ich ein Multithread-Programm zum Herunterladen von Seiten in Python geschrieben. Für E/A-intensive Anwendungen ist Multithreading offensichtlich eine gute Lösung. Der Thread-Pool, den ich gerade geschrieben habe, kann ebenfalls verwendet werden. Tatsächlich ist es sehr einfach, Python zum Crawlen von Seiten zu verwenden. Es gibt ein urllib2-Modul, das sehr praktisch ist und im Wesentlichen in zwei oder drei Codezeilen ausgeführt werden kann. Obwohl die Verwendung von Modulen von Drittanbietern Probleme sehr bequem lösen kann, ist dies für die persönliche technische Akkumulation nicht von Vorteil, da die Schlüsselalgorithmen von anderen und nicht von Ihnen implementiert werden. Viele Details sind für Sie überhaupt nicht nachvollziehbar. Wir, die in der Technologie arbeiten, können nicht einfach von anderen geschriebene Module oder APIs verwenden. Wir müssen sie selbst implementieren, damit wir mehr lernen können.

Ich habe mich entschieden, mit dem Socket zu beginnen, der auch das GET-Protokoll kapselt und den Header analysiert. Ich kann den DNS-Analyseprozess auch separat durchführen, z. B. das DNS-Caching. Wenn ich ihn also selbst schreibe, ist das der Fall kontrollierbarer. Für die Timeout-Verarbeitung verwende ich eine globale 5-Sekunden-Timeout-Verarbeitung. Für die Verlagerungsverarbeitung (301 oder 302) beträgt die maximale Verlagerung, da ich während des vorherigen Testprozesses festgestellt habe, dass die Verlagerungen vieler Sites auf mich selbst umgeleitet wurden Endlosschleife, daher ist eine Obergrenze festgelegt. Das spezifische Prinzip ist relativ einfach. Schauen Sie sich einfach den Code an.

Nachdem ich mit dem Schreiben fertig war, stellte ich fest, dass die Effizienz meines eigenen Schreibens relativ hoch war und die Fehlerrate von urllib2 etwas höher war. Einige Leute im Internet sagen, dass urllib2 im Multithread-Kontext einige kleinere Probleme hat, aber die Details sind mir nicht ganz klar.

Veröffentlichen Sie zuerst den Code:

fetchPage.py

Verwenden Sie die Get-Methode des HTTP-Protokolls, um die Seite herunterzuladen und speichern Sie es. Für die Datei


unten werde ich den Thread-Pool im vorherigen Artikel als Hilfsmittel verwenden, um paralleles Crawlen unter Multi-Threads zu implementieren , und schreibe es selbst mit den oben genannten Methoden. Vergleichen wir die Leistung der Download-Seitenmethode mit urllib2.
'''
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'


'''
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)
Experimentelle Analyse:

Experimentelle Daten:

Die 3000 von Larbin erfassten URLs werden vom Mercator-Warteschlangenmodell verarbeitet (ich habe es in C++ implementiert und werde in Zukunft einen Blog veröffentlichen, wenn ich die Gelegenheit dazu habe. Die URL-Sammlung erfolgt zufällig und repräsentativ). Verwenden Sie einen Thread-Pool mit 50 Threads.

Experimentelle Umgebung: Ubuntu10.04, gutes Netzwerk, Python2.6
Speicherung: kleine Dateien, jede Seite, eine Datei zur SpeicherungPS: Da der Internetzugang der Schule auf basiert Traffic Für das Web-Crawling fallen Gebühren an, was eine Verschwendung von regulärem Traffic darstellt! ! ! In ein paar Tagen führen wir möglicherweise ein groß angelegtes URL-Download-Experiment durch und testen es mit Hunderttausenden URLs.

Experimentelle Ergebnisse:

Verwendungurllib2

, usingThreadpoolUrllib2(3000,50)

Beginn am: 16.03.2012 22: 18: 20.956054 Ende bei: 2012-03-16 22: 22: 15.203018

Gesamtkosten: 0: 03: 54.246964

Gesamt-URL: 3001
Gesamtgeholt: 2442
Lo lo lo st-URL: 559

Physische Speichergröße der Download-Seite: 84088 KB

Verwenden Sie Ihre eigene getPageUsingGet, usingThreadpool(3000,50)

Start am: 16.03.2012 22 : 23:40.206730

Ende am: 2012-03-16 22:26:26.843563

Gesamtkosten: 0:02:46.636833

Gesamt-URL: 3002
Gesamtabgerufen: 2484
Verloren URL : 518
Fehler 404: 94
Fehler-Timeout: 312
Fehler Zu oft versuchen 0
Fehler Andere Fehler 112

Physische Speichergröße der Download-Seite: 87168 KB

Zusammenfassung: Das Programm zum Herunterladen von Seiten, das ich selbst geschrieben habe, ist sehr effizient und hat weniger verlorene Seiten. Wenn Sie darüber nachdenken, gibt es jedoch tatsächlich noch viele Stellen, die optimiert werden können. Beispielsweise führt die Erstellung und Freigabe zu vieler kleiner Dateien zu einem erheblichen Leistungsaufwand für das Programm Verwendet Hash-Benennung, was auch bei der Berechnung zu vielen Problemen führen kann. Wenn Sie eine gute Strategie haben, können diese Kosten tatsächlich weggelassen werden. Zusätzlich zu DNS müssen Sie nicht die mit Python gelieferte DNS-Auflösung verwenden, da die Standard-DNS-Auflösung ein synchroner Vorgang ist und die DNS-Auflösung im Allgemeinen zeitaufwändig ist, sodass sie asynchron mit mehreren Threads erfolgen kann In Verbindung mit entsprechendem DNS-Caching kann die Effizienz erheblich verbessert werden. Darüber hinaus gibt es während des eigentlichen Seiten-Crawling-Prozesses eine große Anzahl von URLs, die nicht gleichzeitig im Speicher gespeichert werden können. Stattdessen sollten sie gemäß einer bestimmten Strategie oder einem bestimmten Algorithmus sinnvoll zugewiesen werden. Kurz gesagt, es gibt noch viele Dinge, die auf der Sammlungsseite erledigt werden müssen und die optimiert werden können.

Das obige ist der detaillierte Inhalt vonPython verwendet Multithreading zum Crawlen von Webseiteninformationen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn