tbdavid2019 commited on
Commit
0b75bed
·
1 Parent(s): 8260f15

提示地址可留白

Browse files
Files changed (3) hide show
  1. README.md +9 -6
  2. app.py +1 -1
  3. app3.py +0 -255
README.md CHANGED
@@ -25,7 +25,7 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
25
  > 3. 本專案僅供研究教學之用,請勿用於商業或非法目的。
26
 
27
  ## 功能
28
- - 以 GPS 座標或地址(目前僅示意)搜尋附近的 7-11 / 全家門市。
29
  - 可自訂搜尋範圍(3 / 5 / 7 / 13 / 21 公里)。
30
  - 顯示每間門市的即期食品清單與剩餘數量。
31
 
@@ -33,7 +33,7 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
33
  1. 安裝 Python 3.8+ 及套件:
34
  ```bash
35
  pip install gradio requests pandas geopy
36
- ```
37
 
38
  2. 執行:
39
 
@@ -45,8 +45,11 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
45
 
46
 
47
  注意事項
48
- 此為個人練習與技術示範,非官方專案。
49
- 若出現「憑證過期」或「Token 失敗」等訊息,表示 MID_V 失效,需要更新。
 
 
 
50
 
51
  ---
52
  Convenience Store Expiring-Food Query (7-11 / FamilyMart)
@@ -59,14 +62,14 @@ Note:
59
  3. This project is for educational and research purposes only. Please do not use it for commercial or illegal purposes.
60
 
61
  Features
62
- • Search nearby 7-11 / FamilyMart stores by GPS coordinates.
63
  • Customizable search radius (3 / 5 / 7 / 13 / 21 km).
64
  • Display each store’s expiring-food items and remaining quantity.
65
 
66
  Usage
67
  1. Install Python 3.8+ and dependencies:
68
  ```
69
- pip install gradio requests pandas geopy
70
  ```
71
 
72
  2. Run:
 
25
  > 3. 本專案僅供研究教學之用,請勿用於商業或非法目的。
26
 
27
  ## 功能
28
+ - 以 GPS 座標或地址(自動轉換為經緯度,支援 Google Geocoding API)搜尋附近的 7-11 / 全家門市。
29
  - 可自訂搜尋範圍(3 / 5 / 7 / 13 / 21 公里)。
30
  - 顯示每間門市的即期食品清單與剩餘數量。
31
 
 
33
  1. 安裝 Python 3.8+ 及套件:
34
  ```bash
35
  pip install gradio requests pandas geopy
36
+ ```
37
 
38
  2. 執行:
39
 
 
45
 
46
 
47
  注意事項
48
+ - 此為個人練習與技術示範,非官方專案。
49
+ - 若出現「憑證過期」或「Token 失敗」等訊息,表示 MID_V 失效,需要更新。
50
+ - 地址查詢需設定 Google Geocoding API 金鑰於環境變數 `googlekey`(Huggingface Space Secrets)。
51
+
52
+ - For address search, set your Google Geocoding API key in the environment variable `googlekey` (Huggingface Space Secrets).
53
 
54
  ---
55
  Convenience Store Expiring-Food Query (7-11 / FamilyMart)
 
62
  3. This project is for educational and research purposes only. Please do not use it for commercial or illegal purposes.
63
 
64
  Features
65
+ • Search nearby 7-11 / FamilyMart stores by GPS coordinates or address (auto geocoding via Google API).
66
  • Customizable search radius (3 / 5 / 7 / 13 / 21 km).
67
  • Display each store’s expiring-food items and remaining quantity.
68
 
69
  Usage
70
  1. Install Python 3.8+ and dependencies:
71
  ```
72
+ pip install gradio requests pandas
73
  ```
74
 
75
  2. Run:
app.py CHANGED
@@ -215,7 +215,7 @@ def main():
215
  3. 意見反應 telegram @a7a8a9abc
