3 Commits

4 changed files with 83 additions and 16 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ config.py
naver_review/build/ naver_review/build/
*.spec *.spec
data/weather_capture_*.png data/weather_capture_*.png
.vscode/

View File

@ -21,25 +21,62 @@ import ftputil
from config import DB_CONFIG, FTP_CONFIG, MAIN from config import DB_CONFIG, FTP_CONFIG, MAIN
from weather import get_precipitation_summary from weather import get_precipitation_summary
from send_message import MessageSender # MessageSender 클래스 임포트
# --------------------------- # ---------------------------
# 이미지 캡처 함수 # 이미지 캡처 함수
# --------------------------- # ---------------------------
def capture_image(script_path, output_path, max_attempts=5): def capture_image(script_path, output_path, max_attempts=5, msg_sender=None):
"""
이미지 캡처 시도
Returns:
tuple: (성공여부, 에러메시지)
"""
for attempt in range(max_attempts): for attempt in range(max_attempts):
print(f"[{datetime.now().strftime('%H:%M:%S')}] 이미지 캡처 시도 {attempt + 1}/{max_attempts}") print(f"[{datetime.now().strftime('%H:%M:%S')}] 이미지 캡처 시도 {attempt + 1}/{max_attempts}")
try: try:
subprocess.run([sys.executable, script_path], check=True) result = subprocess.run(
except subprocess.CalledProcessError as e: [sys.executable, script_path],
print(f"[오류] weather_capture.py 실행 실패: {e}") check=True,
capture_output=True,
text=True,
timeout=60 # 60초 타임아웃 설정
)
if os.path.isfile(output_path): if os.path.isfile(output_path):
print(f"[성공] 이미지가 정상적으로 캡처되었습니다: {output_path}") print(f"[성공] 이미지가 정상적으로 캡처되었습니다: {output_path}")
return True return True, None
time.sleep(2)
print(f"[실패] {max_attempts}회 시도 후에도 이미지가 생성되지 않았습니다.")
return False
except subprocess.TimeoutExpired:
error_msg = f"캡처 스크립트 실행 타임아웃 (시도 {attempt + 1}/{max_attempts})"
print(f"[오류] {error_msg}")
except subprocess.CalledProcessError as e:
error_msg = f"weather_capture.py 실행 실패:\n{e.stderr if e.stderr else str(e)}"
print(f"[오류] {error_msg}")
except Exception as e:
error_msg = f"예상치 못한 오류: {type(e).__name__}: {e}"
print(f"[오류] {error_msg}")
time.sleep(2)
# 모든 시도 실패
final_error = f"❌ **날씨 이미지 캡처 실패**\n\n"\
f"📅 날짜: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"\
f"🔄 시도 횟수: {max_attempts}\n"\
f"📁 출력 경로: `{output_path}`\n"\
f"⚠️ 파일이 생성되지 않았습니다."
print(f"[실패] {max_attempts}회 시도 후에도 이미지가 생성되지 않았습니다.")
# Mattermost 알림 전송
if msg_sender:
msg_sender.send(final_error, platforms=['mattermost'])
return False, final_error
# --------------------------- # ---------------------------
# 파일 관련 유틸 함수 # 파일 관련 유틸 함수
@ -70,11 +107,17 @@ def file_upload(filename, bf_file):
print(f"[FTP 오류] {type(e).__name__}: {e}") print(f"[FTP 오류] {type(e).__name__}: {e}")
return False return False
# --------------------------- # ---------------------------
# 게시글 작성 함수 # 게시글 작성 함수
# --------------------------- # ---------------------------
def write_board(board, subject, content, mb_id, nickname, ca_name=None, file_list=None): def write_board(board, subject, content, mb_id, nickname, ca_name=None, file_list=None, msg_sender=None):
"""
게시글 작성
Returns:
tuple: (성공여부, 에러메시지)
"""
conn = None
try: try:
conn = pymysql.connect( conn = pymysql.connect(
host=DB_CONFIG['HOST'], host=DB_CONFIG['HOST'],
@ -135,20 +178,40 @@ def write_board(board, subject, content, mb_id, nickname, ca_name=None, file_lis
size, width, height, img_type, now)) size, width, height, img_type, now))
file_count += 1 file_count += 1
else: else:
raise Exception(f"[FTP 오류] 파일 업로드 실패: {file}") raise Exception(f"파일 업로드 실패: {file}")
curs.execute(f"UPDATE g5_write_{board} SET wr_file = %s WHERE wr_id = %s", (file_count, wr_id)) curs.execute(f"UPDATE g5_write_{board} SET wr_file = %s WHERE wr_id = %s", (file_count, wr_id))
conn.commit() conn.commit()
print("[성공] 게시글과 첨부파일 등록 완료") print("[성공] 게시글과 첨부파일 등록 완료")
return True, None
except Exception as e: except Exception as e:
if conn:
conn.rollback() conn.rollback()
if "[FTP 오류]" in str(e):
# 에러 메시지 생성
error_detail = traceback.format_exc()
error_msg = f"❌ **게시글 등록 실패**\n\n"\
f"📅 날짜: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"\
f"📋 게시판: `{board}`\n"\
f"📝 제목: {subject}\n"\
f"⚠️ 오류 유형: `{type(e).__name__}`\n"\
f"💬 오류 메시지: {str(e)}\n"\
f"```\n{error_detail}\n```"
if "FTP" in str(e) or "파일 업로드" in str(e):
print(f"[FTP 오류] {e}") print(f"[FTP 오류] {e}")
else: else:
print(f"[DB 오류] {type(e).__name__}: {e}") print(f"[DB 오류] {type(e).__name__}: {e}")
raise
# Mattermost 알림 전송
if msg_sender:
msg_sender.send(error_msg, platforms=['mattermost'])
return False, error_msg
finally: finally:
if conn:
conn.close() conn.close()
@ -156,6 +219,7 @@ def write_board(board, subject, content, mb_id, nickname, ca_name=None, file_lis
# 메인 실행 함수 # 메인 실행 함수
# --------------------------- # ---------------------------
def main(): def main():
# 기상청 API로 얻어오는 데이터와 캡처의 데이터가 다르므로 내용에 대해 업데이트 하지 않음. # 기상청 API로 얻어오는 데이터와 캡처의 데이터가 다르므로 내용에 대해 업데이트 하지 않음.
# 날씨 정보 문자열 얻기 # 날씨 정보 문자열 얻기
#weather_content = get_precipitation_summary() #weather_content = get_precipitation_summary()
@ -167,6 +231,8 @@ def main():
MAIN["content"] = """ MAIN["content"] = """
<p>Rainy Day 이벤트 적용안내</p> <p>Rainy Day 이벤트 적용안내</p>
<p><b>10:00 ~ 22:00까지의 예보를 합산하며, ~1mm인 경우 0.5mm로 계산합니다.</b></p> <p><b>10:00 ~ 22:00까지의 예보를 합산하며, ~1mm인 경우 0.5mm로 계산합니다.</b></p>
<p>레이니데이 이벤트 정보 확인</p>
<p><a href="https://firstgarden.co.kr/news/60">이벤트 정보 보기</a></p>
""" """
today = datetime.today().strftime('%Y%m%d') today = datetime.today().strftime('%Y%m%d')

View File

@ -61,7 +61,7 @@ try:
# 캡처 대상 요소 대기 후 찾기 # 캡처 대상 요소 대기 후 찾기
target_element = wait.until(EC.presence_of_element_located( target_element = wait.until(EC.presence_of_element_located(
(By.XPATH, '/html/body/div[2]/section/div/div[2]') (By.XPATH, '/html/body/div[1]/main/div[2]/div[1]')
)) ))
# 저장 경로 설정 # 저장 경로 설정

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB