上一講學(xué)習(xí)了編寫網(wǎng)頁代碼的方法,到目前為止,創(chuàng)建的網(wǎng)頁文件只能用瀏覽器打開。如果需要用同一網(wǎng)絡(luò)中的其它電腦或者手機訪問該頁面,則需要搭建HTTP服務(wù)。
普通電腦上也可以搭建HTTP服務(wù),成為小型的HTTP服務(wù)器,使用Python搭建HTTP服務(wù)非常簡單,不需要額外安裝軟件,只要安裝Python的三方模塊Flask即可實現(xiàn)。
使用Python開發(fā)網(wǎng)站,只需要加入少量代碼,就可以將Python的工作成果快速地展示給用戶。
18.1 簡單例程
Flask是一個輕量級的Web應(yīng)用框架,占用資源少,使用簡單。本節(jié)將學(xué)習(xí)如何用Flask創(chuàng)建一個最簡單的網(wǎng)站。
在Anaconda安裝時已經(jīng)安裝了Flask,因此可以直接使用,程序代碼如下:
01 from flask import Flask
02
03 app = Flask(__name__)
04
05 @app.route('/test.html')
06 def hello_world():
07 return '<h1>Hello World! </h1>'
08
09 app.run(host='0.0.0.0', port=8088)
第01行引入了flask三方模塊的Flask類。
第03行創(chuàng)建一個flask對象,并賦值給app,傳入的參數(shù)name(注意:前后都是兩條下劃線)是當(dāng)前模塊的名字。
第05行用于指定在訪問網(wǎng)址的路徑“/test.html”時調(diào)用的函數(shù)。
第06-07行定義訪問路徑對應(yīng)的函數(shù)hello_word(),函數(shù)返回的字符串”<h1>Hello World!</h1>”是html風(fēng)格的簡單網(wǎng)頁數(shù)據(jù),其作用是將字符串“Hello World!”作為標(biāo)題顯示。
此處是本節(jié)的重點,程序定義了hello_word函數(shù),但并沒有看到調(diào)用它的代碼,這是由于第05行將其下面定義的函數(shù)關(guān)聯(lián)到該網(wǎng)站的“/test.html”路徑下,也就是說當(dāng)用戶訪問該網(wǎng)址時,hello_world()函數(shù)被調(diào)用,其返回值被返回給瀏覽器顯示。
第09行用run函數(shù)開啟了Web服務(wù)的主循環(huán),它將一直運行,直到程序退出,參數(shù)將主機host設(shè)置為IP地址’0.0.0.0’,啟動程序的端口為8088?!?.0.0.0’是一個特殊的IP地址,設(shè)置之后,網(wǎng)絡(luò)上的其它設(shè)備才能訪問該服務(wù),否則只有本機可以訪問。
在Jupyter Notebook中運行服務(wù)后,程序?qū)⒁恢碧幱谶\行狀態(tài),如果想停止該服務(wù),需要點擊Jupyter界面上的“中斷服務(wù)”(“運行”圖標(biāo)右邊的黑色矩形圖標(biāo)),重啟服務(wù)時也需要先中斷,再開啟,這點非常重要。否則修改可能不起作用。
程序只使用了不到10行代碼,在本機的8088端口啟動了HTTP服務(wù),此時用瀏覽器打開網(wǎng)址:http://127.0.0.1: 8088/test.html,即可看到本機啟動的網(wǎng)絡(luò)服務(wù)。其中127.0.0.1 是一個特殊的IP地址,它代表當(dāng)前計算機。
利用本機對外的IP地址,可以讓同一網(wǎng)絡(luò)上的其他計算機或者手機訪問當(dāng)前的HTTP服務(wù),方法如下,先打開Windows命令行:開始菜單->所有程序->附件->命令提示符,在其中輸入ipconfig命令,其結(jié)果中顯示的IPv4地址(如:192.168.1.107),即本機的IP地址。
通過手機瀏覽器打開當(dāng)前HTTP服務(wù)的效果如圖18.1所示:

18.2 地址和端口
18.2.1 地址
網(wǎng)頁是使用上一講介紹的工具制作的HTML文件,可通過瀏覽器解析成圖文格式。網(wǎng)站指的是互聯(lián)網(wǎng)上特定內(nèi)容相關(guān)網(wǎng)頁的集合。
瀏覽網(wǎng)頁時,在瀏覽器上方的地址欄輸入網(wǎng)址,一般用英文字母表示。此處的網(wǎng)址指的是URL統(tǒng)一資源定位符,一般由三部分組成:第一部分是協(xié)議(如HTTP);第二部分是存有該資源的主機地址,有時也包括端口號;第三部分是主機資源的具體路徑。例如:
https://blog.csdn.net/xieyan0811 其中https是協(xié)議,blog.csdn.net是主機地址,xieyan0811是主機資源的具體路徑。
主機地址可以用IP地址表示,例如192.168.0.1,為了方便記憶,采用域名來代替IP地址標(biāo)識站點地址,如blog.csdn.net,域名一般由有意義的字符串表示。域名解析就是域名到IP地址的轉(zhuǎn)換過程。域名的解析工作由DNS服務(wù)器完成。DNS服務(wù)器一般是由運營商負責(zé)維護的,它也是互聯(lián)網(wǎng)的重要組成部分。
打開Windows命令行:開始菜單->所有程序->附件->命令提示符,在其中輸入ipconfig命令,其結(jié)果中顯示的IPv4地址,即本機的IP地址。
18.2.2 端口號
客戶端可以通過ip地址或者域名找到對應(yīng)的服務(wù)器,服務(wù)器端則可以提供一種或者多種服務(wù),比如Web服務(wù)、文件傳輸服務(wù)、郵件服務(wù)等等,不同的服務(wù)使用端口號區(qū)分,例如:郵件服務(wù)常用110端口,文件傳輸常用21端口,HTTP常用80端口等等。端口號的取值范圍是1-65535,1-1023為系統(tǒng)端口,其中大多數(shù)端口號已經(jīng)定義了對應(yīng)的功能,如上面列出的常用端口;1024-5000為臨時端口,5001-65535用于自定義端口,開發(fā)者開發(fā)的服務(wù)一般使用這一端口范圍。
上例中使用Flask建立的Web服務(wù)默認啟動在5000端口,而程序用port參數(shù)指定了8088為服務(wù)啟動的端口號。在瀏覽器的地址欄中輸入網(wǎng)址時,用冒號分隔IP地址和端口號,形如http://192.168.1.107:8088/test.html。
18.2.3 URL命名規(guī)則
URL請求允許使用小寫字母,數(shù)字,部分特殊符號(非制表符)組成。其中的中文空格等特殊字符需要轉(zhuǎn)碼成特殊字符。因此,請盡量減少使用中文以及特殊符號,以使用字母、數(shù)字下劃線為主。
18.3 動態(tài)網(wǎng)頁
18.3.1 網(wǎng)頁模板
上例中服務(wù)端返回的簡單網(wǎng)頁是由程序生成的,網(wǎng)頁內(nèi)容被寫在Python代碼文件之中,當(dāng)網(wǎng)頁內(nèi)容較多時,一般存儲在單獨的文件之中。
網(wǎng)頁常常是由較多的靜態(tài)內(nèi)容和較少的動態(tài)內(nèi)容共同構(gòu)成的,使用模板用于組合靜態(tài)內(nèi)容和動態(tài)內(nèi)容。
模板是一個包含響應(yīng)文本的文件,它通常是html文件,該文件中允許包含“占位變量”來表示動態(tài)的內(nèi)容,"占位變量"在程序中被真實的值所替換。Flask內(nèi)部使用 Jinja2 模板引擎實現(xiàn)模板功能。從模板文件中讀出數(shù)據(jù),用真實數(shù)據(jù)替代占位變量,并將文件中的數(shù)據(jù)轉(zhuǎn)換成Python字符串,這一過程稱為渲染render。
Flask中的模板文件保存在templates目錄下,該目錄與源碼存儲在同一目錄之中。
模板中的“占位變量”用兩個大括號{{占位變量名}}表示,例如:
01 用戶名:{{name}}
其中的name將在渲染時被程序中的真實值代替。
18.3.2 生成動態(tài)網(wǎng)頁
本例用于生成一個動態(tài)網(wǎng)頁,網(wǎng)頁中的大部分數(shù)據(jù)保存在templates目錄下,名為demo.html的HTML文件中。以Jupyter Notebook編輯器為例。
首先,創(chuàng)建目錄templates:在文件列表界面的右上點擊:New->Folder創(chuàng)建目錄,選中該目錄(在目錄名前的方框中打勾),點左上角的rename將目錄名改為templates。
然后,創(chuàng)建網(wǎng)頁文件:進入templates目錄,點擊右上:New->Text File創(chuàng)建文本文件,寫入以下HTML格式文本,然后在列表界面,選中該文件,點左上角的rename將文件改名為demo.html。在Jupyter中,HTML文件不能像Python其它文件那樣通過點擊直接打開,需要先選中該文件,然后點擊上方的編輯銨鈕Edit,才能修改,直接點擊HTML文件,會在瀏覽器中顯示該網(wǎng)頁效果。將demo.html修改成以下內(nèi)容:
01 <html>
02 <body>
03 用戶名:{{name}}
04 </br>
05 密碼:{{password}}
06 </body>
07 </html>
第03和05行,分別使用了兩個占位變量,用于插入動態(tài)數(shù)據(jù)。
在與templates目錄平級的位置(不在templates目錄之中)創(chuàng)建Python代碼文件,輸入以下代碼:
01 from flask import Flask
02 from flask import render_template
03
04 app = Flask(__name__)
05
06 @app.route('/show.html')
07 def page2():
08 return render_template('demo.html', name="張三", password="123456")
09
10 app.run(host='0.0.0.0', port=8088)
第02行導(dǎo)入了用于渲染網(wǎng)頁的三方庫render_template。
第06行指定在訪問網(wǎng)站的show.html路徑時,調(diào)用page2函數(shù)。
第07-08行實現(xiàn)了page2函數(shù),使用render_template渲染上面編輯的網(wǎng)頁demo.html(程序在templates目錄下讀取文件),然后設(shè)置了文件中的兩個占位變量name和password。此處涉及的文件目錄較為復(fù)雜,請讀者在計算機上完成以上實驗。
程序運行結(jié)果如圖18.2所示,可以看到網(wǎng)頁中的占位變量被程序中設(shè)置的參數(shù)所代替。

