用Python操作Redis資料庫
Redis官網整理一份Node.js, Python, PHP,…等各種語言的Redis前端程式庫,本文使用Python語言,並採用官網推薦的redis-py程式庫。
先執行pip命令安裝redis程式庫:
pip install redis
透過Python連線到Redis Labs線上資料庫的第一個步驟是建立redis物件:

Redis物件的操作指令(方法)名稱,就是小寫的Redis CLI命令名稱,只有少數例外。例如:
- set():設定字串鍵值
- get():取得字串鍵值
- delete():刪除鍵值。Redis CLI的刪除命令寫成DEL,但由於del是Python的保留關鍵字(Python內建的刪除變數指令),所以這裡改名成delete()。
- hmset():設定雜湊(hash)的多組欄位和值。
- hgetall():取得雜湊的全部欄位和值。
完整的指令列表,請參閱Redis程式庫官方說明文件。
實際用Python 3直譯器測試連線操作Redis Labs資料庫,請在命令提示字元或終端機中啟動Python,再輸入底下的指令:

接著透過Redis物件操作資料庫。底下的命令將設定一個其值為‘hello!’的‘foo’鍵。讀取出的值為byte(位元組)類型,所以字串資料被b”包圍:

若執行set()方法出現如下的錯誤訊息,請確認主機、埠號或密碼是否輸入正確。

透過Redis程式庫存取的字串都會經過UTF-8編碼成位元組格式:

所以取出資料之後,要透過decode()解碼:

讀寫雜湊型鍵值
底下是建立名叫“phones:2”的雜湊型資料,存入Python字典值的例子:

使用Redis資料庫儲存Heroku平台程式的全域資料
本單元將改寫《超圖解Python程式設計入門》第13章「紀錄心情留言悄悄話」的LINE聊天程式,把存在全域變數的悄悄話改存在Redis Labs資料庫,這樣的話,將此LINE程式佈署到Heroku平台也不會發生資料不同步的情況。
存在Redis的LINE用戶資料,將以 “users:用戶ID” 格式的鍵名儲存,如下圖所示:

本例將改寫書本的bot_secr.py範例檔,負責連線Redis Labs與存取資料的程式,全都歸納到RedisUser類別,這是類別的部份程式碼:

這段自訂類別程式可以擺在import敘述之後:

完整的RedisUser類別程式碼如下:
class RedisUser():
def __init__(self, host, port, password):
self.rds = redis.Redis(
host=host,
port=port,
password=password)
def get_words(self, id):
'''
傳回UTF-8解碼後的悄悄話文字
'''
words = self.rds.hget(f'users:{id}', 'words').decode('utf-8')
return words
def set_words(self, id, val):
'''
設定用戶的悄悄話
'''
self.rds.hset(f'users:{id}', 'words', val)
def set_save(self, id, val):
self.rds.hset(f'users:{id}', 'save', val)
def get_save(self, id):
'''
傳回轉成整數的寫入狀態值,0或1。
'''
save = self.rds.hget(f'users:{id}', 'save')
return int(save)
def get_name(self, id):
'''
傳回UTF-8解碼後的用戶名字
'''
name = self.rds.hget(f'users:{id}', 'name').decode('utf-8')
return name
def check(self, id, name):
exist = self.rds.exists(f'users:{id}')
if exist:
print('用戶已經存在,id:', id)
else:
print('新增用戶,id:', id)
self.rds.hmset(f'users:{id}', {
'name': name,
'words': '',
'save': 0
})
自訂類別物件時,要傳入Redis主機位址、埠號和密碼,例如,底下敘述將一個名叫redis_user的物件。
# 請自行替換主機、埠號和密碼
redis_user = RedisUser('redis-1984.redislabs.com',
1984, 'zzzzz')
原本使用全域變數存取悄悄話的程式,都要改成透過redis_user物件操作,像處理回應文字的reply_text函式:
def reply_text(token, id, txt):
'''
處理回應文字
'''
if (txt == 'Hi') or (txt == "你好"):
name = redis_user.get_name(id)
reply = f"{name}你好!"
elif '悄悄話' in txt:
words = redis_user.get_words(id)
if words != '':
reply = f'你的悄悄話是:\n\n{words}'
else:
reply = '放膽說出心裡的話吧~'
redis_user.set_save(id, 1) # 準備儲存祕密
elif redis_user.get_save(id):
redis_user.set_words(id, txt) # 儲存祕密
redis_user.set_save(id, 0) # 停止儲存祕密
reply = '我會好好保護這個祕密~'
else:
reply = txt
msg = TextSendMessage(reply)
line_bot_api.reply_message(token, msg)
以及接收文字訊息的事件處理程式:
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
'''
接收文字訊息的事件處理程式
'''
profile = line_bot_api.get_profile(event.source.user_id)
# 紀錄用戶資料
_id = event.source.user_id
_name = profile.display_name
_txt = event.message.text
redis_user.check(_id, _name)
reply_text(event.reply_token, _id, _txt)
程式修改完畢,命名成bot_secr.py存檔。
佈署網站應用程式到Heroku平台
參閱10-23頁「Heroku雲端應用程式的檔案結構」單元的說明,在src路徑準備好這些檔案:

