偷個懶,公號摳腚早報80%自動化——3.Flask速成大法

簡述

在上一節中,咱們編寫了抓取新聞的爬蟲腳本,天天早上八點定時抓取新聞保存到 MySQL數據庫中。直接用DataGrip連下數據庫,就能夠查看爬取到的新聞了。不過, 並非咱們想要的最終形態。我但願新聞篩選的工做能夠直接在手機上進行,而不是 每次都要打開電腦,打開DataGrip黑乎乎的頁面,篩選,複製粘貼。在寫這個APP以前, 要先學點web的東東,寫點接口。Python中有比較流行的兩個Flask和Django,筆者選擇 的比較輕量級的Flask。在寫《如何用Python投機倒把幾天「暴富」》,有些同窗私信我有 沒有Flask的教程推薦。索性本節就來過一過Flask,下一節再來利用Flask來寫API接口和 靜態頁面,以及直接生成公號文章樣式。內容較多,比較枯燥,建議先收藏後慢慢觀看~css


一、Flask簡介

一個輕量級的「微內核」Web應用框架,基於Werkzeug實現的WSGI套件和Jinja2模板引擎, 內核精簡,易於擴展,花費較少的學習成本,便可能開發一個簡單的網站。html

相關文檔python


二、Flask開發環境搭建

直接經過pip命令安裝便可:mysql

pip install flask
複製代碼

這裏要注意「全局安裝」和「虛擬環境安裝」的區別,以前有不少讀者都問過這樣的問題:jquery

我明明在命令行裏已經執行了pip install xxx庫,可是進去pycharm,仍是提示模塊找不到?nginx

對此,有兩種可選的解決方案:git

  • 方案一在PyCharm處的終端處執行pip安裝命令(注:前面有個venv)

  • 方案二勾選 inherit global stie-packages

我的更傾向於第一種,爲了解決維護不一樣項目對應不一樣版本的問題,Python使用了虛擬環境的概念, 在虛擬環境中安裝第三方包只會做用到虛擬環境中,全局的Python解釋器不受影響。在Python3中, 虛擬環境已成爲一個內置模塊,建立一個帶虛擬環境的文件示例以下:github

mkdir Test
cd Test
python -m venv venv
複製代碼

執行完上述命令後,Python會運行venv包,建立一個venv的虛擬環境,上面的兩個venv參數分別爲:web

  • Python虛擬環境包的名稱,固定寫venv
  • 應用於這個特定的虛擬環境的名稱,能夠改爲你喜歡的名字,不過筆者習慣命名爲venv,切換到別 的項目時,都能快速的找到對應的虛擬環境。

虛擬環境建立後,須要激活後才能進入,經過下述命令「激活虛擬環境」:正則表達式

source venv/bin/activate
複製代碼

執行完後會看到終端前綴多了個venv,激活虛擬環境後,終端會話的環境配置就會被修改, 此時鍵入Python或者pip,實際上調用的都是虛擬環境中的Python解釋器。一個應用場景: 打開多個終端調試多個應用,每一個終端窗口能夠激活不一樣的虛擬環境,且不相互干擾。

注:若是你使用的是Python2或者Windows系統,若是想使用虛擬環境,要先經過pip命令 安裝先安裝一波virtualenvwrapper:pip install virtualenvwrapper。而後建立虛擬環境: virtualenv venv,最後是激活虛擬環境:venv\Scripts\activate.bat


三、最簡單的Hello Flask

新建一個hello.py的腳本,內容以下:

# coding=utf-8
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello Flask!"

if __name__ == "__main__":
    app.run()
複製代碼

在終端鍵入下述命令執行腳本:

python hello.py
複製代碼

運行後能夠看到輸出以下信息:

瀏覽器打開:http://127.0.0.1:5000,能夠看到如圖所示的Hello Flask!

服務啓動後就會進入輪詢,等待並處理請求,知道程序被終止,也能夠直接按Ctrl+C停掉。 接着逐行解析一波代碼:

  • 第1行:用於聲明Python源文件的編碼語法,該信息後續會被Python解析器用於解析源文件, 通常統一用utf-8。
  • 第2行:引入Flask類。
  • 第4行:實例化一個Flask類實例app,該實例接收包或模塊名稱做爲參數,通常直接傳入__name__, 讓flask.helpers.get_root_path函數經過傳入這個名字肯定程序的根目錄,以便得到靜態文件和 模板文件的目錄。
  • 第6-8行:app:route裝飾器會將URL和執行的視圖函數的關係保存到app.url_map屬性上。 這三行簡單點說就是:當瀏覽器訪問服務器程序的根地址("/")時,Flask實例執行視圖函數hello(), 而後返回:Hello Flask!
  • 第10行:其餘文件引用此文件時,不會執行這個判斷內的代碼。
  • 第11行:啓動服務,默認Flask只監聽本地127.0.0.1地址和5000端口,若是你想修改端口,能夠 傳入參數「port=端口號」,想支持遠程訪問的話,則傳入「host="0.0.0.0"」,你還能夠設置 調試模式,只需傳入參數「debug=True」,啓用調試模式後,服務器會在代碼修改後會自動從新 載入,並在發生錯誤的時候提供一個能獲取錯誤上下文及可執行代碼的調試頁面。

四、flask-script模塊

該模塊的做用是:經過命令行的形式來操做Flask,使用命令行傳參,而不只僅是經過 app.run()函數來傳。直接經過pip命令便可安裝此模塊:

pip install flask-script
複製代碼

新建一個manage.py的文件:

from flask_script import Manager
from flask import Flask

app = Flask(__name__)
manager = Manager(app)

if __name__ == '__main__':
    manager.run()
複製代碼

接着命令行鍵入:

python manage.py runserver -h ip -p 端口
複製代碼

除此以外的參數還有:-d(開啓調試模式),-r(代碼修改後自動加載),--help(查看幫助信息)。


五、路由與視圖

上面這種:

@app.route("/")
def hello():
    return "Hello Flask!"
