PhilipL commited on
Commit
ddeadeb
·
verified ·
1 Parent(s): 34d3b51

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +47 -37
app.py CHANGED
@@ -9,10 +9,7 @@ import subprocess
9
  os.environ["PLAYWRIGHT_BROWSERS_PATH"] = "/home/user/.cache/ms-playwright"
10
 
11
  try:
12
- # 如果目錄不存在,才執行下載,節省啟動時間
13
  if not os.path.exists(os.environ["PLAYWRIGHT_BROWSERS_PATH"]):
14
- print("正在為 Debian Trixie 環境安裝 Chromium...")
15
- # 僅安裝 chromium 即可,依賴項由 packages.txt 處理
16
  subprocess.run(["python3", "-m", "playwright", "install", "chromium"], check=True)
17
  except Exception as e:
18
  print(f"Playwright 初始化提示: {e}")
@@ -83,57 +80,64 @@ def sync_google_maps(url):
83
  yield "🚀 [1/4] 啟動瀏覽器並準備抓取實景圖..."
84
  with sync_playwright() as p:
85
  browser = p.chromium.launch(headless=True)
86
- page = browser.new_page()
 
 
87
  page.goto(clean_url, wait_until="domcontentloaded", timeout=60000)
88
- time.sleep(10) # 等待圖片加載
 
89
 
90
- yield "📄 [2/4] 正在解析餐廳資訊與圖片..."
91
-
92
- # 定位所有的餐廳卡片
93
- items = page.locator('div[role="article"]').all()
94
 
95
  restaurant_list = []
96
- for item in items:
97
  try:
98
- # 抓取店名
99
- name_el = item.locator('div.fontHeadlineSmall')
100
- if name_el.count() == 0: continue
101
- name = name_el.inner_text().strip()
102
 
103
- # 抓取地址/描述
104
- detail_el = item.locator('div.fontBodyMedium').first
105
- addr = detail_el.inner_text().strip() if detail_el.count() > 0 else "位置詳見地圖"
106
 
107
- # ✅ 抓取圖片網址 (Google Maps 的縮圖通常在 img 標籤中)
108
- img_el = item.locator('img').first
109
- img_url = None
110
- if img_el.count() > 0:
111
- img_url = img_el.get_attribute('src')
112
 
113
- # 如果圖片是 Data URL (base64) 或太小,通常代表沒載入成功,可以設個預設圖
114
- if not img_url or img_url.startswith('data:'):
115
- img_url = "https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?q=80&w=400" # 預設餐廳美圖
 
 
 
 
 
116
 
117
  restaurant_list.append({
118
  "Name": name,
119
  "Address": addr,
120
- "Image_URL": img_url, # ✅ 新增欄位
121
- "RAG_Content": f"餐廳:{name},資訊:{addr}",
122
  "URL": f"https://www.google.com/search?q={urllib.parse.quote(name)}"
123
  })
124
- except:
125
- continue
126
-
127
  browser.close()
128
 
129
  if not restaurant_list:
130
- yield "⚠️ 找不到餐廳資訊,請確認連結格式。"
131
  return
132
 
133
  # 更新向量資料庫
134
  global_df = pd.DataFrame(restaurant_list)
135
  embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
136
- vectorstore = FAISS.from_texts(global_df['RAG_Content'].tolist(), embeddings, metadatas=global_df.to_dict('records'))
 
 
 
137
  global_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
138
  rag_initialized = True
139
  yield f"✅ 同步成功!已載入 {len(restaurant_list)} 間包含實景圖的餐廳。"
@@ -147,6 +151,9 @@ def get_restaurant_data(mood_score_str, food_choice):
147
  init_static_data()
148
  if global_df is None or global_df.empty:
149
  return None, True, "資料庫未載入", "尚未同步地圖資料", ""
 
 
 
150
 
151
  # 取得目前資料庫中所有餐廳的名稱清單,方便回報給使用者
152
  all_restaurant_names = "、".join(global_df['Name'].tolist())
@@ -200,14 +207,18 @@ def mood_agent_logic(score_input, food_input, diary_input, debug_mode):
200
  # 取得推薦資料
201
  restaurant, is_match, rec_categories, mood_reason, all_names = get_restaurant_data(score_input, food_input)
202
 
 
203
  prefix = ""
 
204
  if not is_match and food_input != "隨便":
205
- prefix = f"> 💡 **溫馨提示**:名單中沒找到『{food_input}』,目前清單有:**{all_names}**。為您挑選了這一間:\n\n"
 
206
 
207
  name = restaurant['Name']
208
  url = restaurant.get('URL', '#')
209
- real_img_url = restaurant.get('Image_URL') # ✅ 取得抓到的真實圖片
210
- rag_info = str(restaurant.get('RAG_Content', ''))
 
211
 
212
  # 產生 AI 回應
213
  ai_text = generate_content_with_groq(name, rag_info, diary_input, score_input, mood_reason)
@@ -215,8 +226,7 @@ def mood_agent_logic(score_input, food_input, diary_input, debug_mode):
215
  final_response = f"{prefix}### 🍽️ 推薦:{name}\n\n{ai_text}"
216
  map_html = f'<div style="text-align:center"><a href="{url}" target="_blank" style="background:#4CAF50;color:white;padding:8px 16px;border-radius:20px;text-decoration:none">🗺️ 開啟 Google Map 導航</a></div>'
217
 
218
- # ✅ 直接回傳圖片網址給 Gradio 的 image_output
219
- # 注意:Gradio 的 gr.Image 可以直接接收 URL 字串
220
  yield final_response, real_img_url, map_html
221
 
222
  # ==========================================
 
