# PTT Chatbot Notes > [name=謝朋諺 (Adam Hsieh)] > [time=Wed, Jan 2, 2019] ###### tags: `NLP` --- ## Scrapy > [Scrapy 官方文件](https://scrapy-chs.readthedocs.io/zh_CN/0.24/intro/tutorial.html) ### Scrapy PTT Python3 編碼問題 ```shell= export PYTHONIOENCODING=utf-8 ``` :::danger :fire: 若編碼不同請暫時轉為 utf-8 ::: ### 開啟爬蟲專案 ```shell= scrapy startproject [爬蟲專案名稱] ``` ### Scrapy 目錄架構 #### 以 PTT_detail 為例: ```graphviz digraph ptt_detail { pf1[label="PTT_detail"] pf2[label="PTT_detail"] pf1->pf2 pf2->{items[label="items.py" fontcolor=RED] spiders middlewares[label="middlewares.py"] pipelines[label="pipelines.py"] settings[label="settings.py" fontcolor=RED]} spiders->{ptt_detail[label="ptt_detail.py" fontcolor=RED]} } ``` * ==ptt_detail.py== 為主要需要自己編輯的檔案,繼承自 scrapy.Spider * **allowed_domains:** 可選。包含了 spider 允許爬取的域名 (domain) 列表 (list) 。 * **start_urls:** URL 列表。當沒有製定特定的 URL 時,spider 將從該列表中開始進行爬取。因此,第一個被獲取到的頁面的URL將是該列表之一。後續的URL將會從獲取到的數據中提取。(但我平常習慣自己做一個 urls 列表,在 parse 裡面用 for 迴圈跑,這樣才能較彈性的使用 url 處理) * **parse:** 當 response 沒有指定回調函數時,該方法是 Scrapy 處理下載的response的默認方法。parse 負責處理response並返回處理的數據以及(/或)跟進的 URL。 Spider 對其他的Request 的回調函數也有相同的要求。該方法及其他的 Request 回調函數必須返回一個包含 Request 及(或) Item 的可迭代的對象。參數: response (Response) – 用於分析的response :::info :cookie: **cookies:** 用來回覆已滿 18 歲的問答。 ::: * ==items.py== 為爬蟲後建立字典的欄位,但我沒用到,主要可用建立方法比如: * ```python= title = scrapy.Field() author = scrapy.Field() date = scrapy.Field() push = scrapy.Field() url = scrapy.Field() ``` 並在 ptt_detail.py 裡寫下要抓的目標,類似於 dictionary 寫法: * ```python= item['title'] = tag.css("div.title a::text")[0].extract() item['author'] = tag.css('div.author::text')[0].extract() item['date'] = tag.css('div.date::text')[0].extract() item['push'] = tag.css('span::text')[0].extract() item['url'] = tag.css('div.title a::attr(href)')[0].extract() ``` ### 執行指令 ```shell= scrapy crawl [爬蟲專案名稱] ``` ### 內建輸出指令(但需要 items.py 已建立) ```shell= scrapy crawl [爬蟲專案名稱] -o output.csv ``` ## Pymysql ### 連接資料庫 ```python= conn = pymysql.connect( user="使用者帳號", password="密碼", host="資料庫IP", database='資料庫名稱', charset="編碼", port=埠號 ) ``` ### 查找資料 ```python= # 使用 cursor() 方法創建一個游標對象 cursor cursor = conn.cursor() # 使用 execute() 方法執行 SQL 查詢 cursor.execute("SELECT * FROM [資料表名稱] WHERE [欄位]=[條件];") # 使用 fetchone() 方法獲取單條結果 data = cursor.fetchone() # 使用 execute() 方法執行 SQL 查詢 cursor.execute("SELECT * FROM [資料表名稱];") # 使用 fetchall() 方法獲取全部結果 datas = cursor.fetchall() # 關閉資料庫連接 conn.close() ``` ### 輸入資料進資料庫 ```python= # 使用 cursor() 方法創建一個游標對象 cursor cursor = conn.cursor() # 執行 sql 語句 cursor.execute("INSERT INTO [資料表名稱] ([欄位一], [欄位二], [欄位三]) VALUES ('值一', '值二', '值三');") # 提交到資料庫執行 conn.commit() ``` :::danger :speech_balloon: 若資料表的儲存引擎為 Innodb 一定要有 commit() 這行才會執行 ::: ### 資料庫架構 ![database](https://i.imgur.com/qnm9MBq.png) * ==ptt_list==: 下載用的資料表,主要是存取之後要抓取的 url 跟備份所用。 * ==ptt_detail==: 存取 PTT 內文的重要資訊。 * ptt_detail_id: 主要鍵,會給 ptt_push 當作外來鍵,有新的資料進來便會自動往上加 1。 * ptt_title: PTT 的標題。 * ptt_author: PTT 發文作者,包含帳號跟暱稱。 * ptt_datetime: PTT 發文時間,直接以字串存入。 * ptt_board: 發文在 PTT 哪個版上。 * ptt_content: 發文內容。 * ==ptt_push==: 存取 PTT 的留言資訊。 * ptt_push_id: PTT 留言的主要鍵,有新的資料進來會自動加一。 * ptt_detail_id: 對 ptt_detail 的 table 做外來鍵,如果 ptt_detail 的某筆資料刪除,留言應該也一併要被處理。 * ptt_push_tag: 留言的標記,有分為三種:推、噓、→。 * ptt_push_author: 留言的作者。 * ptt_push_content: 留言內容。 * ptt_push_datetime: 留言時間,直接以字串存入。 ## jieba 中文斷詞 > [jieba 官方 github](https://github.com/fxsjy/jieba) ### 字典 * [臺灣常用疊詞表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d2ef68d766f396ac155b497f747e8a8d694ce0a122acc6753a9f69d4c0d6e6c7ad81a7f6e47e65a2c5) * [臺灣常用成語表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d267c3cabf7b902c9e7caa0391ffe5e8d5da944c3a677fc5aa00ff10e8eaf399cafbf4fc1477b68d9a) * [常用題辭表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d2fa306ccf3d60c07e33549662d90f16d37af4d3f6139884e3714c4176a8adbe4fa87dd2f504dad847) * [臺灣常用中文縮語表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d21cc805eff492a54cde97723e7765b38bcc21407d9462af5c9bfce833dadb2645aea8d1c48aaf5959) * [外國品牌中譯對照表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d2f2ffd717eea130d4b45156bb9fd3585b2662abf377e7d91dfcaa5b34c366f2989e6dd9fef5bb7894) * [外國國名中譯對照表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d2e55ee3dd0dbc99392e497ce06ded17fc8b73ede6c23767247da989beac2568929c3ae0bbecc51f31) * [外國首都名中譯對照表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d226614ca204a0e8501ba6615f6445ec5376afd457f899b90e710c014aedf26b6401d3d77bc7beb941) * [臺灣特有詞詞表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d2f32cd164514429447e4666bef0bae3e23be7ecce2466273806fcd37ee6dc03e5a8a19c98e431bdbd) * [兩岸餐飲類詞語對照表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d27c1c9f193332787db3b03573a90a90f03c68b0513cc5b7c74b6711fe9fec8650235c0efa393eea4e) * [兩岸交通類詞語對照表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d2b87e223970f0bd6eec2e112bb1b770c438fa41c9b01f9e92dd0850c95b023da4c864cf122e01d7a5) * [兩岸資訊類詞語對照表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d25c44b2b51320a8f7a1c3d0282ec94cc1657623756003744bd24f4317e0acbcb074f49dbf4f85d5fa) * [兩岸旅遊類詞語對照表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d211a5e2a9d9949fbb90a4394b77afeccbb067a6813a6b05f10b0391d085cad6d48a0eb02c446c5026) * [兩岸生活類詞語對照表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d239a6f9a89db718fc768ff3b10659decfac8e91a7107fe5df4e891da827d22f8ed47dd52bd89e3018) * [兩岸地名古今對照表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d2b85bd1d86a7acc917719aab62c0374efdbb0e6d57fa0a39417c86576d622110294cf93b0e246036e) * [臺灣夜市常見小吃中英對照表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d24a6b8a9b0b868307184d005f720361a58c009e1453730b357fc5f9785d6f2175e44995c12e0529c5) * [臺灣主要節慶中英對照表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d20606ef70d55c5dc584042aef9d9093bc9ce4ff53c9a24eded962ef2bbd821df48467665d8d20a41a) * [常見職稱中英對照表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d2f27363ea16441705b564c0c7c1433e0c65e7b613517f33f3c5a19aa4c492f0c00ff61da8a25efe9a) * [親屬稱謂中英對照表](http://data.chinese-linguipedia.org/content/application/datapedia/metadata/guest-cnt-browse.php?vars=7f8ee7ff939cad426ba349a01baca255f625365a8ae284d2a49854c70d6b098913330fb489952c85b546cceaedd854ba4fe0df26c16697136b4e24dabcf07d25) * [中文維基百科標題](https://dumps.wikimedia.org/zhwiki/) :::warning :speech_balloon: 中文維基百科標題只選擇3~7個字的進入字典檔,由於中英文混雜情況嚴重,以及各種符號型單詞,因此有一個小小判定若第一個 word 在 unicode 中非 u'\u4e00 ~ u'\u9fff 將使之略過 ::: 以上是自己新增的字典檔,字典量有:**1,080,615** 外加上中央研究院資訊科學所詞庫小組的詞庫 dict_zh.txt 大概有 **308,431** 數量 全部字典檔合起來大概有 **14,142,679** 個詞量 ### 結巴使用方法 ```python= # 引入 jieba,如果是 github 上載下來的,請記得放在同目錄下 import jieba # 設定內建的字典檔 jieba.set_dictionary('jieba_folder/extra_dict/dict_zh.txt') # 加入自訂的字典檔 jieba.load_userdict("jieba_folder/extra_dict/dict_new.txt") # 使用多個執行緒,在 apple 上建議只用 4 個 jieba.enable_parallel(4) # 開始斷詞,cut_all 為 False 代表精確模式,True 為全模式 sentence_jieba = jieba.cut(sentence, cut_all=False) # 將斷詞結果以空格合併為字串 sentence_result = ' '.join(sentence_jieba) ``` ## Word2vec > [gensim 官網](https://radimrehurek.com/gensim/models/word2vec.html#module-gensim.models.word2vec) ### 訓練 | Parameter|size|window size| sg|alpha|min_count|workers|iter| |-----------:|---:|----------:|---:|----:|--------:|------:|---:| |**Default**| 100| 5|CBOW|0.025| 5| 3| 5| |**Setting**| 300| 5|CBOW|0.025| 5| 3| 5| 上面是 Word2Vec 訓練函式中最主要的參數,本專案只更改了 Vector Size * ==size==: 向量的維度 * ==window size==: 句子中預測和當前 word 之間最遠距離 * ==sg==: 1 代表演算法設為 skip-gram,0 則是 CBOW * ==alpha==: 初始的 learning rate * ==min_count==: 此單詞如果出現次數低於此值,將會不列入計算 * ==workers==: 用多少的執行緒執行 * ==iter==: 迭代次數 :::info :fire::speech_balloon: 依照 Tomas Mikolov 的論文中提到 CBOW 模型會有較好的 syntactic accuary,而 Skip-gram 模型會有較好的 semantic accuary;而 TensorFlow 的平台上提到 CBOW 模型在小資料上就可有好的表現,但 Skip-gram 模型必須在較大的資料集才會有好的表現 ::: ```python= from gensim.models import word2vec, Word2Vec # sentences 資料結構為 # [['歐幾裡', '得'], # ['西元', '前三世紀', '的', '古希臘數學', '家'], # ['現在', '被', '認為', '是', '幾何', '之父']] # 已被斷詞切開為 list # 開始訓練 word2model = word2vec.Word2Vec(sentences, size=300) # 保存模型,以便重用 word2model.save("corpus_wiki_ptt_push.model") # 載入模型 word2model = Word2Vec.load("corpus_wiki_ptt_push.model") ``` ### 實際應用 輸出相近詞 ```python= # 找出相近詞,預設前十名,可設置 topn 即可設定輸出前幾名 print(word2model.wv.similar_by_word('小英')) print(word2model.wv.most_similar('館長')) ``` 結果 ``` [('蔡英文', 0.8838791847229004), ('空心菜', 0.781982421875), ('賴清德', 0.7640219926834106), ('蔡', 0.7350651025772095), ('蔡總統', 0.7305333018302917), ('柯P', 0.7296426296234131), ('馬英九', 0.7275056838989258), ('dpp', 0.7234020233154297), ('DPP', 0.7211335897445679), ('蔡政府', 0.712647557258606)] [('陳之漢', 0.6845790147781372), ('三斤', 0.6147425770759583), ('柯P', 0.6084757447242737), ('統神', 0.5917705297470093), ('呱吉', 0.5852810144424438), ('柯文哲', 0.5807035565376282), ('KKC', 0.5691466927528381), ('陳沂', 0.5669381022453308), ('段宜康', 0.5649300813674927), ('王世堅', 0.5588330030441284)] ``` 比對句子相似程度 ```python= str1 = '今天天氣真好' str2 = '今天天氣一直下雨好焦躁' str3 = '今天天氣真糟' str1 = jieba.cut(str1, cut_all=False) str1 = ' '.join(str1) str2 = jieba.cut(str2, cut_all=False) str2 = ' '.join(str2) str3 = jieba.cut(str3, cut_all=False) str3 = ' '.join(str3) print('str1: ', str1) print('str2: ', str2) print('str3: ', str3) print() print('str1 跟 str2 的距離: ', word2model.wv.wmdistance(str1,str2)) print('str1 跟 str3 的距離: ', word2model.wv.wmdistance(str1,str3)) print('str2 跟 str3 的距離: ', word2model.wv.wmdistance(str2,str3)) print() print('str1 跟 str2 的 cosine similarity: ', word2model.wv.n_similarity(str1.split(), str2.split())) print('str1 跟 str3 的 cosine similarity: ', word2model.wv.n_similarity(str1.split(), str3.split())) print('str2 跟 str3 的 cosine similarity: ', word2model.wv.n_similarity(str2.split(), str3.split())) ``` 結果 ``` str1: 今天 天氣 真好 str2: 今天 天氣 一直 下雨 好 焦躁 str3: 今天 天氣 真糟 str1 跟 str2 的距離: 11.43087864355426 str1 跟 str3 的距離: 3.415383523379702 str2 跟 str3 的距離: 12.891501548419308 str1 跟 str2 的 cosine similarity: 0.821362 str1 跟 str3 的 cosine similarity: 0.899028 str2 跟 str3 的 cosine similarity: 0.832933 ``` ## QA PAIR > [Pycon2017 chatbot 鄉民聊天機器人](https://www.slideshare.net/RyanChao3/pycon2017-chatbot) 引用連結內作者的做法,透過比對 PTT 標題跟留言的相似程度,超過閾值者當作訓練資料 QA Pair,他們比對方法使用的是 BM25 跟 POS-TAG,本專案則使用 wmdistance 的函式去預測兩句的距離。 ```python= ptt_pairs_re = list() for i, ptt_pair in enumerate(ptt_pairs_li): try: str1 = jieba.cut(ptt_pair[0], cut_all=False) str1 = ' '.join(str1) str2 = jieba.cut(ptt_pair[1], cut_all=False) str2 = ' '.join(str2) if word2model.wv.wmdistance(str1,str2) < 20: # print(ptt_pair, word2model.wv.wmdistance(str1,str2)) ptt_pairs_re.append(ptt_pair) if i % 10000 == 0: print('已跑至: ', i, 'QA Pair 已收錄: ', len(ptt_pairs_re)) except Exception as e: print(e) ``` 待 word2vec 訓練完畢,將字串比對的句子透過結巴斷詞完後經過 wmdistance 可比對兩組字串之間的距離相似程度 ## Training ``` __________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== Encoder_Input (InputLayer) (None, None) 0 __________________________________________________________________________________________________ Decoder_Input (InputLayer) (None, None) 0 __________________________________________________________________________________________________ Encoder_Embedding (Embedding) (None, None, 300) 193872300 Encoder_Input[0][0] __________________________________________________________________________________________________ Decoder_Embedding (Embedding) (None, None, 300) 193872300 Decoder_Input[0][0] __________________________________________________________________________________________________ Encoder_LSTM (LSTM) [(None, 300), (None, 721200 Encoder_Embedding[0][0] __________________________________________________________________________________________________ Decoder_LSTM (LSTM) (None, None, 300) 721200 Decoder_Embedding[0][0] Encoder_LSTM[0][1] Encoder_LSTM[0][2] __________________________________________________________________________________________________ Decoder_Output (Dense) (None, None, 646241) 194518541 Decoder_LSTM[0][0] ================================================================================================== Total params: 583,705,541 Trainable params: 195,960,941 Non-trainable params: 387,744,600 __________________________________________________________________________________________________ Train on 3759949 samples, validate on 417773 samples ``` 646,241 的字典檔導致只能跑 4 batch size 且一個 epoch 258 小時的訓練,雖然 Loss 經過 18 小時就降到 0.3018