Python 网络爬虫程序详解
#!/usr/bin/python #调用python from sys import argv #导入sys是导入python解释器和他环境相关的参数 from os import makedirs,unlink,sep #os主要提供对系统路径,文件重命名和删除文件所需的函数 #makedirs是创建递归文件夹的函数。
#比如说我们要创建一个新的目录,/python/HTML/crawl,但是目前这三个文件夹都不存在,如果使用mkdir命令的话需要使用三次才能完成,
#但是使用os.makedir只需使用一次就可以创建好整个目录。 #os.makedirs(os.path.join(os.erviron["HOME"],"python","HTML","crawl") #os.unlink(path)删除file路径,和remove()相同。 #sep os.sep系统用此来分割路径名 from os.path import dirname,exists,isdir,splitext #使用os中的这些模块来提取dirname路径名,exists,isdir是文件类型测试,测试是否是一个目录,splitext是将文件名和文件后缀分离。分成目录文件名和后缀两部分。 from string import replace,find,lower #导入string模块,用于字符串的替换,查找,和小写化。 from htmllib import HTMLParser from urllib import urlretrieve #urlretrieve()函数用于将HTML文件整个下载到你的本地硬盘中去。 from urlparse import urlparse,urljoin #urlparse用于将URL分解成6个元素,而urljoin用于将baseurl和newurl组合在一起 from formatter import DumbWriter,AbstractFormatter #formatter函数主要用于格式化文本 from cStringIO import StringIO #调用cStringIO函数对内存中的文件进行处理
#Retriever类负责从网上下载网页并对每一个文档里面的连接进行分析,如果符合下载原则就添加到“待处理”队列中。
#从网上下载到的每个主页都有一个与之对应的Retriever实例。Retriever有几个帮助实现功能的方法,分别是:
#构造器(__init__()),filename(),download()和parseAndGetLinks()。 class Retriever: def __init__(self,url):
#定义构造器,指向当前类的当前实例的引用。self 指向新创建的对象,另外一个参数是url.
#构造器实例化一个Retriever对象,并且把URL字符串和从filename()返回的与之对应的文件名保存为本地属性。 self.url=url #将url的值付给self.url self.file=self.filename(url) def filename(self,url,deffile="index.html"): #定义filename方法,涉及另外两个参数,url,deffile,很明显deffile是后缀 parsedurl=urlparse(url,"http:",0) urlparse(urlstr,defProtsch=None,allowFrag=None),defProtsch定义了缺醒的网络协议和下载方式,allow是一个表示是否允许在URL中使用不完整成分的操作标志。allow_fragment如果是false,即使在URL addressing scheme支持fragment identifiers得情况下fragment identifiers也不允许,默认情况下fragment的默认值是true. path=parsedurl[1]+parsedurl[2] 从urlparse分离出来的六个元素分别是(prot_shc,net_loc,path,params,query,frag). parseurl[1]是net_loc,parseurl[2]是path. 和在一起正好是整个路径 ext=splitext(path) 将path分解成目录文件名和后缀标志。 if ext[1]=="": 如果没有文件。ext是一个字符串,ext[0]就是目录文件名,而ext[1]就是后缀名,说明没有后缀 if path[-1]=="/": 并且path是比如说是以我的博客为例,http://blog.csdn.net/yangwenchao1983,分离后path[-1]=3,也就是字符串的最后一个字母,如果是/,说明有文件内容, path=path+deffile 如果URL没有尾缀的文件名,就用缺性的"index.html“作为文假名,可以说是一个主王爷,上面有各种文件公下载,现在没有合适的文件,我们酒吧index.html作为补充。 else: path=path+"/"+deffile #如果是一个完整的文件名,我们需要在后面加上/index.html 如果不含有"/"符号的话, dir=dirname(path) #提取path字符串的目录名称 if sep!="/": #如果文件的分割符不是/ dir=replace(dir,"/",sep) #将dir中的/替换成分割符,/ if not isdir(dir): #使用isdir辨别文件类型不是目录。 if exists(dir): unlink(dir) #如果不是目录文件,就是用unlink移除, makedirs(dir) #重新使用makedirs创建目录文件 return path #返回经过整理的路径 def download(self): #定义download()方法,使用try...except...来进行异常处理, try: retval=urlretrieve(self.url,self.file) urlretrieve()不像urlopen()那样对URL进行读操作,它只是简单的把位于urlstr处的HTML文件整个下载到你的本地硬盘中去,如果没有给出localfile,它就会把数据保存到一个临时文件中去。很明显,这行程序的意思就是将self.url从望上的某个地方拷贝到硬盘的self.file中去。 except IOError: 如果文件不存在,就会引发IOerror, retval=("***ERROR: invalid URL "%s"" %\self.url,) 没有在有效的网址上找到这个文件,就将"***ERROR: invalid URL "%s""打印出来 return retval #返回得到的文件 def parseAndGetLinks(self): 如果上面的的处理没有发现任何错误,就会调用parseAndGetLinks()对新下载打破的主页进行分析,确定对那个主页上的每一个连接应该采取什么样的行动。 self.parser=HTMLParser(AbstractFormatter(DumbWriter(StringIO()))) 使用HTMLParser的方法进行处理,StringIO是从内存中读取数据,DumbWriter将事件流转换为存文本文档。 self.parser.feed(open(self.file).read()) 将self.file文件打开并且一次性读入上面定义的的文件中去 self.parser.close() #关闭文件 return self.parser.anchorlist #返回地址和日期 class Crawler: Crawler由三个数据项组成,这三个数据项是由构造器在实例化阶段报存在这里的。 count = 0 #静态下载主页计数器 def __init__(self,url): self.q=[url] 第一个数据是q,这是一个有下载连接组成的队列,这个清单在执行过程中是会变化的,没处理一个主页它就缩短一次,而在各下载主页中发现一个新的连接就会被加长。 self.seen=[] Crawler的另外两个数据项包括seen-这是我们已经下载过的全体连接所组成的一个列表; self.dom=urlparse(url)[1] 把主连接的域名报存在dom中,用这个值核对后续连接是否属于这同一个区域。 def getPage(self,url): getPage()方法用第一个连接实例化出一个Retriever对象,从她开始进行后续的处理。 r=Retriever(url) 使用上面定义过得Retriever类,付给r。 retval=r.download() #下载网页连接, if retval[0]=="*": print retval,"...skipping parse" return Crawler.count=Crawler.count+1 Crawler还有一个静态数据叫做count。这个计数器的作用就是记录我们呢已经从望红色那个下载到的对象的个数,每成功下载一个主页,就让它增加一个数。 print "\n(",Crawler.count,")" print "URL:",url print "FILE:",retval[0] self.seen.append(url) links=r.parseAndGetLinks() for eachLink in Links: if eachLink[:4]!="http" and find(eachLink,"://")==-1 print "*",eachLink, 以下链接将被忽略,不会被添加到待处理队列里去的:属于另外一个域的连接,已经被下载过得链接,已经放入待处理队列里去的连接或者是"mailto:"连接。 if find(lower(eachLink),"mailto:")!=-1:应该是超连接 print "...discard,mailto link" contine if eachlink not in self.seen: if find(eachLink,self.dom)==-1: print "...discarded,not in domain" else: if eachLink not in self.q: self.q.append(eachLink) print "...new,aded to Q" else: print "...discarded,already in Q" else: print "...discarded,already processed" def go(self): while self.q: url=self.q.pop() self.getPage(url) def main(): if len(argv)>1: url=argv[1] else: try: url=raw_input("Enter starting URL:") except(KeyboardInterrupt,EOFError): url="" if not url: return robot=Crawler(url) robot.go() if __name__=="__main__": main() main()只有在这个脚本程序在直接被调用时才会执行,它是程序的出发点,其他导入了crawl.py的模块需要明确的调用main()才能开始处理。要让main()开始执行,需要给它一个URL,如果已经在一个命令行给出URL(例如我们直接调用这个脚本程序的时候),它就会从给定的URL起开始运行;否则,脚本程序将进入交互模式,提示用户输入一个URL。有了初始连接之后,程序将对Crawler类进行实例化并开始执行。