因為在研究爬蟲,所以也了解了下域名解析。要提高爬蟲的效率,就需要提高域名解析的效率。我將爬蟲記錄下的域名作為待解析的域名來測試各域名解析方法的效率。我嘗試以下四種方法:1. 單線程依次解析各域名,2. 多線程同時解析各域名,3. 線程池解析各域名,4. 使用adns庫解析各域名。其中,第四種方法最高效也最安全,推薦大家使用。完整的代碼請見:https://github.com/sunada/dnsResolve
1. 單線程依次解析域名
這種方法最直觀。使用一個循環,依次使用socket.getaddrinfo('host',None)來進行解析。這種方法很低效:解析100個域名花費了392s,解析500個域名花費了1695s。這是由於getaddrinfo方法解析單個域名就比較費時間,使用單線程阻礙的方法來解析500個域名,自然會耗時較長。
import time import socket def ReadHost(file): hosts=[] ... return hosts def SynResolve(fr): hosts=ReadHost(fr) IPs={} for host in hosts: try: results=socket.getaddrinfo(host,None) for result in results: print host, result[4][0] IPs[result[4][0]]=host except Exception,e: print e file.close() if __name__=='__main__': start=time.time() print 'starting at: ',start SynResolve('host') print 'ending at: ',time.time()-start
2. 多線程解析域名
這種方法也好理解。每解析一個域名時,就建立一個新的線程,用這個線程去解析此域名。這種方法較為高效,但在線程數量太大時錯誤率較高。解析500個域名需要創建500個線程,試驗結果表示:執行程序時給出了“thread.error: can't start new thread的提示。可能受此影響,運行腳本時提示個別域名無法解析。對這些域名單獨使用socket.getaddrinfo進行解析時,都可解析成功;如果只需解析100個線程,則不會提示thread.error的提示,僅有一個域名提示無法解析成功。使用此方法,解析100個域名大約耗時1分鍾左右。因為我運行腳本的機器所在的網速不太穩定,所以
也許這種方法所需要的時間更短。
代碼主要分為兩個部分:一部分為一個繼承自threading.Thread的子類,並通過run()函數這一成員函數來實現每個線程所要完成的工作;另一部分為一個函數,負責將需要完成的任務分配給一個新創建的線程。由於需要將解析得到的域名和對應的IP保存下載,所以需要各線程共同修改IPhost這一字典。代碼中使用了一個鎖(mutex),只有得到鎖的線程才能向IPhost寫入,否則只能等待。這樣可保證IPhost中IP與域名信息正確的對應關系。否則在線程的來回快速切換中,可能造成這種對應關系記錄的不准確。
import time import socket def ReadHost(file): hosts=[] ... return hosts class ThreadClass(threading.Thread): def __init__(self,host): self.host=host threading.Thread.__init__(self) def run(self): global IPhost try: res=socket.getaddrinfo(self.host,None) if mutex.acquire(1): for re in res: IPhost[re[4][0]]=self.host mutex.release() except Exception, e: print self.host, e def MulThreadResolve(fr): start=time.ctime() print 'starting MulThreadResolve at: ',start hosts=ReadHost(fr) threads=[] for host in hosts: t=ThreadClass(host) threads.append(t) cntHost=len(hosts) for i in range(cntHost): threads[i].start() for i in range(cntHost): threads[i].join() print 'ending MulThreadResolve at :', time.ctime() if __name__='__main__': IPhost={} mutex=threading.Lock() MulThreadResolve('host1') print IPhost
3. 利用線程池進行域名解析
通過方法2,我們知道在一個進程中創建過多的線程來執行任務,是危險的。自然的,我們就會想到利用有限個線程來進行域名解析。比如,用100個線程去解析500域名。當一個線程解析完成后,無需關閉,繼續從隊列中取出一個域名進行解析。如此反復,直到隊列中為空,所有域名都得到解析為止。使用線程池的另一個好處是省略了新建線程和關閉線程的時間;如果線程執行的任務耗時較短,那么通過線程池節省下來的這筆時間將會是可觀的。可以預計,此法將比方法2耗時更多。
利用線程池進行域名解析的代碼較方法2要復雜一些,但也不難理解。代碼仍舊主要分為兩個部分:一部分為一個繼承自threading.Thread的子類,並通過run()函數這一成員函數來實現每個線程所要完成的工作。run()函數中,當一個線程空閑時,就去隊列中取出一個域名;直到隊列為空,函數的任務也就完成了。另一部分為一個類,負責將任務分配給一個新創建的線程,並檢查交由run()完成的任務是否都已完成。由於需要進行的管理行為較方法2更多,所以將這些管理行為整合在一起,弄成了一個類。因為一個線程需要進行多次域名解析工作,所以需要將這些待解析的域名進行排隊。隊伍的容量是有限的,只有當隊未滿時才能將域名加入隊列等待線程將其取走並進行解析。由於代碼較長,我就把它放在這里了。