2023年11月30日 星期四

Python 學習筆記 : 用 Jieba 模組做中文斷詞

今天在 Hahow 企業版的 "開啟 AI 即戰力 : NL 無痛入門" 課程中, 學習到如何將中文斷詞模組 Jieba 用在語音聊天機器人的作法, 這模組在我手邊的幾本 Python 與 NLP 的書上都有介紹, 以前看完也沒時間測試, 趁著今天記憶猶新紀錄一下測試結果唄. 

Jieba 是中國搜尋引擎百度所開發, 是目前最為廣用的中文斷詞工具, 其預設詞庫據說最早是來自人民日報. Jieba 基本上使用詞庫匹配方式進行規則斷詞, 但對於詞庫中沒有的詞條也支援使用隱藏式馬可夫模型 (HMM) 進行統計斷詞. Jieba 採 MIT 開源政策, 其原始碼寄存於 GitHub, 參考 :


Jieba 的特點摘要如下 :
  • 支援簡體繁體斷詞
  • 支援自定義詞典
  • 提供四種斷詞模式 : 精確, 全文, 搜尋引擎, 以及 Paddle 模式
其中 Paddle 模式使用深度學習框架斷詞, 須先安裝 paddlepadle-tiny 模組才能使用, 且僅支援 Jieba 0.40 版以上. 雖然 Jieba 同時支援繁簡中文, 但因為預設是針對簡體中文詞彙用法而開發, 繁體文本的斷詞結果可能不甚完美, 但這可以透過所提供的設定詞庫功能下載繁體中文詞庫解決.


1. 安裝 Jieba :

可以直接在命令列下 pip install 指令直接安裝 Jieba 模組 :

pip install jieba   

我習慣使用 Thonny 開發 Python 程式, 所以是在 "套件管理" 搜尋 Jieba 後按 "安裝" :




在命令列用 import 匯入 jieba 若沒報錯即可開始用它來斷詞 :

>>> import jieba    
>>> jieba.__version__    
'0.42.1'

用 Python 內建函式 ddir() 檢視 Jieba 模組的內容 : 

>>> dir(jieba)    
['DEFAULT_DICT', 'DEFAULT_DICT_NAME', 'DICT_WRITING', 'PY2', 'Tokenizer', '__builtins__', '__cached__', '__doc__', '__file__', '__license__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', '_compat', '_get_abs_path', '_lcut', '_lcut_all', '_lcut_for_search', '_lcut_for_search_no_hmm', '_lcut_no_hmm', '_pcut', '_pcut_for_search', '_replace_file', 'absolute_import', 'add_word', 'calc', 'check_paddle_install', 'cut', 'cut_for_search', 'default_encoding', 'default_logger', 'del_word', 'disable_parallel', 'dt', 'enable_paddle', 'enable_parallel', 'finalseg', 'get_DAG', 'get_FREQ', 'get_dict_file', 'get_module_res', 'initialize', 'iteritems', 'iterkeys', 'itervalues', 'lcut', 'lcut_for_search', 'load_userdict', 'log', 'log_console', 'logging', 'marshal', 'md5', 'os', 'pkg_resources', 'pool', 're', 're_eng', 're_han_default', 're_skip_default', 're_userdict', 'resolve_filename', 'setLogLevel', 'set_dictionary', 'strdecode', 'string_types', 'suggest_freq', 'sys', 'tempfile', 'text_type', 'threading', 'time', 'tokenize', 'unicode_literals', 'user_word_tag_tab', 'xrange']

常用函式如下表 : 


 Jeiba 常用函式 說明
 cut(text) 支援精確, 全文, 與 Paddle 模式斷詞, 傳回一個 generator
 cut_for_search(text) 支援搜尋引擎模式斷詞, 傳回一個 generator
 lcut(text) 支援精確, 全文, 與 Paddle 模式斷詞, 傳回一個 list
 lcut_for_search(text) 支援搜尋引擎模式斷詞, 傳回一個 list
 enable_paddle() 開啟 Paddle 斷詞模式 (僅支援 0.40 以上版本)
 set_dictionary(file_name) 設定自訂詞典 (傳入字典檔案名稱)
 load_dictionary(file_name) 載入自訂詞典 (傳入字典檔案名稱)


其中的 cut() 與 cut_for_sarch() 函式為主要的斷詞的工具, 兩者均傳回一個生成器 generator, 可以將生成器傳給 list() 轉成串列, 或傳給字串的 join() 方法用一個 delimiter 字元將斷詞結果串接成字串, 但對於超大文本則可用迴圈迭代方式從 generator 取出斷詞結果, 以免發生記憶體不足問題.  

>>> type(jieba.cut)   
<class 'method'>  
>>> type(jieba.cut_for_search)  
<class 'method'>


2. 呼叫 jieba.cut() 或 jieba.cut_for_seach() 將文本斷詞 :

jieba.cut() 的參數有 4 個 : 

jieba.cut(text [, cut_all=False, HMM=False, use_paddle=False])

必要參數 text 為要進行斷詞的文本, 必須是 UTF-8 或 unicode 編碼的字串; 參數 cut_all 用來設定精確或完全斷詞模式 : 
  • cut_all=False (預設) : 精確模式, 將句子中的詞精確地切開
  • cut_all=True : 完全模式, 將句子中所有可以成詞的都切出來, 速度快但可能有歧異
HMM 參數用來設定是否使用隱藏式馬可夫模型, 用於處理未登錄詞; use_paddle 參數用來設定是否開啟 paddle 模式 (深度學習框架). 

jieba.cut_for_search() 是以精確模式為基礎對較長的詞彙再切分, 主要用在搜尋引擎的斷詞, 其參數有 2 個, 用法與 cut() 一樣 : 

