Python Web Flask 對JSON的存取-1

Sean Yeh
Python Everywhere -from Beginner to Advanced
17 min readMar 2, 2021

--

Chishang, Taitung, Taiwan, photo by Sean Yeh

為什麼需要學會用JSON?

JSON是JavaScript Object Notation的縮寫。它是一種格式,這種格式原先是在JavaScript之中用以交換資料的一種方式。

JSON被廣泛使用

JSON是目前廣泛使用的格式。在JSON出現之前,以往大家可能使用XML、TOML、YML等格式來整理與傳輸資料。然而由於JSON格式的簡潔,目前已經被大家廣泛的採用。

網站API多採用JSON

許多網站的API都採用JSON作為資料結構。例如Facebook、Twitter、Google等等目前您可以想到會提供API的主流網站,都提供JSON格式的資料。

方便擷取資料

當我們想到要擷取某個網站時,直覺上可能想到「網路爬蟲」。實際上,如果可以透過該網站的API存取資料,會比透過爬蟲方式來下載資料、再透過Beautiful Soup來解析網站結構來的方便許多。

JSON只有兩種結構:物件與陣列。JSON物件是由key與value組成的資料,放在 { } 括號中;而JSON陣列是有一組有順序的資料放在 [ ]括號中。簡單的說,JSON物件長得像是Python字典,而JSON陣列則長得像Python串列。例如下面的JSON:

{    "people": [        {            "name": "Superman",            "website": "superman.com",            "from": "Mars"        }    ]}

Python 如何讀 JSON檔案

load與loads

想要讀取JSON格式的資料,我們可以透過json套件中的loads()方法來將JSON物件轉換為Python字典,將JSON陣列轉換為Python串列。

除了loads()方法之外,json套件還有一個load()方法,兩者的差別在於:

  • json.loads()用於將str型別的資料轉成Python字典;
  • 而json.load()則用於從json檔案中讀取資料。

接下來,我們就來實際測試看看。在使用JSON前,我們需要先匯入JSON套件。

import json

使用 loads來讀取前面的JSON檔案,我們先把上面的JSON存入一個副檔名為.json的檔案中( config_data.json )。

json.loads()

在loads後面的括弧中,傳入JSON檔案,程式碼應該長得像下面一樣:

json_url = 'config_data.json'json_file = open(json_url, 'r')json.loads(json_file)

執行看看。成功了嗎?

我想,應該是顯示錯誤訊息。

前面我們曾經提到『json.loads()用於將str型別的資料轉成Python字典』,而目前的狀況卻是需要讀取.json檔案中的JSON資料,而非將str型別的資料轉成Python字典。於是,這時候該使用的是json.load()。透過json.load()可以從json檔案中讀取資料。我們可以藉由這個原則把上面程式碼修改一下:

json_url = 'config_data.json'json_file = open(json_url, 'r')json.load(json_file)

修改完畢可以再執行程式碼看看,這次應該是成功的。

或許你會問,如果想要使用json.loads()的話該如何修改?這時你可以試著將json.loads()傳入的檔案改成下面:

json.loads(json_file.read())

這樣子應該也可以讀到資料。

我們可以把上面的結果,寫成下面的函式,以便於後續方便呼叫使用。

def read_from_json(json_url):    """讀取json資料"""    json_file = open(json_url, 'r')    j = json.loads(json_file.read())    json_file.close()    return j

開始寫入JSON前

Python 如何寫入純文字檔案

在開始寫入JSON前,我們先來看看Python 如何寫入 txt 純文字檔案。我們可以透過open()來開啟檔案、透過read()可以讀取檔案的內容,並且透過close()關閉檔案。若採用with語法,就不需要加上close()。以上關於Python對檔案寫入與讀取的說明,曾經在先前的文章中介紹過。

同樣的,我們也可以使用類似的方式,將資料寫入JSON檔案中。

Python 如何寫 JSON檔案

假設資料

假設我們有下面一組字典格式的資料,並指定給data變數。

data = {'people':[{'name': 'Superman', 'website': 'superman.com', 'from': 'Mars'}]}

dumps

我們可以使用dumps來將字典資料轉換成JSON格式。indent=2是指「空兩格」的意思,您如果習慣空四格的話,可以使用indent=4。

json.dumps(dic_data, indent=2)

輸出結果如下:

輸出結果

我們可以把它寫成函式,以便於取用:

def dump_json(dic_data):
j_file = json.dumps(dic_data, indent=2)
print(f'Wiil output: {j_file}')
return j_file

將讀寫JSON的功能套入Flask中

最近剛好在業務上面有個需求,情況如下:

