"""
결과 출력 관련 유틸리티 함수 모음 - 카테고리 항목 제거
- HTML 테이블 생성
- 엑셀 파일 생성
"""
import pandas as pd
import tempfile
import os
import threading
import time
import logging
# 로깅 설정
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
# 임시 파일 추적 리스트
_temp_files = []
def create_table_without_checkboxes(df):
"""DataFrame을 HTML 테이블로 변환 - 키워드 클릭 시 네이버 쇼핑 이동 기능 추가"""
if df.empty:
return "
검색 결과가 없습니다.
"
# === 수정된 부분: 카테고리 관련 열 제거 ===
df_display = df.copy()
# "상품 등록 카테고리(상위100위)" 또는 "관련 카테고리", "카테고리 항목" 열이 있으면 제거
columns_to_remove = ["상품 등록 카테고리(상위100위)", "관련 카테고리", "카테고리 항목"]
for col in columns_to_remove:
if col in df_display.columns:
df_display = df_display.drop(columns=[col])
logger.info(f"테이블에서 '{col}' 열 제거됨")
# HTML 테이블 스타일 정의 - Z-INDEX 수정
html = '''
'''
# === 수정된 부분: 열 이름과 클래스 매핑 - 카테고리 관련 제거 ===
col_mapping = {
"순번": "col-seq",
"조합 키워드": "col-keyword",
"PC검색량": "col-pc",
"모바일검색량": "col-mobile",
"총검색량": "col-total",
"검색량구간": "col-range",
"키워드 사용자순위": "col-rank",
"키워드 사용횟수": "col-count"
# 카테고리 관련 매핑 제거됨
}
# 테이블 컨테이너 시작
html += ''
# 단일 테이블 구조로 변경 (헤더는 position: sticky로 고정)
html += '
'
html += '
'
# colgroup으로 열 너비 정의
html += ''
html += f''
for col in df_display.columns:
col_class = col_mapping.get(col, "")
html += f''
html += ''
# 테이블 헤더
html += ''
html += ''
html += f'순번 | '
for col in df_display.columns:
col_class = col_mapping.get(col, "")
html += f'{col} | '
html += '
'
html += ''
# 테이블 본문
html += ''
for idx, row in df_display.iterrows():
html += ''
# 순번 표시 - 1부터 시작하는 순차적 번호
html += f'{idx + 1} | '
# 데이터 셀 추가
for col in df_display.columns:
col_class = col_mapping.get(col, "")
value = str(row[col])
if col == "키워드 사용자순위":
# 긴 텍스트의 셀은 그대로 표시 (줄바꿈 허용)
html += f'{value} | '
elif len(value) > 30:
# 다른 긴 텍스트는 hover로 전체 표시
html += f'{value[:30]}... | '
else:
# 일반 텍스트
html += f'{value} | '
html += '
'
html += ''
html += '
'
html += '
' # data-container 닫기
html += '
' # table-container 닫기
return html
def cleanup_temp_files(delay=300):
"""임시 파일 정리 함수"""
global _temp_files
def cleanup():
time.sleep(delay) # 지정된 시간 대기
temp_files_to_remove = _temp_files.copy()
_temp_files = []
for file_path in temp_files_to_remove:
try:
if os.path.exists(file_path):
os.remove(file_path)
logger.info(f"임시 파일 삭제: {file_path}")
except Exception as e:
logger.error(f"파일 삭제 오류: {e}")
# 새 스레드 시작
threading.Thread(target=cleanup, daemon=True).start()
def download_keywords(df, auto_cleanup=True, cleanup_delay=300):
"""키워드 데이터를 엑셀 파일로 다운로드 - 카테고리 항목 제거"""
global _temp_files
if df is None or df.empty:
return None
# 임시 파일로 저장
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.xlsx')
temp_file.close()
filename = temp_file.name
# 임시 파일 추적 목록에 추가
_temp_files.append(filename)
# === 수정된 부분: 카테고리 관련 열 제거 ===
df_export = df.copy()
# 카테고리 관련 열들 제거
columns_to_remove = ["상품 등록 카테고리(상위100위)", "관련 카테고리", "카테고리 항목"]
for col in columns_to_remove:
if col in df_export.columns:
df_export = df_export.drop(columns=[col])
logger.info(f"엑셀 내보내기에서 '{col}' 열 제거됨")
# 키워드 데이터를 엑셀 파일로 저장
with pd.ExcelWriter(filename, engine='xlsxwriter') as writer:
# 키워드 목록 시트
df_export.to_excel(writer, sheet_name='키워드 목록', index=False)
# 열 너비 조정 - 카테고리 열 제거 후 조정
worksheet = writer.sheets['키워드 목록']
worksheet.set_column('A:A', 20) # 조합 키워드 열
worksheet.set_column('B:B', 12) # PC검색량 열
worksheet.set_column('C:C', 12) # 모바일검색량 열
worksheet.set_column('D:D', 12) # 총검색량 열
worksheet.set_column('E:E', 12) # 검색량구간 열
worksheet.set_column('F:F', 20) # 키워드 사용자순위 열
worksheet.set_column('G:G', 12) # 키워드 사용횟수 열
# 카테고리 열들 제거로 H, I 열 설정 제거됨
# 헤더 형식 설정
header_format = writer.book.add_format({
'bold': True,
'bg_color': '#009879',
'color': 'white',
'border': 1
})
# 헤더에 형식 적용
for col_num, value in enumerate(df_export.columns.values):
worksheet.write(0, col_num, value, header_format)
logger.info(f"엑셀 파일 생성: {filename}")
# 파일 자동 정리 옵션
if auto_cleanup:
# 별도 정리 작업 요청 없이 추적 목록에 추가만 하여 일괄 처리
pass
return filename
def register_cleanup_handlers():
"""앱 종료 시 정리를 위한 핸들러 등록"""
import atexit
def cleanup_all_temp_files():
global _temp_files
for file_path in _temp_files:
try:
if os.path.exists(file_path):
os.remove(file_path)
logger.info(f"종료 시 임시 파일 삭제: {file_path}")
except Exception as e:
logger.error(f"파일 삭제 오류: {e}")
_temp_files = []
# 앱 종료 시 실행될 함수 등록
atexit.register(cleanup_all_temp_files)