Demo源码地址

https://github.com/CHUNL09/tornado/tree/master/demos/webspider

这个Demo的作用是用来获取特定URL的网页中的链接(链接是以特定URL作为开头的,比如设置了base_url="http://www.baidu.com",那么只会获取以"http://www.baidu.com开头的链接")。代码如下:

  1. #!/usr/bin/env python
  2. import time
  3. from datetime import timedelta
  4.  
  5. try: #python 2.7 适用
  6. from HTMLParser import HTMLParser
  7. from urlparse import urljoin, urldefrag
  8. except ImportError:
  9. from html.parser import HTMLParser
  10. from urllib.parse import urljoin, urldefrag
  11.  
  12. from tornado import httpclient, gen, ioloop, queues
  13.  
  14. base_url = 'http://www.tornadoweb.org/en/stable/'
  15. concurrency = 10
  16.  
  17. @gen.coroutine
  18. def get_links_from_url(url):
  19. """Download the page at `url` and parse it for links.
  20.  
  21. Returned links have had the fragment after `#` removed, and have been made
  22. absolute so, e.g. the URL 'gen.html#tornado.gen.coroutine' becomes
  23. 'http://www.tornadoweb.org/en/stable/gen.html'.
  24. """
  25. try:
  26. response = yield httpclient.AsyncHTTPClient().fetch(url)
  27. print('fetched %s' % url)
  28.  
  29. html = response.body if isinstance(response.body, str) \
  30. else response.body.decode()
  31. urls = [urljoin(url, remove_fragment(new_url))
  32. for new_url in get_links(html)]
  33. except Exception as e:
  34. print('Exception: %s %s' % (e, url))
  35. raise gen.Return([])
  36.  
  37. raise gen.Return(urls)
  38.  
  39. def remove_fragment(url):
  40. pure_url, frag = urldefrag(url)
  41. return pure_url
  42.  
  43. def get_links(html): # get all links in html page
  44. class URLSeeker(HTMLParser):
  45. def __init__(self):
  46. HTMLParser.__init__(self)
  47. self.urls = []
  48.  
  49. def handle_starttag(self, tag, attrs):
  50. href = dict(attrs).get('href')
  51. if href and tag == 'a':
  52. self.urls.append(href)
  53.  
  54. url_seeker = URLSeeker()
  55. url_seeker.feed(html)
  56. return url_seeker.urls
  57.  
  58. @gen.coroutine
  59. def main():
  60. q = queues.Queue()
  61. start = time.time()
  62. fetching, fetched = set(), set()
  63.  
  64. @gen.coroutine
  65. def fetch_url():
  66. current_url = yield q.get()
  67. try:
  68. if current_url in fetching:
  69. return
  70.  
  71. print('fetching %s' % current_url)
  72. fetching.add(current_url)
  73. urls = yield get_links_from_url(current_url)
  74. fetched.add(current_url)
  75.  
  76. for new_url in urls:
  77. # Only follow links beneath the base URL
  78. if new_url.startswith(base_url):
  79. yield q.put(new_url)
  80.  
  81. finally:
  82. q.task_done()
  83.  
  84. @gen.coroutine
  85. def worker():
  86. while True:
  87. yield fetch_url()
  88.  
  89. q.put(base_url)
  90.  
  91. # Start workers, then wait for the work queue to be empty.
  92. for _ in range(concurrency):
  93. worker()
  94. yield q.join(timeout=timedelta(seconds=300))
  95. assert fetching == fetched
  96. print('Done in %d seconds, fetched %s URLs.' % (
  97. time.time() - start, len(fetched)))
  98.  
  99. if __name__ == '__main__':
  100. import logging
  101. logging.basicConfig()
  102. io_loop = ioloop.IOLoop.current()
  103. io_loop.run_sync(main)

webspider

下面开始分析这个代码。

1 从程序的最终执行部分看起:

  1. if __name__ == '__main__':
  2. import logging
  3. logging.basicConfig()
  4. io_loop = ioloop.IOLoop.current()
  5. io_loop.run_sync(main)

这里logging.basicConfig()貌似没有起作用,这个方法是在logging模块中用来设置日志的基本格式用的。这里显然没有用到。IOLoop.current()用来返回当前线程的IOloop. run_sync方法是用来启动IOLoop,运行,并且结束(Starts the IOLoop, runs the given function, and stops the loop.)。

run_sync函数和tornado.gen.coroutine配合使用,主要是为了在mian函数中能够异步调用。Tornado官方给出了如下的使用示例:

  1. @gen.coroutine
  2. def main():
  3. # do stuff...
  4.  
  5. if __name__ == '__main__':
  6. IOLoop.current().run_sync(main)

关于IOLoop.current()和IOLoop.instance()的区别请点击这里

2 main函数。