216
  """)
217
 
218
- address = gr.Textbox(label="輸入地址(可留空)")
219
  lat = gr.Number(label="GPS 緯度", value=0, elem_id="lat")
220
  lon = gr.Number(label="GPS 經度", value=0, elem_id="lon")
221
 
 
215
  3. 意見反應 telegram @a7a8a9abc
216
  """)
217
 
218
+ address = gr.Textbox(label="地址(可留空)", placeholder="可留空白,通常不用填")
219
  lat = gr.Number(label="GPS 緯度", value=0, elem_id="lat")
220
  lon = gr.Number(label="GPS 經度", value=0, elem_id="lon")
221
 
app3.py DELETED
@@ -1,255 +0,0 @@
1
- import gradio as gr
2
- import requests
3
- import json
4
- import os
5
- import pandas as pd
6
- from geopy.distance import geodesic
7
-
8
- # =============== 7-11 所需常數 ===============
9
- # 請確認此處的 MID_V 是否有效,若過期請更新
10
- MID_V = "W0_DiF4DlgU5OeQoRswrRcaaNHMWOL7K3ra3385ocZcv-bBOWySZvoUtH6j-7pjiccl0C5h30uRUNbJXsABCKMqiekSb7tdiBNdVq8Ro5jgk6sgvhZla5iV0H3-8dZfASc7AhEm85679LIK3hxN7Sam6D0LAnYK9Lb0DZhn7xeTeksB4IsBx4Msr_VI" # 請填入有效的 mid_v
11
- USER_AGENT_7_11 = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
12
- API_7_11_BASE = "https://lovefood.openpoint.com.tw/LoveFood/api"
13
-
14
- # =============== FamilyMart 所需常數 ===============
15
- FAMILY_PROJECT_CODE = "202106302" # 若有需要請自行調整
16
- API_FAMILY = "https://stamp.family.com.tw/api/maps/MapProductInfo"
17
-
18
- # 3 公里範圍
19
- MAX_DISTANCE = 3000
20
-
21
- # -----------------------------------------------------------
22
- # 7-11: 取得 AccessToken
23
- # -----------------------------------------------------------
24
- def get_7_11_token():
25
- url = f"{API_7_11_BASE}/Auth/FrontendAuth/AccessToken?mid_v={MID_V}"
26
- headers = {
27
- "user-agent": USER_AGENT_7_11
28
- }
29
- resp = requests.post(url, headers=headers, data="")
30
- resp.raise_for_status()
31
- js = resp.json()
32
- if not js.get("isSuccess"):
33
- raise RuntimeError(f"取得 7-11 token 失敗: {js}")
34
- token = js["element"]
35
- return token
36
-
37
- # -----------------------------------------------------------
38
- # 7-11: 取得附近門市清單 (含剩餘即期品總數量)
39
- # -----------------------------------------------------------
40
- def get_7_11_nearby_stores(token, lat, lon):
41
- url = f"{API_7_11_BASE}/Search/FrontendStoreItemStock/GetNearbyStoreList?token={token}"
42
- headers = {
43
- "user-agent": USER_AGENT_7_11,
44
- "content-type": "application/json",
45
- }
46
- body = {
47
- "CurrentLocation": {"Latitude": lat, "Longitude": lon},
48
- "SearchLocation": {"Latitude": lat, "Longitude": lon}
49
- }
50
- resp = requests.post(url, headers=headers, json=body)
51
- resp.raise_for_status()
52
- js = resp.json()
53
- if not js.get("isSuccess"):
54
- raise RuntimeError(f"取得 7-11 附近門市失敗: {js}")
55
- return js["element"].get("StoreStockItemList", [])
56
-
57
- # -----------------------------------------------------------
58
- # 7-11: 取得單一門市的即期品清單
59
- # -----------------------------------------------------------
60
- def get_7_11_store_detail(token, lat, lon, store_no):
61
- url = f"{API_7_11_BASE}/Search/FrontendStoreItemStock/GetStoreDetail?token={token}"
62
- headers = {
63
- "user-agent": USER_AGENT_7_11,
64
- "content-type": "application/json",
65
- }
66
- body = {
67
- "CurrentLocation": {"Latitude": lat, "Longitude": lon},
68
- "StoreNo": store_no
69
- }
70
- resp = requests.post(url, headers=headers, json=body)
71
- resp.raise_for_status()
72
- js = resp.json()
73
- if not js.get("isSuccess"):
74
- raise RuntimeError(f"取得 7-11 門市({store_no})資料失敗: {js}")
75
- return js["element"].get("StoreStockItem", {})
76
-
77
- # -----------------------------------------------------------
78
- # FamilyMart: 取得附近門市即期品清單 (單次呼叫可拿到所有商品細項)
79
- # -----------------------------------------------------------
80
- def get_family_nearby_stores(lat, lon):
81
- headers = {
82
- "Content-Type": "application/json;charset=utf-8",
83
- }
84
- body = {
85
- "ProjectCode": FAMILY_PROJECT_CODE,
86
- "latitude": lat,
87
- "longitude": lon
88
- }
89
- resp = requests.post(API_FAMILY, headers=headers, json=body)
90
- resp.raise_for_status()
91
- js = resp.json()
92
- # 根據回傳範例,成功時 code 為 1
93
- if js.get("code") != 1:
94
- raise RuntimeError(f"取得全家門市資料失敗: {js}")
95
- return js["data"]
96
-
97
- # -----------------------------------------------------------
98
- # Gradio 查詢邏輯
99
- # -----------------------------------------------------------
100
- def find_nearest_store(address, lat, lon):
101
- print(f"🔍 收到查詢請求: address={address}, lat={lat}, lon={lon}")
102
- if lat == 0 or lon == 0:
103
- return [["❌ 請輸入地址或提供 GPS 座標", "", "", "", ""]]
104
-
105
- result_rows = []
106
-
107
- # ------------------ 7-11 ------------------
108
- try:
109
- token_711 = get_7_11_token()
110
- nearby_stores_711 = get_7_11_nearby_stores(token_711, lat, lon)
111
- for store in nearby_stores_711:
112
- dist_m = store.get("Distance", 999999)
113
- if dist_m <= MAX_DISTANCE:
114
- store_no = store.get("StoreNo")
115
- store_name = store.get("StoreName", "7-11 未提供店名")
116
- remaining_qty = store.get("RemainingQty", 0)
117
- if remaining_qty > 0:
118
- detail = get_7_11_store_detail(token_711, lat, lon, store_no)
119
- for cat in detail.get("CategoryStockItems", []):
120
- cat_name = cat.get("Name", "")
121
- for item in cat.get("ItemList", []):
122
- item_name = item.get("ItemName", "")
123
- item_qty = item.get("RemainingQty", 0)
124
- # 在最後加一個 float 距離欄位以便排序
125
- row = [
126
- f"7-11 {store_name}",
127
- f"{dist_m:.1f} m",
128
- f"{cat_name} - {item_name}",
129
- str(item_qty),
130
- dist_m # 供排序用
131
- ]
132
- result_rows.append(row)
133
- else:
134
- row = [
135
- f"7-11 {store_name}",
136
- f"{dist_m:.1f} m",
137
- "即期品 0 項",
138
- "0",
139
- dist_m # 供排序用
140
- ]
141
- result_rows.append(row)
142
- except Exception as e:
143
- print(f"❌ 取得 7-11 即期品時發生錯誤: {e}")
144
-
145
- # ------------------ FamilyMart ------------------
146
- try:
147
- nearby_stores_family = get_family_nearby_stores(lat, lon)
148
- for store in nearby_stores_family:
149
- dist_m = store.get("distance", 999999)
150
- if dist_m <= MAX_DISTANCE:
151
- store_name = store.get("name", "全家 未提供店名")
152
- info_list = store.get("info", [])
153
- has_item = False
154
- for big_cat in info_list:
155
- big_cat_name = big_cat.get("name", "")
156
- for subcat in big_cat.get("categories", []):
157
- subcat_name = subcat.get("name", "")
158
- for product in subcat.get("products", []):
159
- product_name = product.get("name", "")
160
- qty = product.get("qty", 0)
161
- if qty > 0:
162
- has_item = True
163
- row = [
164
- f"全家 {store_name}",
165
- f"{dist_m:.1f} m",
166
- f"{big_cat_name} - {subcat_name} - {product_name}",
167
- str(qty),
168
- dist_m # 供排序用
169
- ]
170
- result_rows.append(row)
171
- if not has_item:
172
- row = [
173
- f"全家 {store_name}",
174
- f"{dist_m:.1f} m",
175
- "即期品 0 項",
176
- "0",
177
- dist_m # 供排序用
178
- ]
179
- result_rows.append(row)
180
- except Exception as e:
181
- print(f"❌ 取得全家 即期品時發生錯誤: {e}")
182
-
183
- if not result_rows:
184
- return [["❌ 附近 3 公里內沒有即期食品", "", "", "", ""]]
185
-
186
- # ============= 在這裡進行排序 =============
187
- # result_rows 的結構是 [門市, 距離(字串), 商品, 數量, float_distance]
188
- # 我們要依照最後一欄 float_distance 做由小到大排序
189
- result_rows.sort(key=lambda x: x[4])
190
-
191
- # 排序完之後,再把最後一欄刪掉 (不顯示給使用者)
192
- for row in result_rows:
193
- row.pop() # 移除 index=4 (float_distance)
194
-
195
- return result_rows
196
-
197
- # -----------------------------------------------------------
198
- # Gradio 介面
199
- # -----------------------------------------------------------
200
- import gradio as gr
201
-
202
- with gr.Blocks() as demo:
203
- gr.Markdown("## 便利商店「即期食品」搜尋示範")
204
- gr.Markdown("""
205
- 1. 按下「使用目前位置」或自行輸入緯度/經度
206
- 2. 點選「搜尋」查詢 3 公里內 7-11 / 全家的即期品
207
- 3. 若要執行,需要有效的 mid_v (7-11 愛食記憶官網)
208
- 4. 在 Logs 查看詳細錯誤或除錯資訊
209
- """)
210
- address = gr.Textbox(label="輸入地址(可留空)")
211
- lat = gr.Number(label="GPS 緯度", value=0, elem_id="lat")
212
- lon = gr.Number(label="GPS 經度", value=0, elem_id="lon")
213
-
214
- with gr.Row():
215
- gps_button = gr.Button("📍 ❶ 使用目前位置-先按這個 並等待3秒 ", elem_id="gps-btn")
216
- search_button = gr.Button("🔍 ❷ 搜尋 ")
217
-
218
- output_table = gr.Dataframe(
219
- headers=["門市", "距離 (m)", "商品/即期食品", "數量"],
220
- interactive=False
221
- )
222
-
223
- search_button.click(fn=find_nearest_store, inputs=[address, lat, lon], outputs=output_table)
224
-
225
- gps_button.click(
226
- None,
227
- None,
228
- [lat, lon],
229
- js="""
230
- () => {
231
- return new Promise((resolve) => {
232
- if (!navigator.geolocation) {
233
- alert("您的瀏覽器不支援地理位置功能");
234
- resolve([0, 0]);
235
- return;
236
- }
237
- navigator.geolocation.getCurrentPosition(
238
- (position) => {
239
- resolve([position.coords.latitude, position.coords.longitude]);
240
- },
241
- (error) => {
242
- alert("無法取得位置:" + error.message);
243
- resolve([0, 0]);
244
- }
245
- );
246
- });
247
- }
248
- """
249
- )
250
-
251
- def main():
252
- demo.launch(server_name="0.0.0.0", server_port=7860, debug=True)
253
-
254
- if __name__ == "__main__":
255
- main()