APP_STREAMLIT0526 / src /streamlit_app.py
Roberta2024's picture
Update src/streamlit_app.py
01fa6c6 verified
import streamlit as st
import requests
from bs4 import BeautifulSoup
import pandas as pd
import base64
from pathlib import Path
# 頁面配置
st.set_page_config(
page_title="台南寵物醫院查詢",
page_icon="🐕",
layout="wide",
initial_sidebar_state="expanded"
)
# 添加背景圖片的CSS樣式
def add_bg_from_local(image_file):
"""添加本地背景圖片"""
try:
with open(image_file, "rb") as image_file:
encoded_string = base64.b64encode(image_file.read())
st.markdown(
f"""
<style>
.stApp {{
background-image: url(data:image/{"png"};base64,{encoded_string.decode()});
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
}}
.main-content {{
background-color: rgba(255, 255, 255, 0.9);
padding: 20px;
border-radius: 10px;
margin: 20px 0;
}}
.stSelectbox > div > div {{
background-color: rgba(255, 255, 255, 0.9);
}}
.stNumberInput > div > div {{
background-color: rgba(255, 255, 255, 0.9);
}}
</style>
""",
unsafe_allow_html=True
)
except FileNotFoundError:
st.warning("背景圖片 'ania.png' 未找到,使用預設背景")
# 嘗試添加背景圖片
add_bg_from_local('src/ania.png')
# 爬蟲函數
@st.cache_data
def scrape_pet_hospitals(selected_sections, max_pages=3):
"""爬取寵物醫院資料"""
data = []
# 進度條
progress_bar = st.progress(0)
total_sections = len(selected_sections)
for i, section in enumerate(selected_sections):
for page in range(0, max_pages):
url = f'https://petoplay.com/hospital/tainan/{section}?page={page}'
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
hospitals = soup.find_all('a', href=True)
for tag in hospitals:
if f'/hospital/tainan/{section}/' in tag['href'] and tag.text.strip():
name = tag.text.strip()
parent = tag.find_parent()
address_tag = parent.find_next('div', class_='petadr')
address = address_tag.get_text(strip=True).replace('\n', '') if address_tag else 'N/A'
score_tag = parent.find_next('span', class_='gscore')
score = score_tag.text.strip() if score_tag else 'N/A'
data.append({
'店名': name,
'地址': address,
'評分': score,
'區段': section
})
except requests.exceptions.RequestException as e:
st.warning(f"區段 {section}{page+1} 頁爬取失敗: {e}")
continue
# 更新進度條
progress_bar.progress((i + 1) / total_sections)
return pd.DataFrame(data)
# LINE Bot 發送函數
def send_line_message(message, channel_access_token):
"""發送LINE訊息"""
try:
from linebot import LineBotApi
from linebot.models import TextSendMessage
line_bot_api = LineBotApi(channel_access_token)
text_message = TextSendMessage(text=message)
line_bot_api.broadcast(text_message)
return True, "訊息發送成功!"
except ImportError:
return False, "請先安裝 line-bot-sdk: pip install line-bot-sdk"
except Exception as e:
return False, f"發送失敗: {str(e)}"
# 主介面
def main():
st.markdown('<div class="main-content">', unsafe_allow_html=True)
# 標題
st.title("🐕 台南寵物醫院查詢系統")
st.markdown("---")
# 側邊欄設定
with st.sidebar:
st.header("🔧 查詢設定")
# 區段選擇
all_sections = [1, 2, 3, 4, 5, 6, 7, 8, 9, 14, 18, 19, 20, 22, 23, 25, 27, 29, 32, 33, 36]
selected_sections = st.multiselect(
"選擇要查詢的區段",
options=all_sections,
default=all_sections[:5], # 預設選擇前5個區段
help="選擇你想要查詢的台南市區段代碼"
)
# 頁數設定
max_pages = st.slider("每個區段查詢頁數", 1, 5, 3)
# 評分篩選
min_score = st.number_input(
"最低評分篩選",
min_value=0.0,
max_value=5.0,
value=4.0,
step=0.1,
help="只顯示評分高於此值的醫院"
)
# LINE Bot 設定
st.header("📱 LINE 通知設定")
channel_access_token = st.text_input(
"LINE Channel Access Token",
type="password",
help="輸入你的 LINE Bot Channel Access Token"
)
# 主要內容區域
col1, col2 = st.columns([3, 1])
with col2:
search_button = st.button("🔍 開始查詢", type="primary", use_container_width=True)
if search_button and selected_sections:
with st.spinner("正在爬取寵物醫院資料..."):
# 爬取資料
df = scrape_pet_hospitals(selected_sections, max_pages)
if not df.empty:
# 處理評分資料
df['評分_數值'] = pd.to_numeric(df['評分'], errors='coerce')
# 篩選資料
filtered_df = df[df['評分_數值'] >= min_score].dropna(subset=['評分_數值'])
# 顯示結果
st.success(f"成功爬取到 {len(df)} 家寵物醫院,篩選後顯示 {len(filtered_df)} 家")
if not filtered_df.empty:
# 資料統計
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("總醫院數", len(df))
with col2:
st.metric("篩選後數量", len(filtered_df))
with col3:
st.metric("平均評分", f"{filtered_df['評分_數值'].mean():.2f}")
with col4:
st.metric("最高評分", f"{filtered_df['評分_數值'].max():.2f}")
st.markdown("---")
# 顯示表格
st.subheader("📋 查詢結果")
# 先按評分排序,然後選擇要顯示的欄位
sorted_df = filtered_df.sort_values('評分_數值', ascending=False)
display_df = sorted_df[['店名', '地址', '評分', '區段']]
st.dataframe(
display_df,
use_container_width=True,
hide_index=True
)
# 下載功能
csv = display_df.to_csv(index=False, encoding='utf-8-sig')
st.download_button(
label="📥 下載 CSV 檔案",
data=csv,
file_name=f"台南寵物醫院_{min_score}分以上.csv",
mime="text/csv"
)
# LINE 發送功能
if channel_access_token:
st.markdown("---")
col1, col2 = st.columns([3, 1])
with col1:
st.subheader("📱 發送到 LINE")
with col2:
if st.button("發送訊息", type="secondary"):
# 準備訊息內容(取前5個結果)
top_5 = display_df.head(5)
message = f"台南寵物醫院推薦 (評分≥{min_score})\n\n"
for _, row in top_5.iterrows():
message += f"🏥 {row['店名']}\n"
message += f"📍 {row['地址']}\n"
message += f"⭐ {row['評分']}\n\n"
success, msg = send_line_message(message, channel_access_token)
if success:
st.success(msg)
else:
st.error(msg)
else:
st.warning(f"沒有找到評分 {min_score} 分以上的寵物醫院")
else:
st.error("未能獲取到任何資料,請檢查網路連線或稍後再試")
elif search_button and not selected_sections:
st.warning("請至少選擇一個區段進行查詢")
st.markdown('</div>', unsafe_allow_html=True)
# 頁尾
st.markdown("---")
st.markdown(
"""
<div style='text-align: center; color: #666; padding: 20px; background-color: rgba(255, 255, 255, 0.8); border-radius: 10px;'>
<p>🐾 台南寵物醫院查詢系統 | 資料來源: petoplay.com</p>
<p>💡 提示: 將 ania.png 圖片檔案放置在與此腳本相同的目錄下作為背景</p>
</div>
""",
unsafe_allow_html=True
)
if __name__ == "__main__":
main()