Compare commits

..

No commits in common. "master" and "v1.0.9" have entirely different histories.

18 changed files with 129 additions and 1569 deletions

View File

@ -56,7 +56,7 @@ exe = EXE(
upx=True, upx=True,
upx_exclude=[], upx_exclude=[],
runtime_tmpdir=None, runtime_tmpdir=None,
console=True, console=False,
onefile=False, onefile=False,
disable_windowed_traceback=False, disable_windowed_traceback=False,
argv_emulation=False, argv_emulation=False,

View File

@ -208,35 +208,3 @@ class ImageDescriptionGenerator:
except Exception as e: except Exception as e:
logger.debug(f"예상치 못한 오류 발생: {e}", exc_info=True) logger.debug(f"예상치 못한 오류 발생: {e}", exc_info=True)
return "예상치 못한 오류 발생: 처리할 수 없습니다." return "예상치 못한 오류 발생: 처리할 수 없습니다."
def generate_option_description(self, product_info, top_k=3):
product_info.option_datas
prompt = f'''
나는 타오바오에서 상품을 가져와서 한국의 온라인 쇼핑몰에서 구매대행업을 하는 사업가야.
너는 온라인 쇼핑몰 상세페이지 제작 전문가야. 니가 만든 상품페이지는 모두를 감동시키고, 구매로 이어지는 마법같은 능력이 있어.
여기 중국어로 상품의 옵션들이 있어.
다음 옵션 목록에서 옵션의 고유한 특징만 추출하여 한국어로 번역하고, 최대 45바이트를 넘지 않도록 간결하게 작성해주세요. 특수 문자는 제외하고 허용되는 특수 문자(!$~()._-=+/) 포함해주세요.
하나씩 차근차근 진행해보자.
[정보]
중국어 옵션 목록은 다음과 같아. '{product_info.option_datas}'
[출력형식]
product_info.option_datas['name']
[
{"options1": "특징1"},
{"options2": "특징2"},
...
]
options_info[f'option_type_{idx}'] = {
'name': option_type_name,
'items_count': option_items_count,
'items': option_items_info
}
'''

View File

@ -24,26 +24,18 @@ logger = logging.getLogger('default_logger')
avg_price = 0 avg_price = 0
def detail_ai(driver, textarea, aicontents, combined_value, product_info, isTransSuccess): def detail_ai(driver, textarea, aicontents, combined_value):
try: try:
logger.debug("detail_ai 맞추기") logger.debug("detail_ai 맞추기")
aicontent_html = aicontents aicontent_html = aicontents
time.sleep(0.5) time.sleep(0.5)
current_value = product_info.current_value current_value = textarea.get_attribute("data-value")
# current_value = textarea.get_attribute("data-value") logger.debug("current_value에서 기존 이미지 삭제")
# logger.debug("current_value에서 기존 이미지 삭제")
# deleted_original_html_tags = original_html(current_value) # deleted_original_html_tags = original_html(current_value)
deleted_original_html_tags = remove_resized_images(current_value)
if isTransSuccess: ai_value = combined_value + aicontent_html + deleted_original_html_tags
logger.debug(f"isTransSuccess : {isTransSuccess} 이므로 기존 이미지 삭제")
modified_html_tags = product_info.current_value
# modified_html_tags = remove_resized_content(current_value)
else:
modified_html_tags = product_info.deleted_value
logger.debug(f"isTransSuccess : {isTransSuccess} 이므로 기존 이미지 유지")
ai_value = combined_value + aicontent_html + modified_html_tags
# ai_value = combined_value + aicontent_html + current_value # ai_value = combined_value + aicontent_html + current_value
# contents 변수의 값이 None이 아닐 때만 send_keys 메서드를 호출합니다. # contents 변수의 값이 None이 아닐 때만 send_keys 메서드를 호출합니다.
if aicontent_html is not None: if aicontent_html is not None:
@ -329,23 +321,7 @@ def find_delivery_fee(weight, delv_collection):
# product_info_text += f'네이버 상품의 최고 가격은 [{high_price}]' # product_info_text += f'네이버 상품의 최고 가격은 [{high_price}]'
# return product_info_text # return product_info_text
def remove_all_image_tags(current_html): def remove_resized_content(current_html):
# BeautifulSoup 객체 생성
soup = BeautifulSoup(current_html, 'html.parser')
# 모든 img, figure, picture 태그를 찾아 제거
image_tags = soup.find_all(['img', 'figure', 'picture'])
for tag in image_tags:
tag.decompose() # 태그 자체를 문서에서 제거
# 수정된 HTML을 문자열로 변환하여 반환
deleted_html = str(soup)
return deleted_html
def remove_resized_content_ori(current_html):
# BeautifulSoup 객체 생성 # BeautifulSoup 객체 생성
soup = BeautifulSoup(current_html, 'html.parser') soup = BeautifulSoup(current_html, 'html.parser')
@ -364,24 +340,6 @@ def remove_resized_content_ori(current_html):
return modified_html return modified_html
def remove_resized_content(current_html):
# BeautifulSoup 객체 생성
soup = BeautifulSoup(current_html, 'html.parser')
# "img" 태그 중 src 속성이 'https://img.alicdn.com'로 시작하는 것을 찾아 모두 삭제
images_to_remove = soup.find_all('img', src=lambda x: x and x.startswith('https://img.alicdn.com'))
images_to_remove_percenty = soup.find_all('img', src=lambda x: x and x.startswith('https://file.percenty.co.kr'))
logger.debug(f"https://img.alicdn.com 의갯수 : [{len(images_to_remove)}] 개")
logger.debug(f"https://file.percenty.co.kr 의갯수 : [{len(images_to_remove_percenty)}] 개")
for img in images_to_remove:
img.decompose() # img 태그 자체를 문서에서 제거
# 수정된 HTML을 문자열로 변환하여 반환
modified_html = str(soup)
logger.debug("기존이미지 삭제 완료")
return modified_html
def original_html(current_html): def original_html(current_html):
# BeautifulSoup 객체 생성 # BeautifulSoup 객체 생성
soup = BeautifulSoup(current_html, 'html.parser') soup = BeautifulSoup(current_html, 'html.parser')
@ -655,8 +613,7 @@ def modify_detail_page(driver, product_info, gemini, translator, delv_collection
except Exception as e: except Exception as e:
logger.debug(f"가격정보 생성 중 에러 : {e}", exc_info=True) logger.debug(f"가격정보 생성 중 에러 : {e}", exc_info=True)
weight_text = ""
cost_add_text = ""
try: try:
logger.debug("HTML 수정 버튼 클릭 .") logger.debug("HTML 수정 버튼 클릭 .")
@ -700,29 +657,22 @@ def modify_detail_page(driver, product_info, gemini, translator, delv_collection
# # data-value 속성 값을 가져옵니다. # # data-value 속성 값을 가져옵니다.
current_value = textarea.get_attribute("data-value") current_value = textarea.get_attribute("data-value")
product_info.current_value = current_value product_info.current_value = current_value
logger.debug("현재 속성값 수집") logger.debug("현재 속성값 수집 완료")
# original_html_tags = original_html(current_value) # original_html_tags = original_html(current_value)
detail_images = fetch_image_urls(current_value) detail_images = fetch_image_urls(current_value)
product_info.detail_image_urls = detail_images product_info.detail_image_urls = detail_images
logger.debug("현재 속성값에서 이미지 URL 추출")
logger.debug(f"원본 이미지 URLs [{len(detail_images)}]개 수집 완료") logger.debug(f"원본 이미지 URLs [{len(detail_images)}]개 수집 완료")
deleted_value = remove_all_image_tags(current_value)
product_info.deleted_value = deleted_value
logger.debug("현재 속성값에서 이미지 제거")
# logger.debug(f"detail_images List : {detail_images}") # logger.debug(f"detail_images List : {detail_images}")
# logger.debug(f"현재 속성값 :{current_value}") # logger.debug(f"현재 속성값 :{current_value}")
# if not simpleMode: # 심플모드일 경우 상품카드 작성하지 않음 # if not simpleMode: # 심플모드일 경우 상품카드 작성하지 않음
logger.debug("product_info_card 결합.") logger.debug("product_info_card 결합.")
# info_value = product_info_card + '<br><br>' + cost_add_text + '<br><br>' # info_value = product_info_card + '<br><br>' + cost_add_text + '<br><br>'
if not weight_text: info_value = product_info_card + '<br><br>' + weight_text + '<br><br>'
weight_text =""
combined_value = product_info_card + '<br><br>' + weight_text + '<br><br>'
# textarea.send_keys(product_info_card) # textarea.send_keys(product_info_card)
time.sleep(0.2) time.sleep(0.2)
combined_value = info_value
logger.debug(f"{combined_value}") logger.debug(f"{combined_value}")
@ -781,14 +731,11 @@ def modify_detail_page(driver, product_info, gemini, translator, delv_collection
trans_img_tag = login_info["whether_modifyImageTanslation"] trans_img_tag = login_info["whether_modifyImageTanslation"]
logger.debug(f"whether_modifyImageTanslation : {trans_img_tag}") logger.debug(f"whether_modifyImageTanslation : {trans_img_tag}")
isTransSuccess = False # 이미지 번역 성공여부
if trans_img_tag: if trans_img_tag:
logger.debug("상세페이지 이미지 번역 시작") logger.debug("상세페이지 이미지 번역 시작")
for i in range(30): for i in range(30):
detail_content.send_keys(Keys.PAGE_DOWN) detail_content.send_keys(Keys.PAGE_DOWN)
detail_content.send_keys(Keys.DOWN) detail_content.send_keys(Keys.DOWN)
logger.debug("이미지 번역을 위해 페이지 최하단 이동")
# detail_content.send_keys(Keys.HOME) # detail_content.send_keys(Keys.HOME)
detail_content.send_keys(Keys.DOWN) detail_content.send_keys(Keys.DOWN)
@ -802,34 +749,61 @@ def modify_detail_page(driver, product_info, gemini, translator, delv_collection
for i, detail_image in enumerate(detail_images): for i, detail_image in enumerate(detail_images):
logger.debug(f"상세페이지 [{i+1}]/[{len(detail_images)}]번째 이미지 번역 시작") logger.debug(f"상세페이지 [{i+1}]/[{len(detail_images)}]번째 이미지 번역 시작")
logger.debug(f"이미지 타입 : {type(detail_image)}") logger.debug(f"이미지 타입 : {type(detail_image)}")
returned_img = image_trans(detail_image, translator, 'translate', logger) returned_img = image_trans(detail_image, translator, 'translate')
if returned_img:
image_to_clipboard(returned_img) image_to_clipboard(returned_img)
logger.debug("번역 완료 및 부텨넣기") logger.debug("번역 완료 및 부텨넣기")
detail_content.send_keys(Keys.CONTROL, 'v') detail_content.send_keys(Keys.CONTROL, 'v')
detail_content.send_keys(Keys.ENTER) detail_content.send_keys(Keys.ENTER)
logger.debug(f"{i+1}번째 이미지 붙여넣기 완료") logger.debug(f"{i+1}번째 이미지 붙여넣기 완료")
else:
logger.warning(f"[{i+1}]/[{len(detail_images)}]번째 이미지 실패")
# translated_image_urls.append(returned_img_path) # translated_image_urls.append(returned_img_path)
logger.debug("이미지 번역 완료") logger.debug("이미지 번역 완료")
detail_content.send_keys(Keys.ENTER) detail_content.send_keys(Keys.ENTER)
time.sleep(0.5) time.sleep(0.5)
isTransSuccess = True
except Exception as e: except Exception as e:
logger.error(f"이미지 번역 중 에러발생 : {e}", exc_info=True) logger.error(f"이미지 번역 중 에러발생 : {e}", exc_info=True)
isTransSuccess = False
finally: finally:
# num_detail_images = len(detail_images)
# logger.debug(f"====기존 이미지 삭제=====\n DEL키 [{num_detail_images}]번 보내기")
# # DELETE 키를 50번 보내기
# for _ in range(num_detail_images + 5):
# detail_content.send_keys(Keys.DELETE)
logger.debug("====번역이미지 붙여넣기 완료=====") logger.debug("====번역이미지 붙여넣기 완료=====")
# try:
# logger.debug("aicontent_html 입력시작")
# html_insert_btn_xpath="//div[@id='productMainContentContainerId']/div/div/div[2]/div[2]/div/div[1]/div[2]/div/div/button[7]"
# click_element(driver, 'XPATH', html_insert_btn_xpath, 5, 'ac')
# logger.debug("html_insert_btn_xpath 클릭")
# html_insert_TEXTAREA_xpath="/html/body/div[7]/div/div[3]/div/div[2]/div[1]/div/div/div[2]/div[2]/div[2]/div/div[2]/div[2]/textarea"
# click_element(driver, 'XPATH', html_insert_TEXTAREA_xpath, 5, 'ac')
# html_insert_TEXTAREA_element = return_element(driver, 'XPATH', 'html_insert_TEXTAREA_xpath', 10)
# html_insert_TEXTAREA_element.send_keys(aicontents)
# logger.debug("html 요소로 전송")
# html_insert_ok_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div[2]/div[2]/div/div[1]/div[2]/div/button[1]"
# click_element(driver, 'XPATH', html_insert_ok_xpath, 5, 'ac')
# logger.debug("html_insert_ok_xpath 클릭")
# logger.debug(f"AI 컨텐츠 입력완료")
# except Exception as e:
# logger.error(f"AI 컨텐츠 입력 중 에러발생 : {e}", exc_info=True)
detail_html(driver) detail_html(driver)
time.sleep(0.5) time.sleep(0.5)
textarea = detail_textarea(driver) textarea = detail_textarea(driver)
time.sleep(0.5) time.sleep(0.5)
detail_ai(driver, textarea, aicontents, combined_value, product_info ,isTransSuccess) detail_ai(driver, textarea, aicontents, combined_value)
time.sleep(0.5) time.sleep(0.5)
detail_html(driver) detail_html(driver)
time.sleep(0.5) time.sleep(0.5)

View File

@ -12,81 +12,10 @@ from ai.deepl_with_playwright import trans_text, trans_list_text
import re import re
import logging import logging
import tempfile
from img_trans.image_trans import image_trans
# 로거 인스턴스 가져오기 # 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger') logger = logging.getLogger('default_logger')
def modify_option_page2(driver, product_info, gemini, translator, login_info): def modify_option_page(driver, product_info, translator, login_info):
# 1. 현재 옵션에 대한 정보 수집
options_info = collect_option_data(driver)
# 2. 옵션타입이 단일옵션상품이 아닌지 체크
if not is_single_option_product(driver):
# 3. 각 옵션타입별로 낮은가격순으로 정렬
sort_options_by_price(driver, options_info)
# 4. 미끼상품 체크 및 제외
options_info = filter_out_bait_items(options_info)
# 5. 각 옵션타입별로 옵션아이템 가격에 따라 상품 선택
select_valid_options(driver, options_info, login_info['whether_simpleMode'])
# 6. 선택된 옵션들의 원본 옵션명과 옵션이미지 수집
option_names, option_images = collect_option_names_and_images(driver, options_info)
# 7. 수집된 원본 옵션명은 AI 처리 메서드를 통해 번역 및 편집 후 입력
update_option_names(driver, option_names, translator)
# 8. 수집된 옵션 이미지 URL은 이미지 번역 및 업로드 과정 수행
update_option_images(driver, option_images, translator)
# 기타 작업이 필요한 경우 추가
finalize_modifications(driver, options_info)
# 저장 작업 등
save_changes(driver)
def sort_options_by_price(driver, options_info):
for idx in range(1, len(options_info) + 1):
try:
# 옵션 타입별로 낮은가격순 정렬
sort_button_xpath = f"//div[{idx}]/div/div/div[2]/div/div/div[4]/div[2]/div[2]/div/div[4]/button"
click_element(driver, 'XPATH', sort_button_xpath, 10, 'js')
logger.debug(f"옵션 타입 {idx} 낮은 가격순 정렬 완료")
except Exception as e:
logger.error(f"옵션 타입 {idx} 낮은 가격순 정렬 중 오류 발생: {e}", exc_info=True)
def filter_out_bait_items(options_info):
for option_type_key, option_data in options_info.items():
item_prices = []
for item in option_data['option_elements']:
price_css = "div:nth-child(3) > div:nth-child(1) > div:nth-child(2) > span > sup"
price_element = item.find_element(By.CSS_SELECTOR, price_css)
price_text = price_element.get_attribute('title').replace(',', '').replace('', '').strip()
if '-' in price_text:
min_price, max_price = map(int, price_text.split('-'))
else:
min_price = max_price = int(price_text)
item_prices.append((min_price, max_price))
# 미끼 상품 필터링 로직 적용
valid_items = []
base_prices = sorted([price[0] for price in item_prices])
for base_price in base_prices:
threshold = base_price * 1.5
valid_items = [price for price in item_prices if price[0] <= threshold]
if len(valid_items) > 1:
break
options_info[option_type_key]['valid_items'] = valid_items
return options_info
def modify_option_page(driver, product_info, gemini, translator, login_info):
simpleMode = login_info['whether_simpleMode'] simpleMode = login_info['whether_simpleMode']
option_css = ".ant-tabs-tab:nth-child(2)" option_css = ".ant-tabs-tab:nth-child(2)"
@ -119,48 +48,41 @@ def modify_option_page(driver, product_info, gemini, translator, login_info):
logger.debug("옵션타입 체크") logger.debug("옵션타입 체크")
# # 옵션 타입 처리 # 옵션 타입 처리
# for i in range(1, 4): # 옵션 타입이 1부터 3까지 존재한다고 가정합니다. for i in range(1, 4): # 옵션 타입이 1부터 3까지 존재한다고 가정합니다.
# option_type_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{i}]/div/div/div[2]/div/div/div[2]/div/span/input" option_type_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{i}]/div/div/div[2]/div/div/div[2]/div/span/input"
# option_type = return_element(driver, 'XPATH', option_type_xpath, 3) option_type = return_element(driver, 'XPATH', option_type_xpath, 3)
# if option_type: if option_type:
# option_type_value = option_type.get_attribute('value') option_type_value = option_type.get_attribute('value')
# options_info[f'option_type_{i}'] = {'options_name': option_type_value, 'options_count': 0, 'price_ranges': []} options_info[f'option_type_{i}'] = {'options_name': option_type_value, 'options_count': 0, 'price_ranges': []}
# logger.debug(f"옵션 타입 추가: {option_type_value}") logger.debug(f"옵션 타입 추가: {option_type_value}")
# else: else:
# logger.debug(f"{option_type_xpath} 요소를 찾을 수 없음.") logger.debug(f"{option_type_xpath} 요소를 찾을 수 없음.")
# option_type_nums = sum(1 for key in options_info.keys() if key.startswith('option_type_')) option_type_nums = sum(1 for key in options_info.keys() if key.startswith('option_type_'))
# logger.debug(f"총 옵션 타입 수: {option_type_nums}") logger.debug(f"총 옵션 타입 수: {option_type_nums}")
options_info = collect_option_data(driver)
logger.debug(f"수집된 옵션 정보: {options_info}")
option_type_nums = len(options_info)
time.sleep(30)
logger.debug(f"============================== 잠시 대기 =============================")
# 현재 전체 옵션갯수 가져오기 # 현재 전체 옵션갯수 가져오기
# 각 옵션 타입별로 XPath를 문자열 포맷을 사용하여 한 줄로 작성 # 각 옵션 타입별로 XPath를 문자열 포맷을 사용하여 한 줄로 작성
# option_num_xpaths = [ option_num_xpaths = [
# "//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{}]/div/div/div[2]/div/div/div[4]/div/div/label/span[2]".format(idx) "//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{}]/div/div/div[2]/div/div/div[4]/div/div/label/span[2]".format(idx)
# for idx in range(1, 4) # 옵션 타입이 3개까지 있음 for idx in range(1, 4) # 옵션 타입이 3개까지 있음
# ] ]
# 각 옵션 타입의 갯수를 가져와서 options_info에 업데이트 # 각 옵션 타입의 갯수를 가져와서 options_info에 업데이트
# for idx, option_num_xpath in enumerate(option_num_xpaths[:option_type_nums], start=1): for idx, option_num_xpath in enumerate(option_num_xpaths[:option_type_nums], start=1):
# try: try:
# option_num_element = return_element(driver, 'XPATH', option_num_xpath, 5) option_num_element = return_element(driver, 'XPATH', option_num_xpath, 5)
# if option_num_element: if option_num_element:
# option_number = int(re.search(r'\d+', option_num_element.text).group()) option_number = int(re.search(r'\d+', option_num_element.text).group())
# logger.debug(f"옵션타입 [{idx}] 의 갯수 : {option_number}") logger.debug(f"옵션타입 [{idx}] 의 갯수 : {option_number}")
# options_info[f'option_type_{idx}']['options_count'] = option_number options_info[f'option_type_{idx}']['options_count'] = option_number
# else: else:
# logger.debug(f"옵션타입 {idx} 요소를 찾을 수 없음.") logger.debug(f"옵션타입 {idx} 요소를 찾을 수 없음.")
# options_info[f'option_type_{idx}']['options_count'] = None options_info[f'option_type_{idx}']['options_count'] = None
# except Exception as e: except Exception as e:
# logger.error(f"옵션 타입 {idx} 갯수를 가져오는 중 오류 발생: {e}", exc_info=True) logger.error(f"옵션 타입 {idx} 갯수를 가져오는 중 오류 발생: {e}", exc_info=True)
# # 가격 낮은 순으로 정렬 # # 가격 낮은 순으로 정렬
# logger.debug("가격 낮은 순으로 정렬") # logger.debug("가격 낮은 순으로 정렬")
@ -228,16 +150,13 @@ def modify_option_page(driver, product_info, gemini, translator, login_info):
try: try:
logger.debug(f"옵션타입 {option_idx}에 대한 처리 시작") logger.debug(f"옵션타입 {option_idx}에 대한 처리 시작")
logger.debug("가격 낮은 순으로 정렬") logger.debug("가격 낮은 순으로 정렬")
# low_price_order_xpath = f"//div[{option_idx}]/div/div/div[2]/div/div/div[4]/div[2]/div[2]/div/div[4]/button" low_price_order_xpath = f"//div[{option_idx}]/div/div/div[2]/div/div/div[4]/div[2]/div[2]/div/div[4]/button"
# low_price_order_css = f"div#productMainContentContainerId div:nth-child(2) > div > button[type=\"button\"]:nth-child(3)" click_element(driver, 'XPATH', low_price_order_xpath, 10, 'js')
low_price_order_css = f"div#productMainContentContainerId div:nth-child({option_idx}) > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-cAkrUM.flTgaK > div.ant-row.ant-row-space-between.ant-row-middle.css-1li46mu > div:nth-child(2) > div > button[type=\"button\"]:nth-child(3)"
# click_element(driver, 'XPATH', low_price_order_xpath, 10, 'js')
click_element(driver, 'CSS_SELECTOR', low_price_order_css, 10, 'js')
if not simpleMode: # 심플모드일 경우 옵션 편집하지 않음. 옵션 번역만. if not simpleMode: # 심플모드일 경우 옵션 편집하지 않음. 옵션 번역만.
edit_option(driver, option_idx, options_info[f'option_type_{option_idx}']['options_count']) # 각 옵션의 전체 체크박스 해제 edit_option(driver, option_idx, options_info[f'option_type_{option_idx}']['options_count']) # 각 옵션의 전체 체크박스 해제
# 가격 낮은 순으로 재정렬 # 가격 낮은 순으로 재정렬
# logger.debug("가격 낮은 순으로 재정렬") logger.debug("가격 낮은 순으로 재정렬")
# click_element(driver, 'XPATH', low_price_order_xpath, 10, 'js') click_element(driver, 'XPATH', low_price_order_xpath, 10, 'js')
option_name_trans(driver, translator, product_info, option_idx, options_info[f'option_type_{option_idx}']['options_count'], allowed_special_chars, special_char_replacements) option_name_trans(driver, translator, product_info, option_idx, options_info[f'option_type_{option_idx}']['options_count'], allowed_special_chars, special_char_replacements)
except KeyError: except KeyError:
logger.error(f"옵션타입 {option_idx}에 대한 정보가 없습니다.") logger.error(f"옵션타입 {option_idx}에 대한 정보가 없습니다.")
@ -332,13 +251,11 @@ def option_name_trans(driver, translator, product_info, option_type_number, opti
logger.debug("선택된 옵션명 DeepL로 번역 후 새로운 옵션명 입력") logger.debug("선택된 옵션명 DeepL로 번역 후 새로운 옵션명 입력")
ori_optionNames = [] ori_optionNames = []
deepl_trans_optionNames = [] deepl_trans_optionNames = []
# 원본 옵션명 추출 및 이미지 URL 확인 # 원본 옵션명 추출
try: try:
for i in range(1, option_count + 1): for i in range(1, option_count + 1):
if option_type_number == 1: if option_type_number == 1:
ori_optionName_xpath = f"//div[{option_type_number}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[3]/div[3]/span" ori_optionName_xpath = f"//div[{option_type_number}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[3]/div[3]/span"
optionImage_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{option_type_number}]/div/div[2]/div/div[{i}]/div/div[2]/div/img"
deleteButton_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{option_type_number}]/div/div[2]/div/div[{i}]/div/div[2]/div/span"
elif option_type_number == 2: elif option_type_number == 2:
ori_optionName_xpath = f"//div[{option_type_number}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[2]/div[3]/span" ori_optionName_xpath = f"//div[{option_type_number}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[2]/div[3]/span"
else: # option_type_number == 3 else: # option_type_number == 3
@ -349,35 +266,10 @@ def option_name_trans(driver, translator, product_info, option_type_number, opti
cleaned_ori_optionName = replace_or_remove_special_chars(ori_optionName, allowed_special_chars, special_char_replacements) cleaned_ori_optionName = replace_or_remove_special_chars(ori_optionName, allowed_special_chars, special_char_replacements)
logger.debug(f"정제된 {i}번째 원본 옵션명 : {cleaned_ori_optionName}") logger.debug(f"정제된 {i}번째 원본 옵션명 : {cleaned_ori_optionName}")
ori_optionNames.append(cleaned_ori_optionName) ori_optionNames.append(cleaned_ori_optionName)
# 1번 옵션타입의 이미지를 번역하고 업로드
if option_type_number == 1:
try:
option_image_element = return_element(driver, 'XPATH', optionImage_xpath, 3)
if option_image_element:
option_image_url = option_image_element.get_attribute('src')
logger.debug(f"옵션 이미지 URL : {option_image_url}")
translated_image = image_trans(option_image_url, translator, 'translate', logger)
if translated_image:
# 이미지 번역 후 업로드
temp_file_path = save_image_to_tempfile(translated_image)
# 이미지 삭제 버튼 클릭
click_element(driver, 'XPATH', deleteButton_xpath, 5, 'ac')
logger.debug(f"이미지 삭제 버튼 클릭 완료")
# 새로운 이미지 업로드
upload_image(driver)
else:
logger.debug(f"옵션 이미지 번역 실패: {option_image_url}")
else:
logger.debug(f"{i}번째 옵션에 이미지 없음")
except Exception as e:
logger.error(f"옵션 이미지 처리 중 오류 발생: {e}", exc_info=True)
except Exception as e: except Exception as e:
logger.debug(f"원본옵션명 처리중 에러발생 : {e}", exc_info=True) logger.debug(f"원본옵션명 처리중 에러발생 : {e}", exc_info=True)
# 원본 옵션명을 하나의 텍스트로 합치기 # 원본 옵션명을 하나의 텍스트로 합치기
# combined_ori_optionNames = '\n\n'.join(ori_optionNames) # combined_ori_optionNames = '\n\n'.join(ori_optionNames)
@ -498,327 +390,3 @@ def update_price_range(driver, options_info, product_info):
logger.debug(f"전체 옵션의 가격 범위 업데이트: 최소값 {overall_min_price}, 최대값 {overall_max_price}") logger.debug(f"전체 옵션의 가격 범위 업데이트: 최소값 {overall_min_price}, 최대값 {overall_max_price}")
except Exception as e: except Exception as e:
logger.error(f"가격 범위 업데이트 중 오류 발생: {e}", exc_info=True) logger.error(f"가격 범위 업데이트 중 오류 발생: {e}", exc_info=True)
def save_image_to_tempfile(image):
"""
이미지를 임시 파일로 저장합니다.
Args:
- image (PIL.Image): 번역된 이미지 객체
Returns:
- temp_file_path (str): 임시 파일 경로
"""
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
image.save(temp_file, format="PNG")
temp_file_path = temp_file.name
temp_file.close()
logger.debug(f"임시 파일로 저장된 이미지 경로: {temp_file_path}")
return temp_file_path
def upload_image(driver, option_item_index, temp_file_path):
"""
번역된 이미지를 업로드합니다.
Args:
- driver: WebDriver 인스턴스
- option_item_index (int): 옵션 아이템의 인덱스 (1, 2, 3 )
- temp_file_path (str): 임시 파일 경로
"""
try:
# 기존 이미지 삭제 버튼 클릭
delete_button_css = f"div#productMainContentContainerId li:nth-child({option_item_index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div.ant-row.ant-row-no-wrap.ant-row-space-between.ant-row-middle.css-1li46mu > div:nth-child(1) > div > span"
delete_button_element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, delete_button_css))
)
delete_button_element.click()
logger.debug(f"{option_item_index}번째 옵션 아이템의 이미지 삭제 버튼 클릭 완료")
time.sleep(1) # 이미지 삭제 대기
# 이미지 업로드 버튼 클릭
upload_button_css = f"div#productMainContentContainerId li:nth-child({option_item_index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div > img"
upload_button_element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, upload_button_css))
)
upload_button_element.click()
logger.debug(f"{option_item_index}번째 옵션 아이템의 이미지 업로드 버튼 클릭 완료")
# 이미지 업로드 다이얼로그의 파일 업로드 버튼 클릭
file_upload_dialog_css = "span > div > div > div:nth-child(2) > div"
file_upload_dialog_element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, file_upload_dialog_css))
)
file_upload_dialog_element.click()
logger.debug("파일 업로드 버튼 클릭 완료")
# 파일 선택 대화상자에 임시 이미지 파일 경로를 입력하여 업로드
driver.find_element(By.CSS_SELECTOR, 'input[type="file"]').send_keys(temp_file_path)
logger.debug(f"임시 이미지 파일 경로 지정: {temp_file_path}")
# 이미지 삽입 버튼 클릭
insert_button_css = "div.ant-modal-footer > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary"
insert_button_element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, insert_button_css))
)
insert_button_element.click()
logger.debug("이미지 삽입 버튼 클릭 완료")
# 임시 파일 삭제
os.remove(temp_file_path)
logger.debug(f"임시 파일 삭제 완료: {temp_file_path}")
except Exception as e:
logger.error(f"이미지 업로드 중 에러 발생: {e}", exc_info=True)
def collect_option_data(driver):
"""
웹페이지에서 옵션 타입의 갯수와 타입별 옵션 아이템의 갯수를 수집합니다.
Parameters:
- driver: WebDriver 인스턴스
Returns:
- filtered_options_info (dict): 옵션 타입과 타입별 옵션 아이템 갯수를 포함하는 필터링된 딕셔너리
"""
options_info = {}
try:
# 먼저 상품 유형을 확인합니다.
if not is_option_product(driver):
logger.debug("단일 상품이므로 옵션 정보 수집을 생략합니다.")
return options_info # 단일 상품일 경우 빈 딕셔너리를 반환
# 옵션 타입 컨테이너의 CSS 선택자
option_types_container_css = "div#productMainContentContainerId div.sc-bCrHVQ.fRmCVg > div:nth-child(2) > div > div > div:nth-child(2)"
# 옵션 타입 컨테이너 요소를 찾습니다.
option_types_container = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, option_types_container_css))
)
# 옵션 타입 갯수를 추출하기 위한 CSS 선택자
option_type_css = "div.ant-col.css-1li46mu > div.sc-stxIr.eeUZbv"
option_type_elements = option_types_container.find_elements(By.CSS_SELECTOR, option_type_css)
option_type_count = len(option_type_elements)
logger.debug(f"옵션 타입의 총 갯수: {option_type_count}")
for idx in range(1, option_type_count + 1):
# 옵션 타입 이름 추출
# option_type_name_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{idx}]/div/div/div[2]/div/div/div[2]/div/span/input"
# option_type_name_element = WebDriverWait(driver, 10).until(
# EC.presence_of_element_located((By.XPATH, option_type_name_xpath))
# )
# option_type_name = option_type_name_element.get_attribute('value').strip()
option_type_name_css = f"div#productMainContentContainerId div:nth-child({idx}) > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.ant-row.ant-row-no-wrap.ant-row-middle.css-1li46mu > div:nth-child(1) > span > input"
option_type_name__by_css_element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, option_type_name_css))
)
option_type_name = option_type_name__by_css_element.get_attribute('value').strip()
# 옵션 타입별 옵션 아이템 갯수 추출
# option_items_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{idx}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li"
# option_items = driver.find_elements(By.CSS_SELECTOR, option_items_xpath)
# option_items_count = len(option_items)
option_items_css = f"div#productMainContentContainerId div:nth-child({idx}) > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-kjNGdX.cQdCox > div.ant-list.ant-list-split.css-1li46mu > div > div > ul > li"
option_items = driver.find_elements(By.CSS_SELECTOR, option_items_css)
option_items_count = len(option_items)
# 각 옵션 타입별 옵션명, 이미지 및 가격을 수집
option_names_and_images = collect_option_names_and_images_and_price(driver, idx, option_items_count)
options_info[f'option_type_{idx}'] = {
'name': option_type_name,
'items_count': option_items_count,
'items': option_names_and_images
}
logger.debug(f"옵션 타입 {idx}: {option_type_name}, 옵션 아이템 수: {option_items_count}")
for item in option_names_and_images:
logger.debug(f" - 옵션 아이템: 이름={item['name']}, 이미지 URL={item['image_url']}, 가격={item['low_price']} - {item['high_price']}")
except Exception as e:
logger.error(f"옵션 타입 및 아이템 수집 중 오류 발생: {e}", exc_info=True)
return options_info
def collect_option_names_and_images_and_price(driver, option_type_number, option_items_count):
"""
옵션 타입별로 원본 옵션명, 옵션 이미지, 옵션 가격을 수집합니다.
Parameters:
- driver: WebDriver 인스턴스
- option_type_number: 옵션 타입 번호 (1, 2, 3 )
- option_items_count: 해당 옵션 타입의 옵션 아이템 갯수
Returns:
- option_data (list): 옵션명, 이미지 URL, 가격을 포함하는 리스트
"""
option_data = []
try:
for i in range(1, option_items_count + 1):
# 원본 옵션명 수집
option_name_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{option_type_number}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[3]/div[3]/span"
option_name_element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, option_name_xpath))
)
option_name = option_name_element.text.strip()
# 옵션 이미지 URL 수집 (옵션 타입 1에만 옵션이미지 존재)
option_image_url = None
if option_type_number == 1:
option_image_xpath = f"div#productMainContentContainerId li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(2) > div > img"
try:
option_image_element = driver.find_element(By.CSS_SELECTOR, option_image_xpath)
option_image_url = option_image_element.get_attribute('src')
except Exception:
logger.debug(f"{option_type_number}번 옵션타입의 {i}번째 옵션에 이미지 없음")
# 옵션 가격 수집
option_price_xpath = f"div#productMainContentContainerId li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(1) > div:nth-child(2) > span > sup"
try:
option_price_element = driver.find_element(By.CSS_SELECTOR, option_price_xpath)
option_price_text = option_price_element.get_attribute('title') # 또는 .text를 사용할 수도 있음
# 가격 문자열에서 숫자만 추출 (천 단위 구분자를 포함하는 경우 처리)
price_range = re.findall(r'\d+', option_price_text.replace(',', ''))
low_price, high_price = map(int, price_range) if len(price_range) == 2 else (int(price_range[0]), int(price_range[0]))
except Exception:
logger.debug(f"{option_type_number}번 옵션타입의 {i}번째 옵션에 가격 없음")
low_price = high_price = None
option_data.append({
'name': option_name,
'image_url': option_image_url,
'low_price': low_price,
'high_price': high_price
})
logger.debug(f"{option_type_number}번 옵션타입의 옵션 {i}: 이름={option_name}, 이미지 URL={option_image_url}, 가격={low_price} - {high_price}")
except Exception as e:
logger.error(f"옵션명, 이미지 및 가격 수집 중 오류 발생: {e}", exc_info=True)
return option_data
def is_option_product(driver):
"""
웹페이지에서 현재 상품이 '단일 상품'인지 '옵션 상품'인지 확인합니다.
Parameters:
- driver: WebDriver 인스턴스
Returns:
- bool: 옵션 상품이면 True, 단일 상품이면 False
"""
try:
# '옵션 상품등록' 또는 '단일 상품등록' 선택 여부 확인
radio_group_css = "div#productMainContentContainerId div.ant-row.css-1li46mu > div"
radio_group_element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, radio_group_css))
)
# 라디오 버튼의 선택 상태 확인
option_radio_xpath = "//label[span[text()='옵션 상품등록']]//input[@type='radio' and @value='false']"
single_radio_xpath = "//label[span[text()='단일 상품등록']]//input[@type='radio' and @value='true']"
option_radio_checked = radio_group_element.find_element(By.XPATH, option_radio_xpath).is_selected()
single_radio_checked = radio_group_element.find_element(By.XPATH, single_radio_xpath).is_selected()
if option_radio_checked and not single_radio_checked:
logger.debug("상품 유형: 옵션 상품등록")
return True
else:
logger.debug("상품 유형: 단일 상품등록")
return False
except Exception as e:
logger.error(f"상품 유형 확인 중 오류 발생: {e}", exc_info=True)
logger.debug("상품 유형 오류 발생으로 기본설정인 단일 상품등록으로 진행합니다.")
return False # 오류 발생 시 기본적으로 단일 상품으로 처리
def filter_and_select_options(driver, option_type_number, option_data):
"""
미끼 옵션을 제거하고 유효한 옵션을 선택합니다.
Parameters:
- driver: WebDriver 인스턴스
- option_type_number: 옵션 타입 번호 (1, 2, 3 )
- option_data (list): 옵션명, 이미지 URL, 가격을 포함하는 리스트
"""
try:
# 가격 데이터만 추출하여 정렬
prices = sorted([item['low_price'] for item in option_data if item['low_price'] is not None])
if not prices:
logger.error("가격 데이터가 없습니다.")
return
# 미끼 옵션을 식별하여 제거
valid_items = []
for i, base_price in enumerate(prices):
range_limit = base_price * 1.5
within_range = [price for price in prices if price <= range_limit]
if len(within_range) == len(prices):
valid_items = [item for item in option_data if item['low_price'] in within_range]
break
if not valid_items:
valid_items = option_data
# 전체 체크박스 해제
select_all_checkbox_xpath = f"//div[{option_type_number}]/div/div/div[2]/div/div/div[4]/div[2]/div[1]/label/span[1]"
WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, select_all_checkbox_xpath))
).click()
time.sleep(1)
# 필터링된 옵션을 낮은 가격순으로 정렬
valid_items = sorted(valid_items, key=lambda x: x['low_price'])
# 최대 10개 옵션만 선택, 옵션이 5개 이하라면 모든 옵션 선택
for i, item in enumerate(valid_items):
if i >= 10:
break
option_checkbox_xpath = f"//div[{option_type_number}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i + 1}]/div/div[1]/div/div[1]/label/span[1]"
WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, option_checkbox_xpath))
).click()
time.sleep(0.5)
logger.debug(f"옵션 타입 {option_type_number}의 유효한 옵션 아이템 선택 완료")
except Exception as e:
logger.error(f"옵션 선택 중 오류 발생: {e}", exc_info=True)
def click_sort_by_price(driver, option_type_number):
"""
옵션 타입별로 가격 내림차순 정렬 버튼을 클릭합니다.
Parameters:
- driver: WebDriver 인스턴스
- option_type_number: 옵션 타입 번호 (1, 2, 3 )
"""
try:
sort_button_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{option_type_number}]/div/div/div[2]/div/div/div[4]/div[2]/div[2]/div/div[4]/button"
sort_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, sort_button_xpath))
)
sort_button.click()
logger.debug(f"옵션 타입 {option_type_number}의 가격 내림차순 정렬 버튼 클릭 완료")
time.sleep(1) # 정렬이 완료될 시간을 줍니다.
# low_price_order_css = "div#productMainContentContainerId div:nth-child(2) > div > button[type=\"button\"]:nth-child(3)"
# # click_element(driver, 'XPATH', low_price_order_xpath, 10, 'js')
# click_element(driver, 'CSS_SELECTOR', low_price_order_css, 10, 'js')
except Exception as e:
logger.error(f"가격 내림차순 정렬 버튼 클릭 중 오류 발생: {e}", exc_info=True)