9
  os.environ["PLAYWRIGHT_BROWSERS_PATH"] = "/home/user/.cache/ms-playwright"
10
 
11
  try:
 
12
  if not os.path.exists(os.environ["PLAYWRIGHT_BROWSERS_PATH"]):
 
 
13
  subprocess.run(["python3", "-m", "playwright", "install", "chromium"], check=True)
14
  except Exception as e:
15
  print(f"Playwright 初始化提示: {e}")
 
80
  yield "🚀 [1/4] 啟動瀏覽器並準備抓取實景圖..."
81
  with sync_playwright() as p:
82
  browser = p.chromium.launch(headless=True)
83
+ context = browser.new_context(user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
84
+ page = context.new_page()
85
+ yield "🌐 [2/4] 連線中..."
86
  page.goto(clean_url, wait_until="domcontentloaded", timeout=60000)
87
+ yield "⏳ [3/4] 等待網頁內容渲染 (10秒)..."
88
+ time.sleep(10)
89
 
90
+ yield "📄 [4/4] 正在解析餐廳資訊與圖片網址..."
91
+ # 【關鍵修改】改用名稱作為基準,再去找對應的圖片,避免因為 role="article" 抓不到
92
+ name_elements = page.locator('div.fontHeadlineSmall').all()
 
93
 
94
  restaurant_list = []
95
+ for el in name_elements:
96
  try:
97
+ name = el.inner_text().strip()
98
+ if not name: continue
 
 
99
 
100
+ # 尋找同一層級或父層級中的圖片與地址
101
+ # Google Maps 的結構通常是名稱在一個 div,圖片在旁邊的 div
102
+ container = page.locator(f"//div[contains(., '{name}')]/ancestor::div[@role='article']").first
103
 
104
+ # 抓取地址
105
+ addr = "位置詳見地圖"
106
+ addr_el = container.locator('div.fontBodyMedium').first
107
+ if addr_el.count() > 0:
108
+ addr = addr_el.inner_text().strip()
109
 
110
+ # 【新增】抓取實景圖 URL
111
+ img_url = "https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?q=80&w=400" # 預設圖
112
+ img_el = container.locator('img').first
113
+ if img_el.count() > 0:
114
+ src = img_el.get_attribute('src')
115
+ if src and not src.startswith('data:'):
116
+ # 將縮圖網址改為較大尺寸
117
+ img_url = src.replace('=w80-h80', '=w400-h400').split('=')[0] + '=w400-h400'
118
 
119
  restaurant_list.append({
120
  "Name": name,
121
  "Address": addr,
122
+ "Image_URL": img_url, # 【新增】存入圖片網址
123
+ "RAG_Content": f"餐廳名稱:{name},地址:{addr}",
124
  "URL": f"https://www.google.com/search?q={urllib.parse.quote(name)}"
125
  })
126
+ except: continue
127
+
 
128
  browser.close()
129
 
130
  if not restaurant_list:
131
+ yield "⚠️ 找不到餐廳資訊。請確認:1.連結是『分享清單』產生的 2.清單已設為公開。"
132
  return
133
 
134
  # 更新向量資料庫
135
  global_df = pd.DataFrame(restaurant_list)
136
  embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
137
+ texts = global_df['RAG_Content'].tolist()
138
+ metadatas = global_df.to_dict('records')
139
+
140
+ vectorstore = FAISS.from_texts(texts, embeddings, metadatas=metadatas)
141
  global_retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
142
  rag_initialized = True
143
  yield f"✅ 同步成功!已載入 {len(restaurant_list)} 間包含實景圖的餐廳。"
 
151
  init_static_data()
152
  if global_df is None or global_df.empty:
153
  return None, True, "資料庫未載入", "尚未同步地圖資料", ""
154
+
155
+ # 【新增】列出目前資料庫中所有餐廳名稱
156
+ all_names = "、".join(global_df['Name'].tolist())
157
 
158
  # 取得目前資料庫中所有餐廳的名稱清單,方便回報給使用者
159
  all_restaurant_names = "、".join(global_df['Name'].tolist())
 
207
  # 取得推薦資料
208
  restaurant, is_match, rec_categories, mood_reason, all_names = get_restaurant_data(score_input, food_input)
209
 
210
+ # 【新增】處理「找不到符合要求」時的告知訊息
211
  prefix = ""
212
+ status_hint = ""
213
  if not is_match and food_input != "隨便":
214
+ prefix = f"> 💡 **小提醒**:在您的清單中沒找到『{food_input}』相關的店,目前名單有:**{all_names}**。所以我先推薦這間給您:\n\n"
215
+ status_hint = f"\n(注意:使用者的清單中無 '{food_input}',請幽默解釋為何這間也值得一試。)"
216
 
217
  name = restaurant['Name']
218
  url = restaurant.get('URL', '#')
219
+ # 【修改】使用抓取到的真實圖片 URL
220
+ real_img_url = restaurant.get('Image_URL')
221
+ rag_info = str(restaurant.get('RAG_Content', '')) + status_hint
222
 
223
  # 產生 AI 回應
224
  ai_text = generate_content_with_groq(name, rag_info, diary_input, score_input, mood_reason)
 
226
  final_response = f"{prefix}### 🍽️ 推薦:{name}\n\n{ai_text}"
227
  map_html = f'<div style="text-align:center"><a href="{url}" target="_blank" style="background:#4CAF50;color:white;padding:8px 16px;border-radius:20px;text-decoration:none">🗺️ 開啟 Google Map 導航</a></div>'
228
 
229
+ # 【修改】直接回傳實景圖網址給 Gradio
 
230
  yield final_response, real_img_url, map_html
231
 
232
  # ==========================================