公司中的A君非常暸解相關業務,但是卻一點都不懂程式。公司剛好有一個P系統,非常仰賴A君提供資訊才可以正確的執行。

目前的作業流程是,A君口頭或寫信提供業務相關資訊,讓身為工程師的我來對程式進行設定,並且操作P系統,讓系統產出結果給A君以及A君的部門使用。由於我雖然會操作程式但卻不是很熟悉A君的業務,依照目前的作業流程與工作方式來看,實在是個耗時費力的事情。於是乎,我想是否有什麼方式可以讓自己更為輕鬆?

我想,或許可以製作一個Web網頁介面,並提供給該介面給A君,讓他逐一填寫必要的資料。填寫完畢之後,透過程式將這些資料儲存成檔案,以便後續提供給P系統讀取使用。

這時候,在Python下想要製作一個Web網頁介面,選擇使用Flask來製作,應該是個不錯的選擇。只要讓Flask表單中傳來的字串等資料寫入檔案中,就可以供日後使用。由於以上的需求,我們衍生出下面兩個問題:

  • 這些傳來的資料是否可以「記錄為純文字格式」?如何進行?
  • 又是否可以將傳來的資料「記錄為JSON格式」?又要如何進行這項工程?

首先就從第一個問題,「記錄為純文字格式」開始吧。

寫入純文字檔案

前面,我們已經演練過,將文字寫入純文字檔案的方法了。接下來,就要把這個功能與Flask的Form表單結合起來,以網頁作為輸入的UI介面,讓A君逐一填寫需要的資料。

我們可以將這個需求分為兩個步驟,首先我們要製作一個函式,該函式可以將資料寫入純文字檔案。然後,再進入Flask的view中,將這個函式放在新增資料的路由中,讓使用者A君可以透過指定路由的網頁,輸入該頁的表單,達成這個目的。

以下就是第一個函式,write_to_text函式,參數data為將來使用者透過form表單提供之資料。

write_to_text函式的核心部份如下:

config_filename = 'config_data.txt'config_file_url = os.path.join(basedir,'config',config_filename)config_file = open(config_file_url, 'a')for data in datas:config_file.write(data+'\n')config_file.close()

先使用config_filename指定純文字的檔案名稱,在這裡為了簡化流程,姑且直接提供一個檔案名稱。而config_file_url則是檔案所在的路徑位置,由於我們不希望整個Flask網頁的架構紊亂,因此提供這個函式一個config路徑來儲存檔案。使用open()方法來開啟檔案,使用for迴圈逐一的將資料寫入該檔案。寫完之後,透過close()方法關閉檔案。

核心說明完後,我們來看其他部分。

def write_to_text(datas):    #檢查資料夾是否存在?    des_folder = os.path.join(basedir,'config')    if not os.path.exists(des_folder):        os.makedirs(des_folder)        print(f"建立資料夾{des_folder}")    config_filename = 'config_data.txt'    config_file_url = os.path.join(basedir,'config',config_filename)    config_file = open(config_file_url, 'a')    for data in datas:        config_file.write(data+'\n')        config_file.close()

擺在核心程式前面的des_folder變數,是用來儲存純文字檔案的路徑,我們希望在寫入檔案前,先檢查該檔案夾的路徑是否已經存在?如果檔案夾尚未建立的話,我們希望可以自動幫我們建立檔案夾。

與Flask的Form表單結合

完成前半段後,我們要把它加入Flask中與Flask的Form表單結合。

forms.py

為了簡化說明,在Flask中有如下程式碼的 forms.py 檔案,在這裡我們使用flask_wtf與 wtforms 製作表單。這個表單只有一個item欄位,並且此欄位為必填欄位( validators=[DataRequired()] )。

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from wtforms import ValidationError
class AddItemForm(FlaskForm): item = StringField('輸入名稱', validators=[DataRequired()]) submit = SubmitField('加入項目')

views.py

我們把路由寫在views.py裡面,分為兩部分「存成txt純文字檔案」與「其他」。路由add_items_txt,可以將form表單輸入的字串取出,存成txt純文字檔案。儲存完畢後,將網頁導向首頁路由(url_for('home'))。如果沒有填寫資料,則顯示add_items_form.html填寫表單畫面。除了「存成txt純文字檔案」的部分外,「其他」的程式碼如下:

@app.route('/add_items_txt',methods=['GET','POST'])
def add_items_txt():
"""write to txt"""
form = AddItemForm()
if form.validate_on_submit():
item = form.item.data
# write to txt
return redirect(url_for('home')) return render_template('add_items_form.html',form=form)

