p763nx9tf / text_utils.py
ssboost's picture
Upload 11 files
1271db4 verified
"""
ν…μŠ€νŠΈ 처리 κ΄€λ ¨ μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜ λͺ¨μŒ
- ν…μŠ€νŠΈ 뢄리 및 μ •μ œ
- ν‚€μ›Œλ“œ μΆ”μΆœ
- Gemini API ν‚€ 톡합 관리 적용
"""
import re
import google.generativeai as genai
import os
import logging
import api_utils # API ν‚€ 톡합 관리λ₯Ό μœ„ν•œ μž„ν¬νŠΈ
# λ‘œκΉ… μ„€μ •
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)
# ===== Gemini λͺ¨λΈ 관리 ν•¨μˆ˜λ“€ =====
def get_gemini_model():
"""api_utilsμ—μ„œ Gemini λͺ¨λΈ κ°€μ Έμ˜€κΈ° (톡합 관리)"""
try:
model = api_utils.get_gemini_model()
if model:
logger.info("Gemini λͺ¨λΈ λ‘œλ“œ 성곡 (api_utils 톡합 관리)")
return model
else:
logger.warning("μ‚¬μš© κ°€λŠ₯ν•œ Gemini API ν‚€κ°€ μ—†μŠ΅λ‹ˆλ‹€.")
return None
except Exception as e:
logger.error(f"Gemini λͺ¨λΈ λ‘œλ“œ μ‹€νŒ¨: {e}")
return None
# ν…μŠ€νŠΈ 뢄리 및 μ •μ œ ν•¨μˆ˜
def clean_and_split(text, only_korean=False):
"""ν…μŠ€νŠΈλ₯Ό λΆ„λ¦¬ν•˜κ³  μ •μ œν•˜λŠ” ν•¨μˆ˜"""
text = re.sub(r"[()\[\]-]", " ", text)
text = text.replace("/", " ")
if only_korean:
# ν•œκΈ€λ§Œ μΆ”μΆœ μ˜΅μ…˜μ΄ μΌœμ§„ 경우
# κ³΅λ°±μ΄λ‚˜ μ‰Όν‘œλ‘œ κ΅¬λΆ„ν•œ λ’€ ν•œκΈ€λ§Œ μΆ”μΆœ
words = re.split(r"[ ,]", text)
cleaned = []
for word in words:
word = word.strip()
# ν•œκΈ€λ§Œ 남기고 λ‹€λ₯Έ λ¬ΈμžλŠ” 제거
word = re.sub(r"[^κ°€-힣]", "", word)
if word and len(word) >= 1: # 빈 λ¬Έμžμ—΄μ΄ μ•„λ‹ˆκ³  1κΈ€μž 이상인 경우만 μΆ”κ°€
cleaned.append(word)
else:
# ν•œκΈ€λ§Œ μΆ”μΆœ μ˜΅μ…˜μ΄ κΊΌμ§„ 경우 - 단어 ν†΅μ§Έλ‘œ 처리
# 곡백과 μ‰Όν‘œλ‘œ κ΅¬λΆ„ν•˜μ—¬ 단어 전체λ₯Ό μœ μ§€
words = re.split(r"[,\s]+", text)
cleaned = []
for word in words:
word = word.strip()
if word and len(word) >= 1: # 빈 λ¬Έμžμ—΄μ΄ μ•„λ‹ˆκ³  1κΈ€μž 이상인 경우만 μΆ”κ°€
cleaned.append(word)
return cleaned
def filter_keywords_with_gemini(pairs, gemini_model=None):
"""Gemini AIλ₯Ό μ‚¬μš©ν•˜μ—¬ ν‚€μ›Œλ“œ μ‘°ν•© 필터링 (κ°œμ„ λ²„μ „) - API ν‚€ 톡합 관리"""
if gemini_model is None:
# api_utilsμ—μ„œ Gemini λͺ¨λΈ κ°€μ Έμ˜€κΈ°
gemini_model = get_gemini_model()
if gemini_model is None:
logger.error("Gemini λͺ¨λΈμ„ κ°€μ Έμ˜¬ 수 μ—†μŠ΅λ‹ˆλ‹€. λͺ¨λ“  ν‚€μ›Œλ“œλ₯Ό μœ μ§€ν•©λ‹ˆλ‹€.")
# μ•ˆμ „ν•˜κ²Œ 처리: λͺ¨λ“  ν‚€μ›Œλ“œλ₯Ό μœ μ§€
all_keywords = set()
for pair in pairs:
for keyword in pair:
all_keywords.add(keyword)
return list(all_keywords)
# λͺ¨λ“  ν‚€μ›Œλ“œλ₯Ό λͺ©λ‘μœΌλ‘œ μΆ”μΆœ (제거된 ν‚€μ›Œλ“œ ν™•μΈμš©)
all_keywords = set()
for pair in pairs:
for keyword in pair:
all_keywords.add(keyword)
# λ„ˆλ¬΄ λ§Žμ€ 쌍이 있으면 μ œν•œ
max_pairs = 50 # μ΅œλŒ€ 50개 쌍만 처리
pairs_to_process = list(pairs)[:max_pairs] if len(pairs) > max_pairs else pairs
logger.info(f"필터링할 ν‚€μ›Œλ“œ 쌍: 총 {len(pairs)}개 쀑 {len(pairs_to_process)}개 처리")
# 보수적인 ν”„λ‘¬ν”„νŠΈ μ‚¬μš© - ν‚€μ›Œλ“œ 제거 μ΅œμ†Œν™”
prompt = (
"λ‹€μŒμ€ μ†ŒλΉ„μžκ°€ 검색할 κ°€λŠ₯성이 μžˆλŠ” ν‚€μ›Œλ“œ 쌍 λͺ©λ‘μž…λ‹ˆλ‹€.\n"
"각 μŒμ€ 같은 단어 μ‘°ν•©μ΄μ§€λ§Œ μˆœμ„œλ§Œ λ‹€λ₯Έ κ²½μš°μž…λ‹ˆλ‹€ (예: μ†μ§ˆμ˜€μ§•μ–΄ vs μ˜€μ§•μ–΄μ†μ§ˆ).\n\n"
"μ•„λž˜μ˜ 기쀀에 따라 각 μŒμ—μ„œ 더 μžμ—°μŠ€λŸ¬μš΄ ν‚€μ›Œλ“œλ₯Ό μ„ νƒν•΄μ£Όμ„Έμš”:\n"
"1. μ†ŒλΉ„μžκ°€ μΌμƒμ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” μžμ—°μŠ€λŸ¬μš΄ ν‘œν˜„μ„ μš°μ„  μ„ νƒν•˜μ„Έμš”.\n"
"2. 두 ν‚€μ›Œλ“œκ°€ λͺ¨λ‘ μžμ—°μŠ€λŸ½κ±°λ‚˜ μ˜λ―Έκ°€ μ•½κ°„ λ‹€λ₯΄λ‹€λ©΄, λ°˜λ“œμ‹œ λ‘˜ λ‹€ μœ μ§€ν•˜μ„Έμš”.\n"
"3. ν™•μ‹€νžˆ λΉ„μžμ—°μŠ€λŸ½κ±°λ‚˜ μ–΄μƒ‰ν•œ κ²½μš°μ—λ§Œ μ œκ±°ν•˜μ„Έμš”.\n"
"4. λΆˆν™•μ‹€ν•œ κ²½μš°μ—λŠ” λ°˜λ“œμ‹œ ν‚€μ›Œλ“œλ₯Ό μœ μ§€ν•˜μ„Έμš”.\n"
"5. μˆ«μžλ‚˜ μ˜μ–΄κ°€ ν¬ν•¨λœ ν‚€μ›Œλ“œλŠ” ν•œκΈ€ 메인 ν‚€μ›Œλ“œκ°€ μ•žμͺ½μ— μ˜€λŠ” ν˜•νƒœλ₯Ό μ„ νƒν•˜μ„Έμš”. (예: '10kg μ˜€μ§•μ–΄' 보닀 'μ˜€μ§•μ–΄ 10kg' 선택)\n"
"6. κ²€μƒ‰λŸ‰μ΄ 0인 ν‚€μ›Œλ“œλΌλ„ 일상적인 ν‘œν˜„μ΄λΌλ©΄ κ°€λŠ₯ν•œ μœ μ§€ν•˜μ„Έμš”. λͺ…λ°±ν•˜κ²Œ 비정상적인 ν‘œν˜„λ§Œ μ œκ±°ν•˜μ„Έμš”.\n\n"
"주의: 기본적으둜 λŒ€λΆ€λΆ„μ˜ ν‚€μ›Œλ“œλ₯Ό μœ μ§€ν•˜κ³ , 맀우 λͺ…ν™•ν•˜κ²Œ λΉ„μžμ—°μŠ€λŸ¬μš΄ κ²ƒλ§Œ μ œκ±°ν•˜μ„Έμš”.\n\n"
"κ²°κ³ΌλŠ” λ‹€μŒ ν˜•μ‹μœΌλ‘œ μ œκ³΅ν•΄μ£Όμ„Έμš”:\n"
"- μ„ νƒλœ ν‚€μ›Œλ“œ (이유: μžμ—°μŠ€λŸ¬μš΄ ν‘œν˜„μ΄κΈ° λ•Œλ¬Έ)\n"
"- μ„ νƒλœ ν‚€μ›Œλ“œ1, μ„ νƒλœ ν‚€μ›Œλ“œ2 (이유: λ‘˜ λ‹€ μžμ—°μŠ€λŸ½κ³  μ˜λ―Έκ°€ 쑰금 닀름)\n\n"
)
# ν‚€μ›Œλ“œ 쌍 λͺ©λ‘
formatted = "\n".join([f"- {a}, {b}" for a, b in pairs_to_process])
full_prompt = prompt + formatted
try:
# νƒ€μž„μ•„μ›ƒ μΆ”κ°€
logger.info(f"Gemini API 호좜 μ‹œμž‘ - {len(pairs_to_process)}개 ν‚€μ›Œλ“œ 쌍 처리 쀑...")
# 응닡 λ°›κΈ° (νƒ€μž„μ•„μ›ƒ κΈ°λŠ₯이 있으면 μΆ”κ°€)
response = gemini_model.generate_content(full_prompt)
logger.info("Gemini API 응닡 성곡")
lines = response.text.strip().split("\n")
# μ„ νƒλœ ν‚€μ›Œλ“œ μΆ”μΆœ (μ‰Όν‘œλ‘œ κ΅¬λΆ„λœ 경우 λͺ¨λ‘ 포함)
final_keywords = []
for line in lines:
if line.startswith("-"):
# 이유 λΆ€λΆ„ 제거
keywords_part = line.strip("- ").split("(이유:")[0].strip()
# μ‰Όν‘œλ‘œ κ΅¬λΆ„λœ ν‚€μ›Œλ“œ λͺ¨λ‘ μΆ”κ°€
for kw in keywords_part.split(","):
kw = kw.strip()
if kw:
final_keywords.append(kw)
# μ²˜λ¦¬λ˜μ§€ μ•Šμ€ 쌍의 첫 번째 ν‚€μ›Œλ“œλ„ μΆ”κ°€ (LLM이 μ²˜λ¦¬ν•˜μ§€ μ•Šμ€ ν‚€μ›Œλ“œ)
if len(pairs) > max_pairs:
logger.info(f"μΆ”κ°€ ν‚€μ›Œλ“œ 처리: 남은 {len(pairs) - max_pairs}개 쌍의 첫 번째 ν‚€μ›Œλ“œ μΆ”κ°€")
for pair in list(pairs)[max_pairs:]:
# 각 쌍의 첫 번째 ν‚€μ›Œλ“œλ§Œ μ‚¬μš©
final_keywords.append(pair[0])
# μ„ νƒλœ ν‚€μ›Œλ“œκ°€ μ—†μœΌλ©΄ κΈ°μ‘΄ ν‚€μ›Œλ“œ λͺ¨λ‘ λ°˜ν™˜
if not final_keywords:
logger.warning("κ²½κ³ : μ„ νƒλœ ν‚€μ›Œλ“œκ°€ μ—†μ–΄ λͺ¨λ“  ν‚€μ›Œλ“œλ₯Ό μœ μ§€ν•©λ‹ˆλ‹€.")
final_keywords = list(all_keywords)
# μˆœμ„œ κ°•μ œ μˆ˜μ •
corrected_keywords = []
# λ‹¨μœ„μ™€ 숫자 κ΄€λ ¨ μ •κ·œμ‹ νŒ¨ν„΄
unit_pattern = re.compile(r'(?i)(kg|g|mm|cm|ml|l|리터|개|팩|λ°•μŠ€|μ„ΈνŠΈ|2l|l2)')
number_pattern = re.compile(r'\d+')
for kw in final_keywords:
# 곡백으둜 뢄리
if ' ' in kw:
parts = kw.split()
first_part = parts[0]
# 첫 뢀뢄이 λ‹¨μœ„λ‚˜ 숫자λ₯Ό ν¬ν•¨ν•˜λŠ”μ§€ 확인
if (unit_pattern.search(first_part) or number_pattern.search(first_part)) and len(parts) > 1:
# μˆœμ„œ λ°”κΎΈκΈ°: λ‹¨μœ„/숫자 뢀뢄을 λ’€λ‘œ 이동
corrected_kw = " ".join(parts[1:] + [first_part])
logger.info(f"ν‚€μ›Œλ“œ μˆœμ„œ κ°•μ œ μˆ˜μ •: '{kw}' -> '{corrected_kw}'")
corrected_keywords.append(corrected_kw)
else:
corrected_keywords.append(kw)
else:
corrected_keywords.append(kw)
# νŠΉλ³„ 처리: "L μ˜€μ§•μ–΄", "2L μ˜€μ§•μ–΄" 같은 경우λ₯Ό λͺ…μ‹œμ μœΌλ‘œ ν™•μΈν•˜κ³  μˆ˜μ •
specific_fixes = []
for kw in corrected_keywords:
# νŠΉμ • νŒ¨ν„΄ 체크
l_pattern = re.compile(r'^([0-9]*L) (.+)$', re.IGNORECASE)
match = l_pattern.match(kw)
if match:
# L λ‹¨μœ„λ₯Ό λ’€λ‘œ 이동
l_part = match.group(1)
main_part = match.group(2)
fixed_kw = f"{main_part} {l_part}"
logger.info(f"특수 νŒ¨ν„΄ μˆ˜μ •: '{kw}' -> '{fixed_kw}'")
specific_fixes.append(fixed_kw)
else:
specific_fixes.append(kw)
# 제거된 ν‚€μ›Œλ“œ λͺ©λ‘ 확인
selected_set = set(specific_fixes)
removed_keywords = all_keywords - selected_set
# 제거된 ν‚€μ›Œλ“œ 좜λ ₯
logger.info("\n=== LLM에 μ˜ν•΄ 제거된 ν‚€μ›Œλ“œ λͺ©λ‘ ===")
for kw in removed_keywords:
logger.info(f" - {kw}")
logger.info(f"총 {len(all_keywords)}개 쀑 {len(removed_keywords)}개 제거됨 ({len(selected_set)}개 μœ μ§€)\n")
return specific_fixes
except Exception as e:
logger.error(f"Gemini 였λ₯˜: {e}")
logger.error("였λ₯˜ λ°œμƒμœΌλ‘œ 인해 λͺ¨λ“  ν‚€μ›Œλ“œλ₯Ό μœ μ§€ν•©λ‹ˆλ‹€.")
logger.error(f"였λ₯˜ μœ ν˜•: {type(e).__name__}")
import traceback
traceback.print_exc()
# μ•ˆμ „ν•˜κ²Œ 처리: λͺ¨λ“  ν‚€μ›Œλ“œλ₯Ό μœ μ§€
logger.info(f"μ•ˆμ „ λͺ¨λ“œ: {len(all_keywords)}개 ν‚€μ›Œλ“œ λͺ¨λ‘ μœ μ§€")
return list(all_keywords)
def get_search_volume_range(total_volume):
"""총 κ²€μƒ‰λŸ‰μ„ 기반으둜 κ²€μƒ‰λŸ‰ ꡬ간을 λ°˜ν™˜"""
if total_volume == 0:
return "100미만"
elif total_volume <= 100:
return "100미만"
elif total_volume <= 1000:
return "1000미만"
elif total_volume <= 2000:
return "2000미만"
elif total_volume <= 5000:
return "5000미만"
elif total_volume <= 10000:
return "10000미만"
else:
return "10000이상"