複製代碼

定義了URL到Python函數間的映射關係,這種映射關係就叫路由。

0x1 動態路由

有時,可能有一些具備相同規則的URL,好比:

app.route("/login")
app.route("/register")
複製代碼

咱們能夠把這類URL抽象成一個URL模式,示例以下:

@app.route('/<do>')
def hello(do):
    return "<h1>當前請求:%s </h1>" % do
複製代碼

Flask支持這種動態形式的路由,動態部分默認是字符串,你也能夠指定參數類型, 好比只接受整數:<int:id>,更多規則以下表所示:

字段標記 描述 示例
string 默認,接受任何沒有斜杆"/"的文本 <string:name>
int 整數 <in:id>
float 浮點數 <float:price>
path 和string相似,但也接受斜槓 <path:address>
uuid 只接受uuid字符串 <string:uuid>
any 能夠指定多種路徑,但須要傳入參數 <any(int,string):params>

另外有一點要注意:惟一URL,好比下面表明的是兩個不一樣的URL:

CodingBoy.io/article/
CodingBoy.io/article
複製代碼

0x2 URL構造

在Flask中,可使用url_for()函數來構造URL,接受一個視圖函數名做爲第一個參數, 也接受對應URL規則變量部分的命名參數,未知變量部分會被添加到URL末尾做爲查詢 參數。這裏務必注意一點:操做的是函數,不是路由裏的路徑!!!! 經過函數構建,而不是直接用字符串拼接,主要出於下面兩個目的:

  • 將來須要修改時,只需一次性修改URL,而不用處處替換;
  • URL構建會自動轉義特殊字符和Unicode數據,而不用咱們本身處理;

使用示例以下

with app.test_request_context():
    print(url_for('hello', uid=1))
    print(url_for('hello', uid=2, kind='測試'))
複製代碼

輸出結果以下

/?uid=1
/?uid=2&kind=%E6%B5%8B%E8%AF%95
複製代碼

若是你想生成一個絕對路徑,能夠添加「_external=True」參數。

注:test_request_context能夠在交互模式下產生請求上下文,不用app.run() 來運行這個項目,直接執行也會有Falsk上下文

0x3 請求方法限定

HTTP支持多種請求方法,默認狀況下,路由只響應GET請求,若是不信能夠自行用 PostMan對接口發起一個POST請求,結果如圖所示:

響應碼是405,表示不容許使用這種請求方法請求此URL,能夠直接在 app.route裝飾器中設置methods參數來修改。修改後的代碼示例以下:

@app.route("/", methods=['GET', 'POST'])
def hello():
    return "Hello Flask!"
複製代碼

固然也支持其餘的請求方法:PUT,HEAD,DELETE,OPTIONS,PATCH,想支持多種方法用逗號隔開便可。


六、模板

在Web開發中,咱們常常會用到模板引擎,什麼是模板?就是一個包含響應文本的文件, 用佔位符(變量)標識動態部分,告訴模板引擎,其具體值須要從使用的數據中獲取

0x1 視圖與模板的關係

在前面的例子中,視圖函數的主要做用是生成請求的響應,而實際開發中,視圖函數有 兩個做用:「處理業務邏輯」和「返回響應內容」。在大型項目中,若是把業務邏輯 和表現內容放在一塊兒的話,會增長代碼的複雜度和維護成本。而模板的做用就是: 「承擔視圖函數返回響應內容這一部分的職責,從而使得代碼結構清晰,耦合度低。」 使用真實值替換變量,再(控制)返回最終獲得的字符串,這個過程稱做「渲染」。 Flask中默認使用「Jinja2」這個模板引擎來渲染模板。

0x2 Jinja2語法

經過一個簡單的例子,引入模板,下面是一個沒有使用模板的簡單程序:

# coding=utf-8
from flask import Flask

app = Flask(__name__)
@app.route("/<user>")
def hello(user):
    if user == 'admin':
        return "<h1>管理員,您好!<h1>"
    else:
        return "<h1>%s, 您好!</h1>" % user
if __name__ == "__main__":
    app.run()
複製代碼

接着咱們使用Jinja2模板進行改寫,默認狀況下,Flask會在「項目的templates子文件夾」 中尋找模板,咱們新建一個templates文件夾,而後新建一個index.html,內容以下:

"<h1>{{name}},您好!<h1>"
複製代碼

接着修改下hello.py文件,修改後的內容以下:

from flask import Flask, render_template

app = Flask(__name__)

@app.route("/<user>")
def hello(user):
    if user == 'admin':
        return render_template("index.html", user="管理員")
    else:
        return render_template("index.html", user=user)

if __name__ == "__main__":
    app.run()
複製代碼

接着運行hello.py,瀏覽器鍵入如下地址,對應輸出結果以下:

http://127.0.0.1:5000/admin     # 輸出結果:管理員,您好!
http://127.0.0.1:5000/jay       # 輸出結果:jay,您好!
複製代碼

以上就是一個簡單的模板使用示例,經過調用Flask提供的render_template函數, 生成了一個模板,第一個參數是模板的名稱,隨後的參數是鍵值對,表示模板中 變量對應的真實值。接着詳細講解一波Jinja2的語法:

  • 1.變量

Jinja2使用**{{變量名}}**來表示一個變量,除了基本數據類型外還可使用列表,字段 或對象等複雜類型,示例以下:

<h1> 帳號:{{ user['name'] }},密碼:{{ user['passwd'] }}
複製代碼
  • 2.控制結構

Jinja2提供了多種控制結構,好比常見的判斷和循環結構,用於修改模板的渲染流程。 咱們把上面判斷的邏輯也放到模板裏,示例以下:

