一个简单的Web客户端(网络爬虫)

来源:互联网 发布:淘宝联盟手机版如何赚钱 编辑:程序博客网 时间:2024/05/25 16:40

本节演示网络爬虫。它是按照一定的规则,自动地抓取万维网信息的程序或者脚本。

        在本节的演示程序中,抓取Web的开始页面地址,下载与开始页面相同域名的后续链接页面。

实现代码:


[python] view plain copy
  1. #-*-coding: utf-8-*-  
  2.   
  3. from sys import argv  
  4. from os import makedirs, unlink, sep  
  5. from os.path import dirname, exists, isdir, splitext  
  6. from string import replace, find, lower  
  7. from htmllib import HTMLParser # HTMLParser是用来解析html页面的,解析html页面中的链接?  
  8. from urllib import urlretrieve  
  9. from urlparse import urlparse, urljoin  
  10. from formatter import DumbWriter, AbstractFormatter # DumbWriter将事件流转换为存文本文档?AbstractFormatter?  
  11. from cStringIO import StringIO # StringIO是指在内存中读写字符串  
  12.   
  13. # 类Retrieve负责从web下载页面  
  14. class Retrieve(object): # download Web pages  
  15.       
  16.     def __init__(self, url):  
  17.         self.url = url  
  18.         self.file = self.filename(url)  
  19.   
  20.     # filename()方法使用给定的url找出安全、有效的相关文件名并储存在本地  
  21.     def filename(self, url, deffile='index.html'):  
  22.         parsedurl = urlparse(url, 'http:'0# 解析路径  
  23.         path = parsedurl[1] + parsedurl[2# web页面(服务器位置+文件路径)  
  24.         ext = splitext(path) # 返回(文件名,扩展名)  
  25.         if ext[1] == ''# 没有扩展名,使用默认的deffile,即path变为'...(服务器路径)/index.html'  
  26.             if path[-1] == '/':  
  27.                 path += deffile  
  28.             else:  
  29.                 path += '/' + deffile  
  30.         ldir = dirname(path) # path所在的目录路径  
  31.         if sep != '/'# path路径分隔符?猜测可能是考虑到不同操作系统平台  
  32.             ldir = replace(ldir, '/', sep) # 统一采用linux系统的格式  
  33.         if not isdir(ldir): # 路径不存在时,存档。  
  34.             if exists(ldir): unlink(ldir) # 删除ldir下原有的文件?  
  35.             makedirs(ldir)  
  36.         return path  
  37.   
  38.     # download()方法连接网络,下载给定链接的页面  
  39.     def download(self): # download web page  
  40.         try:  
  41.             retval = urlretrieve(self.url, self.file) # 下载url页面成功,并保存在filename中,retval是一个元组,第一项是文件名(包括路径),第二项是服务器的响应头,?  
  42.         except IOError:  
  43.             retval = ('*** ERROR: invalid URL "%s"' % self.url,) # 下载页面失败,retval是一个包含字符串的单元组  
  44.         return retval  
  45.   
  46.     # 如果上面的的处理没有发现任何错误,就会调用parseAndGetLinks()对新下载的主页进行分析,确定对那个web页面上的每一个连接应该采取什么样的行动。  
  47.     def parseAndGetLinks(self): # 解析HTML,保存链接  
  48.         self.parser = HTMLParser(AbstractFormatter(DumbWriter(StringIO()))) # 解析器?  
  49.         self.parser.feed(open(self.file).read()) # open(self.file).read()就是下载的页面,feed()方法是提取其中的链接,自动存入anchorlist列表。  
  50.         self.parser.close() # 关闭解析器?  
  51.         return self.parser.anchorlist # anchorlist里储存解析出来的html页面的链接  
  52.   
  53. class Crawler(object): # 管理爬虫进程  
  54.       
  55.     count = 0 # 已下载web页面计数器  
  56.       
  57.     def __init__(self, url):  
  58.         self.q =[url] # 待下载链接队列,页面处理完毕变短,如果页面中发现新的链接,则会变长。  
  59.         self.seen = [] # 已下载队列  
  60.         self.dom = urlparse(url)[1# 存储主链接域名,用于判断后续链接是否是该域的一部分。  
  61.   
  62.     # 核心  
  63.     def getPage(self, url):  
  64.         r = Retrieve(url) # 实例化Retrieve对象  
  65.         retval = r.download() # 下载web页面  
  66.         if retval[0] == '*'# 下载出错,不进行解析  
  67.             print retval, '... skipping parse'  
  68.             return  
  69.         Crawler.count += 1 # 下载web页面没有问题,则已下载web页面计数器count加1,表示已经下载了一个web页面  
  70.         print '\n(', Crawler.count, ')'  
  71.         print 'URL:', url  
  72.         print 'FILE:', retval[0]   
  73.         self.seen.append(url) # 将url添加到已下载队列  
  74.           
  75.         links = r.parseAndGetLinks() # links包含了url指向的HTML页面的所有链接  
  76.         for eachlink in links:  
  77.             if eachlink[:-4] != 'http' and find(eachlink, '://') == -1:  
  78.                 eachlink = urljoin(url, eachlink)   
  79.             print '* ', eachlink,  
  80.   
  81.             if find(lower(eachlink), 'mailto:') != -1:  
  82.                 print '... discarded, mailto link'  
  83.                 continue # 如果url指向的HTML页面有mailto链接(自动发送电子邮件的链接),则其将被忽略  
  84.   
  85.             if eachlink not in self.seen:  
  86.                 if find(eachlink, self.dom) == -1:  
  87.                     print '... discarded, not in domain' # 不在主链接域名的链接也会被忽略,比如说很多网站上都会有另外一个网站(不同域名)的链接,这类链接也会被忽略。  
  88.                 else:  
  89.                     if eachlink not in self.q:  
  90.                         self.q.append(eachlink)  
  91.                         print '... new, added to Q' # 将链接加入到待下载链接队列中  
  92.                     else:  
  93.                         print '... discarded, already in Q' # 忽略已在待下载链接队列中的链接  
  94.                           
  95.             else:  
  96.                 print '... discarded, already processed' # 忽略已经下载过的链接  
  97.   
  98.     # 启动Crawler(),不停地推进处理过程,直到队列为空。  
  99.     def go(self): # 处理待下载链接队列里的链接  
  100.         while self.q:  
  101.             url = self.q.pop()  
  102.             self.getPage(url)  
  103.   
  104. def main():  
  105.     if len(argv) > 1:  
  106.         url = argv[1# 就是在命令行以 python crawl.py url 启动程序  
  107.     else:  
  108.         try:  
  109.             url = raw_input('Enter starting URL: ')  
  110.         except (KeyboardInterrupt, EOFError):  
  111.             url = ''  
  112.    
  113.         if not url: return  
  114.         robot = Crawler(url)  
  115.         robot.go()  
  116.   
  117. if __name__ == "__main__":  
  118.     main()  

运行结果如下·:

Enter starting URL: write.blog.csdn.net


( 1 )
URL: http://write.blog.csdn.net/
FILE: write.blog.csdn.net/index.html
* http://edu.csdn.net/huiyiCourse/series_detail/65?utm_source= ... new, added to Q
* http://write.blog.csdn.net/account/fpwd?action=forgotpassword&service=http%3A%2F%2Fwrite.blog.csdn.net%2F ... new, added to Q
* https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=100270989&redirect_uri=https%3A%2F%2Fpassport.csdn.net%2Faccount%2Flogin%3Foauth_provider%3DQQProvider&state=test ... new, added to Q
* javascript:void(0) ... discarded, not in domain
* https://api.weibo.com/oauth2/authorize?client_id=2601122390&response_type=code&redirect_uri=https%3A%2F%2Fpassport.csdn.net%2Faccount%2Flogin%3Foauth_provider%3DSinaWeiboProvider ... new, added to Q
* https://openapi.baidu.com/oauth/2.0/authorize?response_type=code&client_id=cePqkUpKCBrcnQtARTNPxxQG&redirect_uri=https%3A%2F%2Fpassport.csdn.net%2Faccount%2Flogin%3Foauth_provider%3DBaiduProvider ... new, added to Q
* https://www.oschina.net/action/oauth2/authorize?response_type=code&client_id=cIh1UuWvSyrYlbe93rM6&redirect_uri=https%3A%2F%2Fpassport.csdn.net%2Faccount%2Flogin%3Foauth_provider%3DOsChinaProvider&state=test ... new, added to Q
* https://github.com/login/oauth/authorize?client_id=4bceac0b4d39cf045157&redirect_uri=https%3A%2F%2Fpassport.csdn.net%2Faccount%2Flogin%3Foauth_provider%3DGitHubProvider ... new, added to Q
* javascript:; ... discarded, not in domain
* http://write.blog.csdn.net/account/mobileregister?action=mobileRegisterGuideView&service=http%3A%2F%2Fwrite.blog.csdn.net%2F ... new, added to Q

阅读全文
0 0