tornado简介
tornado是使用Python语言编写的一款高性能,可扩展的web服务器框架。
在命令行中执行pip install tornado来安装tornado
本节项目实战所需使用的tornado子模块:
| 描述 |
web | tornado的基础 web 框架,包含了 tornado 的大多数重要的功能 |
template | 基于 Python 的 web 模板系统,这里的模板指的是html文件 |
ioloop | 提供了核心的 I/O 事件循环 |
使用tornado快速搭建http服务器
通过tornado下的web以及ioloop模块,即可快速地搭建http服务器。请同学们按照以下步骤进行操作:
(1) 创建simple_http_server_with_tornado.py并输入以下代码:
import tornado.ioloop
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello Python")
application = tornado.web.Application([
(r"/", IndexHandler),
])
if __name__ == "__main__":
# 定义server_port变量,表示服务器的监听端口号
server_port = 8090
application.listen(server_port)
# 启动事件循环
tornado.ioloop.IOLoop.instance().start()(2) 进入windows命令行,切换到simple_http_server_with_tornado.py所在的目录,在命令行中执行python simple_http_server_with_tornado.py。
(3) 打开浏览器,在地址栏中输入:http://localhost:8090/, 敲下回车键,页面中输出的内容为:Hello Python。
配置项目的静态路径
静态路径是指静态文件的存储路径,项目中的静态文件主要包含html,css, js, 图片等文件。在对Application进行实例化时,可以在构造函数的static_path中指定服务器静态文件的路径。tornado默认将服务器运行的当前目录作为根目录,执行os.path.dirname(__file__),可获取服务器的当前目录。如需将服务器目录下的static作为静态目录,则可以参照以下代码:
os.path.join(os.path.dirname(__file__), "static")
tornado的模板系统
tornado 中的模板,主要是指HTML 文件。在模板文件中同样可以定义控制结构,以及使用表达式。控制结构使用 {% 和 %} 进行定义,例如 {% if len(items) > 2 %}。表达式则由{{ 和 }} 进行包裹,例如 {{ book["author"] }}。模板中的控制结构的结束位置需要用 {% end %}来做标记,表示语句块的结束。 模板文件中的if结构实例:
{% if 2 > 1%}
<p>2 > 1</p>
{% end %}模板文件中的for结构实例:
<ul>
{% for number in range(5)%}
<li>{{number}}</li>
{% end %}
</ul>在tornado.web.RequestHandler子类定义的处理方法中,通过执行self.render(template_file, **kwargs)方法,可以传递一个上下文参数,并对模板进行渲染。template_file表示模板文件的路径,kwargs表示传递给模板文件的上下文参数。加载模板文件前需要在Application对象中配置模板文件的路径,这样在执行render方法时,只需传递对应的文件名,这样tornado会自动在模板目录中加载该模板文件。
在Application类的构造函数中,通过指定关键字参数template_path的值,可以配置模板文件的路径。
现在同学们按照以下步骤,来练习如何在tornado中进行模板渲染:
(1) 更新simple_http_server_with_tornado.py,并输入以下代码:
import os
import tornado.ioloop
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self):
greeting = "Hello, Python"
self.render("index.html", greeting = greeting)
if __name__ == "__main__":
server_port = 8090
# 定义字典变量 settings,保存模板文件的路径
settings = {
# 在template_path中指定模板文件的路径
"template_path": os.path.join(os.path.dirname(__file__), "templates")
}
application = tornado.web.Application(
[(r"/", IndexHandler)],
**settings
)
application.listen(server_port)
tornado.ioloop.IOLoop.instance().start()(2) 在项目根目录中创建templates目录
(3) 在templates目录中创建index.html文件
index.html文件中的内容:
<p>{{greeting}}</p>同学们要注意的是模板文件中的模板变量需要与render函数中的参数名一一对应。
(4) 打开浏览器,在地址栏中输入:http://localhost:8090/, 敲下回车键,页面中输出的内容为:Hello,Python
elasticsearch简介
elasticsearch是一个高扩展,分布式的实时搜索和分析引擎,利用elasticsearch可以快速地实现一个搜索服务器。从数据管理的角度来看,elasticsearch是一种面向文档的数据库,其存储的数据在elasticsearch中被称为文档。在elasticsearch中,需要先建立索引,这里的索引类似于MySQL中的数据表。在windows系统中安装elasticsearch:
(1)进入elasticsearch的官方下载页:https://www.elastic.co/cn/downloads/elasticsearch
在页面中点击WINDOWS链接,开始进行windows版本的elasticsearch的下载。下载完毕以后,将压缩包解压到指定目录。解压完毕以后,执行bin目录下的elasticsearch.bat文件,以启动elasticsearch服务器。启动过程中需要1-2分钟时间,读者需要耐心等待。elasticsearch启动成功以后,再打开浏览器,在地址栏中输入:http://localhost:9200/。
elasticsearch的基本数据类型
在本节教程中仅介绍与该实时搜索项目相关的数据类型。
字符串类型
(1) text类型:text类型表示该字段内容会被elasticsearch中的分词器拆分成一个一个词项。
例如对于一个查询串,elasticsearch中的分词器会对查询串进行分词,然后根据拆分出的词项在elasticsearch中进行搜索,最后将搜索的结构进行聚合。
(2) keyword: 设置了keyword类型的字段不会被分析器进行词项的拆分,适合进行精确匹配的搜索场景。
数值类型:
(1) integer:表示该字段的数据类型是整型
(2) float: 表示该字段的数据类型是单精度浮点类型
(3) double: 表示该字段的数据类型是双精度浮点类型
elasticsearch中的mapping
elasticsearch中的mapping类似于关系型数据库中的表结构定义,其主要用途为定义elasticsearch索引中的字段名,以及字段的数据类型、相关属性。
elasticsearch中的查询SQL
elasticsearch使用基于JSON的查询DSL来定义查询语句,DSL表示领域特定的语言。本节仅介绍在elasticsearch中常用的多匹配查询multi_math。multi_match查询格式举例:
query = {
"query":
{
"bool": {
"must": [{
"multi_match":
{
"query": "learn python" ,
"fields": ["language", "description"]
}
}]
}
},
"from": 0,
"size": 10
}在上文的查询语句中,multi_match中的query字段用来定义待查询的内容,fields字段表示根据文档中的哪些字段进行匹配。from用于分页,size表示每页的大小。
使用elasticsearch的核心流程
(1) 在elasticsearch中创建索引
(2) 在指定的elasticsearch索引中插入数据
(3) 使用elasticsearch的搜索api来搜索数据
项目的目录组织
在前面几小节的内容中,已经分别介绍了tornado框架以及elasticsearch的基础知识 。在掌握这些知识的基础上,可以着手开发一个基于tornado,Elasticsearch的web搜索系统。项目的目录组织,如下所示:
├── app.py
├── templates
│ ├── index.html
│ ├── result.html
├── static
│ ├── images
│ │ ├──logo.png
│ ├── js
│ │ ├──jquery-3.4.1.min.js
│ │ ├──search.js
│ ├── css
│ │ ├──index.css
│ │ ├──result.css
├── dal
│ ├── database.py
├── utils
│ ├── es.py
├── config
│ ├── __init__.py
对目录结构的说明
(1) app.py是应用程序的入口函数,负责启动tornado服务器。
(2) templates目录下存放的是tornado的模板文件,index.html对应的是搜索首页,result.html对应的是搜索的结果页。
(3) static表示项目的静态目录,images目录存放项目所需的图片文件,js目录存放javascript脚本文件,css目录存放样式文件。
(4) dal表示的书数据访问层,database.py对数据库的操作进行了封装。
(5) utils目录存放工具类的脚本程序,es.py负责创建elasticsearch索引,以及将mysql中的数据插入到elasticsearch中。
(6) config目录中的__init__.py中定义项目的配置信息
基于tornado,ElasticSearch的web搜索系统
请同学们按照以下步骤进行操作。
(1) 安装elasticsearch模块
进入windows命令行,执行pip install elasticsearch安装elasticsearch模块
(2) 编辑database.py, 以及__init__.py
打开database.py文件,输入以下代码:
# __author__ = 薯条老师
# 导入MySQLdb模块
import MySQLdb
from config import DBConfig,DatabaseType
from elasticsearch import Elasticsearch
from elasticsearch import helpers
class Database:
"""
对数据库操作进行了简单的封装
"""
# 类属性__db__instances是一个字典类型,用来保存数据库的实例
__db_instances= {}
@classmethod
def get_instance(cls, db_type= DatabaseType.MYSQL):
"""
定义get_instance类方法,用来获取数据库对象的单例
所谓的单例就是一个类只有一个实例,调用该方法每次获取到
的都是同一个数据库实例,,type默认为MYSQL类型,表示
默认获取的是mysql的数据库实例
"""
if db_type not in cls.__db_instances:
# 如果不存在,就构造一个Database的实例对象
cls.__db_instances[db_type] = Database(db_type)
return cls.__db_instances[db_type]
def __init__(self, db_type=DatabaseType.MYSQL):
"""
:param db_type: 数据库的类型,数据库的类型在DatabaseType中进行了定义
默认为MYSQL类型,表示创建mysql类型的数据库实例
"""
self.__db_type = db_type
self.__db = self.__get_database()
self.__cursors = {}
def __get_database(self):
db = None
# 根据类型字段,来创建对应的数据库实例
if self.__db_type == DatabaseType.MYSQL:
try:
db = MySQLdb.connect(DBConfig[DatabaseType.MYSQL]["host"],
DBConfig[DatabaseType.MYSQL]["user"],
DBConfig[DatabaseType.MYSQL]["password"],
DBConfig[DatabaseType.MYSQL]["database"],
charset="utf8"
)
except IOError:
db = None
elif self.__db_type == DatabaseType.ELASTICSEARCH:
db = Elasticsearch([{"host": DBConfig[DatabaseType.ELASTICSEARCH]["host"],
"port": DBConfig[DatabaseType.ELASTICSEARCH]["port"]}])
return db
def batch_insert(self, sql = None, args = None, data=None):
"""
:param sql: 客户端传递的查询语句
:param args: 查询语句对应的参数
:param data: 批量插入的数据
:return:True表示批量写入成功,False表示失败
"""
status = False
if not self.__db:
return status
if self.__db_type == DatabaseType.MYSQL:
# 如果数据库的实例对象为MySQLdb,则执行executemany方法来进行批量写入
if "mysql" not in self.__cursors:
self.__cursors["mysql"] = self.__db.cursor()
try:
self.__cursors["mysql"].executemany(sql, args)
self.__db.commit()
status = True
except:
status = False
elif self.__db_type == DatabaseType.ELASTICSEARCH:
"""
如果数据库类型为ELASTICSEARCH,则通过helpers模块的bulk
方法来进行数据的批量插入
"""
try:
helpers.bulk(self.__db, data)
except:
status = False
return status
def create_database(self, **params):
"""
:param params: 可变参数,
params中的name表示数据库名,body表示创建数据库的额外参数
:return: 返回一个状态信息,True表示创建成功,False表示创建失败
"""
status = True
if self.__db_type == DatabaseType.ELASTICSEARCH:
es_index = params.get("name", None)
mappings = params.get("body", None)
if not self.__db.indices.exists(index = params["name"]):
try:
self.__db.index(index = es_index, body = mappings)
except:
status = False
return status
def query(self, ql, *args):
"""
:param ql:表示查询语句
:param args:表示查询的参数
:return:
"""
data = None
if self.__db_type == DatabaseType.MYSQL:
if "mysql" not in self.__cursors:
self.__cursors["mysql"] = self.__db.cursor()
if not args:
self.__cursors["mysql"].execute(ql)
else:
self.__cursors["mysql"].execute(ql, args)
data = self.__cursors["mysql"].fetchall()
elif self.__db_type == DatabaseType.ELASTICSEARCH:
data = self.__db.search(index=args[0], body=ql)
return data打开__init__.py文件,并输入以下代码:
class DatabaseType:
"""
定义数据库类型的枚举变量
"""
MYSQL = 1
ELASTICSEARCH = 2
# DBConfig是一个字典类型,存储了数据库的配置信息
DBConfig = {
DatabaseType.MYSQL:{
"host": "localhost",
# 填写安装mysql时设置的登录账户名
"user": "user",
# 填写安装mysql时设置的登录密码
"password": "password",
"database": "crawler"
},
DatabaseType.ELASTICSEARCH:{
"host": "localhost",
"port": 9200,
},
}(3) 将MySQL中的数据批量插入到elasticsearch
编写utils目录中的es.py, 查询mysql中的数据,并将数据批量写入到elasticsearch中,创建的索引为github_repos,索引的类型为github。启动成功elasticsearch服务器以后,进入到windows命令行,再切换到项目根目录下的utils目录,执行python es.py。程序执行完毕以后,打开浏览器,在地址栏中输入:http://localhost:9200/github_repos/_search?q=language:python, 敲下回车键后如有看到github项目信息的输出,则说明执行成功。
(4) 新增搜索结果页,并定义结果页面的样式
templates中的index.html表示搜索首页,其代码为:
<html>
<head>
<link rel="stylesheet" type="text/css" href="{{static_url('css/index.css')}}" />
<script type="text/javascript" src="{{static_url('js/jquery-3.4.1.min.js')}}"></script>
<script type="text/javascript" src="{{static_url('js/search.js')}}"></script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Github 项目搜索</title>
</head>
<body>
<div id="wrapper">
<div>
<div id="logo"><img width="271" height="106" src="{{static_url('images/logo.png')}}" alt="logo" /></div>
<div>
<form action="/search/" method="get">
<input type="text" id="query" name="query"/>
<input type="submit" id="submit" value="搜索" />
</form>
</div>
</div>
</div>
</body>
</html>templates目录中的results.html文件表示搜索结果的列表页,文件的代码为:
<html>
<head>
<link rel="stylesheet" type="text/css" href="{{static_url('css/result.css')}}" />
<script type="text/javascript" src="{{static_url('js/jquery-3.4.1.min.js')}}"></script>
<script type="text/javascript" src="{{static_url('js/search.js')}}"></script>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Github 项目搜索</title>
</head>
<body>
<div id="wrapper">
<div>
<form action="/search/" method="get">
<img width="140" height="45" src="{{static_url('images/logo.png')}}" alt="logo" />
<input type="text" id="query" name="query"/>
<input type="submit" id="submit" value="搜索" />
</form>
</div>
<div id="search-results">
<p id="prompt">为您找到的相关结果约{{hits['total']}}个,每次显示最匹配的前20条数据</p>
<p id="desc">
基于tornado与Elasticsearch的web搜索系统。使用github api从github中爬取
项目信息,然后存储到mysql数据库中。再将mysql中的数据写入到elasticsearch,
实现数据的实时搜索。
</p>
<ul id="results">
{%for result in hits['hits']%}
<li>
<div>
<img width="30" height="30" src="{{result['avatar_url']}}" />
<a href="{{result['html_url']}}">{{result['description']}}</a>
<p>作者:{{result['author']}}•项目名称:{{result['name']}}•{{result['stars']}}人喜欢</p>
</div>
</li>
{%end%}
</ul>
</div>
</div>
</body>
</html>在模板文件中使用到了模板变量hits,在第4步的操作中,会将hits变量传递至模板文件中。搜索结果页的显示页面:
static目录中的result.css,用来对搜索结果页定义样式。result.css中的css代码为:
div#wrapper{
margin-bottom:100px;
}
div.search-form{
margin:0;
padding-bottom:10px;
border-bottom:1px solid #F5F5F5;
}
form{
margin:0;
padding:0;
}
input,img{vertical-align:middle;}
input{
line-height:40px;
}
input#query{
border:0;
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.15);
width:512px;
}
input#submit{
background: #DDDDDD;
background:-moz-linear-gradient(top,#fffeff,#dddddd);
background:linear-gradient:(top, #ffffff, #dddddd);
border: 0;
font-size: 16px;
line-height:40px;
padding: 0;
width: 105px;
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.35);
}
div#search-results{
padding:0;
padding-left:145px;
}
p#prompt{
color:#C0C0C0;
font-size:15px;
}
p#desc{
color:gray;
width:600px;
padding:10px;
border:1px solid #F5F5F5;
}
ul#results{
padding:0;
list-style:none;
}
div.info{
width:600px;
padding:10px;
border-bottom:1px solid #F5F5F5;
color:gray;
}
img.avatar{
border-radius:20px;
}
a{
text-decoration:none;
color:#778899;
}
div#pages a{
border:1px solid #5F9EA0;
padding:8px;
padding-left:15px;
padding-right:15px;
}css文件中使用了css3的属性:border-radius,来定义边框的圆角。
(4) 将查询结果渲染到列表页
打开项目根目录下的app.py, 并输入以下代码:
import os
import tornado.ioloop
import tornado.web
from dal.database import Database,DatabaseType
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
class SearchHandler(tornado.web.RequestHandler):
def initialize(self, es):
self.__es = es
def get(self):
query = self.get_argument('query', '')
# from用于实现分页
from_ = self.get_argument('from', 0)
# 定义elasticsearch的搜索语句
github_query = {
"query":
{
"bool": {
"must": [{
"multi_match":
{
"query": query,
"fields": ["language", "description"]
}
}]
}
},
"from": from_,
"size": 20
}
"""
(1)执行Database实例对象的query方法
(2)在Database类中,对mysql,elasticsearch的查询操作进行了封装
"""
results = self.__es.query(github_query, "github_repos")
max_desc_length = 40
hits = {"total": results["hits"]["total"]["value"], "hits":[]}
for hit in results["hits"]["hits"]:
if len(hit["_source"]["description"]) > max_desc_length:
hit["_source"]["description"]=hit["_source"]["description"][:max_desc_length]+"..."
hits["hits"].append(hit["_source"])
self.render("result.html", hits = hits)
if __name__ == "__main__":
server_port = 8090
# 定义字典变量 settings,保存静态文件和模板文件的路径
settings = {
# 在static_path中指定静态文件的路径
"static_path": os.path.join(os.path.dirname(__file__), "static"),
# 在template_path中指定模板文件的路径
"template_path": os.path.join(os.path.dirname(__file__), "templates"),
# debug表示是否开启调试模式,在调试模式中,对项目文件的修改会立即生效
"debug": True,
}
application = tornado.web.Application(
[
(r"/", IndexHandler),
(r"/search/", SearchHandler, dict(es=Database.get_instance(DatabaseType.ELASTICSEARCH))),
],
**settings
)
application.listen(server_port)
tornado.ioloop.IOLoop.instance().start()在app.py中,定义了/search/路由,同时在/search/路由对应的处理方法中传递了elasticsearch的实例对象,然后在get方法中根据查询参数去elasticsearch服务器中实时搜索,将最后查询的结果传递给模板文件result.html。
(5) 在浏览器中进行测试
测试前,需要先启动elasticsearch服务器。同学们现在进入windows命令行,切换到项目所在的根目录,然后执行python app.py。接着在浏览器的地址栏中输入:http://localhost:8090/,敲下回车键后,出现如下页面:
在输入框中输入python, 按下回车键,出现如下页面:
最具实力的小班培训
薯条老师在广州做Python和Java的小班培训,一个班最多10人。不在广州的同学可以报名线上直播班,跟线下小班的同学们同步学习。打算参加小班培训的同学,必须遵守薯条老师的学习安排,认真做作业和项目。把知识学好,学扎实,那么找到一份高薪的工作就是很简单的一件事。
(1) Python后端工程师高薪就业班,月薪11K-18K,免费领取课程大纲
(2) Python爬虫工程师高薪就业班,年薪十五万,免费领取课程大纲
(3) Java后端开发工程师高薪就业班,月薪11K-20K, 免费领取课程大纲
(4) Python大数据分析,量化投资就业班,月薪12K-25K,免费领取课程大纲
扫码免费领取Python学习资料:
[object Object]