首先,main函数前面带了@gen.coroutine装饰器,为了能够在main函数中实现异步调用。

  1. @gen.coroutine
  2. def main():
  3. q = queues.Queue()
  4. start = time.time()
  5. fetching, fetched = set(), set()
  6.  
  7. @gen.coroutine
  8. def fetch_url():
  9. current_url = yield q.get()
  10. try:
  11. if current_url in fetching:
  12. return
  13.  
  14. print('fetching %s' % current_url)
  15. fetching.add(current_url)
  16. urls = yield get_links_from_url(current_url) # 获取current_url页面中的link
  17. fetched.add(current_url)
  18.  
  19. for new_url in urls: # 对于子链接进行处理,只有符合条件的链接才会放入到queue中
  20. # Only follow links beneath the base URL
  21. if new_url.startswith(base_url):
  22. yield q.put(new_url)
  23.  
  24. finally:
  25. q.task_done() #Indicate that a formerly enqueued task is complete. 表示get从queue中取出的任务已经完成
  26.  
  27. @gen.coroutine
  28. def worker():
  29. while True:
  30. yield fetch_url()
  31.  
  32. q.put(base_url)
  33.  
  34. # Start workers, then wait for the work queue to be empty.
  35. for _ in range(concurrency):
  36. worker()
  37. yield q.join(timeout=timedelta(seconds=300))
  38. assert fetching == fetched
  39. print('Done in %d seconds, fetched %s URLs.' % (
  40. time.time() - start, len(fetched)))

line3 初始化了一个queue,这里使用的是tornado提供的queue(需要from tornado import queues ).

line5 初始化了两个集合fetching和fetched. fetching中存放正在处理的URL,而fetched中存放处理完成的URL。

line7-25 定义了函数fetch_url()主要是用来从queue中获取URL,并处理。

line27-30 定义了worker()函数,在其中使用了while True, 会不停的去yield fetch_url(). 这里while True是必须的,否则执行过一次的yield fetch_url()会hang住直到timeout.

line35-36 模拟并发效果,这里也可以取消for循环,但是实际结果消耗时间会大大多于并发的情况(可以自行测试实验)。

line37 q.join()的作用是block,直到queue中所有的任务都完成或者timeout.

line38 用断言来判断fetching 和fetched集合,正常情况下,两个集合中的URL数量应该是相等的。否则的话会raise一个断言的error出来。

3 其他定义的函数

代码如下:

  1. @gen.coroutine
  2. def get_links_from_url(url):
  3. """Download the page at `url` and parse it for links.
  4.  
  5. Returned links have had the fragment after `#` removed, and have been made
  6. absolute so, e.g. the URL 'gen.html#tornado.gen.coroutine' becomes
  7. 'http://www.tornadoweb.org/en/stable/gen.html'.
  8. """
  9. try:
  10. response = yield httpclient.AsyncHTTPClient().fetch(url)
  11. print('fetched %s' % url)
  12.  
  13. html = response.body if isinstance(response.body, str) \
  14. else response.body.decode()
  15. urls = [urljoin(url, remove_fragment(new_url))
  16. for new_url in get_links(html)]
  17. except Exception as e:
  18. print('Exception: %s %s' % (e, url))
  19. raise gen.Return([])
  20.  
  21. raise gen.Return(urls)
  22.  
  23. def remove_fragment(url):
  24. pure_url, frag = urldefrag(url)
  25. return pure_url
  26.  
  27. def get_links(html): # get all links in html page
  28. class URLSeeker(HTMLParser):
  29. def __init__(self):
  30. HTMLParser.__init__(self)
  31. self.urls = []
  32.  
  33. def handle_starttag(self, tag, attrs):
  34. href = dict(attrs).get('href')
  35. if href and tag == 'a':
  36. self.urls.append(href)
  37.  
  38. url_seeker = URLSeeker()
  39. url_seeker.feed(html)
  40. return url_seeker.urls

get_links_from_url函数

line 1-21定义的get_links_from_url函数,函数接收一个URL参数,并返回这个URL页面中所有的链接数量。使用URL获取页面内容这里使用的是tornado的httpclient中的方法httpclient.AsyncHTTPClient().fetch(). [也可以使用urllib.request.urlopen来抓取页面内容].

line15-16 分别调用了两个函数get_links和remove_fragment来获取新的URLs.

最终返回的是一个URL的列表。line 21 这里的raise gen.Return(urls) 可以直接替换为return urls,前者是旧版本tornado的用法。

get_links函数

line29-42定义了get_links函数,它接收html页面的内容,并将页面中的a标签的链接返回。实现方式是用HTMLParser。具体实现时要重写handle_starttag方法

remove_fragment 函数

line 24-26定义了remove_fragment函数,函数接收一个URL,并且会把URL中'#'后面的内容截掉,如:

  1. >>> pure_url,frag = urldefrag("http://docs.python.org/2/reference/compound_stmts.html#the-with-statement #h1 #h2")
  2. >>> pure_url
  3. 'http://docs.python.org/2/reference/compound_stmts.html'
  4. >>> frag
  5. 'the-with-statement #h1 #h2'

小结

整体代码比较简洁,主要是使用了tornado的异步方式来获取。后续有时间会在这个基础上扩展下实现一个完整的爬虫。