然後依照10-32頁「設置Heroku CLI與發布檔案」的說明,在src路徑執行git init(如果之前沒有在此路徑初始化git的話),再依序執行add, commit和push命令。
程式佈署完畢之後,記得到LINE開發人員頁面修改callback路徑,就能在LINE app上測試。


你好,
當我在line 輸入’悄悄話’後,程式出現
‘NoneType’ object has no attribute ‘decode’.
請問發生了甚麼error?
Thanks very much
應該是讀取使用者資料的程式碼沒有改到,這是完整的程式:
from flask import Flask, request, abort from linebot import LineBotApi, WebhookHandler from linebot.exceptions import InvalidSignatureError from linebot.models import ( MessageEvent, TextMessage, TextSendMessage, StickerMessage, StickerSendMessage, FollowEvent ) import redis rds = redis.Redis( host='你的Redis主機網址', port=19583, password='你的Redis密碼') line_bot_api = LineBotApi('你的LINE存取代碼 access code') handler = WebhookHandler('你的LINE頻道密鑰 secret') app = Flask(__name__) def get_words(id): words = rds.hget(f'users:{id}', 'words') return words def set_words(id, val): rds.hset(f'users:{id}', 'words', val) def set_save(id, val): rds.hset(f'users:{id}', 'save', val) def get_save(id): save = rds.hget(f'users:{id}', 'save') return save def get_name(id): name = rds.hget(f'users:{id}', 'name') return name def check_user(id, name): exist = rds.exists(f'users:{id}') if exist: print('用戶已經存在,id:', id) else: print('新增用戶,id:', id) rds.hmset(f'users:{id}', { 'name': name, 'words': '', 'save': 0 }) @app.route('/') def index(): return 'Welcome to Line Bot!' @app.route("/callback", methods=['POST']) def callback(): signature = request.headers['X-Line-Signature'] body = request.get_data(as_text=True) try: handler.handle(body, signature) except InvalidSignatureError: abort(400) return 'OK' def reply_text(token, id, txt): ''' 處理回應文字 ''' if (txt == 'Hi') or (txt == "你好"): name = get_name(id) reply = f"{name}你好!" elif '悄悄話' in txt: words = get_words(id) if words != '': reply = f'你的悄悄話是:\n\n{words}' else: reply = '放膽說出心裡的話吧~' set_save(id, 1) elif get_save(id): set_words(id, txt) set_save(id, 0) reply = '我會好好保護這個祕密~' else: reply = txt msg = TextSendMessage(reply) line_bot_api.reply_message(token, msg) @handler.default() def default(event): print('捕捉到事件:', event) @handler.add(MessageEvent, message=TextMessage) def handle_message(event): ''' 接收文字訊息的事件處理程式 ''' profile = line_bot_api.get_profile(event.source.user_id) _id = event.source.user_id _name = profile.display_name _txt = event.message.text check_user(_id, _name) reply_text(event.reply_token, _id, _txt) @handler.add(MessageEvent, message=StickerMessage) def handle_sticker_message(event): line_bot_api.reply_message( event.reply_token, StickerSendMessage( package_id=event.message.package_id, sticker_id=event.message.sticker_id) ) @handler.add(FollowEvent) def followed(event): _id = event.source.user_id profile = line_bot_api.get_profile(_id) _name = profile.display_name print('歡迎新好友,ID:', _id) print('名字:', _name) if __name__ == "__main__": app.run(host='0.0.0.0', port=80)thanks,
jeffrey