使用Python脚本进行域名解析

时间:2022-02-19 15:42:39

因为在研究爬虫,所以也了解了下域名解析。要提高爬虫的效率,就需要提高域名解析的效率。我将爬虫记录下的域名作为待解析的域名来测试各域名解析方法的效率。我尝试以下四种方法: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更多,所以将这些管理行为整合在一起,弄成了一个类。因为一个线程需要进行多次域名解析工作,所以需要将这些待解析的域名进行排队。队伍的容量是有限的,只有当队未满时才能将域名加入队列等待线程将其取走并进行解析。由于代码较长,我就把它放在这里了。