在chatbot中,我們需要用到模式切換,而且不是用NLP處理,我們就需要寫一個中介的程式去處理這個工作。以下程式會詢問模式並記錄下來,10分鐘後會重問。此程式不需資料庫,直接記在檔案裡。
--------------------------------------------------------------------------------------------------------------------------------
$timeLimit = 600; // 保持模式的最長時間
$arrModeName = array();
$arrModeName[0] = '哈囉,這裡是入口,您想要處理務還是詢問相關問題? ';
$arrModeName[2] = '處理務';//務模式
$arrModeName[3] = '詢問相關問題';//問題模式
function router($message)
{
global $timeLimit;
global $arrModeName;
if($message['message']['text'] == $arrModeName[2])
{
setUserMode($message['source']['userId'],2);
}
else if($message['message']['text'] == $arrModeName[3])
{
setUserMode($message['source']['userId'],3);
}
list($mode,$time) = getUserMode($message['source']['userId']);
if(time() - $time > $timeLimit)
{//超過時間就重設為詢問模式
$mode = 1;
setUserMode($message['source']['userId'],$mode);
}
if($mode == 1)
{//詢問模式
echo $arrModeName[0];
}else if($mode == 2){
//傳給務模組
receiveMsg($message);
}
else if($mode == 3)
{//傳給問題模組
receiveMsg2($message);
}else
{
echo ("\n ERROR MODE");
}
}
function setUserMode($user,$mode)
{
$filename = './user/'.$user;
$strWrite = $mode.':'.time();
$filenum=fopen($filename,'w');
if(fwrite($filenum,$strWrite) === FALSE)
echo "Cannot write to file ($filename)";
fclose($filenum);
}
function getUserMode($user)
{
$filename = './user/'.$user;
if(file_exists($filename) ){
$filenum=fopen($filename,'r');
$str = fread($filenum,1);
fread($filenum,1);
$time = fread($filenum,10);
fclose($filenum);
$mode = intval($str);
$time = intval($time);
$strWrite = $mode.':'.time();
$filenum=fopen($filename,'w');
if(fwrite($filenum,$strWrite) === FALSE)
echo "Cannot write to file ($filename)";
}
else
{
$time = time();
$mode = 1;
$filenum=fopen($filename,'w');
$strWrite = $mode.':'.$time;
if(fwrite($filenum,$strWrite) === FALSE)
echo "Cannot write to file ($filename)";
chmod($filename,0666);
}
fclose($filenum);
return array($mode,$time);
}
?>
2020年1月31日 星期五
LINE機器人範例
中國信託LINE
- 選單是圖片,指定座標範圍當按鈕
- 訊息是圖片,點進去是網頁,看細節
- 美工圖片和自然語言處理一樣重要!
---------------------------------------------------------------------------------------------------------------------
卡米狗 - 各大Line群已被攻佔! 《卡米狗》嗆爆所有人
單純的上下句配對,就能達到吸引人的效果。
------------------------------------------------------------------------------------------------------------------------------------------------------------
Line@酒店攬客機器人
定時傳設計好的訊息
設計話語(資料庫?演算法?)
照片也很重要!
chatbots如何改善印度的醫療狀況
chatbots能同時提供多個使用者服務,節省時間和成本。
chatbots己經成功幫助數百萬人說明他們的健康問題並提供適當的指導
- 根據病患自己輸入的症狀,幫助歸納出自己的具體疾病
- 幫助病患參與(了解、決定)自己的治療過程
- 發送各種重要的提醒通知給病患
--------------------------------------------------------------------------------------------------------------------------
可以幫助使用者:
預約看診
醫院資訊
醫生資訊
疾病問題
利用選單幫助使用者Query
--------------------------------------------------------------------------------------------------------------------------
理解使用者要預約掛號。
接著問使用者的城市,利用Quick Reply做到。
2020年1月30日 星期四
line-bot-sdk套件 - Python
Line提供Python的套件,給Python程式設計者可以使用套件API跟LINE互動,就不需要撰寫太多複雜的程式及JSON傳輸格式。套件是以Flask提供服務,啟動後,就是LINE的Webhook。
下載套件方法:
pip install line-bot-sdk
範例程式碼,可以Echo使用者輸入的字句:
from flask import Flask, request, abort from linebot import ( LineBotApi, WebhookHandler ) from linebot.exceptions import ( InvalidSignatureError ) from linebot.models import ( MessageEvent, TextMessage, TextSendMessage, ) app = Flask(__name__) userNo = 1 line_bot_api = LineBotApi('k2j34l2kj34l2kj34l2kh143;kljh51;kl2h5;l1k2hj;45lkh12;l3k4h2kj3hn4kj2lh34lkj2h34lk2jh34lkjh1242kl3jh4lk2jh34lk2jh34lk1jh243lkjh2lk3j4hlk23h4') handler = WebhookHandler('kj2h34lkj2h433lk2jh4lk3j2h4') @app.route("/") def hello(): return "Hello World!" @app.route("/callback", methods=['POST']) def callback(): # get X-Line-Signature header value signature = request.headers['X-Line-Signature'] # get request body as text body = request.get_data(as_text=True) app.logger.info("Request body: " + body) #handle webhook body try: handler.handle(body, signature) except InvalidSignatureError: abort(400) return 'OK' @handler.add(MessageEvent, message=TextMessage) def handle_message(event): if (event.reply_token == '00000000000000000000000000000000'): return None global userNo text_message = TextSendMessage(text='Hello, world', quick_reply=QuickReply(items=[ QuickReplyButton(action=MessageAction(label="label", text="text")) ])) line_bot_api.reply_message( event.reply_token, TextSendMessage(text=userNo)) userNo += 1 if __name__ == "__main__": app.run( host = '0.0.0.0', port = 12345, debug = True, ssl_context ='adhoc' )
下載套件方法:
pip install line-bot-sdk
範例程式碼,可以Echo使用者輸入的字句:
from flask import Flask, request, abort from linebot import ( LineBotApi, WebhookHandler ) from linebot.exceptions import ( InvalidSignatureError ) from linebot.models import ( MessageEvent, TextMessage, TextSendMessage, ) app = Flask(__name__) userNo = 1 line_bot_api = LineBotApi('k2j34l2kj34l2kj34l2kh143;kljh51;kl2h5;l1k2hj;45lkh12;l3k4h2kj3hn4kj2lh34lkj2h34lk2jh34lkjh1242kl3jh4lk2jh34lk2jh34lk1jh243lkjh2lk3j4hlk23h4') handler = WebhookHandler('kj2h34lkj2h433lk2jh4lk3j2h4') @app.route("/") def hello(): return "Hello World!" @app.route("/callback", methods=['POST']) def callback(): # get X-Line-Signature header value signature = request.headers['X-Line-Signature'] # get request body as text body = request.get_data(as_text=True) app.logger.info("Request body: " + body) #handle webhook body try: handler.handle(body, signature) except InvalidSignatureError: abort(400) return 'OK' @handler.add(MessageEvent, message=TextMessage) def handle_message(event): if (event.reply_token == '00000000000000000000000000000000'): return None global userNo text_message = TextSendMessage(text='Hello, world', quick_reply=QuickReply(items=[ QuickReplyButton(action=MessageAction(label="label", text="text")) ])) line_bot_api.reply_message( event.reply_token, TextSendMessage(text=userNo)) userNo += 1 if __name__ == "__main__": app.run( host = '0.0.0.0', port = 12345, debug = True, ssl_context ='adhoc' )
Flask+HTTPS
如果直接用Python+Flask,提供REST API的服務,只會開啟http://127.0.0.1:5000的Daemon給別人呼叫,但現在流行用HTTPS,很多Webhook功能都強迫你要能提供HTTPS的通訊協定給它呼叫,故我上網查怎麼讓Flask支援SSL。
只要在你的app.run()裡,加上 ssl_context ='adhoc' 就可以了,如下列Python程式片段:
if __name__ == "__main__":
app.run(
host = '0.0.0.0',
port = 55373,
debug = True,
ssl_context ='adhoc'
)
上述用到adhoc功能,需要pyOpenSSL套件。但你如果有申請SSL憑證,就不需要這個套件。
有些Client要求你一定要用合法的憑證,否則就不連,甚至連用Postman測試HTTPS的API,也要求這樣,否則不幫你測(或到setting裡把SSL certificate verification)。
若你有合法的憑證,把ssl_context 改成下列參數就能用:
ssl_context =('/PATH/TO/SSL.CRT','/PATH/TO/SSL.KEY')
只要在你的app.run()裡,加上 ssl_context ='adhoc' 就可以了,如下列Python程式片段:
if __name__ == "__main__":
app.run(
host = '0.0.0.0',
port = 55373,
debug = True,
ssl_context ='adhoc'
)
上述用到adhoc功能,需要pyOpenSSL套件。但你如果有申請SSL憑證,就不需要這個套件。
有些Client要求你一定要用合法的憑證,否則就不連,甚至連用Postman測試HTTPS的API,也要求這樣,否則不幫你測(或到setting裡把SSL certificate verification)。
若你有合法的憑證,把ssl_context 改成下列參數就能用:
ssl_context =('/PATH/TO/SSL.CRT','/PATH/TO/SSL.KEY')
聊天機器人(Chatbot)的程式架構
我們在設計聊天機器人(Chatbot)時,直覺會用傳統的 「網路程式語言+資料庫」,例如PHP+MySQL,而PHP會用到資料庫的原因,除了要存一些長期用到的資料,還有短期的資料。所謂短期是指幾分鐘或幾秒,甚至以毫秒計算,例如一天的網站累計進站人次,一般常把累計值存在資料庫裡。
短期資料的問題是會造成經常性的資料庫存取,使得資料庫來不及反應,進而需要調整資料庫的參數、換更好的電腦,甚至需要Load Balance。
如果我們回到原點想一個機器人應該有的程式架構,就會發現為什麼不是程式自己記得前幾秒的資料,而是需要額外的資料倉儲機制。而原因也很簡單,因為像PHP之類的Web程式,用一次就關掉了,Process就消失了,它根本不記得幾秒鐘之前的request已經服務了什麼事,所以又額外需要一個資料庫,幫忙記東西。
我們希望程式能記得前一回的事情,就不能用傳統的架構。程式啟動後,會成為Process,這時我們應該要保持住這個Process,不能讓它被關掉,如果是一般Web程式寫法一定不行。
解決的方法就是,程式要能自己運行(例. 在command line呼叫起來且不會結束),程式會不斷的迴圈,監聽有沒有連線進來(例. 開Socket),有就開始處理、回傳訊息,然後再繼續監聽。利用程式的Global variable記憶短期的資料,就不需要存取資料庫。
自己運行也不需要一直initial程式,節省Initial時間,加快服務速度。如果需要資料庫存資料,也可以一直保持連線,節省communication成本。
要注意的是,記在Global variable短期的資料不能太多,否則會memory overflow,記憶體會不夠用。如果需要記的短期資料太多,可以存到資料庫或檔案。也可以增加電腦作Load Balance。
短期資料的問題是會造成經常性的資料庫存取,使得資料庫來不及反應,進而需要調整資料庫的參數、換更好的電腦,甚至需要Load Balance。
如果我們回到原點想一個機器人應該有的程式架構,就會發現為什麼不是程式自己記得前幾秒的資料,而是需要額外的資料倉儲機制。而原因也很簡單,因為像PHP之類的Web程式,用一次就關掉了,Process就消失了,它根本不記得幾秒鐘之前的request已經服務了什麼事,所以又額外需要一個資料庫,幫忙記東西。
我們希望程式能記得前一回的事情,就不能用傳統的架構。程式啟動後,會成為Process,這時我們應該要保持住這個Process,不能讓它被關掉,如果是一般Web程式寫法一定不行。
解決的方法就是,程式要能自己運行(例. 在command line呼叫起來且不會結束),程式會不斷的迴圈,監聽有沒有連線進來(例. 開Socket),有就開始處理、回傳訊息,然後再繼續監聽。利用程式的Global variable記憶短期的資料,就不需要存取資料庫。
自己運行也不需要一直initial程式,節省Initial時間,加快服務速度。如果需要資料庫存資料,也可以一直保持連線,節省communication成本。
要注意的是,記在Global variable短期的資料不能太多,否則會memory overflow,記憶體會不夠用。如果需要記的短期資料太多,可以存到資料庫或檔案。也可以增加電腦作Load Balance。
Restful的LOG
Flask預設就有log功能,但是不會直接存到外面的檔案,若需要存到LOG檔,就需要import logging來支援。
Flask產生LOG訊息,只需呼叫app.logger,有三種LOG項目: Debug、Warning、Error。
若要指定LOG檔,需要產生FileHandler,"logging.FileHandler('flask.log') "。
如下範例:
from flask import Flask
import logging
app = Flask(__name__)
@app.route('/')
def root():
app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')
if __name__ == '__main__':
app.logger.addHandler( logging.FileHandler('flask.log') )
app.run( debug=True )
Restful的錯誤處理
Restful在通訊的過程,發生錯誤時,可以經由http的header知道錯誤,也有錯誤代碼可以判讀,如果要從程式引起,或客制化錯誤回應,接受方只要讀取http的標頭,就可以知道成功或失敗、失敗的原因為何。程式碼如下:
from flask import Flask, request, abort
app = Flask(__name__)
@app.route('/login')
def login():
abort(404)
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404
if __name__ == "__main__":
app.run(debug=True)
多話的虛擬客服
虛擬客服應用自然語言處理的技術,讓使用者可以用自己的話來詢問機器人,用語言和機器人自然的互動,但技術的必要性常被質疑,"用按鈕介面按一按、選一選就好了,不需要用到技術複雜、成本又高的NLP" "用任務導引方式,引導使用者輸入關鍵字完成任務就好",故NLP常被認為不必要,儘管我們努力讓機器人理解語言的能力有多精確。
我看了一些對話型助理的例子,包含實際的試作成果和科幻影片所呈現的,發現除了精確的理解句子以外,滿足使用者話中的隱喻、需求,更能表現出虛擬客服的價值,也就是你的回應不能只單單回應使用者字面上的意思,還要講一些相關的話,而使用者會感覺只要講一點簡單的話,就可以得到豐富的回應。如下面的例子:
Q.早安
A.(動作-自動開窗)早安,今天是星期六,你有5個會議,室溫設定到"涼爽",20度。
Q.現在幾點?
A.現在是12點,你要吃午餐了嗎? 有幾間推薦、你收藏的餐廳可以試看看,.....
Q.我要訂高鐵下午3點達新竹。
A.班次1314,14:30從台北發車,15:00到新竹,標準車廂如果要搭區間車往市區,15:05及15:20有車可以坐。要訂嗎?
參考文獻: https://youtu.be/ZGLPxEv_EWo
我看了一些對話型助理的例子,包含實際的試作成果和科幻影片所呈現的,發現除了精確的理解句子以外,滿足使用者話中的隱喻、需求,更能表現出虛擬客服的價值,也就是你的回應不能只單單回應使用者字面上的意思,還要講一些相關的話,而使用者會感覺只要講一點簡單的話,就可以得到豐富的回應。如下面的例子:
Q.早安
A.(動作-自動開窗)早安,今天是星期六,你有5個會議,室溫設定到"涼爽",20度。
Q.現在幾點?
A.現在是12點,你要吃午餐了嗎? 有幾間推薦、你收藏的餐廳可以試看看,.....
Q.我要訂高鐵下午3點達新竹。
A.班次1314,14:30從台北發車,15:00到新竹,標準車廂如果要搭區間車往市區,15:05及15:20有車可以坐。要訂嗎?
參考文獻: https://youtu.be/ZGLPxEv_EWo
LINE機器人加入群組發系統通知
我將LINE機器人邀請加入我們的群組,讓它可以在系統有問題時,發通知給大家。LINE機器人要加入群組,必需被邀請才能,故先用自己的LINE帳號邀請。Script中,"Authorization: Bearer {"後的亂碼是LINE_CHANNEL_TOKEN,要換成自己LINE機器人的,"to :"後面的亂碼是GROUP_ID,可以從webhook收到群組發訊息,從LOG中取得。"text :"後面接的就是你要主動推送的訊息。
這個功能可以用在系統錯誤通知,也可以用在埋樁在各個群組中,推銷產品。
這個功能可以用在系統錯誤通知,也可以用在埋樁在各個群組中,推銷產品。
Deep Q-network
Deep Q Network 的簡稱叫DQN, 是將Q learning 的優勢和Neural networks 結合了. 如果我們使用tabular Q learning, 對於每一個state, action 我們都需要存放在一張q_table 的表中. 如果像顯示生活中, 情況可就比那個迷宮的狀況復雜多了, 我們有千千萬萬個state, 如果將這千萬個state 的值都放在表中, 受限於我們電腦硬體, 這樣從表中獲取數據, 更新數據是沒有效率的. 這就是DQN 產生的原因了. 我們可以使用神經網絡來估算這個state 的值, 這樣就不需要一張表了.
整個算法乍看起來很複雜, 不過我們拆分一下, 就變簡單了. 也就是個Q learning 主框架上加了些裝飾.
這些裝飾包括:
- 記憶庫(用於重複學習)
- 神經網絡計算Q 值
- 暫時凍結
q_target
參數(切斷相關性)
Laravel, the PHP web framework
Laravel是一個PHP web framework,它可以經由幾個簡單的指令,完成一個漂亮的網站。
依官方網站的說明,安裝好後,只需執行下列指令,就能完成一個簡單的網站:
laravel new blog
上面指令會產生一個blog目錄,到目錄裡執行下列指令,就會啟動網站:
php artisan serve
接著開Browser,輸入下列網址就能看到網站
http://localhost:8000
也可以用虛擬機安裝方式,安裝Homestead ,就能一次裝好所有需要的環境,包含下列軟體:
執行下列指令,就能自動將Homestead安裝到本機:
vagrant box add laravel/homestead
git clone https://github.com/laravel/homestead.git Homestead
bash init.sh
依官方網站的說明,安裝好後,只需執行下列指令,就能完成一個簡單的網站:
laravel new blog
上面指令會產生一個blog目錄,到目錄裡執行下列指令,就會啟動網站:
php artisan serve
接著開Browser,輸入下列網址就能看到網站
http://localhost:8000
也可以用虛擬機安裝方式,安裝Homestead ,就能一次裝好所有需要的環境,包含下列軟體:
- Ubuntu 16.04
- Git
- PHP 7.1
- Nginx
- MySQL
- MariaDB
- Sqlite3
- Postgres
- Composer
- Node (包含 Yarn, PM2, Bower, Grunt, and Gulp)
- Redis
- Memcached
- Beanstalkd
執行下列指令,就能自動將Homestead安裝到本機:
vagrant box add laravel/homestead
git clone https://github.com/laravel/homestead.git Homestead
bash init.sh
範圍型時間詞的自然語言處理
範圍型時間詞(例下午、下個月) 的自然語言處理 和 時間點的詞(下午3點、明天) 處理方式不一樣,範圍型時間詞轉換成電腦可處理的時間,需輸出兩個時間點,一個是啟始時間,一個是結束時間,我以如下範例說明範圍型時間詞轉換結果:
"下午" 的轉換結果
{
"start" : {
"unixtime": 1554872400,
"hour": 13,
"minute": 0,
"second": 0
},
"end" : {
"unixtime": 1554963123,
"hour": 18,
"minute": 0,
"second": 0
}
}
下個月的轉換結果
{
"start" : {
"unixtime": 1554872400,
"year": 2019,
"month": 5,
"day": 1
},
"end" : {
"unixtime": 1554963123,
"year": 2019,
"month": 5,
"day": 31
}
}
搬資料表並修改欄位的shell script
以下的LINUX shell script可以搬資料表並修改欄位
#!/bin/bash
echo "start..."
#DUMP遠端資料表2個,不用在Inser資料時LOCK,不然LOCK來不及放掉,其它Client就要等很久。
mysqldump --skip-add-locks -h HOST_IP -u USERNAME -pPASSWORD DBNAME TABLENAME1 TABLENAME2 > /tmp/BACKUP.sql
#搜尋一些東西,替代掉
sed -i -e 's/TABLENAME/NEW_TABLENAME/g' /tmp/BACKUP.sql
sed -i -e 's/utf8mb4_unicode_ci/utf8_general_ci/g' /tmp/BACKUP.sql
sed -i -e 's/utf8mb4/utf8/g' /tmp/BACKUP.sql
sed -i -e 's/InnoDB/MyISAM/g' /tmp/BACKUP.sql
sed -i -e 's/`Query` text COLLATE utf8_general_ci COMMENT/`Query` text COLLATE utf8_general_ci NOT NULL DEFAULT "" COMMENT/g' /tmp/BACKUP.sql
#導入資料表到新的資料庫
mysql -u username -pPassword DBNAME < /tmp/BACKUP.sql
#修改一些資料表格式,剛才沒替代掉的
mysql -u username -pPassword -e "ALTER TABLE mytablename CHANGE colname newcolname CHAR(191)_type" DBNAME
mysql -u username -pPassword -e "ALTER TABLE mytablename MODIFY COLUMN colname VARCHAR(191)_newtype" DBNAME
echo "finish"
日期範圍詞 正規化
- 日期範圍詞 正規化目前可處理下列Regular Expression所接受的關鍵字:
r"(上|下)(週|周|禮拜|星期)"
Ex. 上星期
r"(明年|去年|前年)?([零一二兩三四五六七八九十0123456789]+)月"
Ex. 明年四月
r"(上|下)個?月"
Ex. 上個月
------------------------------------------------------------------------------------------------------------------------
- 回應如下:
"start": {
"year": 2020,
"month": 2,
"day": 1
},
"end": {
"year": 2020,
"month": 2,
"day": 29
}
------------------------------------------------------------------------------------------------------------------------
- 處理方法:
def normDateRange(strDate): strDate = wf2nf.wf2nf(strDate) d = datetime.datetime.now() ts = int(d.timestamp()) oneDay = 86400 # 24*60*60 listMonthWith31day = [1, 3, 5, 7, 8, 10, 12] listMonthWith30day = [4, 6, 9, 11] theYear = d.year theDay = d.day theMonth = d.month theWeekday = d.weekday() regex = r"(這|上|下)?週末" match = re.search(regex, strDate, re.MULTILINE) if match != None: day2Weekend = 6 - theWeekday tsEnd = ts + day2Weekend * oneDay tsStart = tsEnd - oneDay if match.group(1) == "上": tsStart -= 7 * oneDay tsEnd -= 7 * oneDay elif match.group(1) == "下": tsStart += 7 * oneDay tsEnd += 7 * oneDay dtStart = datetime.datetime.fromtimestamp(tsStart) dtEnd = datetime.datetime.fromtimestamp(tsEnd) return [dtStart.year, dtStart.month, dtStart.day, dtEnd.year, dtEnd.month, dtEnd.day] regex = r"(這|上|下)(週|周|禮拜|星期)" match = re.search(regex, strDate, re.MULTILINE) if match != None: if match.group(1) == "上": tsEnd = ts - (theWeekday + 1) * oneDay # last sunday tsStart = tsEnd - (6 * oneDay) # last monday elif match.group(1) == "下": day2Weekend = 7 - theWeekday tsStart = ts + day2Weekend * oneDay tsEnd = tsStart + ( 6 * oneDay ) elif match.group(1) == "這": tsStart = ts - (theWeekday * oneDay ) tsEnd = tsStart + ( 6 * oneDay ) else: return [-1] dtStart = datetime.datetime.fromtimestamp(tsStart) dtEnd = datetime.datetime.fromtimestamp(tsEnd) return [dtStart.year, dtStart.month, dtStart.day, dtEnd.year, dtEnd.month, dtEnd.day] regex = r"(明年|去年|前年)?([零一二兩三四五六七八九十0123456789]+)月" match = re.search(regex, strDate, re.MULTILINE) if match != None: if match.group(1) == "明年": theYear += 1 elif match.group(1) == "去年": theYear -= 1 elif match.group(1) == "前年": theYear -= 2 theMonth = chinese_to_arabic2(match.group(2)) if theMonth in listMonthWith31day: result = [theYear, theMonth, 1, theYear, theMonth, 31] elif theMonth in listMonthWith30day: result = [theYear, theMonth, 1, theYear, theMonth, 30] elif theMonth == 2: ts = datetime.datetime(year=theYear, month=(theMonth+1), day=1).timestamp() d2 = datetime.datetime.fromtimestamp(ts - oneDay) result = [theYear, theMonth, 1, theYear, theMonth, d2.day] else: result = [-1] return result regex = r"下個?月" match = re.search(regex, strDate, re.MULTILINE) if match != None: theMonth += 1 if theMonth in listMonthWith31day: lastDay = 31 elif theMonth in listMonthWith30day: lastDay = 30 elif theMonth == 2: ts = datetime.datetime(year=theYear, month=(theMonth+1), day=1).timestamp() d2 = datetime.datetime.fromtimestamp(ts - oneDay) lastDay = d2.day elif theMonth == 13: theYear += 1 theMonth = 1 lastDay = 31 else: return [-1] return [theYear, theMonth, 1, theYear, theMonth, lastDay] regex = r"上個?月" match = re.search(regex, strDate, re.MULTILINE) if match != None: theMonth -= 1 if theMonth in listMonthWith31day: lastDay = 31 elif theMonth in listMonthWith30day: lastDay = 30 elif theMonth == 2: ts = datetime.datetime(year=theYear, month=(theMonth+1), day=1).timestamp() d2 = datetime.datetime.fromtimestamp(ts - oneDay) lastDay = d2.day elif theMonth == 0: theYear -= 1 theMonth = 12 lastDay = 31 else: return [-1] return [theYear, theMonth, 1, theYear, theMonth, lastDay] return [-1]
產生台灣IP清單給iptables防火牆
這個Python程式可以線上將TWNIC的台灣IP清單,轉成iptables的指令,貼到/etc/iptables.rules上,就可以限制只能台灣的IP能存取。
限制port時,用如下寫法,port 80使用"TaiwanIP"規則(平常我們是寫ACCEPT):
-A INPUT -p tcp -m tcp --dport 80 -j TaiwanIP
所以你的iptables.rules裡會先有這些描述:
:INPUT DROP [27:1852]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [164:21816]
:TaiwanIP - [0:0]
如果是更改作用中的防火牆,用以下指令加入:
iptables -N TaiwanIP
設定好iptables.rules後,用如下指令讓新規則產生做用:
sudo iptables-restore < /etc/iptables.rulesP.S. 有些單位的IP範圍不在清單上,好像是比較敏感的單位。
程式碼
# -*- coding: utf-8 -*- ''' 產生台灣IP清單及iptables指令 ''' import urllib.request import pandas as pd import ipaddress from bs4 import BeautifulSoup response = urllib.request.urlopen('https://rms.twnic.net.tw/twnic/User/Member/Search/main7.jsp?Order=ORG.ID') html = response.read().decode(response.headers.get_content_charset()) soup = BeautifulSoup(html, 'html.parser') listTagTable = soup.find_all('table') df = pd.read_html( str(listTagTable[5]),header=0 )[0] def getIP(x): ipStart, ipEnd = x.split(' - ') ipStartInt = int(ipaddress.IPv4Address(ipStart)) ipEndInt = int(ipaddress.IPv4Address(ipEnd)) return pd.Series([ipStart, ipEnd, ipStartInt, ipEndInt], index=['ipStart', 'ipEnd', 'ipStartInt', 'ipEndInt']) df2 = df.iloc[:,3].apply( getIP ) #只一行Series,可以不指定axis df['ipStart'] = 0 df['ipEnd'] = 0 df['ipStartInt'] = 0 df['ipEndInt'] = 0 df[ ['ipStart', 'ipEnd', 'ipStartInt', 'ipEndInt'] ] = df2 df = df.sort_values(by=['ipStartInt']) dicMerge = {'name':[df.iloc[0, 0]] , 'ID':[df.iloc[0, 1]] , 'ipStart':[df.iloc[0, 5]] , 'ipEnd':[df.iloc[0, 6]] , 'ipStartInt':[df.iloc[0, 7]] , 'ipEndInt':[df.iloc[0, 8]] } rowN, colN = df.shape dicMergeIndex = 0 #下列For是將IP範圍相鄰的合併,如此只需一組 for i in range(1,rowN): if (df.iloc[i, 7] - dicMerge['ipEndInt'][dicMergeIndex]) == 1: dicMerge['ipEndInt'][dicMergeIndex] = df.iloc[i, :]['ipEndInt'] dicMerge['ipEnd'][dicMergeIndex] = df.iloc[i, :]['ipEnd'] else: dicMerge['name'].append(df.iloc[i, 0]) dicMerge['ID'].append(df.iloc[i, 1]) dicMerge['ipStart'].append(df.iloc[i, 5]) dicMerge['ipEnd'].append(df.iloc[i, 6]) dicMerge['ipStartInt'].append(df.iloc[i, 7]) dicMerge['ipEndInt'].append(df.iloc[i, 8]) dicMergeIndex += 1 dfTaiwanIP = pd.DataFrame(dicMerge) rowN2, colN2 = dfTaiwanIP.shape with open('TaiwanIP.txt', 'w', encoding='utf8') as f: for i in range(0, rowN2): f.write("-A TaiwanIP -m iprange --src-range %s-%s -j ACCEPT\n" % (dfTaiwanIP.iloc[i]["ipStart"], dfTaiwanIP.iloc[i]["ipEnd"]) ) #上述會產生一連串的iptables指令到TaiwanIP.txt這個檔案,直接貼到/etc/iptables.rules。但我是寫給"TaiwanIP" chain,故前面要有":TaiwanIP - [0:0]"
基於Hetero-ConvLSTM的交通風險預測
參考: Yuan, Zhuoning, Xun Zhou, and Tianbao Yang. "Hetero-convlstm: A deep learning approach to traffic accident prediction on heterogeneous spatio-temporal data." Proceedings of the 24th ACM SIGKDD International Conference on Knowledge Discovery & Data Mining. ACM, 2018.
三角形知三邊長求高
已知三角形三個邊的長,求三角形高度h,如下圖:
a,b,c是已知的三個邊,x是h的垂直交點左邊的長度,x是未知。
定理: a^2-x^2 = h = b^2 - (c-x)^2
x = (a^2 - b^2 + c^2) / 2c
所以h=a^2-( (a^2 - b^2 + c^2) / 2c )^2
gunicorn無法啟動
裝好conda的環境後,發現gunicorn怎麼都沒辦法啟動,直接跑指令如下,出現錯誤訊息。
$ gunicorn -c conf/gunicorn_conf.py -p RUNPID_1234 -b 0.0.0.0:1234 webhook:app --certfile=server.crt --keyfile=server.key
Error: class uri 'gthread' invalid or not found:
[Traceback (most recent call last):
File "/home/3333/.conda/envs/shopbot/lib/python3.6/site-packages/gunicorn-19.1.0-py3.6.egg/gunicorn/util.py", line 130, in load_class
section, uri)
File "/home/3333/.conda/envs/shopbot/lib/python3.6/site-packages/pkg_resources/__init__.py", line 570, in load_entry_point
return get_distribution(dist).load_entry_point(group, name)
File "/home/3333/.conda/envs/shopbot/lib/python3.6/site-packages/pkg_resources/__init__.py", line 2686, in load_entry_point
raise ImportError("Entry point %r not found" % ((group, name),))
ImportError: Entry point ('gunicorn.workers', 'gthread') not found
Google完知道是BUG,但沒有說怎麼解。
查版本發現我的Gunicorn是舊版19.1,我是從conda install gunicorn裝的。
移除舊版,改用pip install gunicorn,裝新版後才正常啟動,真的是浪費我很多時間~
以後都用pip裝python套件,不用conda install裝了。
2020年1月29日 星期三
An Efficient Trajectory Compression Algorithm for Moving Objects
這篇介紹壓縮GPS軌跡點的演算法,這篇的PATCH演算法改良自 SQUISH-E :
J. Muckell, P. Olsen, J. Hwang, C. Lawson, and S. Ravi,“Compression of trajectory data: a comprehensive evaluation and new approach.”Geoinformatica, vol. 18, no. 3, pp. 435-460, 2014
PATCH壓縮方式是截彎取直,同時有最小的誤差,不會和原來軌跡的形狀差太多。
壓縮率計算方法: R=(1-(a/b)) x 100%
下列是PATCH演算法總覽,其中Filtration()、Divergence_Identification()、Neighbour_Calculation()分成三個Algorithm描述。
Filtration利用Sliding Window的方式,一個一個GPS point塞到priority queue,達到最大容量時,還會繼續塞,但會把低priority的point移除。
Divergence_Identification會將前一個步驟(Filtration)的路線轉向關鍵point,決定保留或移除。
Neighbour_Calculation檢查Divergence_Identification 輸出的point數量,如果沒有達到參數的數量,就重跑Filtration,將之前過濾掉的拿出來再重新考慮,直到達到參數設定的數量。
J. Muckell, P. Olsen, J. Hwang, C. Lawson, and S. Ravi,“Compression of trajectory data: a comprehensive evaluation and new approach.”Geoinformatica, vol. 18, no. 3, pp. 435-460, 2014
PATCH壓縮方式是截彎取直,同時有最小的誤差,不會和原來軌跡的形狀差太多。
壓縮率計算方法: R=(1-(a/b)) x 100%
下列是PATCH演算法總覽,其中Filtration()、Divergence_Identification()、Neighbour_Calculation()分成三個Algorithm描述。
Filtration利用Sliding Window的方式,一個一個GPS point塞到priority queue,達到最大容量時,還會繼續塞,但會把低priority的point移除。
Divergence_Identification會將前一個步驟(Filtration)的路線轉向關鍵point,決定保留或移除。
Neighbour_Calculation檢查Divergence_Identification 輸出的point數量,如果沒有達到參數的數量,就重跑Filtration,將之前過濾掉的拿出來再重新考慮,直到達到參數設定的數量。
對話機器人和真人客服協作
常見方法,因AI訓練資料不足,先由真人客服和客戶對話,讓機器人批次學習,待機器人可以應付少數對話,再導入機器人處理設定好的對話任務。
•
1.真人客服和客戶對話 -> 訓練資料
2.對話機器人學習
3.對話機器人和客戶對話 -> Unknow和答得不好, 對話任務拋回到1
4.對話機器人取代真人客服
臉書買下以色列的聊天機器人新創Servicefriend
https://www.ithome.com.tw/news/133219
安裝CKAN
CKAN 是著名的開放資料入口平台(Data Portal Platform)
因為原始安裝方法,問題太多,裝不進去,故改用docker-compose安裝,很順利的迅速裝好,以下說明如何安裝:
1.準備好專案目錄及git CKAN下來
cd /path/to/my/projects
git clone https://github.com/ckan/ckan.git
2. 將範例.env.template檔,複製成.env檔,並將.env裡的CKAN_SITE_URL參數改成連到CKAN的網址
cp contrib/docker/.env.template contrib/docker/.env
3. 輸入下列指令建立docker
cd contrib/docker
docker-compose up -d --build
4. 剛安裝ckan是沒有管理者,用下列指令在docker中建管理者,[your name] 換成你的帳號
docker exec -it ckan /usr/local/bin/ckan-paster --plugin=ckan sysadmin -c /etc/ckan/production.ini add [your name]
因為原始安裝方法,問題太多,裝不進去,故改用docker-compose安裝,很順利的迅速裝好,以下說明如何安裝:
1.準備好專案目錄及git CKAN下來
cd /path/to/my/projects
git clone https://github.com/ckan/ckan.git
2. 將範例.env.template檔,複製成.env檔,並將.env裡的CKAN_SITE_URL參數改成連到CKAN的網址
cp contrib/docker/.env.template contrib/docker/.env
3. 輸入下列指令建立docker
cd contrib/docker
docker-compose up -d --build
4. 剛安裝ckan是沒有管理者,用下列指令在docker中建管理者,[your name] 換成你的帳號
docker exec -it ckan /usr/local/bin/ckan-paster --plugin=ckan sysadmin -c /etc/ckan/production.ini add [your name]
命令列執行的python程式 - 抓Json list超過時間的項目
本篇是寫一個在命令列執行的Python程式,功能是抓Json list裡超過基準時間的項目。這個程式特別在要保持可攜性,因為Python程式如果要求要一堆特別的套件,才能實現功能,交給同事使用時,就會很難正常使用。這個Python程式的原則是,只處理shell script不容易作到的事情,其它盡量在命令列模式下完成,使用時指令會像下列格式:
python parse.py list.json 2019-12-05T03:28:14Z > id.txt
輸入和輸出都是靠命令列完成,Python不用管太多。
以下Python程式還是要裝Python-dateutil或py-dateutil套件,但已經非常精簡,而且不分Python版本都能執行:
-----------------------------------------------------------------------------------------------------------------------------
import sys import json import dateutil.parser # 檔案名,此檔包含從ckan抓回來的,資料集內的檔案清單 jsonFile = sys.argv[1] # 基準時間,時間格式是ISO 8601 baseTime = sys.argv[2] # 將Dataset的資料JSON讀出來 with open(jsonFile, 'r') as f: try: jsonDatasetMeta = json.load(f) except: print('-1') print('JSON format error') exit(1) # 將基準時間轉成時間變數 try: lastTime = dateutil.parser.parse(baseTime) except: print('-1') print('base time error') exit(1) if 'result' not in jsonDatasetMeta: print('-1') print('JSON file content error: result not in jsonDatasetMeta') exit(1) if 'resources' not in jsonDatasetMeta['result']: print('-1') print('JSON file content error: resources not in jsonDatasetMeta') exit(1) try: for file in jsonDatasetMeta['result']['resources']: fileCreated = dateutil.parser.parse(file['created'] + 'Z') if fileCreated > lastTime: print(file['url']) except Exception as e: print('-1') print('File created time error.') print(e) exit(1)
---------------------------------------------------------------------------------------------------
2020年1月14日 星期二
Tesseract OCR
Tesseract OCR 是一套可以辨識照片中的文字的強大工具。
本篇文章介紹如何安裝及使用。
1.
在Linux安裝套件可以簡單的執行
sudo apt-get install tesseract-ocr
sudo apt-get install tesseract-ocr-chi-tra #中文語言包
sudo apt-get install tesseract-ocr-eng #英文語言包
2.
安裝好後,再到
自行下載最佳語言包,到下列語言包目錄
/usr/share/tesseract-ocr/5/tessdata
覆蓋舊的chi-tra及eng.traineddata
3.
執行方法:tesseract sample.jpg sample -l chi_tra+eng --psm 1 --oem 1 alto
參數說明:
sample.jpg是你要辨識的照片
sample是輸出主檔名
chi_tra+eng是中文模型加英文模型(模型名稱可以參考這個模型清單https://github.com/tesseract-ocr/tessdata_best)
--psm 1是Automatic page segmentation with OSD
--oem 1是LSTM引擎
alto 是Output in ALTO format (OUTPUTBASE.xml).,類似hOCR的格式
4.
辨識的結果是XML格式,裡面包含了辨識的字和位置,要讀這個檔案的內容,可以用下列軟體:
http://www.prima.cse.salford.ac.uk/tools/PAGEViewer
開啟剛才完成的XML檔案和原始圖片,PageViewer會合併在一起秀給你看。將鼠標放在圖片的字上,就會秀出辨識的字串。
簡潔啟動gunicorn_service.sh
本篇介紹將Gunicorn的gunicorn_service.py,再包一個shell script,讓服務啟動的指令簡潔一些。
原本python程式包好Gunicorn後,啟動方式如下:
source activate normTime
python gunicorn_service.py --port 1234 start
source deactivate
如果要關閉服務,或是reload,也要照上面的指令,但start改成stop或reload。
我另外寫一個shell script,將上述指令包起來,並把變數抽出來,執行檔存為gunicorn_service.sh :
#!/bin/bash
PORT=1234
APPNAME=timeRestful
ENVNAME=normTime
source activate ${ENVNAME}
python gunicorn_service.py --port ${PORT} --app ${APPNAME}:app $1
source deactivate
上述程式碼將port號碼、APP檔名、python虛擬環境名稱,抽出來,使更換服務時,方便修改。
gunicorn_service.py要加下列兩段程式碼,使"--app"參數可以用:
--------------------------------------
parser.add_argument(
'-a',
'--app',
metavar='APP',
type=str,
default='',
help='Flask APP name with "....:app".'
)
--------------------------------------
.
.
if args.app != '':
self.config.app = args.app
--------------------------------------
啟動時,可以直接下./gunicorn_service.sh start ,停止服務./gunicorn_service.sh stop,重載服務./gunicorn_service.sh reload。
其實後面的start, stop, reload都是直接傳遞給gunicorn_service.py,故任何gunicorn_service.py可以接授的關鍵字,都可以用。
Flask用Route參數和Get參數比較
原本之前寫Python的Flask是用Route的方式,如下範例:
http://server:50000/norm1/字串/norm2/字串
程式寫法如下:
@app.route("/norm1//norm2/ ")
def normAPcode(APstr,APstr2):
使用GET的方式取後參數,形式如下:
http://server:50000?norm1=字串&norm2=字串
程式寫法如下:
@app.route("/nenormalize")
def nenormalize():
dicParam = request.args
dicParam["norm1"]
dicParam["norm2"]
Route方式傳參數缺點
1.最大的缺點是,字串裡有"/"的話,會無法處理(用瀏覽器開時會被當成網址裡的路徑),也許寫程式時,用url encode可以避免,但變成不能用瀏覽器測試。
2.如果有多個參數要傳,就要寫死在Route上,如果有時候只想傳一個,就要另外再寫一個Route,跟GET傳參數比起來,GET可以可有可無,在程式碼內加個if判斷norm2 in dicParam就好。
LINE webhook整合在同一Flask
這篇介紹把LINE的webhook整合到同一個Flask裡,方便管理。但流量大的時候,還是要分開。
程式LINE webhook framework的Python版,故其它語言就沒有參考價值。下列列出模組化的部份,不列出完整程式。
首先LINE_bot物件變成兩組
#R
line_bot_api = LineBotApi('KEY')
handler = WebhookHandler('SECRET')
#E
line_bot_api_e = LineBotApi('KEY')
handler_e = WebhookHandler('SECRET')
路由分成兩個,裡面就可以做各自的事情。
'''
R
'''
@app.route("/webhook", methods=['POST'])
def callback():
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
'''
E
'''
@app.route("/webhook02", methods=['POST'])
def callback_E():
@handler_e.add(MessageEvent, message=TextMessage)
def handle_message_E(event):
如此Restful介面就變成如下,port號一樣,僅路由不同:
https://url:port/webhook
https://url:port/webhook02
程式LINE webhook framework的Python版,故其它語言就沒有參考價值。下列列出模組化的部份,不列出完整程式。
首先LINE_bot物件變成兩組
#R
line_bot_api = LineBotApi('KEY')
handler = WebhookHandler('SECRET')
#E
line_bot_api_e = LineBotApi('KEY')
handler_e = WebhookHandler('SECRET')
路由分成兩個,裡面就可以做各自的事情。
'''
R
'''
@app.route("/webhook", methods=['POST'])
def callback():
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
'''
E
'''
@app.route("/webhook02", methods=['POST'])
def callback_E():
@handler_e.add(MessageEvent, message=TextMessage)
def handle_message_E(event):
如此Restful介面就變成如下,port號一樣,僅路由不同:
https://url:port/webhook
https://url:port/webhook02
LINE Front-end Framework(LIFF)
LIFF是LINE利用web page來實現LINE原有功能無法實現的一個管道。
下面範例是開啟一個網頁,可以在網頁中抓取LINE使用者的ID及相關資料。
------------------------------------------------------------------------------------------------------------------------------------------------
我用的範例網頁程式是LINE提供,網址如下:
https://github.com/line/line-liff-starter
將這個程式放在你的網站上的一個目錄,記下該目錄的URL,下面步驟要用到。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
進入LIFF頁籤,點ADD新增。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Size是選這個LIFF窗格的佔螢幕範圍,Full是全滿,Compact是半滿。
Endpoint URL存取你的網頁的URL。
BLE feature可以直接略過,它是Bluetooth Low Energy實體物件在用的(要另外買這個東西)。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
結果會秀出LIFF URL,把這個URL貼到要觸發這個LIFF的地方,例如LINE的選單(RichMenu)
----------------------------------------------------------------------------------------------------------------------------------------
把LIFF URL貼到選單(RichMenu)
-------------------------------------------------------------------------------------
觸發按鈕會出現在選單上
--------------------------------------------------------------------------------------------------------------------------------
下面範例是開啟一個網頁,可以在網頁中抓取LINE使用者的ID及相關資料。
------------------------------------------------------------------------------------------------------------------------------------------------
我用的範例網頁程式是LINE提供,網址如下:
https://github.com/line/line-liff-starter
將這個程式放在你的網站上的一個目錄,記下該目錄的URL,下面步驟要用到。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
進入LIFF頁籤,點ADD新增。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Size是選這個LIFF窗格的佔螢幕範圍,Full是全滿,Compact是半滿。
Endpoint URL存取你的網頁的URL。
BLE feature可以直接略過,它是Bluetooth Low Energy實體物件在用的(要另外買這個東西)。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
結果會秀出LIFF URL,把這個URL貼到要觸發這個LIFF的地方,例如LINE的選單(RichMenu)
----------------------------------------------------------------------------------------------------------------------------------------
把LIFF URL貼到選單(RichMenu)
-------------------------------------------------------------------------------------
觸發按鈕會出現在選單上
--------------------------------------------------------------------------------------------------------------------------------
MariaDB的Pool connection
因為發現我們的JAVA程式連MySQL資料庫的建立連線過程浪費很多時間,故想用connection pool的觀念來作,找到mariaDB(MySQL的免費後繼版)有直接提供pool的功能給JAVA,故我就研究如何套用到程式上。
mariaDB原生的pool功能,測試後效能比沒用pool快4倍。要用mariaDB原生的Driver,要用JAVA 8以上的版本,資料庫軟體可直接去mariaDB的網站下載。
程式碼在最下面,程式中最後一個區塊是最佳pool範例程式,改程式很簡單。
資料庫連結的JAR檔可在下列原廠下載
https://mariadb.com/kb/en/library/mariadb-connector-j/
這裡有教Eclipse怎麼載入connector的JAR檔
https://www.jianshu.com/p/489e4f0c1621
import java.sql.*;
import org.mariadb.jdbc.MariaDbPoolDataSource;
public class helloworld {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
long time1, time2, time3;
Class.forName("org.mariadb.jdbc.Driver");
//**********************************
// Original Database connection method
//**********************************
time1 = System.currentTimeMillis();
for(int i = 1; i <=1000;i++) {
try(
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/drink?user=root&password=0000");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM history LIMIT 6,5"))
{
rs.next();
//System.out.println(i + "\t" + rs.getString(2) );
}
}
time2 = System.currentTimeMillis();
time3 = time2-time1;
System.out.println("花了:" + time3 + " ms");
//**********************************
// mariaDB pool with minimal modified
//**********************************
time1 = System.currentTimeMillis();
for(int i = 1; i <=1000;i++) {
String connectionString = "jdbc:mysql://localhost:3306/drink?user=root&password=0000&maxPoolSize=100&pool";
try (Connection connection = DriverManager.getConnection(connectionString)) {
try (Statement stmt = connection.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT * FROM history LIMIT 5,5");
rs.next();
}
}
}
time2 = System.currentTimeMillis();
time3 = time2-time1;
System.out.println("花了:" + time3 + " ms");
//**********************************
// mariaDB pool (best)
//**********************************
//所有參數都在JDBC的連結中,而且增加了"&maxPoolSize=100",設定pool大小是100
time1 = System.currentTimeMillis();
for(int i = 1; i <=1000;i++) {
MariaDbPoolDataSource pool = new MariaDbPoolDataSource("jdbc:mysql://localhost:3306/drink?user=root&password=0000&maxPoolSize=100");
try (Connection connection = pool.getConnection()) {
try (Statement stmt = connection.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT * FROM history");
rs.next();
//System.out.println(i + "\t" + rs.getString(2) );
}
connection.close();
}
}
time2 = System.currentTimeMillis();
time3 = time2-time1;
System.out.println("花了:" + time3 + " ms");
}
}
mariaDB原生的pool功能,測試後效能比沒用pool快4倍。要用mariaDB原生的Driver,要用JAVA 8以上的版本,資料庫軟體可直接去mariaDB的網站下載。
程式碼在最下面,程式中最後一個區塊是最佳pool範例程式,改程式很簡單。
資料庫連結的JAR檔可在下列原廠下載
https://mariadb.com/kb/en/library/mariadb-connector-j/
這裡有教Eclipse怎麼載入connector的JAR檔
https://www.jianshu.com/p/489e4f0c1621
import java.sql.*;
import org.mariadb.jdbc.MariaDbPoolDataSource;
public class helloworld {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
long time1, time2, time3;
Class.forName("org.mariadb.jdbc.Driver");
//**********************************
// Original Database connection method
//**********************************
time1 = System.currentTimeMillis();
for(int i = 1; i <=1000;i++) {
try(
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/drink?user=root&password=0000");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM history LIMIT 6,5"))
{
rs.next();
//System.out.println(i + "\t" + rs.getString(2) );
}
}
time2 = System.currentTimeMillis();
time3 = time2-time1;
System.out.println("花了:" + time3 + " ms");
//**********************************
// mariaDB pool with minimal modified
//**********************************
time1 = System.currentTimeMillis();
for(int i = 1; i <=1000;i++) {
String connectionString = "jdbc:mysql://localhost:3306/drink?user=root&password=0000&maxPoolSize=100&pool";
try (Connection connection = DriverManager.getConnection(connectionString)) {
try (Statement stmt = connection.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT * FROM history LIMIT 5,5");
rs.next();
}
}
}
time2 = System.currentTimeMillis();
time3 = time2-time1;
System.out.println("花了:" + time3 + " ms");
//**********************************
// mariaDB pool (best)
//**********************************
//所有參數都在JDBC的連結中,而且增加了"&maxPoolSize=100",設定pool大小是100
time1 = System.currentTimeMillis();
for(int i = 1; i <=1000;i++) {
MariaDbPoolDataSource pool = new MariaDbPoolDataSource("jdbc:mysql://localhost:3306/drink?user=root&password=0000&maxPoolSize=100");
try (Connection connection = pool.getConnection()) {
try (Statement stmt = connection.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT * FROM history");
rs.next();
//System.out.println(i + "\t" + rs.getString(2) );
}
connection.close();
}
}
time2 = System.currentTimeMillis();
time3 = time2-time1;
System.out.println("花了:" + time3 + " ms");
}
}
LINE機器人傳sticker貼圖
stickers可以讓對話更有趣、更生動。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
以下是PHP程式碼,先將所有會用到的sticker列出來,然後判斷LINE傳來的資料型態是否為sticker,然後再隨機挑一個一開始列出來的sticker,回傳給使用者。
程式碼
$_CONFIG['arrStickerNum']['1'] = array(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,21,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430);
$_CONFIG['arrStickerNum']['2'] = array(18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,501,502,503,504,505,506,507,508,509,510,511,512,513,514,515,516,517,518,519,520,521,522,523,524,525,526,527);
$_CONFIG['arrStickerNum']['11537'] = array(52002734,52002735,52002736,52002737,52002738,52002739,52002740,52002741,52002742,52002743,52002744,52002745,52002746,52002747,52002748,52002749,52002750,52002751,52002752,52002753,52002754,52002755,52002756,52002757,52002758,52002759,52002760,52002761,52002762,52002763,52002764,52002765,52002766,52002767,52002768,52002769,52002770,52002771,52002772,52002773);
$_CONFIG['arrStickerNum']['11538'] = array(51626494,51626495,51626496,51626497,51626498,51626499,51626500,51626501,51626502,51626503,51626504,51626505,51626506,51626507,51626508,51626509,51626510,51626511,51626512,51626513,51626514,51626515,51626516,51626517,51626518,51626519,51626520,51626521,51626522,51626523,51626524,51626525,51626526,51626527,51626528,51626529,51626530,51626531,51626532,51626533);
$_CONFIG['token'] = '
include ('lineJSONmaker.class.php');
class LineJSONmaker3 extends LineJSONmaker{
public function setSticker($packageId, $stickerId){
{
"type": "sticker",
"packageId": "$packageId",
"stickerId": "$stickerId"
}
EOT;
$this->arrJSON[] = $json;
}
}
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
$objLineJSON = new LineJSONmaker3($_CONFIG['token'], $data['events'][0]['replyToken']);
if($data['events'][0]['message']['type'] == 'sticker')
{
$keysPackage = array_keys($_CONFIG['arrStickerNum']);
$indexPackage = rand(0,count($keysPackage)-1);
$packageId = $keysPackage[$indexPackage];
$indexSticker = rand(0,count($_CONFIG['arrStickerNum'][$packageId])-1);
$stickerId = $_CONFIG['arrStickerNum'][$packageId][$indexSticker];
$objLineJSON->setSticker($packageId,$stickerId);
}
$result = $objLineJSON->send();
public function setSticker($packageId, $stickerId){
$json = <<
{
"type": "sticker",
"packageId": "$packageId",
"stickerId": "$stickerId"
}
EOT;
$this->arrJSON[] = $json;
}
?>
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
下列是LINE官方文件所列出的sticker代號表,下面列出我會用到的部份。
Sticker列表
2020年1月13日 星期一
LINE的quickReply
設計LINE機器人時,有些固定的輸入可以使用泡泡選單呈現,在LINE的JSON語法裡它叫quickReply。
下面是quickReply的JSON格式:
{
"replyToken": "",
"messages": [
{
"type": "text",
"text": "測試泡泡選單。",
"quickReply": {
"items": [
{
"type": "action",
"action": {
"type": "message",
"label": "泡泡1",
"text": "泡泡1"
}
},
{
"type": "action",
"action": {
"type": "message",
"label": "泡泡2",
"text": "泡泡2"
}
},
{
"type": "action",
"action": {
"type": "message",
"label": "泡泡3",
"text": "泡泡3"
}
}
]
}
}
]
}
若當初建立LINE的channel時,是選Developer Trial,你就可以用下列語法PUSH訊息到LINE,把換成你的,要從webhook收到的JSON取得,最後加入上面的quickReply的JSON,就可以主動發送了。
curl -v -X POST https://api.line.me/v2/bot/message/push \ -H 'Content-Type:application/json' \ -H 'Authorization: Bearer {}' \ -d '{ "to": "", }'
也可以在webhook收到訊息後,用reply的格式回傳,參考下列文章。
參考本部落格的LINE機器人:
https://oscarhsu70.blogspot.com/2020/01/line.html
參考官方文件:
https://developers.line.biz/en/docs/messaging-api/using-quick-reply/
下面是quickReply的JSON格式:
{
"replyToken": "
"messages": [
{
"type": "text",
"text": "測試泡泡選單。",
"quickReply": {
"items": [
{
"type": "action",
"action": {
"type": "message",
"label": "泡泡1",
"text": "泡泡1"
}
},
{
"type": "action",
"action": {
"type": "message",
"label": "泡泡2",
"text": "泡泡2"
}
},
{
"type": "action",
"action": {
"type": "message",
"label": "泡泡3",
"text": "泡泡3"
}
}
]
}
}
]
}
若當初建立LINE的channel時,是選Developer Trial,你就可以用下列語法PUSH訊息到LINE,把
curl -v -X POST https://api.line.me/v2/bot/message/push \ -H 'Content-Type:application/json' \ -H 'Authorization: Bearer {
也可以在webhook收到訊息後,用reply的格式回傳,參考下列文章。
參考本部落格的LINE機器人:
https://oscarhsu70.blogspot.com/2020/01/line.html
參考官方文件:
https://developers.line.biz/en/docs/messaging-api/using-quick-reply/
LINE官網機器人申請過程
第一次開發LINE機器人,需要LINE develop的帳號,請參考下列連結,建立開發者帳號。
https://developers.line.biz/en/docs/messaging-api/getting-started/
重要提醒! 一定要驗證你的email,LINE機器人才能正常工作。
先建立channel
選Messaging API
輸入你的機器人的設定,來建立你的LINE機器人。包含它的大頭照(App icon),機器人的名字(App name)。
這裡我們選Developer Trial,為了多一個PUSH MESSAGES的功能,可以主動發訊息給別人,需不是只能Reply(回應訊息)。Category及Subcategory選擇你的分類。Email是有其它事的話,可以連絡到你。
完成剛才的建立APP後,再進入你的APP繼續設定。
把下列4個地方設定好:
第一個Channel access token (long-lived),小時數預設0就好,表示這個Token沒有時間限制。
第二個Use webhooks,把它設定成Enabled。如果等下自己的PHP程式出錯,Use webhooks會自動調成Disable,你就要再回來改成Enable。
第三個Webhook URL,把你的Webhook server網址設定進去,如果你是第一次接觸Webhook,可參考Facebook Messenger API使用方法,裡面有對Webhook作解釋。但在設定這一項之前,你可能需要先建好你的Webhook程式網頁,不然它會回應失敗,這在下一段會講。
第四個Auto-reply messages,把它Disable掉,使用者才不會收到預設回應訊息。
建立下列PHP程式網頁,放在你的Server上,我喜歡設定檔名為webhook.php,存取網址要如同你上面設定的一樣。"" 要改成剛才ISSUE出來的Channel access token。記得建一個webhook.txt,放在這個程式檔的旁邊,記錄傳來的Webhook事件,到時候有問題,你可以自己打開來看發生什麼事。
$_CONFIG['token'] = '';
$logName = 'webhook.txt';
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
rewrite($logName, "\n\n");
rewrite($logName, "\n".$_SERVER['REQUEST_URI']);
rewrite($logName, "\n".$raw);
receiveMsg($data['events'][0]);
function receiveMsg($messaging)
{
global $logName;
$result = array();
$result = sendText($messaging['replyToken']
, 'Text received, echo: '.$messaging['message']['text']);
rewrite($logName, "\n".$result[0].' : '.$result[1]);
}
function sendText($receive,$text){
global $logName;
$url = 'https://api.line.me/v2/bot/message/reply';
$data['replyToken'] = $receive;
$data['messages'] = array(array( 'type' => 'text', 'text' => $text) );
$json = json_encode($data);
rewrite($logName, "\n".$json);
//rewrite($logName, "\n".'before http_post_data: '.$json);
$result = http_post_data($url, $json);
return $result;
}
function http_post_data($url, $data_string) {
global $_CONFIG;
//$token = $_CONFIG['token'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json; charset=utf-8' ,
'Authorization: Bearer {'.$_CONFIG['token'].'}' ,
'Content-Length: ' . strlen($data_string))
);
ob_start();
curl_exec($ch);
$return_content = ob_get_contents();
ob_end_clean();
$return_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
return array($return_code, $return_content);
}
function rewrite($filename, $data)
{
$filenum=fopen($filename,'a');
fwrite($filenum,$data);
fclose($filenum);
}
從剛才設定畫面的最下面,找到QRcode,用你自己的手機的LINE加入好友,就可以開始測試。
對話結果就如同下面
https://developers.line.biz/en/docs/messaging-api/getting-started/
重要提醒! 一定要驗證你的email,LINE機器人才能正常工作。
先建立channel
選Messaging API
輸入你的機器人的設定,來建立你的LINE機器人。包含它的大頭照(App icon),機器人的名字(App name)。
這裡我們選Developer Trial,為了多一個PUSH MESSAGES的功能,可以主動發訊息給別人,需不是只能Reply(回應訊息)。Category及Subcategory選擇你的分類。Email是有其它事的話,可以連絡到你。
完成剛才的建立APP後,再進入你的APP繼續設定。
把下列4個地方設定好:
第一個Channel access token (long-lived),小時數預設0就好,表示這個Token沒有時間限制。
第二個Use webhooks,把它設定成Enabled。如果等下自己的PHP程式出錯,Use webhooks會自動調成Disable,你就要再回來改成Enable。
第三個Webhook URL,把你的Webhook server網址設定進去,如果你是第一次接觸Webhook,可參考Facebook Messenger API使用方法,裡面有對Webhook作解釋。但在設定這一項之前,你可能需要先建好你的Webhook程式網頁,不然它會回應失敗,這在下一段會講。
第四個Auto-reply messages,把它Disable掉,使用者才不會收到預設回應訊息。
建立下列PHP程式網頁,放在你的Server上,我喜歡設定檔名為webhook.php,存取網址要如同你上面設定的一樣。"
$_CONFIG['token'] = '
$logName = 'webhook.txt';
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
rewrite($logName, "\n\n");
rewrite($logName, "\n".$_SERVER['REQUEST_URI']);
rewrite($logName, "\n".$raw);
receiveMsg($data['events'][0]);
function receiveMsg($messaging)
{
global $logName;
$result = array();
$result = sendText($messaging['replyToken']
, 'Text received, echo: '.$messaging['message']['text']);
rewrite($logName, "\n".$result[0].' : '.$result[1]);
}
function sendText($receive,$text){
global $logName;
$url = 'https://api.line.me/v2/bot/message/reply';
$data['replyToken'] = $receive;
$data['messages'] = array(array( 'type' => 'text', 'text' => $text) );
$json = json_encode($data);
rewrite($logName, "\n".$json);
//rewrite($logName, "\n".'before http_post_data: '.$json);
$result = http_post_data($url, $json);
return $result;
}
function http_post_data($url, $data_string) {
global $_CONFIG;
//$token = $_CONFIG['token'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json; charset=utf-8' ,
'Authorization: Bearer {'.$_CONFIG['token'].'}' ,
'Content-Length: ' . strlen($data_string))
);
ob_start();
curl_exec($ch);
$return_content = ob_get_contents();
ob_end_clean();
$return_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
return array($return_code, $return_content);
}
function rewrite($filename, $data)
{
$filenum=fopen($filename,'a');
fwrite($filenum,$data);
fclose($filenum);
}
從剛才設定畫面的最下面,找到QRcode,用你自己的手機的LINE加入好友,就可以開始測試。
對話結果就如同下面
LINE-BOT-SDK套件(Python)介紹
Line提供Python的套件,給Python程式設計者可以使用套件API跟LINE互動,就不需要撰寫太多複雜的程式及JSON傳輸格式。套件是以Flask提供服務,啟動後,就是LINE的Webhook。
下載套件方法:
pip install line-bot-sdk
範例程式碼,可以Echo使用者輸入的字句:
下載套件方法:
pip install line-bot-sdk
範例程式碼,可以Echo使用者輸入的字句:
from flask import Flask, request, abort
from linebot import (
LineBotApi, WebhookHandler
)
from linebot.exceptions import (
InvalidSignatureError
)
from linebot.models import (
MessageEvent, TextMessage, TextSendMessage,
)
app = Flask(__name__)
userNo = 1
line_bot_api = LineBotApi('')
handler = WebhookHandler('')
@app.route("/")
def hello():
return "Hello World!"
@app.route("/callback", methods=['POST'])
def callback():
# get X-Line-Signature header value
signature = request.headers['X-Line-Signature']
# get request body as text
body = request.get_data(as_text=True)
app.logger.info("Request body: " + body)
#handle webhook body
try:
handler.handle(body, signature)
except InvalidSignatureError:
abort(400)
return 'OK'
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
if (event.reply_token == '00000000000000000000000000000000'):
return None
global userNo
text_message = TextSendMessage(text='Hello, world',
quick_reply=QuickReply(items=[
QuickReplyButton(action=MessageAction(label="label", text="text"))
]))
line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text=userNo))
userNo += 1
if __name__ == "__main__":
app.run(
host = '0.0.0.0',
port = 12345,
debug = True,
ssl_context ='adhoc'
)
訂閱:
文章 (Atom)
IKEA吊櫃廚櫃
好不容易裝好IKEA買來的吊櫃,花了三天。 從組裝,鑽牆,上牆調水平,累死我了。
-
Line提供Python的套件,給Python程式設計者可以使用套件API跟LINE互動,就不需要撰寫太多複雜的程式及JSON傳輸格式。套件是以Flask提供服務,啟動後,就是LINE的Webhook。 下載套件方法: pip install line-bot-sdk...