Tornado Demo1---webspider分析
Demo源码地址
https://github.com/CHUNL09/tornado/tree/master/demos/webspider
这个Demo的作用是用来获取特定URL的网页中的链接(链接是以特定URL作为开头的,比如设置了base_url="http://www.baidu.com",那么只会获取以"http://www.baidu.com开头的链接")。代码如下:
#!/usr/bin/env python
import time
from datetime import timedelta try: #python 2.7 适用
from HTMLParser import HTMLParser
from urlparse import urljoin, urldefrag
except ImportError:
from html.parser import HTMLParser
from urllib.parse import urljoin, urldefrag from tornado import httpclient, gen, ioloop, queues base_url = 'http://www.tornadoweb.org/en/stable/'
concurrency = 10 @gen.coroutine
def get_links_from_url(url):
"""Download the page at `url` and parse it for links. Returned links have had the fragment after `#` removed, and have been made
absolute so, e.g. the URL 'gen.html#tornado.gen.coroutine' becomes
'http://www.tornadoweb.org/en/stable/gen.html'.
"""
try:
response = yield httpclient.AsyncHTTPClient().fetch(url)
print('fetched %s' % url) html = response.body if isinstance(response.body, str) \
else response.body.decode()
urls = [urljoin(url, remove_fragment(new_url))
for new_url in get_links(html)]
except Exception as e:
print('Exception: %s %s' % (e, url))
raise gen.Return([]) raise gen.Return(urls) def remove_fragment(url):
pure_url, frag = urldefrag(url)
return pure_url def get_links(html): # get all links in html page
class URLSeeker(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.urls = [] def handle_starttag(self, tag, attrs):
href = dict(attrs).get('href')
if href and tag == 'a':
self.urls.append(href) url_seeker = URLSeeker()
url_seeker.feed(html)
return url_seeker.urls @gen.coroutine
def main():
q = queues.Queue()
start = time.time()
fetching, fetched = set(), set() @gen.coroutine
def fetch_url():
current_url = yield q.get()
try:
if current_url in fetching:
return print('fetching %s' % current_url)
fetching.add(current_url)
urls = yield get_links_from_url(current_url)
fetched.add(current_url) for new_url in urls:
# Only follow links beneath the base URL
if new_url.startswith(base_url):
yield q.put(new_url) finally:
q.task_done() @gen.coroutine
def worker():
while True:
yield fetch_url() q.put(base_url) # Start workers, then wait for the work queue to be empty.
for _ in range(concurrency):
worker()
yield q.join(timeout=timedelta(seconds=300))
assert fetching == fetched
print('Done in %d seconds, fetched %s URLs.' % (
time.time() - start, len(fetched))) if __name__ == '__main__':
import logging
logging.basicConfig()
io_loop = ioloop.IOLoop.current()
io_loop.run_sync(main)
webspider
下面开始分析这个代码。
1 从程序的最终执行部分看起:
if __name__ == '__main__':
import logging
logging.basicConfig()
io_loop = ioloop.IOLoop.current()
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官方给出了如下的使用示例:
@gen.coroutine
def main():
# do stuff... if __name__ == '__main__':
IOLoop.current().run_sync(main)
关于IOLoop.current()和IOLoop.instance()的区别请点击这里。
2 main函数。
首先,main函数前面带了@gen.coroutine装饰器,为了能够在main函数中实现异步调用。
@gen.coroutine
def main():
q = queues.Queue()
start = time.time()
fetching, fetched = set(), set() @gen.coroutine
def fetch_url():
current_url = yield q.get()
try:
if current_url in fetching:
return print('fetching %s' % current_url)
fetching.add(current_url)
urls = yield get_links_from_url(current_url) # 获取current_url页面中的link
fetched.add(current_url) for new_url in urls: # 对于子链接进行处理,只有符合条件的链接才会放入到queue中
# Only follow links beneath the base URL
if new_url.startswith(base_url):
yield q.put(new_url) finally:
q.task_done() #Indicate that a formerly enqueued task is complete. 表示get从queue中取出的任务已经完成 @gen.coroutine
def worker():
while True:
yield fetch_url() q.put(base_url) # Start workers, then wait for the work queue to be empty.
for _ in range(concurrency):
worker()
yield q.join(timeout=timedelta(seconds=300))
assert fetching == fetched
print('Done in %d seconds, fetched %s URLs.' % (
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 其他定义的函数
代码如下:
@gen.coroutine
def get_links_from_url(url):
"""Download the page at `url` and parse it for links. Returned links have had the fragment after `#` removed, and have been made
absolute so, e.g. the URL 'gen.html#tornado.gen.coroutine' becomes
'http://www.tornadoweb.org/en/stable/gen.html'.
"""
try:
response = yield httpclient.AsyncHTTPClient().fetch(url)
print('fetched %s' % url) html = response.body if isinstance(response.body, str) \
else response.body.decode()
urls = [urljoin(url, remove_fragment(new_url))
for new_url in get_links(html)]
except Exception as e:
print('Exception: %s %s' % (e, url))
raise gen.Return([]) raise gen.Return(urls) def remove_fragment(url):
pure_url, frag = urldefrag(url)
return pure_url def get_links(html): # get all links in html page
class URLSeeker(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.urls = [] def handle_starttag(self, tag, attrs):
href = dict(attrs).get('href')
if href and tag == 'a':
self.urls.append(href) url_seeker = URLSeeker()
url_seeker.feed(html)
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中'#'后面的内容截掉,如:
>>> pure_url,frag = urldefrag("http://docs.python.org/2/reference/compound_stmts.html#the-with-statement #h1 #h2")
>>> pure_url
'http://docs.python.org/2/reference/compound_stmts.html'
>>> frag
'the-with-statement #h1 #h2'
小结
整体代码比较简洁,主要是使用了tornado的异步方式来获取。后续有时间会在这个基础上扩展下实现一个完整的爬虫。
Tornado Demo1---webspider分析的更多相关文章
- tornado源码分析-iostream
tornado源码分析-iostream 1.iostream.py作用 用来异步读写文件,socket通信 2.使用示例 import tornado.ioloop import tornado.i ...
- Tornado源码分析 --- 静态文件处理模块
每个web框架都会有对静态文件的处理支持,下面对于Tornado的静态文件的处理模块的源码进行分析,以加强自己对静态文件处理的理解. 先从Tornado的主要模块 web.py 入手,可以看到在App ...
- Tornado源码分析系列之一: 化异步为'同步'的Future和gen.coroutine
转自:http://blog.nathon.wang/2015/06/24/tornado-source-insight-01-gen/ 用Tornado也有一段时间,Tornado的文档还是比较匮乏 ...
- Tornado源码分析之http服务器篇
转载自 http://kenby.iteye.com/blog/1159621 一. Tornado是什么? Facebook发布了开源网络服务器框架Tornado,该平台基于Facebook刚刚收购 ...
- tornado源码分析-模块介绍
1.Core web framework tornado.web - web框架功能模块,包括RequestHandler和Application两个重要的类 tornado.httpserver - ...
- Tornado源码分析 --- Cookie和XSRF机制
Cookie和Session的理解: 具体Cookie的介绍,可以参考:HTTP Cookie详解 可以先查看之前的一篇文章:Tornado的Cookie过期问题 XSRF跨域请求伪造(Cross-S ...
- Tornado源码分析 --- Redirect重定向
“重定向”简单介绍: “重定向”指的是HTTP重定向,是HTTP协议的一种机制.当client向server发送一个请求,要求获取一个资源时,在server接收到这个请求后发现请求的这个资源实际存放在 ...
- Tornado源码分析 --- Etag实现
Etag(URL的Entity Tag): 对于具体Etag是什么,请求流程,实现原理,这里不进行介绍,可以参考下面链接: http://www.oschina.net/question/234345 ...
- tornado源码分析系列一
先来看一个简单的示例: #!/usr/bin/env python #coding:utf8 import socket def run(): sock = socket.socket(socket. ...
- tornado源码分析
初识tornado 首先从经典的helloword案例入手 import tornado.ioloop import tornado.web class MainHandler(tornado.web ...
随机推荐
- leetcode-973-最接近原点的K个点
题目描述: 可参考:题215 方法一:排序 class Solution: def kClosest(self, points: List[List[int]], K: int) -> List ...
- 校园商铺-2Logback配置与使用-2Logback配置
logback配置文件加载顺序 logback:程序在运行的时候,会按照一定的顺序去加载logbook相关的配置文件. 如果我们在配置里面制定了logbackConfigurationFile这个属性 ...
- 20175323《Java程序设计》第二周学习总结
一.教材学习内容总结 标识符第一个字符不能是数字且区分大小写数据类型转换时只允许把精度低的给精度高的,否则必须强制转换输入数据语法 Scanner reader = new Scanner(Syste ...
- 牛客多校第四场 A meeting 树的半径
题意: 有一棵树,树上有许多人,他们要聚会,找一个点使得所有人到这个点的距离的最大值最小. 题解: 首先,以一个有人的点为根,求一个生成树,删掉所有没有人的子树,保证所有的悬挂点(只连接一条边的点)都 ...
- 尚学linux课程---8、rpm软件包安装
尚学linux课程---8.rpm软件包安装 一.总结 一句话总结: rpm安装软件包的话要解决依赖问题,推荐使用yum安装软件包 1.比如cd /home中的斜线表示什么意思? 表示根目录,linu ...
- 一篇关于Matcher find方法深刻理解的文章
文章目录 知识点 find find(int var1) reset group(int var1) 源码 故事是这样的 探索 问题解决 方法一: 方法二: 方法三: 总结 知识点 find 首先fi ...
- centos6.4下安装python3.6.1
1.安装编译环境所需包 #yum install zlib-devel bzip2-devel openssl-devel ncurese-devel gcc zlib 安装成功 2.下载源码包 #w ...
- UVA 511 Do You Know the Way to San Jose?
题目链接:https://vjudge.net/problem/UVA-511 题目翻译摘自<算法禁赛入门经典> 题目大意 有 n 张地图(已知名称和某两个对角线端点的坐标)和 m 个地名 ...
- QT 获取文件的路径、打开文件的弹出对话框
1.打开获取文件夹路径的对话框 QString filePath = QFileDialog::getExistingDirectory(this, "请选择文件保存路径...", ...
- 一个小村庄的烦恼(netty-nio)
背景:一座大山有个小村庄,住着几百户人家,隔着大山那边几十里山路,有个小集市,家家户户经常翻山越岭买日用品,苦不堪言(同步阻塞,单线程,每户人家一个线程,去赶集了,今天也干不了别的活). 后来村长看着 ...