Tornado Demo1---webspider分析的更多相关文章

  1. tornado源码分析-iostream

    tornado源码分析-iostream 1.iostream.py作用 用来异步读写文件,socket通信 2.使用示例 import tornado.ioloop import tornado.i ...

  2. Tornado源码分析 --- 静态文件处理模块

    每个web框架都会有对静态文件的处理支持,下面对于Tornado的静态文件的处理模块的源码进行分析,以加强自己对静态文件处理的理解. 先从Tornado的主要模块 web.py 入手,可以看到在App ...

  3. Tornado源码分析系列之一: 化异步为'同步'的Future和gen.coroutine

    转自:http://blog.nathon.wang/2015/06/24/tornado-source-insight-01-gen/ 用Tornado也有一段时间,Tornado的文档还是比较匮乏 ...

  4. Tornado源码分析之http服务器篇

    转载自 http://kenby.iteye.com/blog/1159621 一. Tornado是什么? Facebook发布了开源网络服务器框架Tornado,该平台基于Facebook刚刚收购 ...

  5. tornado源码分析-模块介绍

    1.Core web framework tornado.web - web框架功能模块,包括RequestHandler和Application两个重要的类 tornado.httpserver - ...

  6. Tornado源码分析 --- Cookie和XSRF机制

    Cookie和Session的理解: 具体Cookie的介绍,可以参考:HTTP Cookie详解 可以先查看之前的一篇文章:Tornado的Cookie过期问题 XSRF跨域请求伪造(Cross-S ...

  7. Tornado源码分析 --- Redirect重定向

    “重定向”简单介绍: “重定向”指的是HTTP重定向,是HTTP协议的一种机制.当client向server发送一个请求,要求获取一个资源时,在server接收到这个请求后发现请求的这个资源实际存放在 ...

  8. Tornado源码分析 --- Etag实现

    Etag(URL的Entity Tag): 对于具体Etag是什么,请求流程,实现原理,这里不进行介绍,可以参考下面链接: http://www.oschina.net/question/234345 ...

  9. tornado源码分析系列一

    先来看一个简单的示例: #!/usr/bin/env python #coding:utf8 import socket def run(): sock = socket.socket(socket. ...

  10. tornado源码分析

    初识tornado 首先从经典的helloword案例入手 import tornado.ioloop import tornado.web class MainHandler(tornado.web ...

随机推荐

  1. Chrome DNS_PROBE_FINISHED_NXDOMAIN

    win10 下的Chrome访问网站时,提示DNS_PROBE_FINISHED_NXDOMAIN 解决方法很简单: 用管理员身份打开cmd后,运行如下指令即可解决问题. 运行命令: netsh wi ...

  2. 本地git安装完成之后,从远程git服务器上面下载代码。报错SSL certificate problem:self signed certificate in certificate chain。

    解决方案:打开git的控制端黑窗口,输入: git config --global http.sslVerify false 点击Entry之后,就会去掉git的ssl验证. 然后就可以正常的下载代码 ...

  3. AM8互联设置方法

    Am8互联设置 这个只需要部署在一个总部的AM8的 Oiorg所在机器上就可以 环境: Windows 2012 or windows 2008,IIS ,.Net4 AM8 数据库必须升级到:201 ...

  4. JPA 基本使用

    ORM简介 对象关系映射(Object Relational Mapping,简称ORM),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换. 实现ORM思想的框架:Mybati ...

  5. Perl 环境安装

    Perl 环境安装 在我们开始学习 Perl 语言前,我们需要先安装 Perl 的执行环境. Perl 可以在以下平台下运行: Unix (Solaris, Linux, FreeBSD, AIX, ...

  6. kubernetes istio的快速安装和使用例子

    安装 [root@master ~]# wget https://github.com/istio/istio/releases/download/1.1.5/istio-1.1.5-linux.ta ...

  7. 如何区分无线AP跟无线路由器

    无线AP是一个无线网络的接入点,俗称“热点”.主要有路由交换接入一体设备和纯接入点设备,一体设备执行接入和路由工作,纯接入设备只负责无线客户端的接入,纯接入设备通常作为无线网络扩展使用,与其他AP或者 ...

  8. 2019/11/1 CSP模拟

    写在前面的反思 该拿的部分分还是得拿完啊,因为懒+动作慢没有写最后一道题的菊花图和链的情况,其实这两个点并不难.. 虽然只有\(10pts\),但是已经足够往上爬一截了啊,额外的\(10pts\)在今 ...

  9. iOS开发Drag and Drop简介

    1.Drag and Drop简介 Drag and Drop是iOS11的新特性,可以将文本.图片进行拖拽到不同app中,实现数据的传递.只不过只能在iPad上使用,iPhone上只能app内部拖拽 ...

  10. node-webkit笔记

    两个月前给一个运营站点做了个封皮,今天再做竟然忘了怎么搞了...为之文以志. 流程参考: http://www.cnblogs.com/2050/p/3543011.html 相关命令: copy / ...