jieba.cut(text [, HMM=False)

例如 :

>>> text='只有退潮的時候,你才知道誰在裸泳'    
>>> g=jieba.cut(text)    
>>> type(g)     
<class 'generator'>      
>>> '|'.join(g)    
'只有|退潮|的|時候|,|你|才|知道|誰|在|裸泳'    
>>> g=jieba.cut(text, cut_all=True)      >>> type(g)     
<class 'generator'>
>>> '|'.join(g)      
'只有|退潮|的|時|候|,|你|才|知道|誰|在|裸泳'     
>>> g=jieba.cut_for_search(text)        
>>> type(g)     
<class 'generator'>  
>>> '|'.join(g)      
'只有|退潮|的|時候|,|你|才|知道|誰|在|裸泳'    

可見不論是 cut() 還是 cut_for_search() 都會傳回一個 generator, 欲取得斷詞結果可以將這個 generator 傳給字串的 join() 方法, 用一個分界字元 (delimiter) 將其產生的元素串起來. 也可以傳給 list() 函式轉成串列 :

>>> text='只有退潮的時候,你才知道誰在裸泳'    
>>> g=jieba.cut(text)  
>>> glist=list(g)      
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\tony1\AppData\Local\Temp\jieba.cache
Loading model cost 0.363 seconds.
Prefix dict has been built successfully.
>>> list(g)     
['只有', '退潮', '的', '時候', ',', '你', '才', '知道', '誰', '在', '裸泳']

上面的測試句子不論用哪個模式得到的斷詞結果都一樣, 改用下面句子就能看出不同 :

>>> text='這次到台北一定要去搭捷運,還要去台北101看看'    
>>> g=jieba.cut(text)                               # 精確模式
>>> '|'.join(g)    
'這次|到|台北|一定|要|去|搭|捷運|,|還要|去|台北|101|看看'    
>>> g=jieba.cut(text, cut_all=True)       # 完全模式
>>> '|'.join(g)    
'這|次|到|台北|北一|一定|定要|去|搭|捷|運|,|還|要|去|台北|101|看看'    
>>> g=jieba.cut_for_search(text)           # 搜尋引擎模式
>>> '|'.join(g)      
'這次|到|台北|一定|要|去|搭|捷運|,|還要|去|台北|101|看看'   

此例可知完全模式會完全切出可能的詞, 例如 "一定要" 會切出 "一定" 與 "定要" 兩個詞; "台北一" 會切出 "台北" 與 "北一"; 而精確模式與搜尋引擎模式結果仍是相同.


3. 使用繁體中文詞庫 :

Jieba 預設詞庫為簡體中文, 詞彙與繁體中文有些差別, 這使得斷詞的結果有時並不恰當, 所幸 Jieba 提供詞庫設定功能, 可以從 GitHub 下載 Jieba 的繁體中文詞庫 dict.txt.big.txt (約 8.4MB, 詞條總數約 58 萬個) :


將其放在目前工作目錄下的 dict 子目錄下, 然後用 jieba.set_dictionary() 函式更改使用的詞庫, 這樣就會套用繁體中文詞庫了 :

jieba.set_dictionary('dict/dict.txt.big.txt')

用純文字編輯器開啟檢閱詞庫內容如下 :

1号店 3 n
1號店 3 n
4S店 3 n
4s店 3 n
AA制 3 n
AB型 3 n
AT&T 3 nz
A型 3 n
A座 3 n
A股 3 n
A輪 3 n
A轮 3 n
.... (略)....

詞條的格式為 : 

詞彙 頻率 詞性

其中詞類 n 為名詞, v 為動詞等. 




例如 :

>>> jieba.set_dictionary('dict/dict.txt.big.txt')   
>>> text='這次到台北一定要去搭捷運,還要去台北101看看'     
>>> g=jieba.cut(text)      
>>> '|'.join(g)    
Building prefix dict from D:\python\test\dict\dict.txt.big.txt ...
Dumping model to file cache C:\Users\tony1\AppData\Local\Temp\jieba.ufcd6a507dc974c1ccf010c4f07a7bccd.cache
Loading model cost 1.488 seconds.
Prefix dict has been built successfully.
'這次|到|台北|一定|要|去|搭|捷運|,|還要|去|台北|101|看看'

原本以為 '台北101' 會被切成一個詞, 可見這個繁中詞庫還是不夠完整, 甚至連 '小港機場' 都被切成 '小港' 與 '機場' 兩個詞, 例如 :

>>> text='這次去日本玩從小港機場出境'  
>>> jieba.set_dictionary('dict/dict.txt.big.txt')   
>>> g=jieba.cut(text)    
>>> '|'.join(g)   
Building prefix dict from D:\python\test\dict\dict.txt.big.txt ...
Loading model from cache C:\Users\tony1\AppData\Local\Temp\jieba.ufcd6a507dc974c1ccf010c4f07a7bccd.cache
Loading model cost 0.722 seconds.
Prefix dict has been built successfully.
'這次|去|日本|玩|從小|港|機場|出境'
>>> g=jieba.cut(text, cut_all=False, HMM=True)     # 啟動 HMM 結果相同
>>> '|'.join(g)   
'這次|去|日本|玩|從小|港|機場|出境'

看來它是把 '從' 與 '小' 切成一個詞了 ('從小' 是一個時間副詞). 解決此問題可以編輯繁中詞庫更改詞頻, 或者使用 Jieba 提供的自定義詞庫功能製作自己的詞庫, 其優先權最高. 


4. 自定義詞庫 :   

教學文件說可以使用自定義詞庫來解決, 先編輯一個自定義詞庫 user_dict.txt, 內容如下 :

小港機場 3 n
台北101 3 n
蔡英文


詞條後面的數字是頻率, 最後面的是詞類, 這兩項可有可無, 有的話格式一定要正確 (即藥用空格隔開), 否則載入時會出現剖析錯誤.  




以 UTF-8 編碼存檔放在 dict 目錄底下, 然後在呼叫 cut() 前先呼叫 load_userdict() 並傳入此字定義詞庫檔名, 但測試結果無效 :

>>> text='這次去日本玩從小港機場出境'   
>>> jieba.set_dictionary('dict/dict.txt.big.txt')   
>>> jieba.load_userdict('dict/user_dict.txt')      
Building prefix dict from D:\python\test\dict\dict.txt.big.txt ...
Dumping model to file cache C:\Users\tony1\AppData\Local\Temp\jieba.uad5f395071014f3797318be3d45203bb.cache
Loading model cost 0.837 seconds.
Prefix dict has been built successfully.
>>> g=jieba.cut(text)   
>>> '|'.join(g)   
'這次|去|日本|玩|從小|港|機場|出境'

我猜可能是版本問題, 於是在另一台電腦指定安裝 0.39 版的 Jieba 來驗證此問題 : 

E:\python\test>pip install jieba==0.39    
Collecting jieba==0.39
  Downloading jieba-0.39.zip (7.3 MB)
     ---------------------------------------- 7.3/7.3 MB 2.6 MB/s eta 0:00:00
  Preparing metadata (setup.py) ... done
Building wheels for collected packages: jieba
  Building wheel for jieba (setup.py) ... done
  Created wheel for jieba: filename=jieba-0.39-py3-none-any.whl size=7282594 sha256=cc61f12c9a40cf1b040490b5c38de9deded61c4d3428b44ac7ab2a3fbdc7bdd0
  Stored in directory: c:\users\yhhuang\appdata\local\pip\cache\wheels\40\03\0d\c7671d2efcae3ed01aee03a390bcb970518abcdcd3d5df6c88
Successfully built jieba
Installing collected packages: jieba
Successfully installed jieba-0.39

>>> import jieba   
>>> jieba.__version__     
'0.39'   
>>> text='這次去日本玩從小港機場出境'    
>>> g=jieba.cut(text)   
>>> '|'.join(g)   
Building prefix dict from the default dictionary ...
Dumping model to file cache C:\Users\yhhuang\AppData\Local\Temp\jieba.cache
Loading model cost 0.797 seconds.
Prefix dict has been built succesfully.
'這次|去|日本|玩|從|小港|機場|出境'

可見 0.39 版的 Jieba 在預設的簡體詞庫下, 正確地切出 '小港', 而上面的新版 0.43 版反而是錯誤地切成 '從小' 與 '港'. 

接著下載上面的繁體詞庫檔 dict.txt.big.txt 放在目前工作目錄的子目錄 dict 底下, 用 set_dictionary() 設定使用此繁體詞庫, 重新進行斷詞, 結果反而跟 0.43 版一樣得到不正確的切詞 :

>>> jieba.set_dictionary('dict/dict.txt.big.txt')   
>>> text='這次去日本玩從小港機場出境'    
>>> g=jieba.cut(text)    
>>> '|'.join(g)     
Building prefix dict from E:\python\test\dict\dict.txt.big.txt ...
Dumping model to file cache C:\Users\yhhuang\AppData\Local\Temp\jieba.ua930d4894de9d82d45250971e025c191.cache
Loading model cost 1.172 seconds.
Prefix dict has been built succesfully.
'這次|去|日本|玩|從小|港|機場|出境'   

這可能是使用的繁中詞庫的關係, 我在下面這個網站找到另一個繁中詞庫 (比較小, 才 4MB) 就能切出正確結果 :


將此 dict.txt 同樣放在 dict 資料夾下測試 OK :

>>> jieba.set_dictionary('dict/dict.txt')   
>>> g=jieba.cut(text)   
>>> '|'.join(g)     
Building prefix dict from E:\python\test\dict\dict.txt ...
Dumping model to file cache C:\Users\yhhuang\AppData\Local\Temp\jieba.u0fd681cbe0a9699d869d3e014fd1e58b.cache
Loading model cost 0.625 seconds.
Prefix dict has been built succesfully.
'這次|去|日本|玩|從|小港|機場|出境'   

我用 0.39 版重新測試使用者定義詞庫結果就正確了 :  

>>> text='這次去日本玩從小港機場出境'   
>>> jieba.set_dictionary('dict/dict.txt')    
>>> jieba.load_userdict('dict/user_dict.txt')     
Building prefix dict from E:\python\test\dict\dict.txt ...
Loading model from cache C:\Users\yhhuang\AppData\Local\Temp\jieba.u0fd681cbe0a9699d869d3e014fd1e58b.cache
Loading model cost 0.515 seconds.
Prefix dict has been built succesfully.
>>> g=jieba.cut(text)    
>>> '|'.join(g)    
'這次|去|日本|玩|從|小港機場|出境'
>>> text='這次到台北一定要去搭捷運,還要去台北101看看'   
>>> jieba.set_dictionary('dict/dict.txt')    
>>> jieba.load_userdict('dict/user_dict.txt')  
Building prefix dict from E:\python\test\dict\dict.txt ...
Loading model from cache C:\Users\yhhuang\AppData\Local\Temp\jieba.u0fd681cbe0a9699d869d3e014fd1e58b.cache
Loading model cost 0.500 seconds.
Prefix dict has been built succesfully.
>>> g=jieba.cut(text)   
>>> '|'.join(g)   
'這次|到|台北|一定|要|去|搭|捷運|,|還要|去|台北101|看看'

結果 '小港機場' 與 '台北101' 都能正確切分出來, 可見自定義詞庫 userdict 會最優先被套用. 總之, 斷詞結果取決於 Jieba 版本與所使用的詞庫.


5. 去除停用詞 (stop word) :   

所謂停用詞 stop words 是指在自然語言處理任務中, 會影響統計與演算效率的字詞符號 (token), 例如標點符號, 或 of, on, at, in, which 等語法功能詞, 這些通常需要在語料錢處理過程中濾除. 停用字並沒有嚴格限定, 是根據 NLP 任務需要而自行定義的一張表. 參考 :


從上面的測試範例可知, Jieba 在斷詞時會把標點符號與空格等字符也切分成一個詞 (token), 這在統計字頻的任務中會影響計算結果, 所以需要將其列入停用字, 在用 Jieba 斷詞後進一步將停用字濾掉. 不過 Jieba 未內建濾除停用字的函式, 必須自行用檔案處理方式濾掉. 

首先編輯一個停用字表 stopword.txt, 一列一個停用字 (此處只是針對標點符號), 然後以 UTF-8 編碼格式存檔, 放在目前工作目錄或例如 dict 的子目錄下 :





然後用檔案處理方式讀取這個停用字表存入串列中, 將文本用 Jieba 斷詞後用迴圈迭代所得倒的 generator, 在每次迭代中檢查 generator 產出的斷詞是否在停用詞表中, 沒有的話就輸出, 這樣就能濾掉停用字了, 例如 : 

>>> with open('dict/stopwords.txt', encoding='utf-8-sig') as f:   
    stopwords=f.read().split('\n')     # 讀取整個檔案後以跳行字元拆分為串列
    
>>> stopwords     
['.', ',', ';', '?', '!', ':', "'", '"', ',', '。', '[', ']', '(', ')', '{', '}', '\\', '/', '|', '「', '」', ':']   
>>> text='這次到台北一定要去搭捷運,還要去台北101看看'      
>>> g=jieba.cut(text)    
>>> words=[]     # 儲存過濾後的 token
>>> for token in g:     # 迭代產生器
    if token not in stopwords:     # 如果不是停用字就存入串列
        words.append(token)    
        
>>> '|'.join(words)      
'這次|到|台北|一定|要|去|搭|捷運|還要|去|台北101|看看'

可見逼點符號已經被濾掉了. 但是在語音聊天機器人中似乎不用濾掉, 因為在 TTS 中合成語音時標點符號可作為停頓標誌. 

參考 :

https://github.com/ldkrsi/jieba-zh_TW (有七年前的繁體詞庫)

2023年11月29日 星期三

好書 : Web Development with Django (第二版)

最近在學習 Django 時發現一本好書 : 



Source : 天瓏


會出第二版想必是頗受好評, 此書根據最新的 Django 4 改寫, 全書以建構一個書評網站 Bookr 為例鋪展貫串全書, 介紹如何在 Django 的 MTV 架構下利用其所提供的各項技術來架設網站, 一氣呵成實用性極強, 書中範例可在 GitHub 下載 :


料理實驗 : 用 Bruno 壓力鍋滷五花肉

周日在全聯買了一盤五花肉, 打算來做 Bruno 壓力鍋食譜的滷五花, 參考 :






材料 :
  • 豬肉 (五花/里肌)
  • 青江菜 2 把
  • 薑 1 塊
  • 蒜 3 片
  • 蔥 1~2 條
  • 米酒 3 大匙
  • 味晽 3 大匙
  • 醬油 3 大匙
  • 砂糖 1 大匙
  • 八角 1 粒 (可有可無)
作法 : 
  1. 豬肉切塊, 薑切片, 蒜片拍扁, 蔥切斷, 放入 Bruno 內鍋, 加水蓋過食材, 按 "燉煮" 鈕.
  2. 等壓力指示棒下降後開鍋蓋, 留 300ml 湯汁其餘倒掉, 加入米酒, 味晽, 醬油與砂糖等調味料, 蓋上鍋蓋, 按 "手動" 設定加熱 5 分鐘 (按 +).
  3. 等壓力指示棒下降後開鍋蓋, 按 "手動" 保持開鍋蓋煮至接近收乾按 "關閉" 即可出鍋. 
因冰箱還有半根紅蘿蔔與花椰菜梗, 也一併下去煮 :






結果還不錯, 但沒有像食譜照片中那樣漂亮, 可能是我用淡色醬油之故. 其次是這次買的五花太肥了些, 以後要買瘦肉較多的里肌肉. 

2023年11月28日 星期二

momo 買書兩本 (Python機器學習錦囊妙計 + 最強 AI 投資)

今天上 momo 買了兩本書 :
No.1 是要拿來賠圖書館的, 這本不知被我遺忘在哪裡, 四處都找不到. No.2 前幾天上市, 旗標的書在明儀只有 85 折, momo 約 79 折 :




兩書合計 1050 元, 我 momo 幣累計有 2606 元, 折 1000 實付 50 元. 

2023年11月27日 星期一

2023 年第 47 周記事

本周最吸睛的莫過於吵了半年的藍白合慘烈互撕告終, 同一天的蕭美琴國際記者會則令我大開眼界, 除了超溜的英文外, 邏輯清楚言之有物才是讓我欣賞之處, 尤其她答覆外媒詢問執政八年後民眾為何要繼續投給貴黨, "We are the best" 這答案簡單又自信. 

Hahow 企業版本周完成 5 小時的 jQuery, 總計這個月以來已經清掉近 10 堂已註冊課程, 應該已達到 HR 本年度目標了, 接下來要緩一緩, 等待新上線課程有沒有感興趣的. 最近有注意到 Hahow 個人版 "互動藝術程式創作入門" 的課程優惠, 折扣下來約 1800 元 (原價 2400), 但估計近期沒時間學 p5.js 就算了. 或許企業版哪天就把它上線了.  

週日去鎮上市集順路到種苗店買了 20 株甜玉米苗, 20 株水果玉米苗, 6 株杓菜苗, 以及 4 株大番茄苗, 花了 130 元, 下午把菜園靠芭樂樹旁的菜地整理出來, 主要是掘地清除香附子, 清了整整一桶. 讓太陽曝曬一周, 下禮拜再把水果玉米種下去. 等收成後打算原地種 3 棵紅肉芭樂. 

 



離年底剩下 5 周, 還沒完成的計畫最近要趕一趕了. 

2023年11月25日 星期六

jQuery EasyUI v1.10 學習筆記 : 頁籤面板 Tabs

Tabs 是網頁中很常用的容器, 就像標籤文件夾一樣可以將資料分門別類. 本篇記錄 EasyUI 標籤面板的用法測試, 主要是根據之前舊版 EasyUI 測試文章改寫 :


本系列之前的文章參考 : 


EasyUI 使用兩層 div 元素來建構頁籤 (或稱標籤) 面板, 只要在外層 div 元素加上 easyui-tabs 樣式類別, 內層 div 用 title 屬性設定頁籤名稱即可, 不需要使用 jQuery 程式做初始化設定, 基本的 EasyUI Tabs 結構如下 :

<div class="easyui-tabs">
  <div title="標籤1">這是標籤1</div>
  <div title="標籤2">這是標籤2</div>
  <div title="標籤3">這是標籤3</div>
</div>

也可以使用 jQuery 程式初始化, 這時就不需要在外層 div 元素套用 easyui-tabs 樣式, 而是要添加 id 屬性以便程式能取得此元件之 jQuery 物件 : 

<div id="tab1">
  <div title="標籤1">這是標籤1</div>
  <div title="標籤2">這是標籤2</div>
  <div title="標籤3">這是標籤3</div>
</div>
<script>
   $(function(){
      $("#tab1").tabs();
      });
</script>

但是這個原始 Tabs 會與父容器 (即瀏覽器) 同寬, 高度則依照資料內容而定, 同時標籤之內容與邊框間隙很小, 故通常會使用 CSS 來設定適合的外觀, 例如 :

<div class="easyui-tabs" style="width:400px;height:200px">
  <div title="標籤1" style="padding:10px">這是標籤1</div>
  <div title="標籤2" style="padding:10px">這是標籤2</div>
  <div title="標籤3" style="padding:10px">這是標籤3</div>
</div>

例如 :



<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title></title>
    <link rel="stylesheet" href="themes/default/easyui.css">
    <link rel="stylesheet" href="themes/icon.css">
    <script src="jquery.min.js"></script>
    <script src="jquery.easyui.min.js"></script>
    <script src="locale/easyui-lang-zh_TW.js"></script>
  </head>
  <body>
    <div class="easyui-tabs">
      <div title="標籤1">這是原始標籤1</div>
      <div title="標籤2">這是原始標籤2</div>
      <div title="標籤3">這是原始標籤3</div>
    </div><br>
    <div class="easyui-tabs" style="width:400px;height:200px">
      <div title="Tab1" style="padding:10px">
        <p style="font-size:14px">這是 Tab1</p>
      </div>
      <div title="Tab2" style="padding:10px">
        <p style="font-size:14px">這是 Tab2</p>
      </div>
      <div title="Tab3" style="padding:10px">
        <p style="font-size:14px">這是 Tab3</p>
      </div>
    </div>
    <script>
      $(function(){
        });
    </script>
  </body>
</html>

此處疊了兩組頁籤面板, 上面是原始的, 下面是使用 CSS 設定尺寸的, 結果如下 :




除了使用 data-options 屬性設定頁籤面板外, 也可以使用 jQuery 物件的 tabs() 方法進行初始化, 這時必須在外層 div 元素上添加 id 屬性, 這樣才能透過 $("#tab_id") 取得 div 元素之 jQuery 物件, 呼叫其 tabs() 方法, 即可建立 Tabs 元件, 語法如下 :

$("#tab1").tabs({屬性物件})

呼叫 tabs() 方法可傳入一個屬性物件來初始化 Tabs 物件, 外層 div 之 data-options 屬性用來設定整個頁籤面板之屬性 :


 外層 div 之 data-options 屬性 說明
 width 頁籤面板寬度 (px 數值)
 height 頁籤面板高度 (px 數值)
 plain 設為 true 可去除頁籤之背景色 (預設 false)
 tabPosition 頁籤位置, "left" (左), "right" (右), "top" (上, 預設), "bottom" (下)
 tabWidth 每個頁籤寬度 (px 數值, 依照頁籤內容自動調整)
 tabHeight 每個頁籤高度 (px 數值, 預設 27)
 headerWidth 設定 tabPosition="left"/"right" 時每個頁籤寬度 (px 數值, 預設 150)
 selected 設定預設被開啟的頁籤之索引 (0 起始), 預設 0


內層 div 之 data-options 屬性用來設定該頁籤之屬性 : 


 內層 div 之 data-options 屬性 說明
 closable 設為 true 時頁籤右方會出現 x 刪除鈕 (預設 false)
 collapsible 設為 true 時頁籤可隱藏
 href 以 Ajax 方式從後端檔案或程式 URL 取得頁籤要顯示的內容
 title 頁籤的標頭文字
 content 頁籤的內容
 tools 指定工具按鈕的 id


例如上面使用 data-options 的範例可以改成呼叫 tabs() 方法初始化 : 



<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title></title>
    <link rel="stylesheet" href="themes/default/easyui.css">
    <link rel="stylesheet" href="themes/icon.css">
    <script src="jquery.min.js"></script>
    <script src="jquery.easyui.min.js"></script>
    <script src="locale/easyui-lang-zh_TW.js"></script>
  </head>
  <body>
    <div id="tab1">
      <div title="Tab1" style="padding:10px">
        <p style="font-size:14px">這是 Tab1</p>
      </div>
      <div title="Tab2" style="padding:10px">
        <p style="font-size:14px">這是 Tab2</p>
      </div>
      <div title="Tab3" style="padding:10px">
        <p style="font-size:14px">這是 Tab3</p>
      </div>
    </div>
    <script>
      $(function(){
        $("#tab1").tabs({
          width:400,
          height:200,    
          plain: true    
          });
        });
    </script>
  </body>
</html>

外層 div 元素添加了 id="tab1" 屬性以便能用 $("#tab1") 取得該 div 之 jQuery 物件, 然後在呼叫 tabs() 方法時傳入 width 與 height 以及 plain 組成之屬性物件進行初始化, 注意, 使用 tabs() 時就不需要在外層 div 元素中使用 data-options 屬性了, 結果如下 :




由於傳入 tabs() 的屬性物件中含有 plain: true 屬性, 它會取消標籤後面的背景色 (預設是藍色). 其次, 因為屬性物件中已設定 width 與 height 屬性, 因此外層 div 的 style 也不需要了. 此範例也可以用 data-options 屬性來做, 例如 : 



<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title></title>
    <link rel="stylesheet" href="themes/default/easyui.css">
    <link rel="stylesheet" href="themes/icon.css">
    <script src="jquery.min.js"></script>
    <script src="jquery.easyui.min.js"></script>
    <script src="locale/easyui-lang-zh_TW.js"></script>
  </head>
  <body>
    <div class="easyui-tabs" style="width:400px;height:200px">
      <div title="Tab1" style="padding:10px">
        <p style="font-size:14px">這是 Tab1</p>
      </div>
      <div title="Tab2" style="padding:10px">
        <p style="font-size:14px">這是 Tab2</p>
      </div>
      <div title="Tab3" style="padding:10px">
        <p style="font-size:14px">這是 Tab3</p>
      </div>
    </div><br>
    <div class="easyui-tabs" data-options="width:400, height: 200, plain:true">
      <div title="Tab1" style="padding:10px">
        <p style="font-size:14px">這是 Tab1</p>
      </div>
      <div title="Tab2" style="padding:10px">
        <p style="font-size:14px">這是 Tab2</p>
      </div>
      <div title="Tab3" style="padding:10px">
        <p style="font-size:14px">這是 Tab3</p>
      </div>
    </div>
    <script>
      $(function(){
        });
    </script>
  </body>
</html>

此例使用兩個標籤面板來對比 plain:true 的效果, 上面的 Tabs 沒有用 data-options 來初始化 Tabs 元件, 只用 style 來設定其尺寸, 預設標籤有背景色. 下面的 Tabs 元件則使用 data-options 來初始化, 其中的 plain: true 屬性用來去除背景色. 注意, data-options 中的各屬性須以逗號隔開, 不可用分號 (會出現 token error), 結果如下 :




此例下方的標籤面板因為有添加 "plain:true" 屬性, 所以標籤後面的背景色被去除了. 一般的頁籤功能使用 data-options 屬性就能達成, 但互動性高的操作較複雜, 需要用 jQuery 程式才能達成. 

當頁籤數量增加, 總寬度超過整個頁籤面板時, 會在頁籤面板左右兩端出現 << 與 >> 滑動鈕, 按下時會讓頁籤滑動以顯示被遮住的頁籤, 例如 :



<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title></title>
    <link rel="stylesheet" href="themes/default/easyui.css">
    <link rel="stylesheet" href="themes/icon.css">
    <script src="jquery.min.js"></script>
    <script src="jquery.easyui.min.js"></script>
    <script src="locale/easyui-lang-zh_TW.js"></script>
  </head>
  <body>
    <div class="easyui-tabs" data-options="width:400, height: 200">
      <div title="Tab1" style="padding:10px">
        <p style="font-size:14px">這是 Tab1</p>
      </div>
      <div title="Tab2" style="padding:10px">
        <p style="font-size:14px">這是 Tab2</p>
      </div>
      <div title="Tab3" style="padding:10px">
        <p style="font-size:14px">這是 Tab3</p>
      </div>
      <div title="Tab4" style="padding:10px">
        <p style="font-size:14px">這是 Tab4</p>
      </div>
      <div title="Tab5" style="padding:10px">
        <p style="font-size:14px">這是 Tab5</p>
      </div>
      <div title="Tab6" style="padding:10px">
        <p style="font-size:14px">這是 Tab6</p>
      </div>
      <div title="Tab7" style="padding:10px">
        <p style="font-size:14px">這是 Tab7</p>
      </div>
      <div title="Tab8" style="padding:10px">
        <p style="font-size:14px">這是 Tab8</p>
      </div>
      <div title="Tab9" style="padding:10px">
        <p style="font-size:14px">這是 Tab9</p>
      </div>
    </div>
    <script>
      $(function(){
        });
    </script>
  </body>
</html>

結果如下 :




頁籤面板預設會打開第一個頁籤, 但可以透過 selected 指定預設要開啟哪一個頁籤, 例如 :



<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title></title>
    <link rel="stylesheet" href="themes/default/easyui.css">
    <link rel="stylesheet" href="themes/icon.css">
    <script src="jquery.min.js"></script>
    <script src="jquery.easyui.min.js"></script>
    <script src="locale/easyui-lang-zh_TW.js"></script>
  </head>
  <body>
    <div class="easyui-tabs" data-options="width:400, height: 200, selected: 2">
      <div title="Tab1" style="padding:10px">
        <p style="font-size:14px">這是 Tab1</p>
      </div>
      <div title="Tab2" style="padding:10px">
        <p style="font-size:14px">這是 Tab2</p>
      </div>
      <div title="Tab3" style="padding:10px">
        <p style="font-size:14px">這是 Tab3</p>
      </div>
    </div>
    <script>
      $(function(){
        });
    </script>
  </body>
</html>

此例設定 selected: 2 表示預設開啟第 3 個頁籤, 結果如下 :




可見當瀏覽器一開啟此網頁, Tab3 預設被打開了.

料理實驗 : 金針菇燉豆腐

前陣子滑臉書時看到一個食譜 "金針菇燉豆腐", 當時只截了圖存參, 忘記記下出處, 今天剛好冰箱食材具備, 中午就動手實驗一番 :





材料 : 
  • 豆腐 1~2 塊
  • 金針菇 1 包
  • 蒜頭 3~5 片
  • 小辣椒 1 條
  • 醬油 2 大匙
  • 蠔油 2 大匙
  • 醋 1 匙
  • 糖 1 匙
  • 太白粉 1 匙
作法 : 
  1. 金針菇去根部洗淨備用, 蒜頭與小辣椒切碎備用, 豆腐切小塊備用. 
  2. 醬油, 蠔油, 醋, 糖等調味料放入碗中攪拌混合, 加入太白粉與適量水調和備用. 
  3. 起油鍋放入蒜頭與小辣椒中火炒香, 放入金針菇與豆腐, 倒入調味料蓋上鍋蓋小火燉煮約 10 分鐘即可起鍋. 






酸酸辣辣的很下飯, 我吃了兩碗飯. 

jQuery EasyUI v1.10 學習筆記 : 環境配置

最近上 Hahow 學校的 jQuery 課程後, 讓我想起 9 年前 (2014) 學過的 EasyUI 套件, 當時用 EasyUI v.1.4 版與後端 PHP 程式搭建了一個股票投資決策輔助系統, 那時為了快速完工僅測試了幾個會用到的元件 (主要是 Tabs 與 DataGrid 等) 便開始搭建, 並沒有對全部元件進行完整測試. 最近想在 Mapleborad 上改用 Python Django 重新建構此系統, 打算對新版的 EasyUI v1.10 進行較全面之測試. 

本系列之前的文章參考 :


EasyUI 官網網址如下 :


功能展示網頁 : 



一. 使用官網 CDN 資源檔 :    

此種方式完全不用準備任何資源檔案, 直接在網頁的 head 中匯入官網 CDN 所提供的 EasyUI 程式與樣式檔即可, 這樣會一直使用 EasyUI 最新版, 目前是 v1.10.17 版, 搭配的 jQuery 則是 v3.3.1 版, 網頁範本如下 (easyui-cdn-template.htm) :

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title></title>
    <link rel="stylesheet" href="https://www.jeasyui.com/easyui/themes/default/easyui.css">
    <link rel="stylesheet" href="https://www.jeasyui.com/easyui/themes/icon.css">
    <script src="https://www.jeasyui.com/easyui/jquery.min.js"></script>
    <script src="https://www.jeasyui.com/easyui/jquery.easyui.min.js"></script>
    <script src="https://www.jeasyui.com/easyui/locale/easyui-lang-zh_TW.js"></script>
  </head>
  <body>
    <!--EasyUI 元件-->
    <script>
      $(function(){
        });
    </script>
  </body>
</html>

注意, 這些 CDN 資源檔以前用 http 協定就可以, 但現在都要改用 https 協定才行, 否則會因為瀏覽器無法載入資源而不能顯示效果 (按滑鼠右鍵選檢查到 Console 會看到出現安全性錯誤. 

這樣就可以在 body 中撰寫包含 EasyUI 元件的網頁內容了, 例如標籤面板 easyui-tabs :



<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title></title>
    <link rel="stylesheet" href="https://www.jeasyui.com/easyui/themes/default/easyui.css">
    <link rel="stylesheet" href="https://www.jeasyui.com/easyui/themes/icon.css">
    <script src="https://www.jeasyui.com/easyui/jquery.min.js"></script>
    <script src="https://www.jeasyui.com/easyui/jquery.easyui.min.js"></script>
    <script src="https://www.jeasyui.com/easyui/locale/easyui-lang-zh_TW.js"></script>
  </head>
  <body> 
    <div class="easyui-tabs" style="width:400px;height:200px">
      <div title="Tab1" style="padding:10px">
        <p style="font-size:14px">這是 Tab1</p>
      </div>
      <div title="Tab2" style="padding:10px">
        <p style="font-size:14px">這是 Tab2</p>
      </div>
      <div title="Tab3" style="padding:10px">
        <p style="font-size:14px">這是 Tab3</p>
      </div>
    </div>
    <script>
      $(function(){
        });
    </script>
  </body>
</html>

結果如下 : 




跟 Bootstrap 很像, EasyUI 的 tabs 元件使用兩層 div 結構, 只要在外層 div 加上 easyui-tabs 樣式類別可, 毋須使用 jQuery 程式進行設定. 


二. 自備 EasyUI 資源檔 :    

此方式適用於需要特定版本的 EasyUI 場合, 因為 EasyUI 官網提供的 CDN 資源檔都是最新版本, 有時版本差異可能造成網頁顯示異常甚至無法運作, 自備特定之資源檔是比較保險的做法. 

首先須下載 EasyUI 壓縮檔, 官網提供目前四大前端技術之版本, 此處下載 jQuery 版 (因為我會的也只有 jQuery, 新式前端框架也只略懂 Vue 而已 XD) : 




點選 Freeware 授權版, 此版本可免費使用於非營利專案 (政府部門除外) :




將下載之 zip 檔解壓縮後複製其中的 jquery.min.js 與 jquery.easyui.min.js 兩個檔案, 以及 themes 與 locale 這兩個資料夾到專案目錄下 :





然後將上面的 CDN 範本網頁修改為如下參考本地資源的範本網頁 (easyui-local-template.htm) : 

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title></title>
    <link rel="stylesheet" href="themes/default/easyui.css">
    <link rel="stylesheet"  href="themes/icon.css">
    <script src="jquery.min.js"></script>
    <script src="jquery.easyui.min.js"></script>
    <script src="locale/easyui-lang-zh_TW.js"></script>
  </head>
  <body>
    <!--EasyUI 元件-->
    <script>
      $(function(){
        });
    </script>
  </body>
</html>

然後就可以在 body 內撰寫包含 EasyUI 元件的網頁內容了, 例如標籤面板 :



<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="cache-control" content="no-cache">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title></title>
    <link rel="stylesheet" href="themes/default/easyui.css">
    <link rel="stylesheet"  href="themes/icon.css">
    <script src="jquery.min.js"></script>
    <script src="jquery.easyui.min.js"></script>
    <script src="locale/easyui-lang-zh_TW.js"></script>
  </head>
  <body>
    <div class="easyui-tabs" style="width:400px;height:200px">
      <div title="Tab1" style="padding:10px">
        <p style="font-size:14px">這是 Tab1</p>
      </div>
      <div title="Tab2" style="padding:10px">
        <p style="font-size:14px">這是 Tab2</p>
      </div>
      <div title="Tab3" style="padding:10px">
        <p style="font-size:14px">這是 Tab3</p>
      </div>
    </div>
    <script>
      $(function(){
        });
    </script>
  </body>
</html>

結果與上面使用 CDN 資源是一樣的. 

2023年11月24日 星期五

好站 : 用 tinypng 壓縮圖檔

最近在 Hahow 企業版上互動網頁課程時得知 tinypng 這個好用的線上工具 : 


只要將圖檔 (png, jpg, bmp 均可) 拖曳到中間的方框內即可 :





原本 176KB 的圖檔經過壓縮後變成 46KB, 縮小了七成, 按 Download 即可下載, 開啟後檢視圖片品質並沒有顯著差異, 實在太棒了. 

2023年11月23日 星期四

好站 : tutorialspoint.com 線上編譯器/編輯器

今天上完 Hahow 學校的 C 語言入門 (當作是複習), 課程中老師使用 tutorialspoint.com 的 C 語言線上編譯器, 其介面簡潔易用, 適合教學與快速測試 :





按 Execute 鈕會編譯並執行 C 程式碼, 結果會顯示在右邊的 Terminal 視窗; 按 Beautify 鈕會將 Source Code 內的程式碼整理成易讀格式; 按 Share 鈕會產生此程式原始碼的可分享超連結 :

 


tutorialspoint.com 也是是知名的線上教學文件網站, 除了 C 語言以外, 還提供其他語言的線上編譯器/編輯器, 例如我目前主要使用的 Python 與 Javascript 語言 :





已安裝大部分 Python 資料科學與爬蟲所需套件 (但 Sympy 沒有) :




HTML 編輯器網址如下, 可用 CDN 方式匯入 Javascript 與 CSS 樣式 : 




2023年11月19日 星期日

2023 年第 46 周記事

11 月又匆匆過一半了, 年底的腳步漸近, 2023 年成長不少, 雖然學習速度慢, 目標達成率低, 但也算沒白過啦. 小舅最近來我家菜園較勤, 靠近豬舍又再搭了一段鐵架, 說是要種南瓜讓它們攀上去, 地上的草莓被清除甚多, 明年要把草莓垂直栽培提上最優先項目. 

本周繼續在 Hahow 學校企業版練功, 看完 Javascript 四個課程, WordPress 一個課程, 以及 App Inventor 入門與進階兩個課程, 收穫頗豐, 從中也得知一些有用的資源, 等有空複習實作時再整理到筆記裡. 自從 9/21 參加高雄 Python 社群 Django 分享會後, 回來便把 PyTorch 先擱下, 專心學習 Django, 希望年底前能全部收尾, 我從 Google App Engine 的 Django 1.18 學到 4 版已歷經 10 年都還沒學完, 這真的是太扯了. 

菁菁車禍腳傷漸漸恢復, 打算休息到 11/22 開始上班, 這兩周我中午都回家準備中餐, 其實也是怕她太無聊, 無聊到拉我看了好幾部韓劇, 本周正在看的是 "大力女子都奉順" (朴寶英主演) 與 "大力女子姜南順" (李瑜美主演), 好看. 

鄉下老家四隻貓 (小黑, 小花, 小白, 小灰) 其中的小黑周五起沒回家吃飯, 若一周沒回來可能就不會回來了. 這四隻貓跟我不親, 出生在車庫旁的雞舍, 不像之前的四兄弟從出生起就被我抓來抓去, 母貓在生下它們不到兩周就離家沒回來, 提早吃我買的貓乾糧長大. 爸懷疑會不會陷在對面之前的芭樂園挖掉後填土灌水的泥濘中, 我下午去尋一遍也沒看到. 

菜園水利工程紀要 (一)

最近小舅較有空來整理我家菜園, 荒廢一年後菜園突然生氣蓬勃, 但用水問題也變得急迫起來, 說來真是汗顏, 我的菜園自動給水系統已規劃超過三年, 管線早就配好卻遲遲無法完成控制系統, 明明手裡一堆太陽能板卻擱在庫房, 只能眼睜睜看著旁邊水圳潔淨的灌溉用水日夜奔流而去.

傍晚放棄爬山巡了一下屋後環境, 覺得要做也不是很難, 革命會成功通常來自於一時的衝動. 

水圳取水口位置在圍牆轉角底下 :



 


中繼的小型儲水桶放在圍牆上轉角與芒果樹幹交叉處 (需要一個板子與支撐架來放小儲水桶), 測量高度約 100 cm (與肩膀同高), 我的 12V 小型抽水機揚程約 1.5m 足以抽水上去 :




小太陽能板預計鎖在在二樓浴室外牆的延伸平台上, 打算用 18650 鋰電池組儲能供電給控制器 (Pi Zero  W + Pico) :




控制器的動作如下 : 
  • 當小儲水桶水位低於下限時啟動抽水, 高於上限時停止抽水.
  • 幫浦空轉超過 1 分鐘停止抽水. 
  • 可遠端設定夜間停止抽水.
  • 正常使用鋰電池組供電, 低於保護電壓時直流 ATS 自動切換至市電. 
首要之務是要先組裝 18650 電池組並進行充放電測試.

Windows 更新後產生的 Windows.old 資料夾問題

上個月將鄉下家的 LEMEL 老電腦成功地更新為 Win10 H22 後, 發現 C 碟幾乎快被占滿, 檢查發現是 C 跌下出現一個 Windows.old 資料夾, 容量居然高達 26GB :




這應該是系統更新時用來暫時存放舊版 Windows 系統資料的地方, 如果升級後反悔想退回舊系統就從這裡還原. 但這對我這台老電腦來說實在太佔空間了, 想刪除去發現動不了它, 爬文找到下面這篇, 提供了刪除此資料夾的方法 :  

如何正確刪除Windows.old資料夾?

但裡面提到在升級一個月後系統就會自動刪除此資料夾, 那就放著等系統自動刪除好了, 今天檢查 C 碟果然一個月後此 Windows.old 資料夾舊消失了.