接著,我們把「存成txt純文字檔案」的部分加入上面的程式碼中:

@app.route('/add_items_txt',methods=['GET','POST'])
def add_items_txt():
"""write to txt"""
form = AddItemForm()
config_data=[]
if form.validate_on_submit(): item = form.item.data
# write to txt
config_data.append(item)
### write to txt ###
write_to_text(config_data)
return redirect(url_for('home'))return render_template('add_items_form.html', form=form)

粗體字部份是新加入的程式碼,config_data=[ ] 是list串列,用來蒐集資料。透過append()方法,將item加入config_data裡面。接著再使用write_to_text 函式將config_data的資料集,轉存為 txt文字檔案。

結果,就可以透過向下面一樣的介面,逐筆的輸入資料。按下「加入項目」的按鈕同時,就會將輸入的字串存入txt純文字檔案中。純文字檔案的檔名是config_data.txt 並且會被儲存在config路徑中。

寫入JSON檔案

前面,我們已經成功的將Flask Form表單輸入的資料,寫入純文字檔案了。在這裡我們要試著將傳來的資料記錄為JSON格式

跟前面一樣的,我們也可以分為兩個步驟,先製作一個可以將資料寫入JSON檔案的函式。然後,再進入Flask的view中,將這個函式放在新增資料的路由中。函式名稱為write_to_json。

def write_to_json(datas):

#檢查資料夾是否存在?
des_folder=os.path.join(basedir,'config')
if not os.path.exists(des_folder):
os.makedirs(des_folder)
print(f"建立資料夾{des_folder}")
config_filename = 'config_data.json' config_file_url = os.path.join(basedir,'config',config_filename) with open(config_file_url, 'w') as config_file: data_set = _make_str_for_dump(datas) j_data =_dump_json(data_set) config_file.write(j_data)

程式的結構與先前提過的存為純文字檔很類似。不過這次我們使用with的方式並且寫入的方式則是採用w模式,也就是全部覆蓋法來寫入JSON檔案。

此外,我在這支程式裡面加上兩個函式:

_make_str_for_dump(datas) 與 _dump_json(data_set)

第一個是_make_str_for_dump函式:我要用這個函式(裡面有for迴圈)組合成一組字串,提供給第二個函式(_dump_json)進行處理。

def _make_str_for_dump(strings):
str_for_dump = []
for str in strings:
str_for_dump.append(str)
return str_for_dump

而另外一個是_dump_json函式。dumps用於將字典格式的資料轉成字串形式,因為若直接將字典格式的資料寫入到JSON檔案中會發生錯誤,因此在將資料寫入時需要用到。其中indent=4 是空四格的意思。

def _dump_json(dic_data):
return json.dumps(dic_data, indent=4)

同樣的在前半段完成後,我們接著要把它加入Flask中與Flask的Form表單結合。

forms.py

在form表單的部分應該是與前面一樣,只有一個item欄位,並且此欄位為必填欄位( validators=[DataRequired()] )

class AddItemForm(FlaskForm):
item = StringField('輸入名稱', validators=[DataRequired()])
submit = SubmitField('加入項目')

views.py

在此,我們依然把路由寫在views.py裡面。同樣的可以被分為兩部分「存成JSON檔案」與「其他」。我們將路由取名為add_items_json,它可以將form表單輸入的字串取出,存成JSON檔案。儲存完畢之後,網頁的反應與前面儲存到純文字檔案的反應一樣。

除了「存成JSON檔案」的部分外,「其他」的程式碼如下:

@app.route('/add_items_json',methods=['GET','POST'])
def add_items_json():
"""write to json"""
form = AddItemForm()
if form.validate_on_submit():
item = form.item.data
# write to txt
return redirect(url_for('home'))return render_template('add_items_form.html',form=form)

接著,我們把「存成JSON檔案」的部分加入上面的程式碼中:

config_data = []@app.route('/add_items_json',methods=['GET','POST'])
def add_items_json():
"""write to json"""
form = AddItemForm()
if form.validate_on_submit():
item = form.item.data
config_data.append(item)
### write to JSON ###
write_to_json(config_data)
### write to JSON ###
return redirect(url_for('home'))return render_template('add_items_form.html',form=form)

我們可以從上面的程式碼粗體字的部分看到,這次我把config_data拉到函式的外面,其他部分與存為純文字一樣。因為我們這次寫入的方式採用的是w模式。必須先將字串組合為串列之後,才可以一口氣寫入JSON檔案中。

最後產生出來的JSON檔案格式如下:

[    "123",    "456",    "789"]

以上是這次的實作。之後,我們會在對這個需求功能進行進一步的改進與優化。

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。