# ♚尼腓工作室♚ 爬蟲 漢堡王GT 自動完成? ## 簡介 基於每月都要填寫相同問卷,究竟是否可以利用程式來完成呢?就讓我們來一探究竟吧~! 有任何建議可在下方留言給建議歐~ {%hackmd ajQJQroLSRKg7mugHyYLUw %} ## **免責申明** <font color=red>本文並非教學文章,只附上片段程式碼說明構思,僅做學術研究,無破壞問卷可靠性之意,若有違反請告知,將盡速移除。 </font><br> ## 什麼是爬蟲 ? 根據維基百科所述,網路爬蟲(英語:web crawler),也叫網路蜘蛛(spider),是一種用來自動瀏覽全球資訊網的網路機器人。 網路搜尋引擎等站點通過爬蟲軟體更新自身的網站內容或其對其他網站的索引。網路爬蟲可以將自己所存取的頁面儲存下來,以便搜尋引擎事後生成索引供使用者搜尋。 簡單來說,可以透過程式碼對網頁進行自動化操作,只是要填個簡單問卷而已,當然是沒問題的拉 ~! 我們將分次進行講解,無論是否學過程式,看完文章都能對網頁結構、爬蟲邏輯有初步的了解。 ## 影片展示 {%youtube 98YGjVFiAVA %} ## 開發環境 **編譯器: Visual Studio Code (VSC) 程式語言:Python3 爬蟲套件:Selenium、Jupyter、ChromeDriver** ## 目錄與構思 漢堡王GT問卷大致分為以下幾個部分, - [x] 市場模型: [市場模型]<font color=heart> ✓ 完成</font> - [x] 下拉式選單與表格輸入: [GT代碼與時間]<font color=heart> ✓ 完成</font> - [x] 隨機單選框點選: [點餐類型(內用、外帶)]<font color=heart> ✓ 完成</font> - [x] 固定單選框點選: [滿意度調查、是否遇到問題、是否推薦餐廳]<font color=heart> ✓ 完成</font> - [x] 隨機生成評價留言: [說明體驗感想]<font color=heart> ✓ 完成</font> - [x] 半固定多選框:[產品購買調查]<font color=heart> ✓ 完成</font> - [ ] 問卷有效化: [代理IP與裝置變換、擬人操作] <font color=red>✗ 建置中</font> ### 市場模型 既然花費時間製作,我們當然不可能讓機器人每個選項都亂填,這樣做的問卷毫無參考價值,這樣真的就是在破壞問卷可靠度,<font color=red>可根據各門市所在地、當月營運調整模型結構,讓問卷更接近真實狀況。</font><br> 可調整比例權重如下,權重說明:權重總和可不必等於100%, 舉例:投擲一枚不公平的硬幣80次正面、50次反面,其權重為(正面:80%反面:50%) ▼ 點餐類型 點餐預設權重(內用:50%,外帶:50%,送餐上門:0%) ![](https://i.imgur.com/MQNSexH.png) ▼ 客人人數統計 人數預設權重: (1人:40%, 2人:35%, 3人:15%, 4人以上:10%) <font color=red>PS: 人數與滿意度留言掛勾,幾個人就購買幾份餐點 (餐點不重複出現)</font><br> ![](https://i.imgur.com/PNQm1cj.png) ▼ 顧客性別 性別預設權重: (40%, 40%, 1%, 4%, 15%) ![](https://i.imgur.com/FlsdADd.png) ▼ 顧客性年齡 年齡預設權重: (10%, 20%, 30%, 20%, 10%, 10%) ![](https://i.imgur.com/ojlWIkn.png) ▼ 本月購買頻率 頻率預設權重: (1次:10%, 2次:20%, 3次:25%, 4次:20%, 5次:25%) ![](https://i.imgur.com/mx6s7be.png) ▼ 購買原因 原因預設權重: (1%,1%,1%,1%,1%,1%,1%,1%) <font color=red>預設權重相同</font><br> ![](https://i.imgur.com/2i9JkVW.png) ### 下拉式選單與表格輸入: [GT代碼與時間] **▼ GT問卷範例** ![](https://i.imgur.com/1tZciqi.png =30%x) **▼ 問卷網站日期格式 (12小時制)** ![](https://i.imgur.com/oSjZ0CL.png) 問卷單上有調查代碼、購買時間(24時制),調查代碼可直接使用,而購買時間與調查網站上時間格式不同,需進行轉換,程式碼如下。 #### 程式碼 日期格式轉換 ```python= def GetTimeData(TimeStr):#取得小時/分鐘/早晚 Dict = {} InputHour = int(TimeStr[0:2]) #取得小時 InputMinute = TimeStr[2:4] #取得分鐘 if InputHour > 12: #小時 > 12 Dict["InputMeridian"] = "PM" #將早晚設為 "PM" InputHour -= 12 #讓小時減去12 InputHour = "0"+ str(InputHour) else: #小時 < 12 Dict["InputMeridian"] = "AM" #將早晚設為 "AM" Dict["InputHour"] = str(InputHour) #設定小時 Dict["InputMinute"] = str(InputMinute) #設定分鐘 return Dict ``` #### 呼叫示範 ```python= #我們可透過呼叫時間轉換函式取得GT網頁網頁中所希望的格式,作為後續網頁輸入用 DataTime = GetTimeData("2015") DataTime["InputHour"] # 小時 = "08" DataTime["InputMinute"] # 分鐘 = "15" DataTime["InputMeridian"] # 早晚 = "PM" ``` ### 隨機單選框點選: [點餐類型(內用、外帶)] 台灣目前並無門市有送餐上門的服務,但網頁中有時會有送餐上門的選項,我們需要排除在外。只在內用、外帶中隨機挑選一個。 ▼ 點餐類型基本款 ![](https://i.imgur.com/YcbeyTD.png) ▼ 點餐類型特殊款 ![](https://i.imgur.com/MQNSexH.png) 利用隨機數隨機點選內用或外帶選項 ```python= def FNSS001000():#選擇內用或外帶 Typelist = ["外帶","內用"] #許可的方式 Dict = {} #爬出來的方式 try: element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, "rbList"))) Allclass = element.find_elements_by_tag_name("div") #找出現幾個div print(f"找到{len(Allclass)}個欄位") #判斷選項有幾個 for i in Allclass: radio = i.find_elements_by_tag_name("input") #button label = i.find_elements_by_tag_name("label") #中文選項 Type ,Num = label[0].text, str(int(radio[0].get_attribute("value"))-1) print(f'類型:{Type} , 編號:{Num}') Dict[Type] = Num ChooseType = random.choice(Typelist) print(f"選擇:{ChooseType},編號:{Dict[ChooseType]}") Button = driver.find_element_by_xpath(f'//*[@id="FNSR001000"]/div/div/div[{Dict[ChooseType]}]/span/span') Button.click() NextButton() except: print("Error 超時未找到欄位") ``` ### 固定單選框點選: [滿意度調查、是否遇到問題、是否推薦餐廳] 這個區塊中我們希望的回答均為固定的選項,但有時網頁會出現不規律的變動,可能增加按鈕選項,又或者是改變滿意度順序("非常滿意","非常不滿意") 的位置,那我們就不能使用固定的寫法,那我們該如何做呢? #### 樣本圖 - 1.滿意度調查_基本款 [**非常滿意**] <font color=chocolate>(5種選項)</font> ![](https://i.imgur.com/8Zh8lp9.png) ▼ 滿意度調查_基本款(網頁程式碼) <font color=chocolate>[**6個單選框**]</font> ![](https://i.imgur.com/8v0wxNh.png) - 1.滿意度調查_特例 <font color=red>[**非常滿意**]</font> <font color=chocolate>(6種選項)</font> ![](https://i.imgur.com/yNnw1IK.png) ▼ 滿意度調查_特例 (網頁程式碼) <font color=chocolate>[**6個單選框**]</font> ![](https://i.imgur.com/cBFgRKt.png) - 2.是否遇到問題? <font color=red>[**否**]</font> <font color=chocolate>(2種選項)</font> ![](https://i.imgur.com/s6goDFx.png) - 3.是否推薦餐廳 <font color=red>[**非常可能**]</font> <font color=chocolate>(5種選項)</font> ![](https://i.imgur.com/m92vshQ.png) #### 結論 經觀察,無論有幾個選項、題目、出現例外情形,程式碼中每列的單選框單選框(radio)數量均相同,只要找出案選項的種類、期望選項之結果、單選框總數,透過迴圈套用公式就可點選所有我們希望的選項。 舉例 : 有5個選項、3個題目、共15個單選框,假設我們希望點選全部 <font color=apple>選項3</font><br> 的選項 | | 選項1 | 選項2 |<font color=apple>選項3</font><br>| 選項4 | 選項5 | | ------| ------| ------|----------- | ---------------- | ------ | | 第一題 | 按鈕1 | 按鈕2 | <font color=heart>按鈕3</font><br> | 按鈕4 | 按鈕5 | | 第二題 | 按鈕6 | 按鈕7 | <font color=heart>按鈕8</font><br> |按鈕9 | 按鈕10 | | 第三題 | 按鈕11 | 按鈕12 | <font color=heart>按鈕13</font><br>|按鈕14</font><br> | 按鈕15 | 公式: 按鈕編號 % 按鈕總數 = 希望選項編號 (%取餘數) 說明: 當算出來的餘數等於希望的選項編號時就代表是我們希望點選的選項。 | 單選按鈕 |公式運算 | 比較 | 執行 | | :------------------------------ |:---------- | :------ | :-------------------------------- | | <font color=blue>按鈕2</font> | 2 % 5 = 2 | 2 != 3 |<font color=red>✗ 不動作</font> | | <font color=blue>按鈕3</font> | 3 % 5 = 3 | 3 = 3 | <font color=heart> ✓ 點選</font> | | <font color=blue>按鈕4</font> | 4 % 5 = 4 | 4 != 3 | <font color=red>✗ 不動作</font> | | <font color=blue>按鈕8</font> | 8 % 5 = 3 | 3 = 3 | <font color=heart> ✓ 點選</font> | | <font color=blue>按鈕13</font> | 13 % 5 = 3 | 3 = 3 | <font color=heart> ✓ 點選</font> | | <font color=blue>按鈕14</font> | 14 % 5 = 4 | 4 != 3 | <font color=red>✗ 不動作</font> | 按照這個公式無論有幾種選項,是否有調換滿意度順序,我們都可以點選到我們希望的選項。 #### 程式碼片段 ```python= def Satisfaction():#自動填寫表單(非常滿意,非常可能,否) try: element = driver.find_elements_by_tag_name("tbody") #找出表格 print(f"找到{len(element)}個表格") Allclass = driver.find_elements_by_tag_name("tr") #找出表格 print(f"找到{len(Allclass)}個欄位") for i in Allclass: Text = i.text #中文選項 Textlist = Text.split() #滿意度列表 Typelist = list(set(["非常滿意","非常可能","否"]) & set(Textlist)) #表單中的文字與我們想要的結果找交集 if len(Typelist)>0: #若有找到 Index = Textlist.index(Typelist[0]) #希望選項的位置 ButtonList = i.find_elements_by_xpath(f'//*[@role="radio"]') #取得網頁全部按鈕 print(f"{Typelist[0]}:位置[{Index}]") break #點選網頁中所有非常滿意的選項 for i in range(len(ButtonList)): #網頁中所有單選的迴圈 if i % len(Textlist) == Index: print(i) ButtonList[i].click() #點選按鈕 NextButton() except: print("Error 超時未找到欄位") ``` ### 隨機生成評價留言 ▼ GT問卷滿意原因 ![](https://i.imgur.com/VKFVKMd.png) 我們當然不可能事先寫好多篇文章,然後隨機挑選一篇,這樣不僅需花費大量時間構思,多提交幾次問卷時,會有許多一模一樣的留言顯得很不自然,那該如何做呢? 我們可將文字拆分品項、以及形容詞, 例:水果: [蘋果、葡萄、芭樂、蓮霧] ,形容詞: [香甜多汁、汁水飽滿、品項很好] 假設有4種水果與3個形容詞,這樣就能產生出 4 * 3 = 12 種結果。 利用這個方法套用在漢堡王上,我們可以分類為主餐、點心、飲品與最後的評價。 以下方程式碼為例 (內用且每種選項均出現1次) 可產生出 128(主餐) * 95(點心) * 70(飲品) * 8(評價) = 共 6,809,600 種留言。 若主餐、點心、飲品出現不只一個,可產生的留言將會有更多種類,可讓我們的留言更有多樣性。 #### 定義餐點品項(主餐、點心、飲品、評價) 程式碼片段 ```python= #最終評價 = 主餐 + 選填(甜品) +選填(飲料) + 評價 #主餐 = 主餐種類+連接詞 #主餐種類 = 17種主餐*5(通用)形容 +5主餐(火烤)*3(火烤)形容 + 5主餐(炸物)*2(炸物)形容 + 9主餐(起司)*2(起司)形容 #主餐組合 = 128種結果 MainMeal_Fire = ["火烤牛肉堡","小華堡","華堡","辣味華堡","火烤雞腿堡"] #主餐燒烤 MainMeal_Fried = ["總匯辣腿堡","鱈魚堡","田園華鱈魚堡","小怪獸香豬脆雞","BK黃金詐雞"] #主餐炸物 MainMeal_Cheese = ["重磅培根牛肉堡","重磅培根辣雞堡","安格斯牛堡","花生安格斯",\ "勁濃安格斯","犇牛堡","勁濃培根烤腿堡","鱈魚堡","田園華鱈魚堡"] #主餐有起司 MainMeal_ALL_Description = ["份量十足","味道很棒","很滿足","很好吃","配料搭配剛好","用料新鮮"] #主餐(通用)形同詞 MainMeal_Fire_Description = ["肉汁很多","肉汁十足","口感獨特"] #主餐(火烤)形容 MainMeal_Fried_Description = ["口感酥脆","酥脆可口"] #主餐(炸物)形容 MainMeal_Cheese_Description = ["起司濃郁","起司香濃"] #主餐(起司)形容 #點心 = 點心種類 +點心種類連接詞 #點心組合 = 14種點心*3(通用)形容+ 10點心(炸物)*4(炸物)形容+4點心(冰品)*3(冰品)形容 #點心組合 = 95種組合 Dessert_Fried = ["水牛城辣雞翅","水牛城辣味薯條","水牛城辣雞塊","BK黃金雞翅",\ "V型薯","洋蔥圈","勁濃起司薯","辣薯球","BK雞塊","薯條"] #點心(炸物) Dessert_Ice = ["巧克力聖代","草莓聖代","濃縮咖啡聖代","冰淇淋"] #點心(冰品) Dessert_ALL_Description = ["很美味","很好吃","物超所值"] #點心(通用)形容 Dessert_Fried_Description = ["酥脆可口","口感酥脆","鹹香","味道很好"] #點心(炸物)形容 Dessert_Ice_Description = ["香甜可口","做的很用心","甜香四溢"] #點心(冰品)形容 #飲品 = 甜品種類+甜品種類連接詞 #飲品組合 = 15種飲品*2通用形容 + 6種咖啡*4咖啡形容 + 3種茶*2茶類形容 + 5種汽水*2汽水形容 #飲品組合 = 70種組合 Drink_Coffee = ["熱咖啡","卡布奇諾","拿鐵","冰拿鐵","美式研磨","冰美式研磨"] Drink_Tea = ["熱紅茶","檸檬紅茶","無糖綠茶"] Drink_Soda = ["激浪","百事","百事Light","七喜","華年達"] Drink_Juice = ["柳橙汁"] Drink_All_Description = ["很好喝","和餐點很搭"] Drink_Tea_Description = ["茶味濃厚","很清爽"] #從未喝過,不清楚 Drink_Coffee_Description = ["很有質感","口感平順","口感溫和","口感柔潤"] #從未喝過,不清楚 Drink_Juice_Description = ["酸爽","充滿果粒"] Drink_Soda_Description = ["氣泡充足","氣泡感十足"] #評價 #通用2 * 內用 5 #評價組合 = 10 MainMeal_Comments_ALL =["用餐體驗良好","令人回味無窮"] #通用評價 MainMeal_Comments_Here =["店員親切很用心服務","店員很細心待人親切","店員很有耐心","服務超讚的","客席區域很乾淨",] #內用評價 ``` #### 呼叫組合 程式碼片段 ```python= def RandomStr(X:list,All_Describe:list,Describe:list):#產生隨機字串 Msg = "" #隨機取1~4個餐點 Num = random.choices([1,2,3,4],[40,35,15,10])[0] # X餐點數量,權重 New_X,New_All_Describe,New_Describe = copy.deepcopy(X),copy.deepcopy(All_Describe),copy.deepcopy(Describe) for X_Num in range(Num): #篩選餐點不重複 Safety_X = [] #餐點類別暫存 X_len_0 = [len(i) for i in New_X] # 取得類別餐點數量 for i in range(len(X_len_0)): if X_len_0[i] > 0 : # 若數量大於零 Safety_X.append(i) # 加入到站存中 X_Type = random.randint(0,len(Safety_X)-1) # X餐點的分類 X_Name = random.choice(New_X[X_Type]) # X餐點名稱 New_X[X_Type].remove(X_Name) if random.random() >0.5 : #使用通用形容詞或專用形容詞 X_Describe = random.choice(New_Describe[X_Type]) else : X_Describe = random.choice(New_All_Describe) Symbol = "、" if X_Num < Num-1 else "," #判斷結尾頓號或逗號 Msg += X_Name + X_Describe + Symbol return Msg #測試1 :重磅培根辣雞堡份量十足、華堡口感獨特,濃縮咖啡聖代很好吃、BK雞塊口感酥脆、洋蔥圈物超所值,無糖綠茶口感柔潤,服務超讚的令人回味無窮。 #測試2 :華堡口感獨特、鱈魚堡口感酥脆,水牛城辣味薯條鹹香、BK黃金雞翅物超所值,百事Light氣泡充足、檸檬紅茶口感溫和,店員很細心待人親切用餐體驗良好。 #測試3 :鱈魚堡味道很棒,BK黃金雞翅酥脆可口、巧克力聖代很美味,拿鐵和餐點很搭、柳橙汁酸爽、無糖綠茶和餐點很搭、百事很好喝,客席區很乾淨用餐體驗良好。 #測試4 :小華堡用料新鮮、辣味華堡用料新鮮,冰淇淋做的很用心,無糖綠茶很好喝、激浪氣泡感十足,服務超讚的令人回味無窮。 #測試5 :安格斯牛堡起司香濃、總匯辣腿堡很滿足、田園華鱈魚堡起司濃郁、火烤雞腿堡肉汁很多,草莓聖代很美味,華年達氣泡感十足,店員親切很用心服務令人回味無窮。 #測試6 :辣味華堡味道很棒、火烤牛肉堡配料搭配協調,濃縮咖啡聖代很美味、草莓聖代做的很用心、巧克力聖代物超所值,熱咖啡和餐點很搭,店員親切很用心服務用餐體驗良好。 ``` ### 半固定多選框 我們在 "隨機生成評價留言"時會從菜單中隨機挑選1~4樣主餐,我們可以按照當時的選擇來勾選表單,做出一份前後相符的問卷。 ▼ 購買產品 ![](https://i.imgur.com/x7qkamI.png) ### 代理IP與裝置變換 相同的IP位置或裝置重複填寫問卷會被系統排除,因此需要使用代理伺服器當作跳板 使問卷接受度提高。 更換IP: [代理IP](https://blog.csdn.net/ExcaliburUlimited/article/details/105873424) 選單: [選單](https://zhuanlan.zhihu.com/p/111995295)