View File

@ -1,871 +0,0 @@
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
import time
from edit.action_elements import click_element, return_element, click_and_confirm_tab
from ai.split import parse_and_extract
from ai.deepl import trans, trans_list
from ai.deepl_with_playwright import trans_text, trans_list_text
# from ai.compare import find_most_similar_image_by_one
import re
import logging
import tempfile
from img_trans.image_trans import image_trans
# 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger')
def modify_option_page2(driver, product_info, gemini, translator, login_info):
# 1. 옵션타입이 단일옵션상품이 아닌지 체크
is_option_product = not is_single_option_product(driver)
product_info.option_datas['is_option_product'] = is_option_product
if is_option_product:
# 2. 현재 옵션에 대한 정보 수집
options_info = collect_option_data(driver)
product_info.option_datas['options_info'] = options_info
# 3. 각 옵션타입별로 낮은가격순으로 정렬
sort_options_by_price(driver, options_info)
# 4. 미끼상품 체크 및 제외
filtered_options_info = filter_out_bait_items(options_info)
product_info.option_datas['filtered_options_info'] = filtered_options_info
# 5. 각 옵션타입별로 옵션아이템 가격에 따라 상품 선택
select_valid_options(driver, filtered_options_info, login_info['whether_simpleMode'])
# 6. 선택된 옵션들의 원본 옵션명과 옵션이미지 수집
option_names, option_images = collect_option_names_and_images_and_prices(driver, filtered_options_info)
product_info.option_datas['option_names'] = option_names
product_info.option_datas['option_images'] = option_images
# 7. 수집된 원본 옵션명은 AI 처리 메서드를 통해 번역 및 편집 후 입력
update_option_names(driver, option_names, translator)
# 8. 수집된 옵션 이미지 URL은 이미지 번역 및 업로드 과정 수행
update_option_images(driver, option_images, translator)
# 기타 작업이 필요한 경우 추가
finalize_modifications(driver, product_info.option_datas)
# 저장 작업 등
save_changes(driver)
def modify_option_page(driver, product_info, gemini, translator, login_info):
simpleMode = login_info['whether_simpleMode']
option_css = ".ant-tabs-tab:nth-child(2)"
thumb_data_note = "1"
click_and_confirm_tab(driver, thumb_data_note, 10)
# 특수문자와 대체할 문자를 정의합니다.
allowed_special_chars = "!$~()._-=+/"
special_char_replacements = {
"*" : "X", # '*' 특수문자를 'X'로 대체합니다.
"" : "(",
"" : ")",
"[" : "(",
"]" : ")",
"," : ".",
}
# 옵션 정보를 담을 딕셔너리 초기화
options_info = {}
# 옵션 탭으로 이동
# logger.debug("옵션탭으로 이동")
# option_tab_XPATH = "//div[@id='rc-tabs-0-tab-1']"
# click_element(driver, 'XPATH', option_tab_XPATH, 10, 'js')
# logger.debug("옵션탭으로 이동 완료")
# logger.debug("페이지 로딩 대기")
time.sleep(0.2) # 페이지 로딩 대기.
logger.debug("옵션타입 체크")
# # 옵션 타입 처리
# for i in range(1, 4): # 옵션 타입이 1부터 3까지 존재한다고 가정합니다.
# option_type_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{i}]/div/div/div[2]/div/div/div[2]/div/span/input"
# option_type = return_element(driver, 'XPATH', option_type_xpath, 3)
# if option_type:
# option_type_value = option_type.get_attribute('value')
# options_info[f'option_type_{i}'] = {'options_name': option_type_value, 'options_count': 0, 'price_ranges': []}
# logger.debug(f"옵션 타입 추가: {option_type_value}")
# else:
# logger.debug(f"{option_type_xpath} 요소를 찾을 수 없음.")
# option_type_nums = sum(1 for key in options_info.keys() if key.startswith('option_type_'))
# logger.debug(f"총 옵션 타입 수: {option_type_nums}")
options_info = collect_option_data(driver)
logger.debug(f"수집된 옵션 정보: {options_info}")
option_type_nums = len(options_info)
time.sleep(30)
logger.debug(f"============================== 잠시 대기 =============================")
# 현재 전체 옵션갯수 가져오기
# 각 옵션 타입별로 XPath를 문자열 포맷을 사용하여 한 줄로 작성
# option_num_xpaths = [
# "//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{}]/div/div/div[2]/div/div/div[4]/div/div/label/span[2]".format(idx)
# for idx in range(1, 4) # 옵션 타입이 3개까지 있음
# ]
# 각 옵션 타입의 갯수를 가져와서 options_info에 업데이트
# for idx, option_num_xpath in enumerate(option_num_xpaths[:option_type_nums], start=1):
# try:
# option_num_element = return_element(driver, 'XPATH', option_num_xpath, 5)
# if option_num_element:
# option_number = int(re.search(r'\d+', option_num_element.text).group())
# logger.debug(f"옵션타입 [{idx}] 의 갯수 : {option_number}")
# options_info[f'option_type_{idx}']['options_count'] = option_number
# else:
# logger.debug(f"옵션타입 {idx} 요소를 찾을 수 없음.")
# options_info[f'option_type_{idx}']['options_count'] = None
# except Exception as e:
# logger.error(f"옵션 타입 {idx} 갯수를 가져오는 중 오류 발생: {e}", exc_info=True)
# # 가격 낮은 순으로 정렬
# logger.debug("가격 낮은 순으로 정렬")
# low_price_order_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div/div/div/div[2]/div/div/div[5]/div/div[3]/button/span"
# click_element(driver, 'XPATH', low_price_order_xpath, 10, 'js')
### ===================================================== #####
### ==이미지 비교는 정확도가 낮아 일단 보류==== #####
# 이미지 비교
# logger.debug("이미지 비교로 유사도 판단 후 옵션 선택")
# # 옵션 이미지 URL을 저장할 리스트 초기화
# option_images_urls = []
# selected_option_indexes = []
# for i in range(1, option_number + 1):
# # 옵션 이미지의 XPath 경로 동적 생성
# opt_img_xpath = f"//li[{i}]/div/div/div/div[2]/div/img"
# try:
# # 해당 XPath를 이용하여 웹 요소 찾기
# opt_img_element = driver.find_element(By.XPATH, opt_img_xpath)
# except Exception as e:
# logger.debug(f"옵션 이미지 xpath를 찾을 수 없음 {e}", exc_info=True)
# opt_img_element = None
# # 옵션이미지가 없을 경우 처리
# if not opt_img_element:
# logger.debug(f"{i}번째 옵션이미지가 없음")
# # 아래의 2가지 상황에 맞추어 추가코드 필요
# # 1. 전체 옵션의 이미지가 없는 경우 처리
# # 2. 일부 옵션만 이미지가 없는 경우 처리
# else:
# # 웹 요소의 src 속성에서 이미지 URL 추출
# img_url = opt_img_element.get_attribute('src')
# # 추출한 이미지 URL을 리스트에 추가
# option_images_urls.append(img_url)
# # 유사도 비교로 적합한 이미지를 가진 옵션번호 저장
# # similarity = find_most_similar_image_by_one(original_img, img_url)
# similarity = 0.7
# if similarity > 0.6:
# selected_option_indexes.append(i)
# logger.debug(f"옵션번호 {i}번이 이미지 유사도 {similarity}로 선택되었습니다.")
### ===================================================== #####
# # 선택된 옵션인덱스로 실제 옵션 체크
# # 옵션타입1의 옵션전체 선택&해제 버튼 xpath=(//input[@type='checkbox'])[22]
# # 옵션타입1의 1번 옵션 선택&해제 버튼 xpath=(//input[@type='checkbox'])[23]
# # 옵션타입1의 2번 옵션 선택&해제 버튼 xpath=(//input[@type='checkbox'])[24]
# # 주의점은 해당 옵션을 체크해제할 경우 이미지 주소를 가져올 수 없으므로, 체크 해제 전 모든 옵션에 해당하는 이미지 주소를 가져올 것.
# # 만약 이미지 주소가 none이거나 없으면 이미지 비교를 하지말고 옵션명으로 비교할 것.
# logger.debug("옵션갯수 다시 가져오기")
# selected_option_num_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div/div/div/div[2]/div/div/div[5]/div/div/label/span[2]"
# selected_option_num_element = return_element(driver, 'XPATH', selected_option_num_xpath, 10)
# selected_options_num = int(re.search(r'\d+', selected_option_num_element.text).group())
# logger.debug("선택된 옵션 인덱스로 옵션박스 체크 실행")
# 각 옵션 타입에 대한 처리
for option_idx in range(1, option_type_nums + 1):
try:
logger.debug(f"옵션타입 {option_idx}에 대한 처리 시작")
logger.debug("가격 낮은 순으로 정렬")
# low_price_order_xpath = f"//div[{option_idx}]/div/div/div[2]/div/div/div[4]/div[2]/div[2]/div/div[4]/button"
# low_price_order_css = f"div#productMainContentContainerId div:nth-child(2) > div > button[type=\"button\"]:nth-child(3)"
low_price_order_css = f"div#productMainContentContainerId div:nth-child({option_idx}) > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-cAkrUM.flTgaK > div.ant-row.ant-row-space-between.ant-row-middle.css-1li46mu > div:nth-child(2) > div > button[type=\"button\"]:nth-child(3)"
# click_element(driver, 'XPATH', low_price_order_xpath, 10, 'js')
click_element(driver, 'CSS_SELECTOR', low_price_order_css, 10, 'js')
if not simpleMode: # 심플모드일 경우 옵션 편집하지 않음. 옵션 번역만.
edit_option(driver, option_idx, options_info[f'option_type_{option_idx}']['options_count']) # 각 옵션의 전체 체크박스 해제
# 가격 낮은 순으로 재정렬
# logger.debug("가격 낮은 순으로 재정렬")
# click_element(driver, 'XPATH', low_price_order_xpath, 10, 'js')
option_name_trans(driver, translator, product_info, option_idx, options_info[f'option_type_{option_idx}']['options_count'], allowed_special_chars, special_char_replacements)
except KeyError:
logger.error(f"옵션타입 {option_idx}에 대한 정보가 없습니다.")
except Exception as e:
logger.error(f"옵션타입 {option_idx} 처리 중 오류 발생: {e}", exc_info=True)
logger.debug("옵션선택과 옵션명 수정 완료 후 옵션명 정리")
# 빈칸제거
# xpath=//span[contains(.,'빈칸 제거')]
space_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div/div/div/div[2]/div/div/div[4]/div/div/div/div[3]/button/span"
click_element(driver, 'XPATH', space_xpath, 10, 'normal')
# A-Z
# xpath=//span[contains(.,'A-Z')]
AtoZ_xpath="//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div/div/div/div[2]/div/div/div[4]/div/div/div/div/button/span"
if not simpleMode: # 심플모드일 경우 옵션 AtoZ 편집하지 않음. 옵션 번역만.
click_element(driver, 'XPATH', AtoZ_xpath, 10, 'normal')
# 1-99
# xpath=//span[contains(.,'1-99')]
# xpath=//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div/div/div/div[2]/div/div/div[4]/div/div/div/div[2]/button/span
logger.debug("옵션 가격범위를 product_info에 업데이트")
update_price_range(driver, options_info, product_info)
save_xpath="//button[contains(.,'저장하기')]"
click_element(driver, 'XPATH', save_xpath, 10, 'js')
logger.debug("옵션 정리 후 저장버튼 클릭 완료")
def edit_option(driver, option_type, option_count):
"""
주어진 옵션 타입에 대해 옵션 편집 동작을 수행합니다.
Parameters:
- driver: WebDriver 인스턴스
- option_type: 옵션 타입 (1 또는 2)
- option_count: 옵션 타입 내의 옵션 갯수
"""
try:
# 전체 체크박스 선택 해제
logger.debug("전체 체크박스 선택 해제")
# select_all_checkbox_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{option_type}]/div/div/div[2]/div/div/div[5]/div/div/label/span"
select_all_checkbox_xpath = f"//div[{option_type}]/div/div/div[2]/div/div/div[4]/div[2]/div[1]/label/span[1]"
# logger.debug("전체체크박스의 상태확인")
# select_all_checkbox_state_xpath = f"//div[{option_type}]/div/div/div[2]/div/div/div[4]/div[2]/div[1]/label/span[1]/input"
# select_all_checkbox_state_element = return_element(driver, 'XPATH', select_all_checkbox_state_xpath, 10)
# # select_all_checkbox_state_element = driver.find_element_by_xpath(select_all_checkbox_state_xpath)
# if select_all_checkbox_state_element.get_attribute("aria-checked") == "mixed":
# logger.debug("전체체크박스의 일부 선택상태 확인")
# click_element(driver, 'XPATH', select_all_checkbox_xpath, 10, 'js')
click_element(driver, 'XPATH', select_all_checkbox_xpath, 10, 'js')
logger.debug(f"옵션타입 {option_type} 전체 선택 체크박스 해제")
time.sleep(1)
logger.debug(f"옵션타입 {option_type}의 옵션 선택 시작")
if option_count <= 2:
# 옵션 갯수가 2개 이하면 모든 옵션 체크
for i in range(1, option_count + 1):
option_xpath = f"//div[{option_type}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[1]/label/span"
click_element(driver, 'XPATH', option_xpath, 10, 'ac')
logger.debug(f"옵션 {i} 체크")
time.sleep(0.5)
else:
# 옵션 갯수가 3개 이상인 경우, 첫 번째 옵션 제외 최대 5개 옵션 체크
for i in range(2, min(option_count, 6) + 1): # 첫 번째 옵션 제외, 최대 5개 선택
option_xpath = f"//div[{option_type}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[1]/label/span"
click_element(driver, 'XPATH', option_xpath, 10, 'ac')
logger.debug(f"옵션 {i} 체크")
time.sleep(0.5)
except Exception as e:
logger.error(f"옵션 타입 {option_type} 편집 중 예외 발생: {e}", exc_info=True)
def option_name_trans(driver, translator, product_info, option_type_number, option_count, allowed_special_chars, special_char_replacements):
"""
원본 옵션명을 수집하여 번역 새로 입력
Parameters:
- param driver: WebDriver 인스턴스
- param options_info : 옵션 정보 Dict
- param allowed_special_chars: 허용되는 특수문자 (기본값: "!$~()._-=+/")
- type allowed_special_chars: str
- param special_char_replacements: 특수문자 대체 규칙 (기본값: None)
- type special_char_replacements: dict
"""
logger.debug("선택된 옵션명 DeepL로 번역 후 새로운 옵션명 입력")
ori_optionNames = []
deepl_trans_optionNames = []
# 원본 옵션명 추출 및 이미지 URL 확인
try:
for i in range(1, option_count + 1):
if option_type_number == 1:
ori_optionName_xpath = f"//div[{option_type_number}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[3]/div[3]/span"
optionImage_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{option_type_number}]/div/div[2]/div/div[{i}]/div/div[2]/div/img"
deleteButton_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{option_type_number}]/div/div[2]/div/div[{i}]/div/div[2]/div/span"
elif option_type_number == 2:
ori_optionName_xpath = f"//div[{option_type_number}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[2]/div[3]/span"
else: # option_type_number == 3
ori_optionName_xpath = f"//div[{option_type_number}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[2]/div[4]/div/span"
ori_optionName_element = driver.find_element(By.XPATH, ori_optionName_xpath)
ori_optionName = ori_optionName_element.text.strip()
cleaned_ori_optionName = replace_or_remove_special_chars(ori_optionName, allowed_special_chars, special_char_replacements)
logger.debug(f"정제된 {i}번째 원본 옵션명 : {cleaned_ori_optionName}")
ori_optionNames.append(cleaned_ori_optionName)
# 1번 옵션타입의 이미지를 번역하고 업로드
if option_type_number == 1:
try:
option_image_element = return_element(driver, 'XPATH', optionImage_xpath, 3)
if option_image_element:
option_image_url = option_image_element.get_attribute('src')
logger.debug(f"옵션 이미지 URL : {option_image_url}")
translated_image = image_trans(option_image_url, translator, 'translate', logger)
if translated_image:
# 이미지 번역 후 업로드
temp_file_path = save_image_to_tempfile(translated_image)
# 이미지 삭제 버튼 클릭
click_element(driver, 'XPATH', deleteButton_xpath, 5, 'ac')
logger.debug(f"이미지 삭제 버튼 클릭 완료")
# 새로운 이미지 업로드
upload_image(driver)
else:
logger.debug(f"옵션 이미지 번역 실패: {option_image_url}")
else:
logger.debug(f"{i}번째 옵션에 이미지 없음")
except Exception as e:
logger.error(f"옵션 이미지 처리 중 오류 발생: {e}", exc_info=True)
except Exception as e:
logger.debug(f"원본옵션명 처리중 에러발생 : {e}", exc_info=True)
# 원본 옵션명을 하나의 텍스트로 합치기
# combined_ori_optionNames = '\n\n'.join(ori_optionNames)
# logger.debug("원본 텍스트의 특수문자 제거 및 대체")
# cleaned_ori_text = replace_or_remove_special_chars(combined_ori_optionNames, allowed_special_chars, special_char_replacements)
# logger.debug(f"원본옵션명 집합 \n {cleaned_ori_text}")
logger.debug(f"원본옵션 총 {len(ori_optionNames)}개 번역 시행")
deepl_trans_optionNames = translator.translate(ori_optionNames) # DeepL 번역 함수
# deepl_trans_optionNames = trans_list_text(ori_optionNames) # DeepL 번역 함수
#deepl_trans_optionNames = trans_list(ori_optionNames) # DeepL 번역 함수
# trans_optionNames_text = trans_text(cleaned_ori_text) # DeepL_with_playwright 번역 함수
# trans_optionNames_text = trans(cleaned_ori_text) # DeepL 번역 함수
logger.debug(f"번역된 텍스트 \n {deepl_trans_optionNames} ")
logger.debug("번역 텍스트의 특수문자 제거 및 대체")
trans_optionNames = []
for deepl_trans_optionName in deepl_trans_optionNames:
cleand_trans_optionName = replace_or_remove_special_chars(deepl_trans_optionName, allowed_special_chars, special_char_replacements)
trans_optionNames.append(cleand_trans_optionName)
parsed_trans_optionNames, common_names = parse_and_extract(trans_optionNames)
# trans_optionNames = trans_optionNames_text.split('\n\n')
# logger.debug("번역 텍스트 나누기")
logger.debug("product_info 옵션명 업데이트")
try:
product_info.update_option_names(option_type_number, ori_optionNames, parsed_trans_optionNames, common_names)
except Exception as e:
logger.debug(f"product_info 옵션명 업데이트 중 에러 발생 {e}", exc_info=True)
# 번역된 옵션명을 각 input 요소에 입력
try:
logger.debug(f"parsed_trans_optionNames 갯수 : {len(parsed_trans_optionNames)}")
for i, trans_optionName in enumerate(parsed_trans_optionNames, 1):
optionName_input_xpath = f"//div[{option_type_number}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[3]/div[2]/div[1]/span/input" if option_type_number != 2 else f"//div[{option_type_number}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[2]/div[2]/div[1]/span/input"
optionName_input_element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, optionName_input_xpath)))
logger.debug("마우스 액션체인으로 클릭하여 안정적인 포커스 이동")
ActionChains(driver).click(optionName_input_element).perform()
logger.debug("기존 텍스트 클리어")
optionName_input_element.send_keys(Keys.CONTROL + "a") # 전체 선택
optionName_input_element.send_keys(Keys.DELETE) # 삭제
time.sleep(0.5)
logger.debug("번역 텍스트 sendkey")
optionName_input_element.send_keys(trans_optionName) # 번역된 옵션명 입력
logger.debug(f"{i}번째 옵션명에 '{trans_optionName}' 입력")
except Exception as e:
logger.debug(f"번역 옵션명 처리중 에러발생 : {e}", exc_info=True)
# 특수문자를 대체하거나 제거하는 함수를 정의합니다.
def replace_or_remove_special_chars(text, allowed_special_chars="!$~()._-=+/", special_char_replacements=None):
"""
주어진 문자열에서 특정 특수문자를 대체하거나 제거합니다.
:param text: 대상 문자열
:type text: str
:param allowed_special_chars: 허용되는 특수문자 (기본값: "!$~()._-=+/")
:type allowed_special_chars: str
:param special_char_replacements: 특수문자 대체 규칙 (기본값: None)
:type special_char_replacements: dict
:return: 특수문자가 대체 또는 제거된 문자열
:rtype: str
"""
# 특수문자 대체 규칙을 설정합니다.
if special_char_replacements is None:
special_char_replacements = {}
# 특수문자를 대체합니다.
for char, replacement in special_char_replacements.items():
text = text.replace(char, replacement)
# 허용되는 특수문자 패턴을 정의합니다.
pattern = f"[^{re.escape(allowed_special_chars)}\w\s]"
# 허용되는 특수문자를 제외하고 모든 특수문자를 제거합니다.
cleaned_text = re.sub(pattern, "", text, flags=re.UNICODE)
return cleaned_text
def update_price_range(driver, options_info, product_info):
try:
# 모든 옵션 타입에서 수집된 가격 범위를 저장할 리스트 초기화
all_price_ranges = []
# 각 옵션 타입에 대한 처리
for option_type_key, option_data in options_info.items():
option_number = option_data.get('options_count')
# 각 옵션의 가격 범위 수집
for i in range(1, option_number + 1):
option_type_number = [int(s) for s in option_type_key.split('_') if s.isdigit()][0]
middle_index = '3' if option_type_number == 1 else '2'
price_xpath = f"//div[{option_type_number}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[{middle_index}]/div[1]/div[2]/span/sup"
try:
price_element = return_element(driver, 'XPATH', price_xpath, 5)
price_text = re.sub(r'[^\d\-]', '', price_element.text.strip())
price_parts = price_text.split('-')
price_range = [int(price.strip()) for price in price_parts]
all_price_ranges.extend(price_range)
except Exception as e:
logger.error(f"옵션 타입 {option_type_key}{i}번 가격 범위를 가져오는데 실패했습니다. 에러: {e}", exc_info=True)
# 모든 옵션의 가격 범위에서 전체 최소값과 최대값 추출
if all_price_ranges:
overall_min_price = min(all_price_ranges)
overall_max_price = max(all_price_ranges)
# product_info 업데이트
product_info.option_high_price = overall_max_price
product_info.option_low_price = overall_min_price
logger.debug(f"전체 옵션의 가격 범위 업데이트: 최소값 {overall_min_price}, 최대값 {overall_max_price}")
except Exception as e:
logger.error(f"가격 범위 업데이트 중 오류 발생: {e}", exc_info=True)
def save_image_to_tempfile(image):
"""
이미지를 임시 파일로 저장합니다.
Args:
- image (PIL.Image): 번역된 이미지 객체
Returns:
- temp_file_path (str): 임시 파일 경로
"""
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
image.save(temp_file, format="PNG")
temp_file_path = temp_file.name
temp_file.close()
logger.debug(f"임시 파일로 저장된 이미지 경로: {temp_file_path}")
return temp_file_path
def upload_image(driver, option_item_index, temp_file_path):
"""
번역된 이미지를 업로드합니다.
Args:
- driver: WebDriver 인스턴스
- option_item_index (int): 옵션 아이템의 인덱스 (1, 2, 3 )
- temp_file_path (str): 임시 파일 경로
"""
try:
# 기존 이미지 삭제 버튼 클릭
delete_button_css = f"div#productMainContentContainerId li:nth-child({option_item_index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div.ant-row.ant-row-no-wrap.ant-row-space-between.ant-row-middle.css-1li46mu > div:nth-child(1) > div > span"
delete_button_element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, delete_button_css))
)
delete_button_element.click()
logger.debug(f"{option_item_index}번째 옵션 아이템의 이미지 삭제 버튼 클릭 완료")
time.sleep(1) # 이미지 삭제 대기
# 이미지 업로드 버튼 클릭
upload_button_css = f"div#productMainContentContainerId li:nth-child({option_item_index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div > img"
upload_button_element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, upload_button_css))
)
upload_button_element.click()
logger.debug(f"{option_item_index}번째 옵션 아이템의 이미지 업로드 버튼 클릭 완료")
# 이미지 업로드 다이얼로그의 파일 업로드 버튼 클릭
file_upload_dialog_css = "span > div > div > div:nth-child(2) > div"
file_upload_dialog_element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, file_upload_dialog_css))
)
file_upload_dialog_element.click()
logger.debug("파일 업로드 버튼 클릭 완료")
# 파일 선택 대화상자에 임시 이미지 파일 경로를 입력하여 업로드
driver.find_element(By.CSS_SELECTOR, 'input[type="file"]').send_keys(temp_file_path)
logger.debug(f"임시 이미지 파일 경로 지정: {temp_file_path}")
# 이미지 삽입 버튼 클릭
insert_button_css = "div.ant-modal-footer > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary"
insert_button_element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, insert_button_css))
)
insert_button_element.click()
logger.debug("이미지 삽입 버튼 클릭 완료")
# 임시 파일 삭제
os.remove(temp_file_path)
logger.debug(f"임시 파일 삭제 완료: {temp_file_path}")
except Exception as e:
logger.error(f"이미지 업로드 중 에러 발생: {e}", exc_info=True)
def collect_option_data_ori(driver):
"""
웹페이지에서 옵션 타입의 갯수와 타입별 옵션 아이템의 갯수를 수집합니다.
Parameters:
- driver: WebDriver 인스턴스
Returns:
- filtered_options_info (dict): 옵션 타입과 타입별 옵션 아이템 갯수를 포함하는 필터링된 딕셔너리
"""
options_info = {}
try:
# 먼저 상품 유형을 확인합니다.
if not is_option_product(driver):
logger.debug("단일 상품이므로 옵션 정보 수집을 생략합니다.")
return options_info # 단일 상품일 경우 빈 딕셔너리를 반환
# 옵션 타입 컨테이너의 CSS 선택자
option_types_container_css = "div#productMainContentContainerId div.sc-bCrHVQ.fRmCVg > div:nth-child(2) > div > div > div:nth-child(2)"
# 옵션 타입 컨테이너 요소를 찾습니다.
option_types_container = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, option_types_container_css))
)
# 옵션 타입 갯수를 추출하기 위한 CSS 선택자
option_type_css = "div.ant-col.css-1li46mu > div.sc-stxIr.eeUZbv"
option_type_elements = option_types_container.find_elements(By.CSS_SELECTOR, option_type_css)
option_type_count = len(option_type_elements)
logger.debug(f"옵션 타입의 총 갯수: {option_type_count}")
for idx in range(1, option_type_count + 1):
# 옵션 타입 이름 추출
# option_type_name_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{idx}]/div/div/div[2]/div/div/div[2]/div/span/input"
# option_type_name_element = WebDriverWait(driver, 10).until(
# EC.presence_of_element_located((By.XPATH, option_type_name_xpath))
# )
# option_type_name = option_type_name_element.get_attribute('value').strip()
option_type_name_css = f"div#productMainContentContainerId div:nth-child({idx}) > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.ant-row.ant-row-no-wrap.ant-row-middle.css-1li46mu > div:nth-child(1) > span > input"
option_type_name__by_css_element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, option_type_name_css))
)
option_type_name = option_type_name__by_css_element.get_attribute('value').strip()
# 옵션 타입별 옵션 아이템 갯수 추출
# option_items_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{idx}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li"
# option_items = driver.find_elements(By.CSS_SELECTOR, option_items_xpath)
# option_items_count = len(option_items)
option_items_css = f"div#productMainContentContainerId div:nth-child({idx}) > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-kjNGdX.cQdCox > div.ant-list.ant-list-split.css-1li46mu > div > div > ul > li"
option_items = driver.find_elements(By.CSS_SELECTOR, option_items_css)
option_items_count = len(option_items)
# 각 옵션 타입별 옵션명, 이미지 및 가격을 수집
option_names_and_images = collect_option_names_and_images_and_price(driver, idx, option_items_count)
options_info[f'option_type_{idx}'] = {
'name': option_type_name,
'items_count': option_items_count,
'items': option_names_and_images
}
logger.debug(f"옵션 타입 {idx}: {option_type_name}, 옵션 아이템 수: {option_items_count}")
for item in option_names_and_images:
logger.debug(f" - 옵션 아이템: 이름={item['name']}, 이미지 URL={item['image_url']}, 가격={item['low_price']} - {item['high_price']}")
except Exception as e:
logger.error(f"옵션 타입 및 아이템 수집 중 오류 발생: {e}", exc_info=True)
return options_info
def collect_option_data(driver, product_info):
options_info = {}
# 옵션 타입을 찾는 CSS 선택자
option_types_container_css = "div#productMainContentContainerId div.ant-col.css-1li46mu div.sc-stxIr.eeUZbv"
option_type_elements = driver.find_elements(By.CSS_SELECTOR, option_types_container_css)
# 각 옵션 타입을 순회하며 정보 수집
for idx, option_type_element in enumerate(option_type_elements, start=1):
try:
# 옵션 타입 이름 추출
option_type_name_css = f"div#productMainContentContainerId div.sc-bCrHVQ.fRmCVg > div:nth-child(2) > div > div > div:nth-child(2) > div > div:nth-child({idx}) > div > div > div.ant-row.ant-row-no-wrap.ant-row-middle.css-1li46mu > div:nth-child(1) > span > input"
option_type_name_element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, option_type_name_css))
)
option_type_name = option_type_name_element.get_attribute('value').strip()
# 옵션 아이템을 찾는 CSS 선택자
option_items_css = f"div#productMainContentContainerId div.sc-bCrHVQ.fRmCVg > div:nth-child(2) > div > div > div:nth-child(2) > div > div:nth-child({idx}) div.ant-list.ant-list-split.css-1li46mu > div > div > ul > li"
option_items_elements = driver.find_elements(By.CSS_SELECTOR, option_items_css)
option_items_count = len(option_items_elements)
# 옵션 아이템 정보 수집
option_items_info = []
for item_idx, item_element in enumerate(option_items_elements, start=1):
try:
# 옵션 이름 추출
name_css = f"div#productMainContentContainerId div:nth-child({idx}) > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-kjNGdX.cQdCox > div.ant-list.ant-list-split.css-1li46mu > div > div > ul > li:nth-child({item_idx}) > div > div.ant-col.css-1li46mu > div > div.ant-col.css-1li46mu > span"
name_element = item_element.find_element(By.CSS_SELECTOR, name_css)
item_name = name_element.text.strip()
# 옵션 이미지 URL 추출
image_css = f"div#productMainContentContainerId div:nth-child({idx}) > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-kjNGdX.cQdCox > div.ant-list.ant-list-split.css-1li46mu > div > div > ul > li:nth-child({item_idx}) > div > div.ant-col.css-1li46mu > div > div > div > img"
image_element = item_element.find_element(By.CSS_SELECTOR, image_css)
image_url = image_element.get_attribute('src') if image_element else None
# 옵션 가격 범위 추출
price_css = f"div#productMainContentContainerId div:nth-child({idx}) > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-kjNGdX.cQdCox > div.ant-list.ant-list-split.css-1li46mu > div > div > ul > li:nth-child({item_idx}) > div > div.ant-col.css-1li46mu > div > div > div:nth-child(3) > div:nth-child(1) > div:nth-child(2) > span > sup"
price_element = item_element.find_element(By.CSS_SELECTOR, price_css)
price_text = price_element.get_attribute('title').strip()
# 가격 범위 추출 및 처리
price_range = re.findall(r'\d+', price_text.replace(',', ''))
price_range = (int(price_range[0]), int(price_range[1])) if len(price_range) == 2 else (int(price_range[0]), int(price_range[0]))
# 옵션 정보 저장
option_items_info.append({
'name': item_name,
'image_url': image_url,
'price_range': price_range
})
except Exception as e:
logger.error(f"옵션 아이템 수집 중 오류 발생: {e}", exc_info=True)
# 옵션 타입 정보 저장
options_info[f'option_type_{idx}'] = {
'name': option_type_name,
'items_count': option_items_count,
'items': option_items_info
}
except Exception as e:
logger.error(f"옵션 타입 및 아이템 수집 중 오류 발생: {e}", exc_info=True)
product_info.option_datas = options_info
return options_info
def collect_option_names_and_images_and_price(driver, option_type_number, option_items_count):
"""
옵션 타입별로 원본 옵션명, 옵션 이미지, 옵션 가격을 수집합니다.
Parameters:
- driver: WebDriver 인스턴스
- option_type_number: 옵션 타입 번호 (1, 2, 3 )
- option_items_count: 해당 옵션 타입의 옵션 아이템 갯수
Returns:
- option_data (list): 옵션명, 이미지 URL, 가격을 포함하는 리스트
"""
option_data = []
try:
for i in range(1, option_items_count + 1):
# 원본 옵션명 수집
option_name_xpath = f"//div[@id='productMainContentContainerId']/div/div[2]/div/div/div[2]/div/div[{option_type_number}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i}]/div/div[1]/div/div[3]/div[3]/span"
option_name_element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, option_name_xpath))
)
option_name = option_name_element.text.strip()
# 옵션 이미지 URL 수집 (옵션 타입 1에만 옵션이미지 존재)
option_image_url = None
if option_type_number == 1:
option_image_xpath = f"div#productMainContentContainerId li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(2) > div > img"
try:
option_image_element = driver.find_element(By.CSS_SELECTOR, option_image_xpath)
option_image_url = option_image_element.get_attribute('src')
except Exception:
logger.debug(f"{option_type_number}번 옵션타입의 {i}번째 옵션에 이미지 없음")
# 옵션 가격 수집
option_price_xpath = f"div#productMainContentContainerId li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(1) > div:nth-child(2) > span > sup"
try:
option_price_element = driver.find_element(By.CSS_SELECTOR, option_price_xpath)
option_price_text = option_price_element.get_attribute('title') # 또는 .text를 사용할 수도 있음
# 가격 문자열에서 숫자만 추출 (천 단위 구분자를 포함하는 경우 처리)
price_range = re.findall(r'\d+', option_price_text.replace(',', ''))
low_price, high_price = map(int, price_range) if len(price_range) == 2 else (int(price_range[0]), int(price_range[0]))
except Exception:
logger.debug(f"{option_type_number}번 옵션타입의 {i}번째 옵션에 가격 없음")
low_price = high_price = None
option_data.append({
'name': option_name,
'image_url': option_image_url,
'low_price': low_price,
'high_price': high_price
})
logger.debug(f"{option_type_number}번 옵션타입의 옵션 {i}: 이름={option_name}, 이미지 URL={option_image_url}, 가격={low_price} - {high_price}")
except Exception as e:
logger.error(f"옵션명, 이미지 및 가격 수집 중 오류 발생: {e}", exc_info=True)
return option_data
def is_single_option_product(driver, product_info):
"""
웹페이지에서 현재 상품이 '단일 상품'인지 '옵션 상품'인지 확인합니다.
Parameters:
- driver: WebDriver 인스턴스
Returns:
- bool: 옵션 상품이면 True, 단일 상품이면 False
"""
try:
# '옵션 상품등록' 또는 '단일 상품등록' 선택 여부 확인
radio_group_css = "div#productMainContentContainerId div.ant-row.css-1li46mu > div"
radio_group_element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, radio_group_css))
)
# 라디오 버튼의 선택 상태 확인
option_radio_xpath = "//label[span[text()='옵션 상품등록']]//input[@type='radio' and @value='false']"
single_radio_xpath = "//label[span[text()='단일 상품등록']]//input[@type='radio' and @value='true']"
option_radio_checked = radio_group_element.find_element(By.XPATH, option_radio_xpath).is_selected()
single_radio_checked = radio_group_element.find_element(By.XPATH, single_radio_xpath).is_selected()
if option_radio_checked and not single_radio_checked:
logger.debug("상품 유형: 옵션 상품등록")
product_info.option_types_onlyone = True
return True
else:
logger.debug("상품 유형: 단일 상품등록")
product_info.option_types_onlyone = False
return False
except Exception as e:
logger.error(f"상품 유형 확인 중 오류 발생: {e}", exc_info=True)
logger.debug("상품 유형 오류 발생으로 기본설정인 단일 상품등록으로 진행합니다.")
product_info.option_types_onlyone = False
return False # 오류 발생 시 기본적으로 단일 상품으로 처리
def filter_and_select_options(driver, option_type_number, option_data):
"""
미끼 옵션을 제거하고 유효한 옵션을 선택합니다.
Parameters:
- driver: WebDriver 인스턴스
- option_type_number: 옵션 타입 번호 (1, 2, 3 )
- option_data (list): 옵션명, 이미지 URL, 가격을 포함하는 리스트
"""
try:
# 가격 데이터만 추출하여 정렬
prices = sorted([item['low_price'] for item in option_data if item['low_price'] is not None])
if not prices:
logger.error("가격 데이터가 없습니다.")
return
# 미끼 옵션을 식별하여 제거
valid_items = []
for i, base_price in enumerate(prices):
range_limit = base_price * 1.5
within_range = [price for price in prices if price <= range_limit]
if len(within_range) == len(prices):
valid_items = [item for item in option_data if item['low_price'] in within_range]
break
if not valid_items:
valid_items = option_data
# 전체 체크박스 해제
select_all_checkbox_xpath = f"//div[{option_type_number}]/div/div/div[2]/div/div/div[4]/div[2]/div[1]/label/span[1]"
WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, select_all_checkbox_xpath))
).click()
time.sleep(1)
# 필터링된 옵션을 낮은 가격순으로 정렬
valid_items = sorted(valid_items, key=lambda x: x['low_price'])
# 최대 10개 옵션만 선택, 옵션이 5개 이하라면 모든 옵션 선택
for i, item in enumerate(valid_items):
if i >= 10:
break
option_checkbox_xpath = f"//div[{option_type_number}]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[{i + 1}]/div/div[1]/div/div[1]/label/span[1]"
WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.XPATH, option_checkbox_xpath))
).click()
time.sleep(0.5)
logger.debug(f"옵션 타입 {option_type_number}의 유효한 옵션 아이템 선택 완료")
except Exception as e:
logger.error(f"옵션 선택 중 오류 발생: {e}", exc_info=True)
def sort_options_by_price(driver, options_info):
"""
옵션 타입별로 가격 내림차순 정렬 버튼을 클릭합니다.
Parameters:
- driver: WebDriver 인스턴스
- options_info: 옵션 타입별 정보를 포함하는 딕셔너리
"""
try:
for option_type_key in options_info:
option_type_number = option_type_key.split('_')[-1] # 옵션 타입 번호 추출
sort_button_css = f"div#productMainContentContainerId div.sc-bCrHVQ.fRmCVg > div:nth-child(2) > div > div > div:nth-child(2) > div > div:nth-child({option_type_number}) > div > div > div:nth-child(2) > div > div > div:nth-child(4) > div:nth-child(2) > div:nth-child(2) > div > div:nth-child(4) > button"
sort_button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, sort_button_css))
)
sort_button.click()
logger.debug(f"옵션 타입 {option_type_number}의 가격 내림차순 정렬 버튼 클릭 완료")
time.sleep(1) # 정렬이 완료될 시간을 줍니다.
# low_price_order_css = "div#productMainContentContainerId div:nth-child(2) > div > button[type=\"button\"]:nth-child(3)"
# # click_element(driver, 'XPATH', low_price_order_xpath, 10, 'js')
# click_element(driver, 'CSS_SELECTOR', low_price_order_css, 10, 'js')
except Exception as e:
logger.error(f"가격 내림차순 정렬 버튼 클릭 중 오류 발생: {e}", exc_info=True)

View File

@ -30,14 +30,11 @@ def modify_price_page(driver, product_infos):
time.sleep(2) # 페이지 로딩 대기. time.sleep(2) # 페이지 로딩 대기.
try: try:
plus_fee_xpath="//div[@id='productMainContentContainerId']/div/div/div/div/div[2]/div/div/div[8]/div/div/div[3]/div/div/div/div/div[2]/input" plus_fee_xpath="//div[@id='productMainContentContainerId']/div/div/div/div/div[2]/div/div/div[8]/div/div/div[3]/div/div/div/div/div[2]/input"
plus_fee_css = "div#productMainContentContainerId div:nth-child(8) > div > div > div:nth-child(3) > div > div > div > div.ant-input-number.css-1li46mu.ant-input-number-outlined > div.ant-input-number-input-wrap > input"
plus_fee_element = return_element(driver, 'XPATH', plus_fee_xpath, 10) plus_fee_element = return_element(driver, 'XPATH', plus_fee_xpath, 10)
plus_fee_element_by_css = return_element(driver, 'CSS', plus_fee_css, 10)
plus_fee_text = plus_fee_element.text plus_fee_text = plus_fee_element.text
print(f"plus_fee_text : {plus_fee_text}")
plus_fee_by_css_text = plus_fee_element_by_css.text
print(f"plus_fee_by_css_text : {plus_fee_by_css_text}")
option_high_price = product_infos.option_high_price option_high_price = product_infos.option_high_price
option_low_price = product_infos.option_low_price option_low_price = product_infos.option_low_price

View File

@ -27,14 +27,10 @@ class ProductInfo:
self.ai_contents_mark = None # ai가 생성한 컨텐츠 결과 self.ai_contents_mark = None # ai가 생성한 컨텐츠 결과
self.current_value = None # 상세페이지의 현재 내용 self.current_value = None # 상세페이지의 현재 내용
self.deleted_value = None # 상세페이지의 이미지가 삭제된 내용
self.new_value = None # 상세페이지의 바뀐 내용 self.new_value = None # 상세페이지의 바뀐 내용
self.naver_products = [] # 네이버 파싱된 상품들 self.naver_products = [] # 네이버 파싱된 상품들
self.is_option_product = False
self.option_datas = {}
self.option_1_names = [] # 상품 옵션 이름 리스트 self.option_1_names = [] # 상품 옵션 이름 리스트
self.trans_option_1_names = [] # 번역된 상품 옵션 이름 리스트 self.trans_option_1_names = [] # 번역된 상품 옵션 이름 리스트
self.trans_option_1_name_common_parts = [] # 번역된 상품 옵션 이름 리스트의 단어별 리스트 저장 self.trans_option_1_name_common_parts = [] # 번역된 상품 옵션 이름 리스트의 단어별 리스트 저장

View File

@ -4,8 +4,7 @@ import base64
from PIL import Image from PIL import Image
import urllib, requests import urllib, requests
import numpy as np import numpy as np
import time
import random
from img_trans.src.text_detection_with_paddle import detect_text from img_trans.src.text_detection_with_paddle import detect_text
from img_trans.src.removal import process_image_based_on_text_area from img_trans.src.removal import process_image_based_on_text_area
from img_trans.src.translation import translate_texts_translatepy from img_trans.src.translation import translate_texts_translatepy
@ -14,12 +13,12 @@ from img_trans.src.text_insertion import insert_text
from img_trans.src.mini_crop_process import resize_and_crop_image from img_trans.src.mini_crop_process import resize_and_crop_image
from img_trans.src.rotate_img import flip_and_rotate_image from img_trans.src.rotate_img import flip_and_rotate_image
from img_trans.src.inpainting import inpaint_text # 기존 인페인팅 메서드 from img_trans.src.inpainting import inpaint_text # 기존 인페인팅 메서드
# import logging import logging
# 로거 인스턴스 가져오기 # 로거 인스턴스 가져오기
# logger = logging.getLogger('default_logger') logger = logging.getLogger('default_logger')
def image_trans(image_url, translator, convert_type, logger): def image_trans(image_url, translator, convert_type):
# 이미지를 URL로부터 읽어옵니다. # 이미지를 URL로부터 읽어옵니다.
try: try:
# image = read_image_from_url(image_url) # image = read_image_from_url(image_url)
@ -66,7 +65,7 @@ def image_trans(image_url, translator, convert_type, logger):
elif convert_type == 'translate': elif convert_type == 'translate':
# 2단계: 인페이팅 기법으로 텍스트 제거 # 2단계: 인페이팅 기법으로 텍스트 제거
inpainted_image = inpaint_text(image, detected_texts, logger) inpainted_image = inpaint_text(image, detected_texts)
# cv2.imwrite(inpainted_image_path, inpainted_image) # cv2.imwrite(inpainted_image_path, inpainted_image)
# 이미지 번역 처리 # 이미지 번역 처리
logger.debug("이미지 번역 처리 시작") logger.debug("이미지 번역 처리 시작")
@ -74,21 +73,22 @@ def image_trans(image_url, translator, convert_type, logger):
texts_to_translate = [text for _, text, _, _, _ in detected_texts] texts_to_translate = [text for _, text, _, _, _ in detected_texts]
logger.debug(f"원본 텍스트: {texts_to_translate}") logger.debug(f"원본 텍스트: {texts_to_translate}")
logger.debug(f"DEEPL 번역시작") logger.debug(f"DEEPL 번역시작")
# translated_texts = translate_texts_deepl(texts_to_translate, logger) # translated_texts = translate_texts_deepl(texts_to_translate)
try: try:
translated_texts = translator.translate(texts_to_translate, logger) # translated_texts = translator.translate(texts_to_translate)
# translated_texts = None translated_texts = None
if not translated_texts or translated_texts == texts_to_translate: # 빈 리스트 체크 if not translated_texts: # 빈 리스트 체크
logger.debug(f"DEEPL 번역실패로 translatepy 시작") logger.debug(f"DEEPL 번역실패로 translatepy 시작")
translated_texts = translate_texts_translatepy(texts_to_translate, logger) translated_texts = translate_texts_translatepy(texts_to_translate)
except Exception as e: except Exception as e:
translated_texts = translate_texts_translatepy(texts_to_translate, logger) translated_texts = translate_texts_translatepy(texts_to_translate)
logger.debug(f"번역 텍스트: {texts_to_translate}") logger.debug(f"번역 텍스트: {texts_to_translate}")
# translated_image = insert_text(inpainted_image_path, translated_texts, detected_texts) # translated_image = insert_text(inpainted_image_path, translated_texts, detected_texts)
translated_image = insert_text(inpainted_image, translated_texts, detected_texts, logger) translated_image = insert_text(inpainted_image, translated_texts, detected_texts)
# cv2.imwrite(translated_image_path, translated_image) # cv2.imwrite(translated_image_path, translated_image)
logger.debug(f"번역된 이미지 생성완료 : {translated_image_path}") logger.debug(f"번역된 이미지 생성완료 : {translated_image_path}")
# 번역된 이미지 변환합니다. # 번역된 이미지 변환합니다.
pil_img = Image.fromarray(cv2.cvtColor(translated_image, cv2.COLOR_BGR2RGB)) pil_img = Image.fromarray(cv2.cvtColor(translated_image, cv2.COLOR_BGR2RGB))
# buffered = BytesIO() # buffered = BytesIO()
@ -114,7 +114,7 @@ def read_image_from_url(image_url):
logger.error(f"이미지 로딩 중 오류 발생: {e}") logger.error(f"이미지 로딩 중 오류 발생: {e}")
return None return None
def read_image_from_url_with_requests_ori(image_url): def read_image_from_url_with_requests(image_url):
if not image_url: # URL이 비어있는지 확인 if not image_url: # URL이 비어있는지 확인
logger.error("빈 URL이 제공되었습니다.") logger.error("빈 URL이 제공되었습니다.")
return None return None
@ -137,43 +137,6 @@ def read_image_from_url_with_requests_ori(image_url):
logger.error(f"이미지 로딩 중 오류 발생: {e}") logger.error(f"이미지 로딩 중 오류 발생: {e}")
return None return None
def read_image_from_url_with_requests(image_url, max_retries=3):
if not image_url: # URL이 비어있는지 확인
logger.error("빈 URL이 제공되었습니다.")
return None
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"DNT": "1", # Do Not Track 요청 헤더
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"Cache-Control": "max-age=0"
}
retries = 0
while retries < max_retries:
try:
response = requests.get(image_url, headers=headers)
if response.status_code == 200:
image = np.asarray(bytearray(response.content), dtype="uint8")
image = cv2.imdecode(image, cv2.IMREAD_COLOR)
return image
else:
# 응답 코드가 200이 아닐 때 로그를 남기고 재시도
logger.error(f"이미지 url 로딩 실패, HTTP 상태 코드: {response.status_code}. 재시도 {retries+1}/{max_retries} : 대상URL : {image_url}")
retries += 1
time.sleep(random.randint(2, 5)) # 5초에서 10초 사이의 랜덤한 시간 동안 대기
except Exception as e:
logger.error(f"이미지 로딩 중 오류 발생: {e}")
retries += 1
time.sleep(random.randint(2, 5)) # 예외 발생 시 랜덤 대기 후 재시도
logger.error("최대 재시도 횟수 초과")
return None
# 외부에서 함수를 사용하는 예 # 외부에서 함수를 사용하는 예
# 이미지 URL과 원하는 변환 유형('thumbnail' 또는 'translate')을 인자로 넘깁니다. # 이미지 URL과 원하는 변환 유형('thumbnail' 또는 'translate')을 인자로 넘깁니다.
# result = image_trans('http://example.com/image.jpg', 'thumbnail') # result = image_trans('http://example.com/image.jpg', 'thumbnail')

View File

@ -1,12 +1,12 @@
import cv2 import cv2
import numpy as np import numpy as np
# from img_trans.generative_inpainting.inpaint_model import InpaintCAModel # from img_trans.generative_inpainting.inpaint_model import InpaintCAModel
# import logging import logging
# # 로거 인스턴스 가져오기 # 로거 인스턴스 가져오기
# logger = logging.getLogger('default_logger') logger = logging.getLogger('default_logger')
def inpaint_text(image, detected_texts, logger, expansion=5): def inpaint_text(image, detected_texts, expansion=5):
""" """
이미지 텍스트 제거를 위해 인페이팅 기법을 사용합니다. 이미지 텍스트 제거를 위해 인페이팅 기법을 사용합니다.
expansion 파라미터는 텍스트 주변의 마스크 확장 크기를 지정합니다. expansion 파라미터는 텍스트 주변의 마스크 확장 크기를 지정합니다.

View File

@ -0,0 +1 @@
model_checkpoint_path: "snap-0"

Binary file not shown.

Binary file not shown.

View File

@ -2,10 +2,10 @@ from PIL import Image, ImageDraw, ImageFont
import numpy as np import numpy as np
import cv2 import cv2
from colorsys import rgb_to_hsv, hsv_to_rgb from colorsys import rgb_to_hsv, hsv_to_rgb
# import logging import logging
# 로거 인스턴스 가져오기 # 로거 인스턴스 가져오기
# logger = logging.getLogger('default_logger') logger = logging.getLogger('default_logger')
def get_dominant_color(image, box, expand=10): def get_dominant_color(image, box, expand=10):
"""이미지의 주어진 영역에서 지배적인 색상을 찾습니다.""" """이미지의 주어진 영역에서 지배적인 색상을 찾습니다."""
@ -22,7 +22,7 @@ def get_complementary_color(color):
complementary = (255 - color[0], 255 - color[1], 255 - color[2]) complementary = (255 - color[0], 255 - color[1], 255 - color[2])
return complementary return complementary
def calculate_font_size(text, logger, font_path="NotoSansKR-Bold.ttf", desired_width=None, desired_height=None): def calculate_font_size(text, font_path="NotoSansKR-Bold.ttf", desired_width=None, desired_height=None):
font_size = 10 # 초기 폰트 크기 font_size = 10 # 초기 폰트 크기
max_font_size = 100 # 초기 폰트 크기 max_font_size = 100 # 초기 폰트 크기
font = ImageFont.truetype(font_path, font_size) font = ImageFont.truetype(font_path, font_size)
@ -62,7 +62,7 @@ def draw_text_with_border(draw, translated_text, position, font, text_color, bor
# 본래 텍스트 그리기 # 본래 텍스트 그리기
draw.text(position, translated_text, font=font, fill=text_color) draw.text(position, translated_text, font=font, fill=text_color)
def draw_text_with_effects(draw, logger, translated_text, position, font, text_color="white", border_color="black"): def draw_text_with_effects(draw, translated_text, position, font, text_color="white", border_color="black"):
try: try:
# 그림자 효과 # 그림자 효과
shadow_offset = (2, 2) shadow_offset = (2, 2)
@ -88,7 +88,7 @@ def draw_translucent_background(draw, box, fill_color=(255, 255, 255, 50)):
logger.error(f"draw_translucent_background 메서드 중 에러발생 : {e}", exc_info=True) logger.error(f"draw_translucent_background 메서드 중 에러발생 : {e}", exc_info=True)
# def insert_text(image_path, translated_texts, detected_texts): # def insert_text(image_path, translated_texts, detected_texts):
def insert_text(inpainted_image, translated_texts, detected_texts, logger): def insert_text(inpainted_image, translated_texts, detected_texts):
try: try:
# # PIL 이미지 객체로 변환합니다. (OpenCV 이미지가 아닐 경우) # # PIL 이미지 객체로 변환합니다. (OpenCV 이미지가 아닐 경우)
if isinstance(inpainted_image, np.ndarray): if isinstance(inpainted_image, np.ndarray):
@ -120,10 +120,10 @@ def insert_text(inpainted_image, translated_texts, detected_texts, logger):
# complementary_color = get_complementary_color(dominant_color) # complementary_color = get_complementary_color(dominant_color)
# translated_text = detected_text[1] # 번역된 텍스트 # translated_text = detected_text[1] # 번역된 텍스트
logger.debug(f"translated_text : {translated_text}") print(f"translated_text : {translated_text}")
text_width = np.linalg.norm(np.array(box[0]) - np.array(box[1])) text_width = np.linalg.norm(np.array(box[0]) - np.array(box[1]))
text_height = np.linalg.norm(np.array(box[1]) - np.array(box[2])) text_height = np.linalg.norm(np.array(box[1]) - np.array(box[2]))
font_size = calculate_font_size(translated_text, logger, "NotoSansKR-Bold.ttf", text_width, text_height) font_size = calculate_font_size(translated_text, "NotoSansKR-Bold.ttf", text_width, text_height)
font_background = ImageFont.truetype("NotoSansKR-Bold.ttf", font_size) font_background = ImageFont.truetype("NotoSansKR-Bold.ttf", font_size)
font = ImageFont.truetype("NotoSansKR-Bold.ttf", font_size) font = ImageFont.truetype("NotoSansKR-Bold.ttf", font_size)
# 반투명 배너 그리기 # 반투명 배너 그리기
@ -135,7 +135,7 @@ def insert_text(inpainted_image, translated_texts, detected_texts, logger):
# draw.text((x_min-1, y_min-1), translated_text, fill="white", font=font_background) # draw.text((x_min-1, y_min-1), translated_text, fill="white", font=font_background)
# draw.text((x_min, y_min), translated_text, fill="black", font=font) # draw.text((x_min, y_min), translated_text, fill="black", font=font)
draw_text_with_effects(draw, logger, translated_text, text_position, font) draw_text_with_effects(draw, translated_text, text_position, font)
cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGBA2BGR) cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGBA2BGR)
return cv_image return cv_image

