ㅅㄷㄴㅅ
This commit is contained in:
parent
d36d7850d9
commit
8367fbdf5a
32
ai/gemini.py
32
ai/gemini.py
|
|
@ -208,3 +208,35 @@ class ImageDescriptionGenerator:
|
|||
except Exception as e:
|
||||
logger.debug(f"예상치 못한 오류 발생: {e}", exc_info=True)
|
||||
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
|
||||
}
|
||||
|
||||
'''
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ from img_trans.image_trans import image_trans
|
|||
# 로거 인스턴스 가져오기
|
||||
logger = logging.getLogger('default_logger')
|
||||
|
||||
def modify_option_page2(driver, product_info, translator, login_info):
|
||||
def modify_option_page2(driver, product_info, gemini, translator, login_info):
|
||||
# 1. 현재 옵션에 대한 정보 수집
|
||||
options_info = collect_option_data(driver)
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ def modify_option_page2(driver, product_info, translator, login_info):
|
|||
save_changes(driver)
|
||||
|
||||
|
||||
def modify_option_page(driver, product_info, translator, login_info):
|
||||
def modify_option_page(driver, product_info, gemini, translator, login_info):
|
||||
simpleMode = login_info['whether_simpleMode']
|
||||
|
||||
option_css = ".ant-tabs-tab:nth-child(2)"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,871 @@
|
|||
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)
|
||||
|
|
@ -31,6 +31,9 @@ class ProductInfo:
|
|||
|
||||
self.naver_products = [] # 네이버 파싱된 상품들
|
||||
|
||||
self.is_option_product = False
|
||||
self.option_datas = {}
|
||||
|
||||
self.option_1_names = [] # 상품 옵션 이름 리스트
|
||||
self.trans_option_1_names = [] # 번역된 상품 옵션 이름 리스트
|
||||
self.trans_option_1_name_common_parts = [] # 번역된 상품 옵션 이름 리스트의 단어별 리스트 저장
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ def modify_products(driver, gemini, translator, mongo_config, login_info, set_nu
|
|||
steps_and_actions = [
|
||||
('tag_modification', edit_tag, (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], translator, login_info)),
|
||||
('option_modification', modify_option_page, (driver, product_infos[i-1], gemini, translator, 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])),
|
||||
('title_modification', modify_product_title, (driver, product_infos[i-1], login_info)),
|
||||
|
|
|
|||
Loading…
Reference in New Issue