Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f134467be7 | |||
| 58cff598a7 |
@ -2,6 +2,7 @@ import os
|
|||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
import importlib.util
|
||||||
from flask import Flask, request, jsonify, send_from_directory, make_response
|
from flask import Flask, request, jsonify, send_from_directory, make_response
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
@ -71,6 +72,126 @@ def get_captured_rainfall(date):
|
|||||||
logger.error(f"캡처 강수량 조회 실패: {e}")
|
logger.error(f"캡처 강수량 조회 실패: {e}")
|
||||||
return [], 0.0, None
|
return [], 0.0, None
|
||||||
|
|
||||||
|
def generate_weather_data(date):
|
||||||
|
"""
|
||||||
|
날씨 데이터가 없을 때 Selenium으로 데이터 추출하여 SQLite에만 저장
|
||||||
|
(캡처 이미지 저장, FTP 업로드, 게시글 등록은 하지 않음)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
date: 'YYYYMMDD' 형식
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 성공 여부
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.info(f"날씨 데이터 추출 시작 ({date})...")
|
||||||
|
|
||||||
|
# weather_capture 모듈에서 필요한 함수들을 동적으로 임포트
|
||||||
|
import importlib.util
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
"weather_capture",
|
||||||
|
"/app/weather_capture.py"
|
||||||
|
)
|
||||||
|
weather_capture = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(weather_capture)
|
||||||
|
|
||||||
|
# Selenium으로 강우량 데이터 추출
|
||||||
|
from selenium_manager import SeleniumManager
|
||||||
|
|
||||||
|
selenium_mgr = SeleniumManager()
|
||||||
|
driver = selenium_mgr.get_driver()
|
||||||
|
|
||||||
|
try:
|
||||||
|
driver.get(weather_capture.WEATHER_URL)
|
||||||
|
rainfall_data = weather_capture.extract_rainfall_from_page(driver)
|
||||||
|
|
||||||
|
if rainfall_data:
|
||||||
|
# SQLite에만 저장 (이미지 저장, FTP 업로드는 하지 않음)
|
||||||
|
success = save_rainfall_to_sqlite(rainfall_data, date)
|
||||||
|
logger.info(f"날씨 데이터 추출 완료 ({date}): {success}")
|
||||||
|
return success
|
||||||
|
else:
|
||||||
|
logger.warning(f"강수량 데이터 추출 실패 ({date})")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
selenium_mgr.close_driver()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"날씨 데이터 추출 중 오류: {type(e).__name__}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def save_rainfall_to_sqlite(rainfall_data, date):
|
||||||
|
"""
|
||||||
|
추출한 강수량 데이터를 SQLite DB에만 저장
|
||||||
|
(이미지 저장, FTP 업로드, 게시글 등록 없음)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
rainfall_data: {시간(int): 강수량(float)} 딕셔너리
|
||||||
|
date: 'YYYYMMDD' 형식
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 성공 여부
|
||||||
|
"""
|
||||||
|
if not rainfall_data:
|
||||||
|
logger.warning("저장할 강수량 데이터가 없음")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(DB_PATH) or '/data', exist_ok=True)
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
curs = conn.cursor()
|
||||||
|
|
||||||
|
# 테이블 생성
|
||||||
|
curs.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS rainfall_capture (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
date TEXT NOT NULL,
|
||||||
|
hour INTEGER NOT NULL,
|
||||||
|
rainfall REAL NOT NULL,
|
||||||
|
UNIQUE(date, hour)
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
curs.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS rainfall_summary (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
date TEXT NOT NULL UNIQUE,
|
||||||
|
total_rainfall REAL NOT NULL,
|
||||||
|
capture_time TEXT NOT NULL
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# 기존 데이터 삭제
|
||||||
|
curs.execute('DELETE FROM rainfall_capture WHERE date = ?', (date,))
|
||||||
|
curs.execute('DELETE FROM rainfall_summary WHERE date = ?', (date,))
|
||||||
|
|
||||||
|
# 시간별 강수량 저장
|
||||||
|
total_rainfall = 0.0
|
||||||
|
for hour in sorted(rainfall_data.keys()):
|
||||||
|
rainfall = rainfall_data[hour]
|
||||||
|
curs.execute(
|
||||||
|
'INSERT INTO rainfall_capture (date, hour, rainfall) VALUES (?, ?, ?)',
|
||||||
|
(date, hour, rainfall)
|
||||||
|
)
|
||||||
|
total_rainfall += rainfall
|
||||||
|
|
||||||
|
# 합계 저장
|
||||||
|
capture_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
curs.execute(
|
||||||
|
'INSERT INTO rainfall_summary (date, total_rainfall, capture_time) VALUES (?, ?, ?)',
|
||||||
|
(date, total_rainfall, capture_time)
|
||||||
|
)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
logger.info(f"SQLite 저장 완료: {date} 총 강수량 {total_rainfall:.1f}mm")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"SQLite 저장 실패: {type(e).__name__}: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
def get_forecast_rainfall(date):
|
def get_forecast_rainfall(date):
|
||||||
"""
|
"""
|
||||||
기상청 API를 통한 강수량 예보 조회
|
기상청 API를 통한 강수량 예보 조회
|
||||||
@ -96,27 +217,47 @@ def get_forecast_rainfall(date):
|
|||||||
'nx': '57',
|
'nx': '57',
|
||||||
'ny': '130'
|
'ny': '130'
|
||||||
}
|
}
|
||||||
|
,
|
||||||
|
'data_status': 'ok' | 'generating' | 'unavailable'
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
today = datetime.now().strftime('%Y%m%d')
|
||||||
|
is_forecast = date_str > today
|
||||||
|
data_status = 'ok'
|
||||||
|
|
||||||
|
if is_forecast:
|
||||||
|
# 미래 날짜: API 예보
|
||||||
|
hourly, total = get_forecast_rainfall(date_str)
|
||||||
|
note = "⚠️ 이는 기상청 08:00 발표 예보입니다. 실제 이벤트 적용 기준은 당일 09:00 캡처 데이터입니다."
|
||||||
|
else:
|
||||||
|
# 당일 이상: 캡처 데이터
|
||||||
|
hourly, total, timestamp = get_captured_rainfall(date_str)
|
||||||
|
|
||||||
response = requests.get(url, params=params, timeout=10)
|
# 데이터가 없을 경우 생성 시도
|
||||||
data = response.json()
|
if not hourly:
|
||||||
|
logger.warning(f"날씨 캡처 데이터 없음 ({date_str}), 데이터 생성 시도...")
|
||||||
if data['response']['header']['resultCode'] != '00':
|
if generate_weather_data(date_str):
|
||||||
return [], 0.0
|
# 다시 조회
|
||||||
|
hourly, total, timestamp = get_captured_rainfall(date_str)
|
||||||
rainfall_by_hour = {}
|
if hourly:
|
||||||
for item in data['response']['body']['items']['item']:
|
data_status = 'generating'
|
||||||
if item['category'] == 'RN1': # 1시간 강수량
|
note = "💡 방금 날씨 데이터를 생성했습니다."
|
||||||
hour = int(item['fcstTime'][:2])
|
else:
|
||||||
if 10 <= hour <= 21: # 10시~21시(오후 9시)
|
data_status = 'unavailable'
|
||||||
rainfall_by_hour[hour] = parse_rainfall_value(item['fcstValue'])
|
note = "⚠️ 날씨 데이터를 생성했으나 조회할 수 없습니다. 나중에 다시 시도해주세요."
|
||||||
|
else:
|
||||||
hourly_list = [(h, rainfall_by_hour.get(h, 0.0)) for h in range(10, 22)]
|
data_status = 'unavailable'
|
||||||
total = sum(rain for _, rain in hourly_list)
|
note = "⚠️ 날씨 데이터를 생성할 수 없습니다. 09:00에 자동으로 생성되며, 나중에 다시 시도해주세요."
|
||||||
|
else:
|
||||||
return hourly_list, total
|
note = None
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"API 강수량 예보 조회 실패: {e}")
|
return {
|
||||||
return [], 0.0
|
'date': date_str,
|
||||||
|
'is_forecast': is_forecast,
|
||||||
|
'hourly_data': hourly,
|
||||||
|
'total': total,
|
||||||
|
'note': note,
|
||||||
|
'data_status': data_status.0
|
||||||
|
|
||||||
def get_rainfall_data(date_str):
|
def get_rainfall_data(date_str):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -288,18 +288,23 @@ def write_board(board, subject, content, mb_id, nickname, ca_name=None, file_lis
|
|||||||
f"📅 날짜: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"\
|
f"📅 날짜: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"\
|
||||||
f"📋 게시판: `{board}`\n"\
|
f"📋 게시판: `{board}`\n"\
|
||||||
f"📝 제목: {subject}\n"\
|
f"📝 제목: {subject}\n"\
|
||||||
f"📎 첨부파일: {file_count}개"
|
f"📎 첨부파일: {file_count}개\n"
|
||||||
|
|
||||||
# 캡처파일 정보 추가 (file_list에 2개 이상의 파일이 있을 경우)
|
# 캡처파일 정보 및 다운로드 링크 추가 (file_list에 2개 이상의 파일이 있을 경우)
|
||||||
if file_list and len(file_list) > 1 and os.path.isfile(file_list[1]):
|
if file_list and len(file_list) > 1 and os.path.isfile(file_list[1]):
|
||||||
capture_filename = os.path.basename(file_list[1])
|
capture_filename = os.path.basename(file_list[1])
|
||||||
capture_size = os.path.getsize(file_list[1]) / 1024 # KB 단위
|
capture_size = os.path.getsize(file_list[1]) / 1024 # KB 단위
|
||||||
success_msg += f"\n🖼️ 캡처파일: `{capture_filename}` ({capture_size:.1f}KB)"
|
success_msg += f"🖼️ **캡처파일**: `{capture_filename}` ({capture_size:.1f}KB)\n"
|
||||||
|
|
||||||
|
# 캡처파일 다운로드 링크 추가 (첫번째 첨부파일의 FTP 경로 기반)
|
||||||
|
if url and board:
|
||||||
|
capture_download_url = f"{url.rstrip('/')}/data/file/{board}/{os.path.basename(file_list[1])}"
|
||||||
|
success_msg += f"📥 **다운로드**: [캡처파일 보기]({capture_download_url})\n"
|
||||||
|
|
||||||
# 게시글 링크 추가
|
# 게시글 링크 추가
|
||||||
if url and board:
|
if url and board:
|
||||||
post_url = f"{url.rstrip('/')}/{board}/{wr_id}"
|
post_url = f"{url.rstrip('/')}/{board}/{wr_id}"
|
||||||
success_msg += f"\n🔗 게시글 링크: {post_url}"
|
success_msg += f"🔗 **게시글 링크**: [{subject}]({post_url})"
|
||||||
|
|
||||||
if msg_sender:
|
if msg_sender:
|
||||||
msg_sender.send(success_msg, platforms=['mattermost'])
|
msg_sender.send(success_msg, platforms=['mattermost'])
|
||||||
@ -378,7 +383,8 @@ def main():
|
|||||||
|
|
||||||
file_list = [MAIN['file1'], MAIN['file2']]
|
file_list = [MAIN['file1'], MAIN['file2']]
|
||||||
|
|
||||||
# 게시글 작성, wr_id = write_board(
|
# 게시글 작성
|
||||||
|
wr_id = write_board(
|
||||||
board=MAIN['board'],
|
board=MAIN['board'],
|
||||||
subject=MAIN['subject'],
|
subject=MAIN['subject'],
|
||||||
content=MAIN['content'],
|
content=MAIN['content'],
|
||||||
@ -388,7 +394,6 @@ def main():
|
|||||||
file_list=file_list,
|
file_list=file_list,
|
||||||
msg_sender=msg_sender,
|
msg_sender=msg_sender,
|
||||||
url=MAIN.get('url')
|
url=MAIN.get('url')
|
||||||
msg_sender=msg_sender
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
|
|||||||
@ -11,6 +11,8 @@ echo "[$(date '+%Y-%m-%d %H:%M:%S')] ========================================"
|
|||||||
# Flask 웹서버를 백그라운드에서 시작
|
# Flask 웹서버를 백그라운드에서 시작
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Flask 웹서버 시작 (포트 5000)..."
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Flask 웹서버 시작 (포트 5000)..."
|
||||||
cd /app
|
cd /app
|
||||||
|
export FLASK_APP=api_server.py
|
||||||
|
export PYTHONUNBUFFERED=1
|
||||||
/usr/bin/python -m flask run --host=0.0.0.0 --port=5000 >> /logs/flask.log 2>&1 &
|
/usr/bin/python -m flask run --host=0.0.0.0 --port=5000 >> /logs/flask.log 2>&1 &
|
||||||
FLASK_PID=$!
|
FLASK_PID=$!
|
||||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Flask PID: $FLASK_PID"
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Flask PID: $FLASK_PID"
|
||||||
|
|||||||
Reference in New Issue
Block a user