View File

@ -8,7 +8,7 @@ import logging
# 로거 인스턴스 가져오기 # 로거 인스턴스 가져오기
logger = logging.getLogger('default_logger') logger = logging.getLogger('default_logger')
def translate_texts_google(texts, logger, src_lang='zh-cn', dest_lang='ko'): def translate_texts_google(texts, src_lang='zh-cn', dest_lang='ko'):
"""텍스트 리스트를 지정된 언어에서 다른 언어로 번역""" """텍스트 리스트를 지정된 언어에서 다른 언어로 번역"""
translated_texts = [] translated_texts = []
translator = Translator() translator = Translator()
@ -22,7 +22,7 @@ def translate_texts_google(texts, logger, src_lang='zh-cn', dest_lang='ko'):
return translated_texts return translated_texts
def translate_texts_deepl_ori(texts, logger): def translate_texts_deepl_ori(texts):
"""텍스트 리스트를 지정된 언어에서 다른 언어로 번역""" """텍스트 리스트를 지정된 언어에서 다른 언어로 번역"""
# 텍스트 리스트를 엔터로 구분된 하나의 문자열로 합침 # 텍스트 리스트를 엔터로 구분된 하나의 문자열로 합침
combined_text = "\n".join(texts) combined_text = "\n".join(texts)
@ -43,7 +43,7 @@ def translate_texts_deepl_ori(texts, logger):
return translated_texts return translated_texts
def translate_texts_deepl(texts, logger): def translate_texts_deepl(texts):
"""텍스트 리스트를 지정된 언어에서 다른 언어로 번역""" """텍스트 리스트를 지정된 언어에서 다른 언어로 번역"""
translated_texts = [] translated_texts = []
@ -60,7 +60,7 @@ def translate_texts_deepl(texts, logger):
return translated_texts return translated_texts
def translate_texts_translatepy(texts, logger, src_lang='zh-cn', dest_lang='ko'): def translate_texts_translatepy(texts, src_lang='zh-cn', dest_lang='ko'):
"""translatepy 라이브러리를 사용하여 텍스트 리스트를 번역합니다.""" """translatepy 라이브러리를 사용하여 텍스트 리스트를 번역합니다."""
translator = Translator() translator = Translator()
translated_texts = [] translated_texts = []

