本篇是承接上一篇web應用(入門級)的內容往下順延的,閱讀后將會了解HTML邏輯顯示優化,如下圖所示,從雜亂無章的日志文件到一個整齊的列表顯示。
—————————————————————————— 我是分割線 —————————————————————————————————
6存儲和管理數據
在下面的內容之前需要了解存儲和管理數據的知識。
關於web應用,應該記錄每個web請求的數據。這樣有利於分析這些問題:
已經響應了多少個請求?最常用的字母列表是什么?請求來自哪個IP地址?哪個瀏覽器用的最多?……
打開、處理(process)和關閉文件
1.建立一個txt空文件(hh.txt),指定一個變量(a)打開這個文件,后面參數‘a’的含義是采用追加模式打開這個文件。open會返回一個流,賦值給a變量
2.然后打印消息到文件流。
3.完成工作,最后關閉,文件流進行清理。
4.讀是open的默認模式,所以不需要提供模式參數,打開文件時仍需要指定一個變量(read),然后open會返回一個文件流賦給read變量。
5.for循環每次循環讀取一個數據行,沒有數據行即終止。
6.工作完成,關閉文件流進行清理。
訪問模式:(r w主要針對文本文件)
訪問模式 |
說明 |
r |
以只讀方式打開文件,文件的指針會放在文件的開頭。(默認模式) |
w |
以可寫方式打開文件,文件存在時:覆蓋,不存在時:創建新文件。 |
a |
以追加方式打開文件,文件存在:指針放在文件結尾,不存在時:創建新文件進行寫入。 |
rb |
以二進制格式打開一個文件用於只讀。 |
wb |
以可讀方式打開二進制文件。 |
ab |
以追加方式打開二進制文件。 |
r+ |
打開一個文件用於讀寫,文件指針在開頭。 |
rb+ |
|
wb+ |
以二進制格式打開一個文件用於只讀。 |
ab+ |
|
x |
打開一個新文件進行寫數據,如果文件存在則失敗。 |
上表沒有寫全,規律:r--read; w--write; a--add to; b--binary; + --讀寫
使用with對文件操作
with open('hh.txt') as a:
for chore in a:
print(chore, end = ' ')
這樣可以完成和之前的open相同的操作,並且后面不用close()關閉文件。這些內容是在Python內部有一個上下文管理協議,它完成收尾的工作,在需要時調用close。
之前提到的程序可以在添加內容了,我們想對這個web應用的數據進行存儲記錄下來。
回顧之前的程序內容:
在下面添加一個函數
def log_request(req: 'flask_request', res: str) -> None:
with open('vsearch.log', 'a') as log:
print(req, res, file=log)
調用這個函數時,req參數作為當前的flask請求對象,res參數作為調用vsearch_for_letters函數的結果.然后函數log_request把req和res的值追加到一個名為vsearch.log的文件。
使用這個函數時,我們添加到上面的do_search中進行調用,當然在調用之前需要定義函數,所以調整位置后的程序如下:
保存后在webapp文件夾中運行cmd,輸入python vsearch_for_web.py
然后打開瀏覽器輸入地址http://127.0.0.1:5000進行測試,試過幾次之后會發現生成了一個log文件
通過web應用查看日志
下來讓日志顯示在web瀏覽器里,所以新建一個URL: /viewlog
在最后面添加代碼:
@app.route('/viewlog')
def view_the_log() -> str:
with open('vsearch.log') as log:
contents = log.read()
return contents
保存測試后鍵入http://127.0.0.1:5000/viewlog看到:
這些好像只是瀏覽器接受和顯示最后的結果,並沒有最開始搜索的內容,檢查網頁的源代碼,直接瀏覽器中右鍵
這些內容和剛才的並沒有太大差別,還是沒有看到搜索的內容,web拒絕顯示用戶搜索的數據,因為HTML中<Request>是一個不合法的標記,瀏覽器會將它忽略。
轉義數據
Flask包含一個escape函數,調用時提供一個字符串,其中不包含任何特殊字符:
對一些包含特殊字符的字符串使用這個函數,它會將<>轉義為<和>
在第一行調用加入escape然后在后面的return后使用函數:escape(contents)
新的測試結果:
注意到剛才是紅色的字變成了黑色字,,不過這些數字並不能看出來什么。
更改代碼:
只在最后的print做了修改,把req的內容通過dir()列出然后轉成字符輸出。
下來測試新的日志記錄代碼,先完成下列步驟:
- 修改log_request與上圖保持一致。
- 保存修改后的代碼,這回重啟我們的web應用。
- 找到並刪除當前的vsearch.log文件。
- 通過瀏覽器輸入3個新搜索。
- 使用/viewlog查看新創建的日志。
仔細看看現在的內容,有意義了嗎?
這似乎有些亂,不過仔細看會發現有一些我們查找到的值。
現在可以看到每個請求都有大量的關聯方法和屬性,記錄所有的屬性是沒有意義的。其中有三個對於日志記錄很重要:
- req.form:從web應用的HTML表單提交的數據。
- req.remote_addr:運行web瀏覽器的IP地址。
- req.user_agent:提交數據的瀏覽器標識。
下面對代碼進行進一步的調整。
記錄特定的web請求屬性
分別輸出這些內容,end=‘|’表示把默認的換行符替換為|,這樣一個請求的數據就是一行內容。
這是新的日志文件的內容:可以看到數據都在一行而且整齊了很多
上文中的連續四個print好像有些多余,其實可以縮減到一個print語句中,有一個可選的參數sep,它可以設置分隔符,默認為空格。
下來改進那幾行print代碼:
def log_request(req: 'falsk請求', res: str) -> None:
with open('vsearch.log', 'a') as log:
print(req.form, req.remote_addr, req.user_agent, res, file=log, sep='|')
繼續剛才那五個步驟,就是重新測試的步驟。提示:存代碼、刪日志、新鍵入。
然后查看URL:/viewlog會發現少了很多
可以找到里面有搜索的字符串也有結果,看來已經有意義了。可是如何更加規范這些內容呢?
從原始數據到可讀的輸出
下面是vsearch.log文件中的一個數據行:
ImmutableMultiDict([('phrase', 'this is s test of the posting capability'), ('letters', 'aeiou')])|127.0.0.1|Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36|{'o', 'a', 'e', 'i'}
三個|分割了四部分內容,第一部分是表單數據。第二部分是遠程機器的IP地址。第三部分是web瀏覽器的標識字符串。最后一部分是函數調用的結果。
- 使用join可以將列表轉換成字符串。
下來在>>>中進行測試:
join前面的‘|’意思是使用|將每個字符串連接起來。
- 而split是將字符串轉換成列表。
使用給定的|,將字符串分割成一個列表。
修改代碼:
使用文件打開的open命令,用|將日志中的記錄轉換成一個列表,這樣會看起來更加美觀,可讀性強。
需要修改的是view_the_log函數。
def view_the_log() -> str:
contents = []
with open('vsearch.log') as log:
for lines in log:
contents.append([])
for item in lines.split('|'):
contents[-1].append(escape(item))
return escape(contents)
有一些地方需要解釋:
也許你會對contents[-1].append(escape(item))有疑問,理解這行代碼技巧從內向外、從左向右讀。首先是外圍for循環的item開始,它會傳遞到escape,在用append將得到的字符串追加到contents末尾 ( [-1] ) 。contents是一個嵌套列表。
保存代碼然后進行測試:
現在的輸出變成了一個嵌套列表,而不再是一個字符串列表。現在我們使用一個設計的jinja2模板處理contents,就基本能得到所需的可讀的輸出了。
用HTML生成可讀的輸出
HTML提供了一組標記來定義表格的內容:包括<table>:一個表格,<th>:一行表格數據,<tr>:一個表格列標題和<td>:一個表格數據項(單元格)。
每個標記都有對應的一個結束標記</table>,</tr>,</th>和<td>。
如果發現需要生成HTML,就應該使用jinja2模板引擎,它主要是設計用來生成HTML,這個引擎包含一些基本的編程構造,可以用來“自動實現”需要的顯示邏輯。
下面是一個新模板下載的地址還是之前的http://python.itcarlow.ie/ed2/,名為viewlog.html,它可以將日志文件中的原始數據轉換成一個HTML表格,這個模板希望傳入contents嵌套列表作為他的參數。jinja2的for循環構造與Python類似,但需要注意的是行尾不需要冒號,因為%}相當於一個分隔符;每個循環的代碼組用{% endfor %}結束。
可以看到,第一個for循環希望在一個名為the_row_titles的變量中查找數據,而第二個for循環希望得到the_data中的數據。第三個for循環希望數據是一個數據項列表。
整個表在一個<table>標記中,描述性標題<th>中有單獨的行<tr>標記。每個日志數據項放在一個<td>標記中,日志文件中的各行有單獨的<tr>標記。(現在可能有些困難,不過后面就會理解這幾句話的含義)
這個模板需要放在templates文件夾下面。
要讓viewlog.html調用render_template(render_template的函數,如果指定一個模板名和所需的參數,調用這個函數時會返回一個HTML串。),為它需要的三個參數分別傳入值。下面創建一個描述性的標題元組,並把它賦值給the_row_titles,然后將contents的值賦給the_data。在呈現這個模板之前,還需要給the_title提供一個適當的值,修改函數view_the_log:
def view_the_log() -> 'html':
contents = []
with open('vsearch.log') as log:
for lines in log:
contents.append([])
for item in lines.split('|'):
contents[-1].append(escape(item))
titles = ('From Data','Remote_addr','User_agent','Results')
return render_template('viewlog.html',
the_title = 'View Log',
the_row_titles = titles,
the_data = contents,)
保存后是flask重啟web應用,然后進入http://127.0.0.1:5000/viewlog查看日志:
我對這個結果很滿意,因為終於得到了想要的輸出,並且看起來很整齊。
點擊右鍵查看網頁源代碼,會看到日志的每一個數據項都放在它自己的<td>標記中,每個數據行也有自己的<tr>標記,整個表放在一個HTML的<table>中。
后面的放大來看:
大體上可以看出來一些,現在對於網頁的源代碼好像有一些眉目了。
回顧整個代碼: