知识点一 (首先呢,先搭建一个git仓库,方便日后操作代码)
Git 全局设置:
git config --global user.name "Aries"
git config --global user.email "6148694@qq.com"
创建 git 仓库:
mkdir fisher
cd fisher
git init
touch README.md
git add README.md
git commit -m "first commit"
git remote add origin https://gitee.com/wanglei230/fisher.git
git push -u origin master
已有仓库?
cd existing_git_repo
git remote add origin https://gitee.com/wanglei230/fisher.git
git push -u origin master
总结:之前对Git仓库的创建,每次都是上网复制粘贴。这次学习,要多理解,争取每次自己能独立创建。
知识点二 pipenv (主要用于隔离环境)
vim编辑虚拟环境的位置
export PIPENV_VENV_IN_PROJECT=1 (bash_profile里填写此内容,可修改虚拟环境的地址为项目内)
vim ~/.bash_profile
source ~/.bash_profile
Python第三方类库下载pipenv
pip3 install pipenv 下载pipenv
指定虚拟环境的Python版本 (我这里是Python3.7.3,尽量下载3.6.0以上的,不然后续会有不兼容的报错)
pipenv --python 3.7.3
进入虚拟环境
pipenv shell
退出虚拟环境
exit
查找虚拟环境的路径 (记得是中线- 不是下划线_)
pipenv --venv
/Users/Aries/fisher/.venv
删除虚拟环境
pipenv --rm
知识点三 pycharm里配置虚拟环境的解释器
知识点四 管理虚拟环境的类库
pip freeze > requirements.txt
下载插件
pip install -r requirements.txt
参与项目 《鱼书》 http://yushu.talelin.com/
接下来所有的学习都会按照这个项目来进行
鱼书是公益性的项目 目的是:将自己不要的书,免费赠送他人
鱼书大致功能概况
- 搜索书籍
- 赠送书籍
- 索要书籍
课前预习 做一个简单的api
from flask import Flask
app = Flask(__name__)
@app.route('/hello/')
def hello():
return "第三遍来学习了,要加油"
app.run()
提前准备的内容
需要提前下载好flask类库
pipenv shell 进入虚拟环境
pipenv install flask 下载第三方类库
pip list 可以查询已下载的类库列表
知识点五 Flask解决路由兼容 /hello /hello/
设定路由地址为 /hello
http://127.0.0.1:5000/hello 可以访问
http://127.0.0.1:5000/hello/ 访问找不到
如果兼容这两种输入
将路由设置为/hello/ 即可解决
知识点六 介绍两种路由的注册 Debug调试自动重启WEB服务器
- 通过装饰器 @app.route('/hello') 优点:简洁方便
- 通过 app.add_url_rule('/hello',view_func=hello) 优点:更加灵活
- 增加debug参数,修改完代码,自动重启服务器
- 装饰器 @app.route是通过调用add_url_rule而来的
from flask import Flask
app = Flask(__name__)
def hello():
return "第三遍来学习了,要加油"
app.add_url_rule('/hello',view_func=hello)
app.run(debug=True)
知识点七 启动服务器添加host和端口号
app.run(host='0.0.0.0',debug=True,port=8080)
- host 0.0.0.0 可以被外网访问 以及localhost 127.0.0.1 所有的网卡访问
- port 是设置的端口号
知识点八 读取配置文件
- config.py 新增文件 ####【变量名必须是大写字母】####
DEBUG = True
- fisher.py
载入配置文件 app.config.from_object('config')
读取配置文件 print(app.config['DEBUG'])
-
疑问1 config配置文件里输入 debug=True 读取app.config['debug']的时候提示找不到‘debug’
答案:因为通过app.config.from_object读取的配置文件,会忽略掉小写字母,所以就找不到debug
-
疑问2
config里输入 debug =True 读取读取app.config['DEBUG']显示False
答案:因为Flask内置的DEBUG=False 所以即使不写也可以找得到
如果你写了DEBUG=True 相当于把这个值覆盖了
知识点九 if name的作用
from flask import Flask
app = Flask(__name__)
app.config.from_object('config')
print(app.config['debug'])
@app.route('/hello')
def hello():
return "第三遍来学习了,要加油"
#app.add_url_rule('/hello',view_func=hello)
if __name__ == '__main__':
app.run(host='0.0.0.0',debug=app.config['DEBUG'],port=8081)
- 入口文件,只执行下面的内容
- 如果此脚本被导入,不会执行下面的内容
知识点十 视图函数的return 和普通函数的return有何区别
@app.route('/hello')
def hello():
return "<html>好好学习 </html>"
def hello1():
return "<html>好好学习 </html>"
-
视图函数的return不仅返回内容 还返回content-type
知识点十一 response
from flask import Flask,make_response
@app.route('/hello')
def hello():
header = {
"content-type":"text/plain",
}
response = make_response("<html>开始读书了<html>",200)
response.headers=header
return response
知识点十二 return也可以按照response返回 更加便捷
@app.route('/hello')
def hello():
header = {
"content-type":"text/plain",
}
return "<html>DAYDAY UP</html>",200,header
自己练习 尝试写JSON格式的返回
@app.route('/hello')
def hello():
header = {
"content-type":"application/json",
}
result ={
"status":200,
"name":'wanglei',
"desc":"leiziyaohaohaoxuexile "
}
return result,200,header
总结
- make_response 和 return 都可以返回数据
- return 更便捷 make_response作为介绍,后续应该不会在出现了
项目 编写查询
- 图书的查询 分为ISBN号查询和普通关键词查询
isbn编号13 13个0到9的数字组成
isbn编号10 10个0到9的数字组成,含有一些'-'
@app.route('/book/search/<q>/<page>')
def search(q,page): //q为关键词 通过输入的q来判断是关键词还是ISBN
is_key_isbn = 'key‘
if len(q) == 13 and q.isdigit(): //isdigit是Python的内置函数来判断是否是纯数字
is_key_isbn = 'isbn'
if '-' in q and q.replace('-','').isdigit() and len(q)==10:
is_key_isbn = 'isbn'
else:
is_key_isbn = 'key'
重构代码
- 视图函数一定要简洁明了
- 封装函数 复用性强
创建helper.py
def is_isbn_or_key(q):
is_key_isbn = 'key'
if len(q) == 13 and q.isdigit():
is_key_isbn = 'isbn'
if '-' in q and q.replace('-','').isdigit() and len(q) == 10:
is_key_isbn = 'isbn'
fisher.py去导入is_isbn_or_key
from helper import is_isbn_or_key
@app.route('/book/search/<q>/<page>')
def search(q,page):
isbn_or_key = is_isbn_or_key(q)
请求api (判断完ISBN然后需要去请求接口)
创建http.py
import requests
class HTTP:
def get(self,url,return_json=True):
r =requests.get(url)
# restful
# json
if r.status_code == 200:
return r.json() if return_json else r.text //三元表达式
return {} if return_json else ''
首先呢 request url
再通过status_code去返回内容
status_code!=200 有可能是没有找到书籍 所以返回空
通过三元表达式 和if return 去简化表达式
知识点十三 三元表达式
r.json() if return_json else r.text
if 成立 执行 r.json 否则 r.text
知识点十四 静态方法 我也不太清楚
@staticmethod 来源于菜鸟课程
class A():
def aa(self,a=1,b=3):
return a+b
@staticmethod
def bb(a=5,b=10):
return a+b
@classmethod
def cc(cls,c=11,d=20):
return c+d
A = A()
print(A.aa())
print(A.bb())
print(A.cc())
/Users/Aries/fisher/.venv/bin/python /Users/Aries/fisher/test.py
4
15
31
Process finished with exit code 0
Traceback (most recent call last):
File "/Users/Aries/fisher/test.py", line 21, in <module>
print(A.aa())
TypeError: aa() missing 1 required positional argument: 'self'
- 优点在于 @staticmethod 不需要self,cls这些参数
- @staticmethod,@classmethod并且不用实例化 直接可以调用函数
知识点十五 python3里创建类后面的括号和不带括号区别
class Http(object):
class Http:
- Python3中没有任何区别
- Python2中一个代码经典类 一个代表新式类 知道就行 不需要了解太多
项目搜索功能 新增 yushu_book.py
from httper import HTTP
class YuShuBook:
isbn_url = "http://t.talelin.com/v2/book/isbn/{}"
keyword_url = "http://t.talelin.com/v2/book/search?q={}&count={}&start={}"
@classmethod
def search_by_isbn(cls,isbn):
url = cls.isbn_url.format(isbn)
result = HTTP.get(url)
return result
@classmethod
def search_by_keyword(cls,keyword,count=15,start=0):
url = cls.keyword_url.format(keyword,count,start)
result = HTTP.get(url)
return result
- 完成isbn的搜索
- 完成关键词的搜索
项目 搜索功能 视图函数去调用 yushu_book的search_by_keyword函数和search_by_isbn函数
@app.route('/book/search/<q>/<page>')
def search(q,page):
isbn_or_key = is_isbn_or_key(q)
if isbn_or_key == 'key':
result = YuShuBook.search_by_keyword(q)
else:
result = YuShuBook.search_by_isbn(q)
return result
请求接口 http://192.168.8.101:8081/book/search/9787501524044/1
返回结果:
{
"author": [
"蔡智恒"
],
"binding": "平装",
"category": "小说",
"id": 1780,
"image": "https://img3.doubanio.com/lpic/s1327750.jpg",
"images": {
"large": "https://img3.doubanio.com/lpic/s1327750.jpg"
},
"isbn": "9787501524044",
"pages": "224",
"price": "12.80",
"pubdate": "1999-11-1",
"publisher": "知识出版社",
"subtitle": "",
"summary": "你还没有试过,到大学路的麦当劳,点一杯大可乐,与两份薯条的约会方法吗?那你一定要读目前最抢手的这部网络小说——《第一次的亲密接触》。\\n由于这部小说在网络上一再被转载,使得痞子蔡的知名度像一股热浪在网络上延烧开来,达到无国界之境。作者的电子信箱,每天都收到热情的网友如雪片飞来的信件,痞子蔡与轻舞飞扬已成为网络史上最发烧的网络情人。",
"title": "第一次的亲密接触",
"translator": []
}
知识点十六 如何将字典转成字符串
import json
json.dumps(result)
知识点十七 flask的jsonfy
import flask import jsonfy
return jsonfy(result)
return json.dumps(result),200,"content-type:application/json"
- 目前的话 直接返回result 就已经是JSON格式,
可能是版本更新了,七月老师的版本还是只能显示字符串,
字典无法显示数据 需要字典转换成字符串 才能显示
目前版本不需要考虑 jsonfy 和 json.dumps(result) 作为一个知识点记录一下吧,有个印象
项目重构 提取Flask核心对象到app/init
- 提取目的还是为了让启动页面干净
from flask import Flask
create_app():
app = Flask(__name__)
app.config.from_object('config')
return app
知识点十八 蓝图
-
蓝图不能独立存在,必须插入到app上
app/web/book.py
from flask import Blueprint
web = Blueprint("web",__name__)
@web.route('/book/search/<q>/<page>')
- 蓝图在Flask核心对象上插入
def create_app():
app =Flask(__name__)
register_blueprint(app) //
return app
def register_blueprint(app):
form app.web.book import web
app.register_blueprint(web)
知识点十九 拆分单蓝图
- app/web/_init.py
from flask import Blueprint
web = Blueprint("web",__name__)
from app.web import book
知识点二十 flask 里的 request
@web.route('/book/search')
def search():
//request.args['']可以获取到请求里?后面的内容
q = request.args['q']
page = request.args['page']
isbn_or_key = is_isbn_or_key(q)
if isbn_or_key == 'key':
result = YuShuBook.search_by_keyword(q)
else:
result = YuShuBook.search_by_isbn(q)
return result
知识点二十一 WTForms参数验证
pipenv install wtforms 首先虚拟环境先下载wtforms的模块
-
新建一个forms的文件 在新建一个book.py
forms/book
from wtforms import Form,StringField,IntegerField
from wtforms.validators import Length,NumberRange
class SearchForm(Form):
q = StringField(validators=[Length(min=1,max=30)])
page = IntegerField(validators=[NumberRange(min=1,max=99)],default=1)
- web/book
@web.route('/book/search')
def search():
form = SearchForm(request.args)
if form.validate():
q = form.q.data.strip()
page = form.page.data
isbn_or_key = is_isbn_or_key(q)
if isbn_or_key == 'key':
result = YuShuBook.search_by_keyword(q)
else:
result = YuShuBook.search_by_isbn(q)
return result
else:
#return {"msg":"输入内容格式有误!"}
return form.errors 可以返回form里的自定义的报错信息
这里的报错信息也可以自己设置 在form里
q = StringField(validators=[Length(min=1,max=30,message='关键词的字符应该是1---30位,请检查输入的关键词')])
- 设置输入空格的判断 DataRequired() 判断空格
validators=[DataRequired(),Length(min=1,max=30)
![判断空格](https://upload-
images.jianshu.io/upload_images/17753024-b0b1fc0e99071007.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
总结三点
- form.errors 输入默认的报错信息
- message 设置自定义的报错信息
- DataRequired() 判断输入空格报错
知识点二十二 current_app
- from flask import current_app
current_app 相当于app
这里不能在重新导入app了 会重复导入报错的 所以用current_app
@staticmethod
def formit_page(page):
return (page-1)*current_app.config['PER_PAGE']
项目
新增了secure.py 配置文件(存一些数据库密码等信息 安全级别高的)
新增了setting.py 配置文件(存储普通信息)
搜索关键词的代码 需要的 count start 通过page替换了一下
setting.py
PER_PAGE=15yushubook.py
from flask import current_app
@classmethod
def search_by_keyword(cls,keyword,page):
url = cls.keyword_url.format(keyword,current_app.config['PER_PAGE'],cls.formit_page(page))
result = HTTP.get(url)
return result
@staticmethod
def formit_page(page):
return (page-1)*current_app.config['PER_PAGE']
项目
- 新建libs类 把httper.py help.py 把帮助类都放进去
-
新建spider类 把yushu_book 放进来 放一些 持久化存储的数据
知识点二十三 sqlalchemy
- 下载 flask_alchemy
-
创建models文件
- String 字符串
- Integer 数字类型
- primary_key 主键
- autoincrement 自增长
- nullable 是否为空
- unique 唯一 不重复 (ISBN)
- default 默认值
实例化 SQLAlchemy
models.book.py
from sqlalchemy import Column,String,Integer
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy() 实例化 SQLAlchemy
class Book(db.Model)://继承db.Model
- 注册到app
from flask import Flask
from app.models.book import db //关键代码
def create_app():
app = Flask(__name__)
app.config.from_object("app.setting")
app.config.from_object("app.secure")
register_blueprint(app)
db.init_app(app) //关键代码
db.create_all(app=app) //关键代码
return app
``
###### 知识点二十四 链接数据库
secure.py 数据库信息存在这个配置文件里
from urllib import parse
pwd = "Gaojunqing1101111asdad@" //因为我的密码有特殊字符 需要转义
pwd_encode = parse.quote_plus(pwd)
数据库+驱动类型+账号+密码+服务器地址+端口号+数据库名
SQLALCHEMY_DATABASE_URI = f"mysql+cymysql://root:{pwd_encode}@39.107.60.13:3306/fisher"
``
这里报错了
RuntimeError: No application found. Either work inside a view function or push an application context. See http://flask-sqlalchemy.pocoo.org/contexts/.解决方法是:
- 因为栈顶没有内容了 所以报错了
2.解决方案1:
with app.app_context():
db.create_all()
解决方案2:
db.create_all(app=app)
解决方案3:
cxt = app.app_context()
cxt.push()
db.create_all()
cxt.pop()
知识点二十五 Flask 上下文
from flask import Flask,current_app
app = Flask(__name__)
ctx = app.app_context()
ctx.push() 推入应用程序栈中
a = current_app
b = current_app.config['DEBUG']
ctx.pop() 弹出栈
- 首先呢,先检测appcontext栈顶是否为空 为空再去 requestcontext中 推入栈顶
- 如果在请求中,appcontext不需要自己主动去推入,Flask会帮助它自动去推入到栈中
- _request_ctx_stack 是对LocalStack栈的实例
- push是推入
- pop是弹出
- 栈是 后进先出 队列是 先进先出
进程和线程的理解
进程相当于是一个车间
线程相当于是车间里的工人
一个车间可以有多个工人
一个进程里可以有多个线程
ViewModel
鱼书API通过ISBN和关键词返回的内容比较乱
通过viewModel合并成一个完整的数据结构
新建一个 view_model 用于格式化数据
class BookViewModel():
#isbn搜索数据
@classmethod
def package_single(cls,data,keyword):
returned = {
'books':[],
'total':0,
'keyword':keyword
}
if data:
returned['total'] = 1
returned['books'] = [cls._cut_book_data(data)]
return returned
#关键词搜索数据
@classmethod
def package_collection(cls,data,keyword):
rerurned = {
'books':[],
'total':0,
'keyword':keyword
}
if data:
rerurned['total'] = data['total']
rerurned['books'] = [ cls._cut_book_data(book) for book in data['books'] ]
return rerurned
@classmethod
def _cut_book_data(cls,data):
book = {
'title':data['title'] or '',
'publisher':data['publisher'] or '', //原数据返回null 使用 or 来改写
'pages':data['pages'] or '',
'author':'、'.join(data['author']), //原数据返回[] 使用join拼接
'price':data['price'] or '',
'summary':data['summary'] or '',
'image':data['image'] or ''
}
return book
代码重构
- 重构 yushu_book.py
from app.libs.httper import HTTP
from flask import current_app
class YuShuBook:
isbn_url = "http://t.talelin.com/v2/book/isbn/{}"
keyword_url = "http://t.talelin.com/v2/book/search?q={}&count={}&start={}"
def __init__(self):
self.total = 0
self.books = []
def search_by_isbn(self,isbn):
url = self.isbn_url.format(isbn)
result = HTTP.get(url)
self.__fill_single(result)
def search_by_keyword(self,keyword,page):
url = self.keyword_url.format(keyword,current_app.config['PER_PAGE'],self.formit_page(page))
result = HTTP.get(url)
self.__fill_collection(result)
def __fill_single(self,data):
if data:
self.total = 1
self.books.append(data)
def __fill_collection(self,data):
self.total = data['total']
self.books = data['books']
def formit_page(self,page):
return (page-1) * current_app.config['PER_PAGE']
- 重构view_models/book.py
class BookViewModel:
def __init__(self,book):
self.title = book['title'] or ''
self.publisher = book['publisher'] or ''
self.author = '、'.join(book['author']) or ''
self.image = book['image'] or ''
self.price = book['price'] or ''
self.summary = book['summary'] or ''
self.pages = book['pages'] or ''
class BookCollection:
def __init__(self):
self.total = 0
self.books = []
self.keyword = ''
def fill(self,yushu_book,keyword):
self.total = yushu_book.total
self.keyword = keyword
self.books = [BookViewModel(book) for book in yushu_book.books]
- web/book
import json
from app.view_models.book import BookViewModel, BookCollection
from app.libs.helper import is_isbn_or_key
from app.spider.yushu_book import YuShuBook
from flask import request
from . import web
from app.forms.book import SearchForm
@web.route('/book/search')
def search():
form = SearchForm(request.args)
books = BookCollection()
if form.validate():
q = form.q.data.strip()
page = form.page.data
isbn_or_key = is_isbn_or_key(q)
yushu_book = YuShuBook()
if isbn_or_key == 'isbn':
yushu_book.search_by_isbn(q)
else:
yushu_book.search_by_keyword(q,page)
books.fill(yushu_book,q)
return json.dumps(books,default=lambda o:o.__dict__)
//books 这里是一个对象 没法返回值 所以通过__dict__去返回
else:
#return {"msg":"输入内容格式有误!"}
return form.errors
-
序列化对象
- 把对象 序列化成字典 dict
default=lambda o:o.dict
项目 访问静态文件 创建一个static的文件夹 存放图片
-
http://0.0.0.0:8080/static/XXX.png 可以访问到文件夹下的图片
Jinja2 if 语句
<body>
{#<p>{{data.name}}</p>
<p>{{data.age}}</p>#} //{##}为注释信息
{% if data.age <=19 %} //Jinja2的if判断语句
<p1>我小于18岁</p1>
{% elif data.age ==18 %}
<p1>我成年了</p1>
{% else %}
<p1>我老了</p1>
{% endif %}
</body>
- HTML读取接口返回的字典数据 {{data.name}}
- 注释信息 {#name#}
- {% if %} {% endif %} 判断语句 一定要有头有尾 可elif else嵌套使用
Jinja2 for in 语句
{% endif %}
{% for i in ['薇恩','逍遥生','九尾狐']%}
{{I}}
{{111}}
{% endfor%}
{% for key,value in data.items() %}
{{key}}
{{value}}
{% endfor %}
模板填充和继承
- 首先创建一个layout.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% block top %}
<p>This is top </p>
{%endblock%}
{% block content %}
<p>This is content </p>
{%endblock%}
{% block foot %}
<p>This is foot </p>
{%endblock%}
</body>
</html>
- 然后用 test.html 去导入填充数据
{% extends 'layout.html' %} //引用模板数据
{% block content %}
{{ super() }} //显示layout里的content里的内容 不写的话不显示**
这里是我的内容
{% endblock %}`
JinJa2 过滤器与管道命令
- 管道命令| 一个管道一个管道的传值
- default | 找不到的值才会给默认值
- length |判断长度
{{data.school | default('内蒙古电子信息科技学院')}} //data.school 不存在 default 只能用在不存在的内容上
{{data.name | length()}} //判断值的长度 不能使用Length(data.name)
反向构建URL url-for
<link rel="stylesheet" href="{{url_for('static',filename='test.css')}}">
- 通过一些参数 构建URL好处是 修改host或者文件名和域名都不会受到影响
消息闪现、SecretyKey、变量作用域
from flask import flash
flash("王磊,你好")
test.html
{% set message = get_flashed_messages() %}
{{message}}
另外需要配置文件输入SECRET_KEY = ‘wangleinmbbnmmnb’ //随便输入
作用域 with endwith 作用域只能在with 和 endwith之间
{% with message = get_flashed_messages() %}
{{message}}
{% endwith %}
项目 接下来项目就要和前端揉在一起了
- 拷贝项目 static文件
- 拷贝项目 templat文件
- 拷贝项目web文件 并把文件注册到蓝图上
- 修改base.html 71~~~79行注释掉
修改book.py代码
@web.route('/book/search')
def search():
form = SearchForm(request.args)
books = BookCollection()
if form.validate():
q = form.q.data.strip()
page = form.page.data
isbn_or_key = is_isbn_or_key(q)
yushu_book = YuShuBook()
if isbn_or_key == 'isbn':
yushu_book.search_by_isbn(q)
else:
yushu_book.search_by_keyword(q,page)
books.fill(yushu_book,q)
#return json.dumps(books,default=lambda o:o.__dict__),200,{"content-type":"application/json"}
else:
#return {"msg":"输入内容格式有误!"}
#return form.errors
flash('搜索的关键字不符合要求,请重新输入关键字') //新增修改
return render_template("search_result.html",books=books) //新增修改
@web.route('/book/<isbn>/detail') //新增
def book_detail(isbn):
pass