課后練習(xí):(練習(xí)答案見本講最后的小結(jié)部分)
練習(xí)一:將本節(jié)中的動態(tài)網(wǎng)頁示例程序輸入計算機,保證程序正常運行。
(練習(xí)中涉及的內(nèi)容較多,實現(xiàn)過程中需要不斷在網(wǎng)頁編輯界面、程序界面、瀏覽器測試效果的界面之間切換,它鍛煉了切分問題,以及分步解決問題的能力。)
18.4 表單
表單form是一種網(wǎng)頁的形式,一般用于收集用戶信息,例如:網(wǎng)站的用戶注冊頁面一般需要輸入用戶名、密碼、聯(lián)系方式、真實姓名等信息,此類網(wǎng)頁一般由表單實現(xiàn)。
18.4.1 POST與GET方式
POST和GET是HTTP請求的兩種方式,上面學(xué)習(xí)的例程都是GET方式,且客戶端沒有向服務(wù)端傳送參數(shù)。POST請求和GET請求都支持客戶端向服務(wù)端傳送數(shù)據(jù),但格式不同。
1.GET方式
GET方式傳遞參數(shù)時,名/值對是在GET請求的URL中發(fā)送的,例如:
01 http://192.168.1.104/login.html?user=a&passwd=123
其中問號之后是客戶端向服務(wù)端傳遞的參數(shù),本例中共有兩個參數(shù),參數(shù)之間用“&”符號分隔,參數(shù)是名/值對,如第一個參數(shù)的參數(shù)名是user,值是a,名值之間用“=”連接。
2.POST方式
POST方式傳遞參數(shù)時,名/值對是在HTTP的消息體中發(fā)送的,從URL中無法得知,POST請求更加安全,例如用POST方式傳送的密碼不會被顯示在網(wǎng)頁地址欄中,有更好的保密性。下面介紹的表單主要使用POST方式傳輸數(shù)據(jù)。
18.4.2 表單
表單是客戶端提交給服務(wù)器端的一組數(shù)據(jù),與之前學(xué)習(xí)過的軟件界面一樣,它可以包含輸入框、單選框、密碼框等等控件供用戶輸入,一般包含提交和重置兩個按鈕,當(dāng)用戶點擊提交按鈕時,瀏覽器將向服務(wù)端發(fā)起請求,將表單中用戶輸入的數(shù)據(jù)發(fā)送給服務(wù)器。因此使用表單一方面需要在HTML文件中添加表單,另一方面需要在服務(wù)端的程序中處理由表單傳來的數(shù)據(jù)。
首先,使用以下程序在模板目錄下創(chuàng)建含有表單的HTML文件login_base.html:
01 <html>
02 <body>
03 <form action="show.html" method="post">
04 用戶名:<input type="text" name="name" value="zhangsan"/>
05 密碼:<input type="password" name="passwd" />
06 <input type="submit" value="登錄"/>
07 </form>
08 </body>
09 </html>
第03行標(biāo)記了表單form元素的開始,并使用action屬性設(shè)置當(dāng)用戶點擊提交時,跳轉(zhuǎn)到網(wǎng)站的show.html路徑,處理方式是POST。
第04行顯示了文字“用戶名”和普通輸入框,表單中的元素由input標(biāo)簽定義,標(biāo)簽的具體類型由其type屬性指定,普通輸入框的類型是“text”,name設(shè)置了被提交數(shù)據(jù)的名字“name”,以便于服務(wù)端的程序讀取不同的用戶輸入內(nèi)容,屬性value指定了輸入框的默認值為“zhangsan”。
第05行顯示了文字“密碼”和密碼輸入框,它的類型為password,密碼輸入框中輸入的任何字符都顯示成“*”以便于保密,name設(shè)置了提交數(shù)據(jù)的名字“passwd”,供服務(wù)器讀出數(shù)據(jù)時使用。
第06行加入提交按鈕,它的類型為submit,意思是提交,value指定了銨鈕上顯示的文字是“登錄”。
第07行的</form>標(biāo)簽標(biāo)記了表單結(jié)束。
然后,編寫Python程序,該程序包含兩個界面,一個是提供給用戶輸入用戶名和密碼的登錄界面login.html,另一個是顯示用戶是否登錄成功的提示界面show.html。
01 from flask import Flask,request
02 from flask import render_template
03
04 app = Flask(__name__)
05
06 @app.route("/login.html")
07 def page1():
08 return render_template('login_base.html')
09
10 @app.route('/show.html',methods=["POST"])
11 def page2():
12 if request.method=='POST':
13 u=request.form['name']
14 p=request.form['passwd']
15 if u == 'zhangsan' and p == '123456':
16 return render_template('demo.html', name=u, password=p)
17 else:
18 return "用戶名或密碼錯誤"
19 else:
20 return "請求錯誤"
21
22 app.run(host='0.0.0.0', port=8088)
第01行引入了flask三方模塊的Flask和request,其中request用于接收客戶端傳來的參數(shù)。
第06行關(guān)聯(lián)了login.html與page1函數(shù),當(dāng)用戶在瀏覽器打開網(wǎng)絡(luò)路徑login.html時調(diào)用page1函數(shù),route譯為路由,它的含義是尋找從源地址到目標(biāo)地址的最佳路徑。
第07-08行實現(xiàn)了page1函數(shù),它從templates模板目錄下加載了login_base.html文件,并將其轉(zhuǎn)換成字符串類型,作為page1函數(shù)的返回值。
第10行關(guān)聯(lián)了show.html與page2函數(shù),并用參數(shù)methods指出接收POST請求發(fā)來的數(shù)據(jù)。
第11-18行實現(xiàn)了page2函數(shù)。
第12行判斷用戶請求是否為POST請求,如果不是POST請求,則跳轉(zhuǎn)到19-20行返回請求錯誤。
第13行從post請求中取出名為“name”的數(shù)據(jù)并將該數(shù)據(jù)賦值給變量u,關(guān)鍵字“name”在HTML文件中定義。
第14行從post請求中取出名為“passwd”的數(shù)據(jù)并將該數(shù)據(jù)賦值給變量p。
第15行判斷用戶名和密碼,如果是zhangsan和123456則執(zhí)行16行,否則返回“用戶名密碼錯誤”。
第16行讀取之前創(chuàng)建的模板文件demo.html,并用真實的用戶名和密碼替換HTML文件中的占位變量。
程序運行結(jié)果如圖18.3所示:

課后練習(xí):
練習(xí)二:在7777端口打開HTTP服務(wù),實現(xiàn)用戶注冊界面,用戶輸入:姓名、用戶名、密碼、年齡,按確認后,顯示注冊成功界面其中包含用戶注冊信息。
18.5 思維訓(xùn)練
18.5.1 建立框架
建立處理問題的統(tǒng)一框架,類似于前幾講提到過的抽象的處理問題,它幾乎是最重要的學(xué)習(xí)方法,通過一次或幾次學(xué)習(xí),總結(jié)出處理一類問題的解決方法。以后再遇到類似問題,不需要重新學(xué)習(xí)具體處理方法,直接代入框架,即可解決問題。人工智能中的“訓(xùn)練機器學(xué)習(xí)模型”就是建立框架的過程;在程序中使用函數(shù),也用到建立框架的思路,具體方法是:
第一步:切分,將整體功能切分成小塊。
第二步:實現(xiàn),將具體實現(xiàn)功能的代碼封裝到函數(shù)之中,建立最基本的結(jié)構(gòu),確定框架中的不變部分和可變部分。
第三步:定義使用場景,在什么情況下可以使用,以及如何使用。
第四步:包容,擴展其功能,增加適用范圍,讓該框架不僅可用于當(dāng)前情況,之后還可以在更多的情況下使用。
18.5.2 積累
如果找不到規(guī)律生成統(tǒng)一框架,就需要記憶具體實例,即積累。但是使用這些未經(jīng)處理的數(shù)據(jù)代價很大,需要大量的記憶空間。此時可以考慮簡化和分解。
簡化時需要區(qū)分和保留實例中的重要特征,去掉不重要的,以及常識性的知識。
而分解則是化整為零。寫程序也同樣有一些約定俗成的要求,比如一個函數(shù)中的代碼長度最好不要超過一屏,單個代碼文件也不要太長,這并不是由于機器無法運行,而是讓程序員閱讀起來更加方便。因此,有時候即使多次調(diào)用,也會把大段代碼拆成函數(shù)。
積累的另一個使用場景是保存統(tǒng)一框架以外的特例。如果建立處理所有情況的統(tǒng)一框架,規(guī)律將非常復(fù)雜。此時,可積累一些特例作為統(tǒng)一框架的補充。需要注意的也是保持積累數(shù)據(jù)的簡潔。
18.5.3 重構(gòu)
建立框架和積累實例是最常用的方法,如果試用了已有的框架和積累的實例仍無法解決問題,可以嘗試重構(gòu),重構(gòu)的核心是使用新的角度把簡化問題,而不是改進具體的方法。重構(gòu)的方法有很多,如:
從思考問題的結(jié)構(gòu)轉(zhuǎn)為思考問題的功能,如果目標(biāo)是出一本校刊,又實在無法畫好其中的插畫,是否可以使用其它途徑,比如從網(wǎng)上下載模板……
把問題劃分成小塊,然后區(qū)分其中重要和次要的成份。重組重要特征,嘗試不同的劃分方法,不同的邊界可能預(yù)示著不同解決方法。
調(diào)整看問題角度,從整體到部分,比如可以把大問題拆分成多個小問題,再逐一尋找解法;或者把問題放入一個更大的框架。
使用類比,并借鑒類似問題的解決方法,比如將學(xué)習(xí)語文的方法代入英語學(xué)習(xí)之中,雖然細節(jié)有所不同,但其中一些技巧仍可以正常工作。
頭腦風(fēng)暴,和小組的其他成員在不受任何限制的氣氛中討論、座談,打破常規(guī),積極思考,暢所欲言,充分發(fā)表看法,拼接擴展思路。
18.6 小結(jié)
18.6.1單詞
本講需要掌握的英文單詞如表18.1所示。

18.6.2 習(xí)題答案
練習(xí)一:將本節(jié)中的動態(tài)網(wǎng)頁示例程序輸入計算機,保證程序正常運行。
練習(xí)二:在7777端口打開HTTP服務(wù),實現(xiàn)用戶注冊界面,用戶輸入:姓名、用戶名、密碼、年齡,按確認后,顯示注冊成功界面其中包含用戶注冊信息。
01 from flask import Flask,request
02 from flask import render_template
03
04 app=Flask(__name__)
05
06 @app.route("/login.html")
07 def page1():
08 return render_template('login.html')
09
10 @app.route('/show.html',methods=["POST"])
11 def page2():
12 if request.method=='POST':
13 u=request.form['name1']
14 p=request.form['mima']
15 l=request.form['name2']
16 w=request.form['old']
17 iiii="注冊成功,用戶名:"+u+",密碼:"+p+", 姓名:"+l+", 年齡:"+w
18 return iiii
19 else:
20 return "不是post, 需要post"
21
22 app.run(host='0.0.0.0',port=7777)