{# 註釋,不會輸出到瀏覽器中 #}
{% if user == 'admin' or user == '管理員' %}
    <h1> 管理員,您好! </h1>
{% else %}
    <h1> {{ user }},您好!</h1>
{% endif %}

{# 循環打印 #}
{% for num in num_list %}
    <h2>{{ num }}</h2>
{% endfor %}
複製代碼

接着修改下hello.py文件:

@app.route("/<user>")
def hello(user):
    return render_template("index.html", user=user, num_list=[1, 2, 3])
複製代碼

鍵入不一樣的URL,對應瀏覽器的輸出結果以下:

http://127.0.0.1:5000/admin
管理員,您好!
1
2
3

http://127.0.0.1:5000/jay
jay,您好!
1
2
3
複製代碼
  • 3.

在Python中若是有一些很經常使用的代碼,咱們會習慣性地抽取成一個函數,在Jinji2中, 可使用宏來實現。語法以下:

# 建立宏
{% macro 標籤名(key=value) %} 
    經常使用代碼
{% end macro %}

# 調用宏
{{ 標籤名(key=value) }}
複製代碼

若是宏比較多,能夠抽到單獨的HTML中,再import進來。

{% import 'xxx.html' as 別名 %}
{{ 別名.標籤名(key=value) }}
複製代碼
  • 4.模板繼承

另外一種代碼複用的方式:模板繼承,和Python中類繼承同樣,須要一個基模板,用 {% block XXX %}{% endblock %} 標識一個代碼塊,能夠在子模塊中重載。 代碼示例以下:

# 基模板:base.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    {% block head %}
        <title>{% block title %} Title {% endblock %} </title>
    {% endblock %}
</head>
<body>
    {% block body %}
    {% endblock %}
</body>
</html>

# 子模板:son.html
{% extends "base.html" %}
{% block title %}Son{% endblock %}
{% block head %}
    {{ super() }}
{% endblock %}
{% block body %}
<h1>Hello Flask!</h1>
{% endblock %}
複製代碼

代碼簡述:

第一行代碼使用extends命令,代表該模板繼承自base.html,接着重寫了title,head和 body代碼塊,上面的super()函數用於獲取基模板原先的內容。

  • 5.inclue

能夠用include語句包含一個模板,在渲染時會把include語句對應位置添加被包含的模板, 使用示例以下:

{% include 'header.html' %}
複製代碼

另外還可使用「ignore missing」標記,若是模板不存在,Jinja2會忽略此語句,示例以下:

{% include 'header.html' ignore missing %}
複製代碼
  • 6.賦值

可使用set標籤來進行賦值,示例以下:

{% set a,b = 1,2}
<h1>{{ a }}{{ b }}</h1>
複製代碼
  • 7.內建過濾器

過濾器的本質就是函數,有時須要修改,格式化或者執行變量間的計算,而在模板中是不能 直接調用Python中的某些方法的,能夠用到過濾器。模板的過濾器支持鏈式調用:

{{ "hello flask" | reverse | upper}},
複製代碼

這串代碼就是反轉+轉換成大寫,常見的內建過濾器有字符串和列表兩種。 字符串操做過濾器以下表所示:

過濾器 描述
safe 禁用轉義
capitalize 把變量值的首字母轉成大寫,其他字母轉小寫
lower 把值轉成小寫
upper 把值轉成大寫
title 把值中的每一個單詞的首字母都轉成大寫
reverse 字符串反轉
format 格式化輸出
striptags 渲染以前把值中全部的HTML標籤都刪掉
truncate 字符串截斷

列表操做過濾器以下表所示:

過濾器 描述
first 取第一個元素
last 取最後一個元素
length 獲取列表長度
sum 列表求和
sort 列表排序
  • 8.自定義過濾器

直接在py文件中編寫,代碼示例以下:

# 1.定義過濾器
def do_listreverse(li):
    temp_li = list(li)
    temp_li.reverse()
    return temp_li

# 2.添加自定義過濾器
app.add_template_filter(do_listreverse, 'listreverse')
複製代碼

總結

宏,繼承和包含都用實現代碼複用,宏功能相似於函數,能夠傳參,須要定義調用; 繼承本質是代碼替換,通常用來實現多個頁面中重複不變的區域;而包含是直接將 目標模板文件整個渲染出來。


七、請求與相應

在Flask中,HTTP請求被封裝成了Request對象,而HTTP響應則被封裝成了Response對象。 所以Flask應用開發的邏輯處理,都是基於這兩個對象。

0x1 Request請求

Flask會將WSGI服務器轉發的http請求數據封裝爲一個Request對象,這個對象中包含了請求的 相關信息,能夠經過下表中的屬性來獲取對應的信息。

屬性 描述 數據類型
form 記錄請求中的表單數據。 MultiDict
args 記錄請求中的查詢參數。 MultiDict
cookies 記錄請求中的cookie。 Dict
headers 記錄請求中的報文頭。 EnvironHeaders
method 記錄請求使用的HTTP方法。 string
environ 記錄WSGI服務器轉發的環境變量。 Dict
url 記錄請求的URL地址。 string
  • 1.讀取request的查詢參數

瀏覽器以GET請求的方式提交的表單數據,Flask會將其存儲在request實例的args,也能夠用values屬性 來查詢,讀取代碼示例以下:

# coding=utf-8
from flask import Flask, request, json

app = Flask(__name__)
@app.route("/index")
def index():
    return ''' <form method="GET" action="/login"> <input type="text" placeholder="帳號" name="user"> <br /> <input type="text" placeholder="密碼" name="pawd"> <br /> <input type="submit" value="登陸"> </form> '''
@app.route("/login")
def login():
    msg = ""
    if 'user' in request.args:
        msg += request.args['user'] + ':'
    msg += request.values.get('pawd','')
    return msg
if __name__ == "__main__":
    app.run(debug=True)
複製代碼

代碼運行後,打開:http://127.0.0.1:5000/index,瀏覽器:

輸入帳號密碼後,跳轉到:http://127.0.0.1:5000/login?user=zpj&pawd=123, 瀏覽器:

  • 2.讀取request的表單數據

瀏覽器以GET請求的方式提交的表單數據,Flask會將其存儲在request實例的form中,可使用[]操做符 讀取指定鍵值。讀取代碼示例以下:

# coding=utf-8
from flask import Flask, request, json

app = Flask(__name__)

@app.route("/index")
def index():
    return ''' <form method="POST" action="/login"> <input type="text" placeholder="帳號" name="user"> <br /> <input type="text" placeholder="密碼" name="pawd"> <br /> <input type="submit" value="登陸"> </form> '''

@app.route("/login", methods=['POST'])
def login():
    msg = ""
    msg += request.form['user'] + ':'
    msg += request.form['pawd']
    return msg

if __name__ == "__main__":
    app.run(debug=True)
複製代碼

代碼運行後,打開:http://127.0.0.1:5000/index,瀏覽器:

輸入帳號密碼後,跳轉到:http://127.0.0.1:5000/login,瀏覽器:

0x2 Response響應

和Request對應,Response用於給瀏覽器發送響應信息,根據視圖函數的返回結果。這個視圖函數 就是咱們路由下面的函數,視圖函數的返回值會自動轉換成一個響應對象,轉換邏輯以下:

  • 一、返回值是合法的響應對象,從視圖直接返回;
  • 二、返回值是字符串,會用字符串和默認參數建立以字符串爲主體,返回碼爲200,MIME類型爲 text/html的werkzeug.wrappers.Response響應對象。
  • 三、返回值是元組,其中的元素能夠提供額外信息,但格式必須是(response,status,headers) 的形式,至少包含一個元素,status值會覆蓋狀態碼,headers能夠是字典或列表,做爲額外的消息頭。
  • 四、若是都不是,Flask會假設返回值是合法的WSGI程序,經過Response.force(rv.request.environ) 轉換爲一個請求對象。除了經過return方式返回,還能夠顯式地調用make_response函數返回,好處是能夠 設置一些額外的信息,示例以下:
def hello():
    resp = make_response("Hello Flask!", 250)
    return resp
複製代碼

另外,如今的API接口都是返回JSON格式的,能夠用jsonify包裝下,修改後的示例代碼以下:

# coding=utf-8
from flask import Flask, make_response, jsonify
from werkzeug.wrappers import Response

app = Flask(__name__)

@app.route("/", methods=['GET', 'POST'])
def hello():
    resp = make_response({'code': '200', 'msg': '請求成功', 'data': [{'data_1': ['數據', '數據'], 'data_2': ['數據', '數據']}]})
    return resp

class JsonResponse(Response):
    @classmethod
    def force_type(cls, response, environ=None):
        if isinstance(response, dict):
            response = jsonify(response)
        return super(JsonResponse, cls).force_type(response, environ)

app.response_class = JsonResponse

if __name__ == "__main__":
    app.run(debug=True)
複製代碼

請求接口輸出以下:

{
    "code": "200",
    "data": [
        {
            "data_1": [
                "數據",
                "數據"
            ],
            "data_2": [
                "數據",
                "數據"
            ]
        }
    ],
    "msg": "請求成功"
}
複製代碼

也能夠用Flask的json模塊的dumps()函數將數組或字典對象轉換爲JSON字符串,代碼示例以下:

data = {'code': '200', 'msg': '請求成功', 'data': [{'data_1': ['數據', '數據'], 'data_2': ['數據', '數據']}]}
return json.dumps(data),200,[('Content-Type','application/json;charset=utf-8')]
複製代碼
  • 3.設置Cookie Response類中提供了set_cookie()函數用於設置客戶端的cookie,若是要設置cookie,須要咱們本身構建Response實例 (經過make_response),可選參數以下:
set_cookie(
 key, //鍵
 value='', //值
 max_age=None, //秒爲單位的cookie壽命,None表示http-only
 expires=None, //失效時間,datetime對象或unix時間戳
 path='/', //cookie的有效路徑
 domain=None, //cookie的有效域
 secure=None, 
 httponly=False)
複製代碼

八、重定向與會話

Web開發中常常須要處理重定向和會話,Flask中內建了「redirect」和「session」來對它們進行處理。

0x1 重定向

頁面重定向很是常見,最經常使用的就是登錄狀態判斷,若是沒登錄將網頁重定向到登陸頁,Flask中可使用redirect 對象對其進行處理,狀態碼默認爲302,能夠傳入code參數來修改,通常是:301,302,303,305和307, 簡單的代碼示例以下:

# coding=utf-8
from flask import Flask, redirect

app = Flask(__name__)

user_name = ''

@app.route('/article')
def article():
    if user_name == '':
        # 若是用戶名爲空,重定向跳轉到登陸頁
        return redirect('/login')
    else:
        return "文章頁"

@app.route("/login")
def login():
    global user_name
    user_name = 'admin'
    return "登陸成功!"

if __name__ == "__main__":
    app.run(debug=True)
複製代碼

運行後操做流程以下:

瀏覽器鍵入:http://127.0.0.1:5000/article
自動跳轉到:http://127.0.0.1:5000/login,顯示登陸成功
再次訪問:http://127.0.0.1:5000/article,顯示文章頁
複製代碼

0x2 會話

咱們能夠把數據存儲在用戶會話(session)中,用戶會話是一種私有存儲,默認狀況下保存在客戶端cookie中。 會話主要爲了解決兩個問題:「訪問者的標識和訪問者信息記錄」。瀏覽器第一次訪問服務器,服務器在其 cookie中設置一個惟一的會話ID,瀏覽器後續對服務器的訪問頭中將自動包含該信息,服務器經過這個ID號來 區分不一樣的訪問者。session依賴於cookie,通常存儲在服務器,Flask提供了session對象來操做用戶會話, 可使用[]操做符讀取或者設置指定鍵值,默認狀況下,Flask將會話對象加密後存儲在客戶端的cookie裏, 所以必需要應用實例的secret_key屬性配置一個加密種子才能使用session。用法示例以下:

# 設置session
session['name'] = 'jay'
# 讀取session
session.get('name')
# 配置加密種子(兩種方法二選一)
app.secret_key = '123456' 
app.config['SECRET_KEY']='123456'
複製代碼

九、靜態文件管理

靜態文件就是那些不會被改變的文件,例如:圖片,CSS樣式文件,JavaScript腳本文件和字體文件等。 Flask默認會在根目錄中名爲static的子目錄中尋找靜態文件,因此若是須要用到靜態文件能夠建立一個 static的文件夾,而後把靜態文件丟裏面。能夠參考下面這樣的結構來組織項目:

static/
    css/
        lib/
            bootstrap.css
        style.css
        home.css
    js/
        lib/
            jquery.js
            chart.js
        home.js
    img/
        logo.svg
        favicon.ico
複製代碼

另外,不要在模板中寫死靜態文件路徑,應該使用url_for函數生成路徑,示例以下:

url_for('static', filename='css/style.css')
複製代碼

固然,若是你想修改靜態文件的真實目錄,能夠在Flask構造函數中傳入參數:static_folder='文件夾名'。 另外,爲了得到更好的處理能力,建議使用Nginx或其餘Web服務器管理靜態文件,圖片這類資源能夠 託管到CDN平臺上。(好比七牛雲)


十、藍圖

藍圖(Blueprint),定義了可用於單個應用的視圖,模板,靜態文件等等的集合。通俗點理解就是 一個實現應用模塊化的好工具,使用藍圖能使得項目層次更加清晰,更易於開發和維護,一般做用於 相同URL前綴的路由。先來看一個沒使用藍圖的示例:

# coding=utf-8
from flask import Flask

app = Flask(__name__)

@app.route('/user/index')
def user_index():
    return 'user_index'

@app.route('/user/show')
def user_show():
    return 'user_show'

@app.route('/user/add')
def user_add():
    return 'user_add'

@app.route('/admin/index')
def admin_index():
    return 'admin_index'

@app.route('/admin/show')
def admin_show():
    return 'admin_show'

@app.route('/admin/add')
def admin_add():
    return 'admin_add'

if __name__ == "__main__":
    app.run(debug=True)
    
複製代碼

上面的代碼挺整齊的,不過有幾個問題:

  • 若是user和admin的功能不止上面的幾個,而是好幾百呢,代碼會很是龐大臃腫。
  • 大型的項目都是多人協做的,全部人都在這裏文件裏開發的話,處理合並衝突會很頭痛。
  • 若是哪天這兩個用戶模塊不要了,還須要一行行的去找,而後刪代碼。

咱們使用藍圖來把user和admin拆分紅兩個不一樣的.py文件,admin.py 文件內容以下:

# coding=utf-8
from flask import Blueprint

admin = Blueprint('admin', __name__,url_prefix='/admin')

@admin.route('/index')
def admin_index():
    return 'admin_index'

@admin.route('/show')
def admin_show():
    return 'admin_show'

@admin.route('/add')
def admin_add():
    return 'admin_add'
複製代碼

user.py 文件內容以下:

# coding=utf-8
from flask import Blueprint

user = Blueprint('user',__name__)

@user.route('/index')
def user_index():
    return 'user_index'

@user.route('/show')
def user_show():
    return 'user_show'

@user.route('/add')
def user_add():
    return 'user_add'
複製代碼

註冊藍圖,hello.py的代碼內容以下:

# coding=utf-8
from flask import Flask
from admin import *
from user import *

app = Flask(__name__)
app.register_blueprint(admin)
app.register_blueprint(user, url_prefix='/user')

if __name__ == "__main__":
    print(app.url_map)
    app.run(debug=True)
複製代碼

利用app.url_map函數,查看全部的路由,打印結果以下:

Map([<Rule '/admin/index' (GET, HEAD, OPTIONS) -> admin.admin_index>,
 <Rule '/admin/show' (GET, HEAD, OPTIONS) -> admin.admin_show>,
 <Rule '/admin/add' (GET, HEAD, OPTIONS) -> admin.admin_add>,
 <Rule '/user/index' (GET, HEAD, OPTIONS) -> user.user_index>,
 <Rule '/user/show' (GET, HEAD, OPTIONS) -> user.user_show>,
 <Rule '/user/add' (GET, HEAD, OPTIONS) -> user.user_add>,
 <Rule '/static/<filename>' (GET, HEAD, OPTIONS) -> static>])
複製代碼

url_prefix這個參數用於設置request.url中的url前綴,另外只有知足前綴的請求才會 經過註冊的藍圖的視圖方法處理請求並返回。能夠寫在子模塊中,也能夠在register_blueprint 註冊藍圖的時候傳入,只需傳入一次!以後打開http://127.0.0.1:5000/admin/index,能夠看到 如圖所示的結果:


十、g對象和鉤子函數

有時在處理請求先後,執行某些特定代碼是很是有用的,這就用到了請求鉤子,好比:請求前建立db連接, 驗證用戶身份等,flask提供了註冊通用函數的功能,只須要寫一個請求鉤子——函數,整個程序實例全局都被應用, 好比請求前驗證用戶狀態的例子,沒登錄跳轉登陸頁面等。鉤子函數須要藉助Flask的全局變量g,g做爲中間變量, 在鉤子函數和視圖函數間傳遞數據。

0x1 g對象

g,global,g對象是專門用來保存用戶數據的,存儲的數據在全局均可以使用。代碼示例以下:

from flask import g, request

@app.route('/')
def index():
    user = request.args.get('user')
    g.user = user   # 保存用戶數據
複製代碼

0x2 鉤子函數

Flask提供下述四種鉤子函數:

  • before_first_request:在第一次請求前調用,能夠在此方法內作一些初始化操做。
  • before_request:在每次請求前調用,通常作校驗,若是校驗不成功,能夠在這個方法內直接響應,直接return的話,不會執行視圖函數。
  • after_request:在執行完視圖函數以後會調用,並把視圖函數生成的響應傳入,能夠在此方法中對響應作最後一步贊成的處理。
  • teardown_request:每一次請求後都會調用,會接收一個參數——服務器出現的錯誤信息。

寫一個程序來驗證下鉤子函數的執行流程(運行後,訪問兩次):

127.0.0.1 - - [30/Aug/2018 10:53:42] "GET / HTTP/1.1" 200 -
before_first_request
before_request
after_request
teardown_request

127.0.0.1 - - [30/Aug/2018 10:53:45] "GET / HTTP/1.1" 200 -
before_request
after_request
teardown_request
複製代碼

十一、上下文

上下文至關於一個容器,保存了Flask程序運行過程當中的一些信息,根據管理機制分爲兩種:

  • 請求上下文(RequestContext) Request:請求的對象,封裝了Http請求的內容; Session:根據請求中的cookie,重載訪問者相關的會話信息。

  • 程序上下文(AppContext) g:處理請求時用做臨時存儲的對象,保存的是當前請求的全局變量,不一樣的請求會有不一樣的全局變量! current_app:當前運行程序的程序實例,保存的是應用程序的變量,好比可使用current_app.name獲取當前應用的名稱, 也能夠在current_app中存儲一些配置信息,變量等,使用示例:current_app.text = 'value'。

Flask中,而關於上下文的管理能夠分爲三個階段:

  • 請求進來時:將request,session封裝到RequestContext類中,app, g封裝在AppContext類中, 並經過LocalStack將RequestContext和AppContext放入Local類中。
  • 視圖函數:經過localproxy -> 偏函數 -> localstack -> local取值。
  • 請求結束前:執行save.session() -> 各自執行pop() -> 清除local中的數據。

十二、異常處理

在開發中,後臺發生異常,但又不想把異常顯示給用戶看,或者須要同一處理的時候,可使用abort()函數 主動拋出異常,再捕獲異常,而後返回一個美化後的頁面,最多見的就是404了,代碼示例以下:

@app.route("/test")
def test():
abort(404)

@app.errorhandler(404)
def error(e):
    return "一個精美的404頁面"
複製代碼

代碼執行後,瀏覽器鍵入:http://127.0.0.1:5000/test,能夠看到如圖所示的結果:

另外,你也能夠把全部異常處理些寫到一個藍圖中,代碼示例以下:

# coding=utf-8
from flask import Blueprint, abort

exception = Blueprint('exception', __name__)

@exception.errorhandler(404)
def error(e):
    return "一個精美的404頁面"

# 註冊藍圖
from error import exception
app.register_blueprint(exception, url_prefix='/error')
複製代碼

1三、ORM框架——SQLAlchemy

使用對象映射關係(Object-Relational Mapper)ORM框架來操做數據庫。所謂的ORM框架就是:

將底層的數據操做指令抽象成高層的面向對象操做

不用再寫繁瑣的SQL操做語句,利用ORM框架能夠簡化成對Python對象的操做。

表映射成類行做爲實例字段做爲屬性.

ORM在執行對象操做時會將對應操做轉換爲數據庫原生語句。Python中用得最普遍的ORM框架是SQLAlchemy。

0x1 安裝flask-sqlalchemy

直接使用pip命令安裝便可

pip install flask-sqlalchemy
複製代碼

另外SQLAlchemy自己沒法操做數據庫,依賴於pymysql等第三方庫,裏面有個Dialect模塊專門用於 和數據API交流,根據配置文件的不一樣而調用不一樣的數據庫API,從而實現對數據庫的操做。數據庫使用 URL限定,常見的數據庫引擎與其對應的URL以下表所示:

數據庫引擎 URL
MySQL mysql+pymysql://username:password@hostname/database
Postgres postgresql://username:password@hostname/database
SQLite (Unix,開頭四個斜線) sqlite:////absolute/path/to/database
SQLite (Windows) sqlite:///c:/absolute/path/to/database
Postgres postgresql://username:password@hostname/database
Oracle oracle://username:password@hostname/database

參數簡述

  • username:登陸數據庫的用戶名。
  • password:登陸數據庫的密碼。
  • hostname:SQL服務所在的主句,能夠是本地也能夠是遠程。
  • database:使用的數據庫。

0x2 鏈接數據庫

鏈接MySQL數據庫的代碼示例以下:

from sqlalchemy import create_engine

engine = create_engine('mysql+pymysql://jay:zpj12345@localhost:3306/todo')
with engine.connect() as con:
    rs = con.execute("SELECT 1")
    print(rs.fetchone())
複製代碼

輸出結果以下

(1,)
複製代碼

另外還能夠經過下面這樣的方法來初始化SQLAlchemy,代碼示例以下:

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
app = Flask(__name__)
    db.init_app(app)
複製代碼

0x3 使用原生SQL

sqlalchemy支持直接執行原生的SQL語句,代碼示例以下:

from sqlalchemy import create_engine

engine = create_engine('mysql+pymysql://jay:zpj12345@localhost:3306/todo')
with engine.connect() as con:
    con.execute("CREATE TABLE IF Not Exists note(_id INT AUTO_INCREMENT PRIMARY KEY, tran TEXT, status int)")
    con.execute("INSERT INTO note(tran, status) VALUES ('吃飯', 1)")
    con.execute("INSERT INTO note(tran, status) VALUES ('睡覺', 1)")
    rs = con.execute("SELECT * FROM note")
    for row in rs:
        print(row)
複製代碼

代碼輸出結果以下:

(1, '吃飯', 1)
(2, '睡覺', 1)
複製代碼

0x4 ORM模型與基本操做

演示一波Flask-SQLAlchemy的基本操做:

  • 1.建表create_all,對應的刪除表能夠用drop_all
# models.py
from manager import db

class Note(db.Model):
    __tablename__ = 'note'
    _id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)
    tran = db.Column(db.TEXT)
    status = db.Column(db.INT,default=0)

db.create_all() # 建立表
複製代碼
  • 2.插入數據
from model import Note
from manager import db

def create_note():
    # 建立一個新對象
    note1 = Note()
    note1.tran = "吃飯"
    note1.status = "1"

    note2 = Note()
    note2.tran = "睡覺"
    note2.status = "2"

    # 將新建筆記添加到數據庫會話中
    db.session.add(note1)
    db.session.add(note2)

    # 將數據庫會話中的變更提交到數據庫中,若是不commit,數據庫內容是不會變化的
    db.session.commit()

create_note()
複製代碼
  • 3.刪除數據
def delete_note():
    # 獲取筆記對象(這裏是獲取_id=1的記錄)
    note = Note.query.filter_by(_id=1).first()

    # 刪除筆記
    db.session.delete(note)

    # 提交數據庫會話
    db.session.commit()

delete_note()
複製代碼
  • 4.修改數據
def update_note():
    # 獲取筆記對象(這裏是獲取_id=2的記錄)
    note = Note.query.filter_by(_id=2).first()

    # 修改筆記內容
    note.tran = "打豆豆"

    # 提交數據庫會話
    db.session.commit()

update_note()
複製代碼
  • 5.查詢數據

說到查詢,必然有查詢條件,SQLAlchemy提供了以下表所示的經常使用查詢函數:

函數 描述 使用示例
filter_by 精確查詢 filter_by(xxx='xxx')
filter 模糊查詢 filter(xxx.endWith('xxx'))
get(主鍵) 根據主鍵查詢,通常爲id get(1)
not_() 邏輯非,也能夠直接把==換成!= not_(xxx='xxx')
and_() 邏輯與 and_(xxx='xxx')
or_() 邏輯或 or_(xxx='xxx')
in_() 在某個範圍裏 XXX.xxx.in_((1,2,3))
notin_() 不在某個範圍內 XXX.xxx.notin_((1,2,3))
first() 返回查詢到的一個對象 XXX.query.first()
all() 返回查詢到的全部對象 XXX.query.all()
order_by() 排序 XXX.order_by(xxx.xxx.desc())
limit() 限制返回條數 XXX.limit(3)
offset() 設置偏移量 XXX.offset()
count() 返回記錄的總條數 xxx.count()

代碼示例以下:

from sqlalchemy import not_, or_

def query_all():
    notes = Note.query.all()
    print("查詢所有數據:", notes)
    note = Note.query.filter(not_(or_(Note.tran == '吃飯', Note.tran == '睡覺'))).first()
    print(note._id, ":", note.tran, ":", note.status)

if __name__ == '__main__':
    # 先插入幾條數據
    create_note()
    create_note()
    query_all()
複製代碼

輸出結果以下:

查詢所有數據: [<Note 2>, <Note 3>, <Note 4>, <Note 5>, <Note 6>]
2 : 打豆豆 : 2
複製代碼

14.Web表單插件——Flask-WTF

Flask中通常不會直接用原始表單,而是經過Flask-WTF擴展,它簡單繼承了WTForms,包括CSRF (跨域請求僞造),驗證表單數據的功能,文件上傳以及Google內嵌的驗證碼。

0x1 WTForms支持的HTML標準字段

字段類型 說明
StringField 文本字段
TextAreaField 多行文本字段
PasswordField 密碼文本字段
HiddenField 隱藏文本字段
DateField 文本字段,值爲datetime.date格式
DateTimeField 文本字段,值爲datetime.datetime格式
IntegerField 文本字段,值爲整數
DecimalField 文本字段,值爲decimal.Decimal
FloatField 文本字段,值爲浮點數
BooleanField 複選框,值爲True和False
RadioField 一組單選框
SelectField 下拉列表
SelectMultipleField 下拉列表,可選擇多個值
FileField 文件上傳字段
SubmitField 表單提交按鈕
FormField 把表單做爲字段嵌入另外一個表單
FieldList 一組指定類型的字段

0x2 WTForms驗證函數

驗證函數 說明
Email 驗證電子郵件地址
EqualTo 比較兩個字段的值,經常使用於要求輸入兩次密碼進行確認的狀況
IPAddress 驗證IPv4網絡地址
Length 驗證輸入字符串的長度
NumberRange 驗證輸入的值在數字範圍內
Optional 無輸入值時跳過其餘驗證函數
Required 確保字段中有數據
Regexp 使用正則表達式驗證輸入值
URL 驗證URL
AnyOf 確保輸入值在可選值列表中
NoneOf 確保輸入值不在可選列表中

0x3 寫個簡單的例子

接着咱們來編寫一個註冊表單的例子:

# coding=utf-8
from flask import Flask, request, render_template
from flask_wtf import FlaskForm
# 導入自定義表單須要的字段
from wtforms import SubmitField, StringField, PasswordField
# 導入表單驗證
from wtforms.validators import DataRequired, EqualTo

app = Flask(__name__)
app.config['SECRET_KEY'] = '123456'


# 自定義表單類
# StringField和PasswordField用於區分文本框類型
# 第一個參數是label值,第二個參數validators是要驗證的內容
class RegisterForm(FlaskForm):
    username = StringField('用戶名:', validators=[DataRequired()])
    password = PasswordField('密碼:', validators=[DataRequired()])
    password2 = PasswordField('確認密碼:', validators=[DataRequired(), EqualTo('password', '兩次輸入的密碼不一致!')])
    submit = SubmitField('註冊')


@app.route("/register", methods=['GET', 'POST'])
def register():
    # 實例化註冊表單類
    register_from = RegisterForm()
    if request.method == 'POST':
        # 獲取請求參數參數
        username = request.form.get('username')
        password = request.form.get('password')
        password2 = request.form.get('password2')
        # 調用validation_on_submit,一次性執行完全部驗證函數的邏輯
        if register_from.validate_on_submit():
            return '註冊成功!'
        else:
            return '先後密碼不一致!'
    return render_template('register.html', form=register_from)


if __name__ == "__main__":
    print(app.url_map)
    app.run(debug=True)

複製代碼
# templates/register.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form method="post">
        {# 設置scrf_token #}
        {{ form.csrf_token() }}
        {{ form.username.label }}&nbsp;&nbsp;{{ form.username }}<br>
        {{ form.password.label }}&nbsp;&nbsp;{{ form.password }}<br>
        {{ form.password2.label }}&nbsp;&nbsp;{{ form.password2 }}<br>
        <br>
        {{ form.submit }}
    </form>
</body>
</html>
複製代碼

輸入一波同樣的密碼和不同的密碼,瀏覽器的輸出結果以下所示:

另外有一點要注意:

使用Flask-WTF須要配置參數SECRET_KEYCSRF_ENABLED是爲了**CSRF(跨站請求僞造)**保護。 SECRET_KEY用於生成加密令牌,當CSRF激活的時候,該設置會根據設置的密鑰生成加密令牌。


15.一個簡單通用的Flask項目結構

Flask基礎學得差很少了,接着咱們來規範下項目結構,一個比較簡單通用的項目結構如圖:

簡述下結構

  • app:整個項目的包目錄。
  • models:數據模型。
  • static:靜態文件,css,JavaScript,圖標等。
  • templates:模板文件。
  • views:視圖文件。
  • config.py:配置文件。
  • venv:虛擬環境。
  • manage.py:項目啓動控制文件。
  • requirements.txt:項目啓動控制文件。

建立流程

__init__.py中初始化app實例,代碼以下:

from flask import Flask

app = Flask(__name__)
複製代碼

views.py中寫個簡單的index路由:

from app import app
from flask import render_template

@app.route('/')
def index():
    return render_template("index.html")
複製代碼

templates文件夾建立一個index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Hello Flask!</h1>
</body>
</html>
複製代碼

接着鍵入:python manage.py runserver,瀏覽器打開:http://127.0.0.1:5000/

項目結構基本就弄好了,接着咱們把項目丟到遠程服務器上。


16.把Flask項目部署到雲服務器上

0x1 代碼上傳至雲服務器

有兩種可選的方法,最簡單的就是經過FTP/SFTP工具直接傳。另一種是把項目託管到 相似於Github這類代碼託管平臺,而後直接ssh連雲服務器,經過git clone命令把項目 拷貝到服務器上,這裏筆者直接用的第一種方法,把項目上傳到雲服務器上。

0x2 安裝nginx

Nginx是一款輕量級、性能強、佔用資源少,能很好的處理高併發的反向代理軟件。 Flask自帶的Web Server只能開單線程,本身測試還行,放到線上就不行了,這裏 咱們用到Nginx,直接經過apt-get命令進行安裝,命令以下:

apt-get install nginx
複製代碼

安裝完後,外網訪問服務器的公網ip,出現下述頁面說明安裝成功:

Nginx安裝完,會默認建立一個目錄:/var/www/,直接經過命令把咱們的 項目移動到這個路徑下。

mv AutoPubNews /var/www/AutoPubNews
複製代碼

0x3 安裝配置uwsgi

WSGI是一種WEB服務器,或者叫網關接口,Web服務器(如nginx)與應用服務器 (如uWSGI)通訊的一種規範(協議)。而uWSGI實現了WSGI的全部接口,是一個 快速、自我修復、開發人員和系統管理員友好的服務器。uWSGI代碼徹底用C編寫, 效率高、性能穩定。舉個例子:uWSGI把HTTP協議轉化成WSGI協議,讓Python能夠 直接使用。直接鍵入pip命令安裝:

pip install uwsgi
複製代碼

通常都是能直接安裝完成的,若是出錯了能夠試試先安裝libpython3.x-dev, 好比筆者的版本是3.5:

apt-get install libpython3.5-dev
複製代碼

接着配置一下uwsgi,在項目裏新建一個config.ini做爲配置文件:

vim config.ini
複製代碼

添加下述內容:

[uwsgi]
# uwsgi 啓動時所使用的地址與端口
socket = 127.0.0.1:8001 # 可使用其餘端口 

# 指向網站目錄 
chdir = /var/www/AutoPubNews                                                                   

# python 啓動程序文件 
wsgi-file = manage.py                                                                      

# python 程序內用以啓動的 application 變量名 
callable = app                                                                              

# 處理器數 
processes = 4                                                                               

# 線程數 
threads = 2                                                                                 

#狀態檢測地址
stats = 127.0.0.1:5000    
複製代碼

接着執行下述命令:

uwsgi config.ini
複製代碼

出現: Stats server enabled on 127.0.0.1:5000,表明正常啓動。

0x4 配置Nginx

接着配置下Nginx,不要去動默認的nginx.conf,直接將:/etc/nginx/sites-available/default 覆蓋掉,新建default文件,添加下述內容:

server {
      listen  80;
      server_name _; 

      location / {
        include      uwsgi_params;
        uwsgi_pass   127.0.0.1:8001;  # 指向uwsgi 所應用的內部地址,全部請求將轉發給uwsgi 處理
        uwsgi_param UWSGI_PYHOME /home/www/AutoPubNews/venv; # 指向虛擬環境目錄
        uwsgi_param UWSGI_CHDIR  /home/www/AutoPubNews; # 指向網站根目錄
        uwsgi_param UWSGI_SCRIPT manager:app; # 
      }
    }
複製代碼

接着鍵入下述命令重啓加載下nginx配置:

sudo service nginx restart
複製代碼

接着啓動uwsgi,而後就能夠經過服務器的公網ip直接訪問咱們的項目了:

0x5 域名解析

每次訪問項目都用ip,顯得有些繁瑣,咱們能夠買個域名作下映射耍耍。 域名直接買就好,須要備案,搞定後打開域名管理頁,找到剛買的域名 點擊解析

而後點擊「添加記錄」會出現如圖所示的對話框,記錄值那裏填你的雲服務器公網ip便可。

此時就能夠直接經過域名來訪問咱們的項目了。


行吧,關於Flask速成就這麼都,下節咱們來利用Flask編寫API接口,動態生成頁面等。 有疑問的歡迎在評論區留言,謝謝~

Tips:公號目前只是堅持發早報,在慢慢完善,有點心虛,只敢貼個小圖,想看早報的能夠關注下~

相關文章
相關標籤/搜索