View File

@ -212,7 +212,6 @@ def main():
#웹 드라이버 종료 #웹 드라이버 종료
driver.quit() driver.quit()
translator.close() translator.close()
QtWidgets.QMessageBox.information("알림", "작업이 완료되었습니다.")
if __name__ == "__main__": if __name__ == "__main__":
CURRENT_VERSION = "1.0.0" CURRENT_VERSION = "1.0.0"

View File

@ -112,8 +112,8 @@ def modify_products(driver, gemini, translator, mongo_config, login_info, set_nu
# product_info = set_product_info() # 상품정보리스트 생성 # product_info = set_product_info() # 상품정보리스트 생성
autoPercentyProductsDB = AutoPercentyProductsDB(mongo_config) autoPercentyProductsDB = AutoPercentyProductsDB(mongo_config)
# autoPercentyProductsDB.reset_product_by_id('660d636271e50111cf71284e') autoPercentyProductsDB.reset_product_by_id('660d636271e50111cf71284e')
# autoPercentyProductsDB.reset_product_by_id('660d635371e50111cf712842') autoPercentyProductsDB.reset_product_by_id('660d635371e50111cf712842')
try: try:
if login_info['per_mode']: if login_info['per_mode']:
@ -197,7 +197,7 @@ def modify_products(driver, gemini, translator, mongo_config, login_info, set_nu
steps_and_actions = [ steps_and_actions = [
('tag_modification', edit_tag, (driver, product_infos[i-1])), ('tag_modification', edit_tag, (driver, product_infos[i-1])),
('thumbnail_modification', modify_thumb_page, (driver, product_infos[i-1])), ('thumbnail_modification', modify_thumb_page, (driver, product_infos[i-1])),
('option_modification', modify_option_page, (driver, product_infos[i-1], gemini, translator, login_info)), ('option_modification', modify_option_page, (driver, product_infos[i-1], translator, login_info)),
('detail_page_modification', modify_detail_page, (driver, product_infos[i-1], gemini, translator, delv_collection, json_naver_codes, login_info)), ('detail_page_modification', modify_detail_page, (driver, product_infos[i-1], gemini, translator, delv_collection, json_naver_codes, login_info)),
('price_modification', modify_price_page, (driver, product_infos[i-1])), ('price_modification', modify_price_page, (driver, product_infos[i-1])),
('title_modification', modify_product_title, (driver, product_infos[i-1], login_info)), ('title_modification', modify_product_title, (driver, product_infos[i-1], login_info)),

File diff suppressed because one or more lines are too long