현재 상태 저장

This commit is contained in:
Envy_PC 2024-11-28 21:23:22 +09:00
parent 9d4d34532e
commit 67b04a2d7a
75 changed files with 3894 additions and 232 deletions

1777
browser_control_ori.py Normal file

File diff suppressed because it is too large Load Diff

1822
browser_control_sync.py Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

81
gui.py
View File

@ -1,14 +1,9 @@
from PySide6.QtWidgets import QInputDialog, QWidget, QSpinBox, QPushButton, QVBoxLayout, QGridLayout, QPlainTextEdit, QTextEdit, QLabel, QLineEdit, QHBoxLayout, QProgressBar, QSizePolicy
from PySide6.QtWidgets import QInputDialog, QWidget, QMainWindow, QSpinBox, QPushButton, QVBoxLayout, QGridLayout, QPlainTextEdit, QTextEdit, QLabel, QLineEdit, QHBoxLayout, QProgressBar, QSizePolicy
from PySide6.QtCore import Qt, Slot, QRect, QSettings, QTimer
from toggleSwitch import ToggleSwitch
from browser_control import BrowserController
from browser_control_sync import BrowserController
# from whale_translator import WhaleTranslator
from whale_new import WhaleTranslator
from clipboardImageManager import ClipboardImageManager
from vertexAI import VertexAITranslator
from option import OptionHandler
from price import PriceHandler
from title import TitleHandler
from locatorManager import LocatorManager
from src.cmb_diag import CMBSettingsDialog
from src.DatabaseManager import DatabaseManager
@ -80,11 +75,6 @@ class AutoPercentyGUI(QWidget):
self.browser_controller.translation_completed.connect(self.on_PercentyJob_completed)
self.browser_controller.translation_error.connect(self.on_PercentyJob_error)
# self.clipboardImageManager = ClipboardImageManager(self, logger, self.browser_controller, watermark_font_size=36, debug=self.debug)
# self.optionHandler = OptionHandler(self.locator_manager, self.browser_controller, self.whale_translator, self.clipboardImageManager, self.logger, self.vertexAI, self.debug)
# self.priceHandler = PriceHandler(self.locator_manager, self.browser_controller, self.logger, self.optionHandler, self.vertexAI, self.cmb_diag, self.debug)
# self.titleHandler = TitleHandler(self.locator_manager, self.browser_controller, self.logger)
self.running = False
# 변수 설정
@ -764,30 +754,22 @@ class AutoPercentyGUI(QWidget):
@Slot()
def start_browser_thread(self):
"""브라우저 스레드 시작 및 GUI 상태 전달"""
time.sleep(2)
# 로그인 정보 설정
self.browser_controller.login_infos = {
'admin_id': self.admin_id_input.text(),
'admin_pw': self.admin_pw_input.text(),
'user_id': self.user_id_input.text(),
'user_pw': self.user_pw_input.text(),
'is_admin': self.admin_toggle.isChecked(),
}
# 로그인 정보 저장
self.save_settings()
# 스레드 시작
self.browser_controller.start()
time.sleep(1)
if self.browser_controller.isRunning():
# 스레드를 처음 시작하여 이벤트 루프를 실행
# self.browser_controller.start() # QThread의 start() 호출로 run() 실행
self.logger.debug("브라우저 스레드가 시작되었습니다.")
self.browser_controller.login_infos = {
'admin_id': self.admin_id_input.text(),
'admin_pw': self.admin_pw_input.text(),
'user_id': self.user_id_input.text(),
'user_pw': self.user_pw_input.text(),
'is_admin': self.admin_toggle.isChecked(),
}
# 로그인 정보 저장
self.save_settings()
# 스레드 시작
self.browser_controller.start_browser_task()
else:
self.logger.warning("브라우저 스레드가 실행중이지 않습니다.")
self.logger.debug("브라우저 스레드 시작됨")
@Slot()
def on_browser_started(self):
@ -803,17 +785,32 @@ class AutoPercentyGUI(QWidget):
"""브라우저 오류 발생 시 처리할 로직"""
self.logger.error(f"브라우저 시작 중 오류 발생: {error_message}")
# def closeEvent(self, event):
# """창 닫기 시 스레드 종료"""
# if self.browser_controller.isRunning():
# self.browser_controller.stop() # 리소스 정리
# self.browser_controller.wait() # 스레드가 종료될 때까지 대기
# event.accept()
def closeEvent(self, event):
"""창 닫기 시 스레드 종료"""
if self.browser_controller.isRunning():
self.browser_controller.stop() # 리소스 정리
self.browser_controller.wait() # 스레드가 종료될 때까지 대기
event.accept()
self.browser_controller.requestInterruption() # QThread 종료 요청
self.browser_controller.stop() # WhaleTranslator 등 리소스 정리
self.browser_controller.wait() # QThread가 종료될 때까지 대기
super().closeEvent(event)
# def close(self):
# self.logger.debug('프로그램을 종료합니다...')
# self.save_settings()
# asyncio.run(self.browser_controller.close_browser()) # 브라우저 종료
# super().close()
def close(self):
self.logger.debug('프로그램을 종료합니다...')
self.save_settings()
asyncio.run(self.browser_controller.close_browser()) # 브라우저 종료
self.browser_controller.requestInterruption() # QThread 종료 요청
self.browser_controller.stop() # WhaleTranslator 등 리소스 정리
self.browser_controller.wait() # QThread가 종료될 때까지 대기
super().close()
@ -822,7 +819,7 @@ class AutoPercentyGUI(QWidget):
"""상품수정 스레드 시작 및 상태 전달"""
if self.browser_controller.isRunning():
# 스레드 시작
self.browser_controller.start_PercentyJob_task()
self.browser_controller.start_Percenty_task()
self.logger.info("상품수정 작업 스레드가 시작되었습니다.")
else:
self.logger.info("브라우저 스레드가 없습니다.")

254
option.py
View File

@ -132,7 +132,7 @@ class OptionHandler:
self.logger.debug(f"최종 선택된 옵션: {[(opt['name'], opt['price']) for opt in final_options]}")
return final_options
async def store_selected_options(self):
def store_selected_options(self):
"""현재 페이지에서 선택된 옵션을 수집하여, 가격 낮은 순으로 정렬한 후 클래스 변수에 저장"""
try:
selected_translated_options = {}
@ -143,17 +143,17 @@ class OptionHandler:
checked_count = sum(value is True for value in self.option_info['checked_states'].values())
self.logger.debug(f"체크된 옵션 수: {checked_count}")
await self.low_order_click()
self.low_order_click()
for i in range(1, total_options_count + 1):
option_excluded_selector = self.option_excluded_selector_template.format(index=i)
option_input_selector = self.option_input_selector_template.format(index=i)
option_excluded_element = await self.page.query_selector(option_excluded_selector)
option_excluded_element = self.page.query_selector(option_excluded_selector)
if not option_excluded_element:
option_input_element = await self.page.query_selector(option_input_selector)
option_input_element = self.page.query_selector(option_input_selector)
if option_input_element:
option_name_value = (await option_input_element.get_attribute('value')).strip()
option_name_value = (option_input_element.get_attribute('value')).strip()
selected_translated_options[option_name_value] = self.option_info['prices'].get(option_name_value, {}).get('low_price', 0)
self.option_info['selected_translated_options'] = selected_translated_options
@ -163,7 +163,7 @@ class OptionHandler:
self.logger.error(f"선택된 옵션 저장 중 오류 발생: {e}", exc_info=True)
async def process_options(self, product_name, max_option_count=20, toggle_states=False):
def process_options(self, product_name, max_option_count=20, toggle_states=False):
"""
옵션 처리 로직. 옵션을 번역하고 이미지를 업데이트함.
@ -182,16 +182,16 @@ class OptionHandler:
# self.low_order_click()
# self.logger.debug(f"휠스크롤 Down")
# await self.browser_controller.scroll_with_wheel('down')
# self.browser_controller.scroll_with_wheel('down')
# self.logger.debug(f"휠스크롤 Up")
# await self.browser_controller.scroll_with_wheel('up')
# self.browser_controller.scroll_with_wheel('up')
# await self.page.wait_for_load_state('domcontentloaded')
# self.page.wait_for_load_state('domcontentloaded')
# self.logger.debug(f"동적요소 로딩완료")
await asyncio.sleep(2)
time.sleep(2)
# 1. 단일 옵션인지 판단
if await self.is_single_option():
if self.is_single_option():
self.logger.debug("단일 옵션 상품입니다. 옵션 수정 과정을 생략합니다.")
self.option_info['is_single_option'] = True
@ -204,7 +204,7 @@ class OptionHandler:
if click_to_check_to_all:
self.logger.debug("옵션이 일부만 체크된 상태입니다. 전체 체크로 바꿉니다.")
await self.is_all_options_checked(click_to_check_to_all)
self.is_all_options_checked(click_to_check_to_all)
else:
self.logger.debug("옵션이 일부만 체크된 상태입니다. 옵션 수정이 완료된 상품으로 판단하여 패스합니다.")
self.option_info['is_completed_option'] = True
@ -212,13 +212,13 @@ class OptionHandler:
return self.option_info
# 3. 가격 낮은 순 정렬 클릭
await self.low_order_click()
self.low_order_click()
# 4. 옵션 정보 수집
try:
self.logger.info(f"옵션 정보 수집")
self.option_info = await self.collect_options_info()
self.option_info = self.collect_options_info()
self.logger.debug(f"수집된 옵션 정보 : {self.option_info}")
except Exception as e:
# 옵션 처리 중 오류 발생 시 전체 로그 출력
@ -233,9 +233,9 @@ class OptionHandler:
try:
# Vertex AI를 통한 번역 시도
translated_options = await self.vertexAItranslator.translate_options(self.option_info.get('original_names'), product_name)
translated_options = self.vertexAItranslator.translate_options(self.option_info.get('original_names'), product_name)
self.logger.debug(f"번역된 옵션 입력")
await self.apply_translated_options(translated_options, self.option_info['edit_fields'])
self.apply_translated_options(translated_options, self.option_info['edit_fields'])
self.is_vertext_success = True # 번역 성공
@ -244,9 +244,9 @@ class OptionHandler:
if "SAFETY" in str(ve):
self.logger.error(f"안전 필터에 의해 번역 요청이 차단되었습니다. {ve}")
self.logger.debug("퍼센티 자체 AI번역 사용 시도")
await self.page.click(self.ai_option_btn_selector)
self.page.click(self.ai_option_btn_selector)
self.logger.debug("번역을 위한 5초간 대기")
await asyncio.sleep(5)
time.sleep(5)
self.is_vertext_success = False
self.is_percenty_success = True
@ -263,9 +263,9 @@ class OptionHandler:
# 기타 예외 처리
self.logger.error(f"번역 처리 중 알 수 없는 오류 발생: {e}", exc_info=True)
self.logger.debug("퍼센티 자체 AI번역 사용 시도")
await self.page.click(self.ai_option_btn_selector)
self.page.click(self.ai_option_btn_selector)
self.logger.debug("번역을 위한 5초간 대기")
await asyncio.sleep(5)
time.sleep(5)
self.is_vertext_success = False # 번역 실패
self.is_percenty_success = True
@ -277,35 +277,35 @@ class OptionHandler:
optionAutoSelect = toggle_states.get('optionAutoSelect')
if optionAutoSelect:
self.logger.debug(f"옵션 필터링 및 조정 : {optionAutoSelect}")
await self.filter_and_adjust_options(max_option_count)
self.filter_and_adjust_options(max_option_count)
# 가격 낮은 순 재정렬 클릭
await self.low_order_click()
self.low_order_click()
# 6. 선택된 옵션정보 재수집
# if self.is_percenty_success:
self.logger.debug(f"옵션정보 재수집")
await self.store_selected_options() # 페이지에서 실제 선택된 옵션을 수집하여 저장
self.store_selected_options() # 페이지에서 실제 선택된 옵션을 수집하여 저장
# 7. 옵션 이미지 업데이트 (옵션 이미지가 있는 경우)
if toggle_states.get('optionIMGTrans'):
self.logger.debug(f"옵션 이미지 번역을 시작합니다.")
await self.update_option_image(toggle_states, debug_flag=self.debug_flag)
self.update_option_image(toggle_states, debug_flag=self.debug_flag)
# 8. A-Z or 1-99 button 클릭
what_prefix_button = '1-99'
if what_prefix_button == 'A-Z':
await self.AtoZ_button_click()
self.AtoZ_button_click()
elif what_prefix_button == '1-99':
# # 9. A-Z 버튼 클릭
await self.one_to_nine_button_click()
self.one_to_nine_button_click()
await self.low_order_click()
self.low_order_click()
# 9. 저장 버튼 클릭
self.logger.debug("저장 버튼을 클릭합니다.")
await self.page.click('button:has-text("저장하기")')
self.page.click('button:has-text("저장하기")')
self.logger.debug("옵션 처리 완료.")
return self.option_info
@ -314,13 +314,13 @@ class OptionHandler:
self.logger.debug(f"옵션 처리 중 오류 발생: {e}", exc_info=True)
return None
async def is_single_option(self):
def is_single_option(self):
"""단일 상품 상태 여부를 확인하는 메서드"""
try:
# 단일 상품 등록 버튼이 선택되었는지 확인
single_option_checked = await self.page.query_selector(self.single_option_locator) is not None
single_option_checked = self.page.query_selector(self.single_option_locator) is not None
# 옵션 상품 등록 버튼이 선택되었는지 확인
option_product_checked = await self.page.query_selector(self.option_product_locator) is not None
option_product_checked = self.page.query_selector(self.option_product_locator) is not None
# 두 요소의 상태를 기반으로 단일 상품 여부 결정
is_single = single_option_checked and not option_product_checked
@ -331,7 +331,7 @@ class OptionHandler:
return False
async def is_all_options_checked(self, click_to_check_to_all=False):
def is_all_options_checked(self, click_to_check_to_all=False):
"""
전체 옵션 체크박스 상태를 확인하고 필요한 경우 전체 체크 상태로 변경.
@ -343,13 +343,13 @@ class OptionHandler:
"""
try:
# 전체 체크박스 요소 가져오기
checkbox_element = await self.page.query_selector(self.is_all_option_checked_selector)
checkbox_element = self.page.query_selector(self.is_all_option_checked_selector)
if not checkbox_element:
self.logger.error("전체 체크박스 요소를 찾을 수 없습니다.")
return False
# 체크박스 상태 확인
aria_checked = await checkbox_element.get_attribute('aria-checked')
aria_checked = checkbox_element.get_attribute('aria-checked')
self.logger.debug(f"aria_checked : {aria_checked}----------------")
@ -359,7 +359,7 @@ class OptionHandler:
if click_to_check_to_all:
# 전체 체크 수행
await checkbox_element.click()
checkbox_element.click()
self.logger.debug("전체 체크박스를 전체 체크 상태로 변경")
return True # 전체 체크 후 True 반환
else:
@ -377,12 +377,78 @@ class OptionHandler:
self.logger.error(f"전체 옵션 체크박스 상태 확인 중 오류 발생: {e}", exc_info=True)
return False
async def collect_options_info(self):
def collect_options_info(self):
"""옵션 정보를 수집 (이미지, 옵션명, 편집 필드, 가격, 체크박스 정보 포함)"""
try:
# 총 옵션 갯수 수집
total_options_element = await self.page.query_selector(self.total_options_selector)
total_options_count = int(''.join(filter(str.isdigit, await total_options_element.inner_text()))) if total_options_element else 0
total_options_element = self.page.query_selector(self.total_options_selector)
total_options_count = int(''.join(filter(str.isdigit, total_options_element.inner_text()))) if total_options_element else 0
self.logger.debug(f"총 옵션 갯수: {total_options_count}")
# 옵션 정보를 동기적으로 수집
for i in range(1, total_options_count + 1):
try:
original_name_selector = self.original_name_selector_template.format(index=i)
edit_field_selector = self.edit_field_selector_template.format(index=i)
checkbox_selector = self.checkbox_selector_template.format(index=i)
image_selector = self.image_selector_template.format(index=i)
price_selector = self.price_selector_template.format(index=i)
# 각 요소 동기적으로 가져오기
original_name_element = self.page.query_selector(original_name_selector)
edit_field_element = self.page.query_selector(edit_field_selector)
checkbox_element = self.page.query_selector(checkbox_selector)
image_element = self.page.query_selector(image_selector)
price_element = self.page.query_selector(price_selector)
self.logger.debug(f"{i}번째 original_name_element : {original_name_element}")
self.logger.debug(f"{i}번째 edit_field_element : {edit_field_element}")
self.logger.debug(f"{i}번째 checkbox_element : {checkbox_element}")
self.logger.debug(f"{i}번째 image_element : {image_element}")
self.logger.debug(f"{i}번째 price_element : {price_element}")
if original_name_element:
original_name = original_name_element.inner_text()
self.option_info['original_names'][f'origin_option_{i}'] = original_name
self.option_info['edit_fields'][original_name] = edit_field_element if edit_field_element else None
self.option_info['checkboxes'].append(checkbox_element if checkbox_element else None)
# 체크박스 상태 수집 (체크 여부는 클래스 이름으로 판단)
checkbox_state = None
if checkbox_element:
checkbox_classes = checkbox_element.get_attribute('class')
if 'ant-checkbox-checked' in checkbox_classes:
checkbox_state = True
elif 'ant-checkbox' in checkbox_classes:
checkbox_state = False
self.option_info['checkboxes'].append({'option_name': original_name, 'checked': checkbox_state})
self.logger.debug(f"=============================================")
self.logger.debug(f"{i}번째 옵션 checkbox_state : {checkbox_state}")
self.logger.debug(f"=============================================")
if image_element:
self.option_info['images'][original_name] = image_element.get_attribute('src')
if price_element:
price_text = price_element.inner_text()
price = [int(p.replace(",", "")) for p in price_text.replace("", "").split(" - ")]
self.option_info['prices'][original_name] = {'low_price': price[0], 'high_price': price[-1]}
self.logger.debug(f"{i}번째 옵션 정보 수집 완료")
except Exception as e:
self.logger.error(f"{i}번째 옵션 수집 중 오류 발생: {e}", exc_info=True)
except Exception as e:
self.logger.error(f"옵션 정보 수집 중 오류 발생: {e}", exc_info=True)
return self.option_info
def collect_options_info_async(self):
"""옵션 정보를 수집 (이미지, 옵션명, 편집 필드, 가격, 체크박스 정보 포함)"""
try:
# 총 옵션 갯수 수집
total_options_element = self.page.query_selector(self.total_options_selector)
total_options_count = int(''.join(filter(str.isdigit, total_options_element.inner_text()))) if total_options_element else 0
self.logger.debug(f"총 옵션 갯수: {total_options_count}")
@ -403,7 +469,7 @@ class OptionHandler:
self.page.query_selector(price_selector)
]
elements = await asyncio.gather(*tasks)
elements = asyncio.gather(*tasks)
original_name_element, edit_field_element, checkbox_element, image_element, price_element = elements
self.logger.debug(f"{i}번째 original_name_element : {original_name_element}")
@ -413,7 +479,7 @@ class OptionHandler:
self.logger.debug(f"{i}번째 price_element : {price_element}")
if original_name_element:
original_name = await original_name_element.inner_text()
original_name = original_name_element.inner_text()
self.option_info['original_names'][f'origin_option_{i}'] = original_name
self.option_info['edit_fields'][original_name] = edit_field_element if edit_field_element else None
self.option_info['checkboxes'].append(checkbox_element if checkbox_element else None)
@ -422,7 +488,7 @@ class OptionHandler:
checkbox_state = None
if checkbox_element:
checkbox_classes = await checkbox_element.get_attribute('class')
checkbox_classes = checkbox_element.get_attribute('class')
if 'ant-checkbox-checked' in checkbox_classes:
checkbox_state = True
elif 'ant-checkbox' in checkbox_classes:
@ -435,9 +501,9 @@ class OptionHandler:
self.logger.debug(f"=============================================")
if image_element:
self.option_info['images'][original_name] = await image_element.get_attribute('src')
self.option_info['images'][original_name] = image_element.get_attribute('src')
if price_element:
price_text = await price_element.inner_text()
price_text = price_element.inner_text()
price = [int(p.replace(",", "")) for p in price_text.replace("", "").split(" - ")]
self.option_info['prices'][original_name] = {'low_price': price[0], 'high_price': price[-1]}
@ -449,16 +515,16 @@ class OptionHandler:
return self.option_info
# async def collect_options_info_ori(self):
# def collect_options_info_ori(self):
# """옵션 정보를 수집 (이미지, 옵션명, 편집 필드, 가격, 체크박스 정보 포함)"""
# self.init_option_info()
# try:
# # 총 옵션 갯수 수집
# total_options_selector = '#productMainContentContainerId label.ant-checkbox-wrapper'
# total_options_element = await self.page.query_selector(total_options_selector)
# total_options_element = self.page.query_selector(total_options_selector)
# if total_options_element:
# total_options_text = await total_options_element.inner_text()
# total_options_text = total_options_element.inner_text()
# total_options_count = int(''.join(filter(str.isdigit, total_options_text))) # 숫자만 추출
# else:
# total_options_count = 0 # 옵션 갯수를 찾지 못할 경우 기본값
@ -470,8 +536,8 @@ class OptionHandler:
# try:
# # 원본옵션명 수집
# original_name_selector = f"div#productMainContentContainerId li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(3) > span"
# original_name_element = await self.page.query_selector(original_name_selector)
# original_name = await original_name_element.inner_text() if original_name_element else None
# original_name_element = self.page.query_selector(original_name_selector)
# original_name = original_name_element.inner_text() if original_name_element else None
# if original_name:
# # 옵션명 기준으로 수집 항목 구성
@ -480,7 +546,7 @@ class OptionHandler:
# # 옵션 편집 필드 수집
# edit_field_selector = f"div#productMainContentContainerId li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(2) > div:nth-child(1) > span > input"
# edit_field_element = await self.page.query_selector(edit_field_selector)
# edit_field_element = self.page.query_selector(edit_field_selector)
# if edit_field_element:
# self.option_info['edit_fields'][original_name] = edit_field_element
# self.logger.debug(f"{i}번째 옵션편집필드 수집 완료 : {edit_field_element}")
@ -491,14 +557,14 @@ class OptionHandler:
# # 옵션 체크박스 수집
# checkbox_selector = f'#productMainContentContainerId li:nth-child({i}) input[type="checkbox"]'
# # f"div#productMainContentContainerId li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(1) > label > span > input"
# checkbox_element = await self.page.query_selector(checkbox_selector)
# checkbox_element = self.page.query_selector(checkbox_selector)
# if checkbox_element:
# self.option_info['checkboxes'].append(checkbox_element)
# self.logger.debug(f"{i}번째 옵션 체크박스 수집 완료 : {checkbox_element}")
# # 체크 상태 수집
# self.logger.debug(f"{i}번째 옵션 체크박스 상태 수집")
# is_checked = await checkbox_element.is_checked()
# is_checked = checkbox_element.is_checked()
# original_name = self.option_info['original_names'].get(f'origin_option_{i}')
# self.option_info['checked_states'][original_name] = is_checked
# self.logger.debug(f"{i}번째 옵션 체크 상태: {is_checked}")
@ -510,9 +576,9 @@ class OptionHandler:
# image_selector = f'#productMainContentContainerId li:nth-child({i}) img.sc-gbvfcU.ezktkd'
# # "div#productMainContentContainerId li:nth-child(1) > div > div:nth-child(1) > div > div:nth-child(2) > div > img"
# # "div#productMainContentContainerId li:nth-child(2) > div > div:nth-child(1) > div > div:nth-child(2) > div > img"
# image_element = await self.page.query_selector(image_selector)
# image_element = self.page.query_selector(image_selector)
# if image_element:
# image_url = await image_element.get_attribute('src')
# image_url = image_element.get_attribute('src')
# self.option_info['images'][original_name] = image_url
# self.logger.debug(f"{i}번째 옵션 이미지 수집 완료 : {image_element}")
# else:
@ -521,9 +587,9 @@ class OptionHandler:
# # 가격 정보 수집
# price_selector = f'#productMainContentContainerId li:nth-child({i}) sup'
# price_element = await self.page.query_selector(price_selector)
# price_element = self.page.query_selector(price_selector)
# if price_element:
# price_text = await price_element.inner_text()
# price_text = price_element.inner_text()
# price_text = price_text.replace(",", "").replace("원", "").strip()
# if " - " in price_text:
@ -545,7 +611,7 @@ class OptionHandler:
# return self.option_info
async def apply_translated_options(self, translated_options, edit_fields):
def apply_translated_options(self, translated_options, edit_fields):
"""번역된 옵션명을 편집 필드에 입력하고 selected_translated_options을 한 번에 업데이트"""
try:
updated_translations = {} # 업데이트된 번역 옵션을 저장할 딕셔너리
@ -562,7 +628,7 @@ class OptionHandler:
self.logger.debug(f"{key}번째 번역옵션 필드 : {edit_field}")
if edit_field:
await edit_field.fill(translated_name) # 필드에 번역된 옵션명 입력
edit_field.fill(translated_name) # 필드에 번역된 옵션명 입력
self.logger.info(f"{key}번째 translated_name : [{translated_name}] 입력 완료")
# 업데이트할 번역된 이름을 임시 딕셔너리에 저장
@ -595,7 +661,7 @@ class OptionHandler:
rounded_number = math.ceil(number / nearest) * nearest
return rounded_number
async def filter_and_adjust_options(self, max_option_count):
def filter_and_adjust_options(self, max_option_count):
"""가격 필터링을 적용하고 옵션을 조정"""
try:
# 옵션 가격 정보 수집
@ -627,12 +693,12 @@ class OptionHandler:
# ]
# 체크박스 상태 조정
await self.adjust_options(filtered_option_names, max_option_count)
self.adjust_options(filtered_option_names, max_option_count)
except Exception as e:
self.logger.error(f"옵션 필터링 및 조정 중 오류 발생: {e}", exc_info=True)
async def adjust_options(self, filtered_option_names, max_option_count):
def adjust_options(self, filtered_option_names, max_option_count):
"""
필터링된 옵션에 맞게 체크박스 상태를 조정하는 메서드.
:param filtered_option_names: 필터링된 옵션 리스트
@ -644,7 +710,7 @@ class OptionHandler:
self.logger.info(f"최대 선택 가능 옵션수 설정값 : {max_option_count}")
for i, name in enumerate(self.option_info['original_names'].values()):
checkbox_selector = self.checkbox_selector_template.format(index=i+1)
checkbox_element = await self.page.query_selector(checkbox_selector)
checkbox_element = self.page.query_selector(checkbox_selector)
# 디버깅 로그: 현재 옵션 이름과 필터링된 옵션 이름 확인
self.logger.debug(f"옵션 이름: {name}, 필터링된 옵션에 포함 여부: {name in filtered_option_names}")
@ -657,7 +723,7 @@ class OptionHandler:
selected_count += 1
# 필터링된 옵션에 포함되지 않거나 최대 선택 가능한 수량을 초과했을 경우 체크 해제
else:
await checkbox_element.click()
checkbox_element.click()
self.logger.debug(f"옵션 '{name}' 체크 해제함")
self.option_info['checked_states'][name] = False
@ -665,7 +731,7 @@ class OptionHandler:
except Exception as e:
self.logger.error(f"옵션 체크 상태 조정 중 오류 발생: {e}", exc_info=True)
async def adjust_options_without_max_count(self, filtered_option_names, max_option_count):
def adjust_options_without_max_count(self, filtered_option_names, max_option_count):
"""
필터링된 옵션에 맞게 체크박스 상태를 조정하는 메서드.
:param filtered_options: 필터링된 옵션 리스트
@ -676,7 +742,7 @@ class OptionHandler:
# 옵션 체크 상태를 수집한 정보에서 필터링된 옵션들만 체크 상태로 유지
for i, name in enumerate(self.option_info['original_names'].values()):
checkbox_selector = self.checkbox_selector_template.format(index=i+1)
checkbox_element = await self.page.query_selector(checkbox_selector)
checkbox_element = self.page.query_selector(checkbox_selector)
# is_checked = self.option_info['checked_states'].get(name, False)
# 디버깅 로그: 현재 옵션 이름과 필터링된 옵션 이름 확인
@ -685,12 +751,12 @@ class OptionHandler:
if checkbox_element:
# 필터링된 이름에 포함되는 경우
if name in filtered_option_names:
# await checkbox_element.click()
# checkbox_element.click()
self.logger.debug(f"옵션 '{name}' 체크함")
self.option_info['checked_states'][name] = True
# 필터링된 이름에 포함되지 않는 경우
else:
await checkbox_element.click()
checkbox_element.click()
self.logger.debug(f"옵션 '{name}' 체크 해제함")
self.option_info['checked_states'][name] = False
@ -699,19 +765,19 @@ class OptionHandler:
self.logger.error(f"옵션 체크 상태 조정 중 오류 발생: {e}", exc_info=True)
async def AtoZ_button_click(self):
def AtoZ_button_click(self):
self.logger.debug("A-Z 버튼을 클릭합니다.")
await self.page.click(self.AtoZ_button_locator)
self.page.click(self.AtoZ_button_locator)
async def one_to_nine_button_click(self):
def one_to_nine_button_click(self):
self.logger.debug("1-99 버튼을 클릭합니다.")
await self.page.click(self.one_to_nine_button_locator)
self.page.click(self.one_to_nine_button_locator)
async def low_order_click(self):
def low_order_click(self):
self.logger.debug("가격 낮은 순 정렬을 클릭합니다.")
await self.page.click(self.low_order_button_locator)
self.page.click(self.low_order_button_locator)
async def update_option_image(self, toggle_states, debug_flag=False):
def update_option_image(self, toggle_states, debug_flag=False):
"""
옵션 이미지가 존재할 경우, 제외된 옵션이 아닌 경우 번역하여 업데이트하는 메서드.
@ -726,11 +792,11 @@ class OptionHandler:
try:
# 모든 옵션 상자 요소 가져오기
option_boxes = await self.page.query_selector_all(self.option_box_selector)
option_boxes = self.page.query_selector_all(self.option_box_selector)
# option_boxes = await self.page.query_selector_all("div#productMainContentContainerId li > div > div:nth-child(1) > div > div:nth-child(2) > div")
# option_boxes = self.page.query_selector_all("div#productMainContentContainerId li > div > div:nth-child(1) > div > div:nth-child(2) > div")
# option_image_element = await self.page.locator("xpath=//*[@id='productMainContentContainerId']/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[1]/div/div[1]/div/div[2]/div/img").element_handle()
# option_image_element = self.page.locator("xpath=//*[@id='productMainContentContainerId']/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[5]/div[1]/div/div/ul/li[1]/div/div[1]/div/div[2]/div/img").element_handle()
total_options = len(option_boxes)
self.logger.debug(f"{total_options}개의 옵션 이미지 번역을 시작합니다.")
@ -743,18 +809,18 @@ class OptionHandler:
add_button_selector = self.add_button_selector.format(index=index)
# 옵션 박스 내부의 텍스트 확인하여 "제외된 옵션" 포함 여부 검사
option_text_content = await option_box.inner_text()
option_text_content = option_box.inner_text()
if "제외된 옵션" in option_text_content:
self.logger.debug(f"{index}번째 옵션은 제외된 옵션입니다. 번역을 생략합니다.")
continue # 제외된 옵션이므로 다음 옵션으로 이동
# 옵션 이미지가 존재하는지 확인
option_image = await option_box.query_selector("img")
option_image = option_box.query_selector("img")
if option_image is None:
self.logger.debug(f"{index}번째 옵션에 이미지가 없습니다. 다음 옵션으로 이동합니다.")
continue
option_image_url = await option_image.get_attribute("src")
option_image_url = option_image.get_attribute("src")
self.logger.debug(f"{index}번째 옵션 이미지 URL: {option_image_url}")
# 이미지가 SVG 형식일 경우 번역을 건너뜀
@ -776,37 +842,37 @@ class OptionHandler:
if is_success_translated and os.path.exists(translated_image_path):
# 삭제 버튼 클릭
# delete_button = await self.page.query_selector(delete_button_selector)
# delete_button = self.page.query_selector(delete_button_selector)
self.logger.debug(f"{index}번째 옵션의 이미지 삭제 버튼 가져오기")
try:
# 기본 선택자로 삭제 버튼 찾기
delete_button = self.page.locator(f'{self.delete_button_selector_template.format(index=index)}')
await delete_button.wait_for(state="attached", timeout=5000) # 타임아웃 설정
delete_button.wait_for(state="attached", timeout=5000) # 타임아웃 설정
if not await delete_button.is_visible():
if not delete_button.is_visible():
# fallback으로 재시도
delete_button = self.page.locator(f'xpath={self.fallback1_delete_button_selector_template.format(index=index)}')
await delete_button.wait_for(state="attached", timeout=5000)
delete_button.wait_for(state="attached", timeout=5000)
if await delete_button.is_visible():
await delete_button.click()
if delete_button.is_visible():
delete_button.click()
self.logger.debug(f"{index}번째 옵션의 삭제 버튼 클릭")
# 다이알로그 확인 후 삭제 버튼 클릭
try:
self.logger.debug(f"{index}번째 옵션의 삭제 다이알로그 확인 중...")
dialog = await self.page.wait_for_selector(self.delete_dialog_selector, timeout=5000) # 다이알로그 클래스 확인
dialog = self.page.wait_for_selector(self.delete_dialog_selector, timeout=5000) # 다이알로그 클래스 확인
if dialog:
self.logger.debug(f"{index}번째 옵션의 삭제 다이알로그 확인됨")
# 삭제 확인 버튼 찾기
confirm_delete_button = await dialog.query_selector(self.confirm_delete_button_selector)
confirm_delete_button = dialog.query_selector(self.confirm_delete_button_selector)
if confirm_delete_button:
await confirm_delete_button.click()
confirm_delete_button.click()
self.logger.debug(f"{index}번째 옵션의 삭제 확인 버튼 클릭됨")
else:
self.logger.error(f"{index}번째 옵션의 삭제 확인 버튼이 보이지 않습니다.")
@ -821,24 +887,24 @@ class OptionHandler:
try:
# '+ 버튼' 클릭 후 파일 업로드
self.logger.debug(f"{index}번째 옵션의 이미지추가 버튼 가져오기")
add_button = await self.page.query_selector(add_button_selector)
add_button = self.page.query_selector(add_button_selector)
if add_button:
await add_button.click()
add_button.click()
self.logger.debug(f"{index}번째 옵션의 이미지추가 버튼 클릭")
# 파일 업로드 영역의 input 요소 직접 선택 (수정된 부분)
file_input = await self.page.query_selector(self.file_upload_button_selector) # Ant Design의 클래스 사용
file_input = self.page.query_selector(self.file_upload_button_selector) # Ant Design의 클래스 사용
if file_input:
# Playwright의 set_input_files를 사용하여 파일 업로드 처리
await file_input.set_input_files(translated_image_path)
file_input.set_input_files(translated_image_path)
self.logger.debug(f"{index}번째 옵션의 파일 업로드 완료")
# '이미지 삽입' 버튼 클릭
confirm_upload_button = await self.page.wait_for_selector(self.confirm_upload_button_selector)
await confirm_upload_button.click()
confirm_upload_button = self.page.wait_for_selector(self.confirm_upload_button_selector)
confirm_upload_button.click()
self.logger.debug(f"{index}번째 옵션에 이미지가 업로드되었습니다.")
else:
self.logger.error(f"{index}번째 옵션의 파일 입력 요소를 찾을 수 없습니다.")

View File

@ -77,14 +77,14 @@ class PriceHandler:
self.optimal_price_config = self.set_optimal_price_config(sold_price=None, cost_price2X=None, calculated_price=None, lower_bound=0.85, upper_bound=1.15, ratios={'sold_price': 0.5, 'cost_price2X': 0.3, 'calculated_price': 0.2})
self.logger.debug(f"self.optimal_price_config : {self.optimal_price_config}")
async def process_price(self, sold_price=0, category=None, is_group=False):
def process_price(self, sold_price=0, category=None, is_group=False):
try:
# 상품정보 초기화
self.initialize_values()
# 1. 페이지에서 가격 정보 수집
self.logger.debug("초기 더하기마진과 해외배송비 가격 정보를 수집합니다.")
initial_plusmargin, initial_shipping_price = await self.get_plusmargin_and_shipping_values()
initial_plusmargin, initial_shipping_price = self.get_plusmargin_and_shipping_values()
# if initial_plusmargin and initial_plusmargin != 10000:
if (initial_plusmargin != 10000 and initial_shipping_price != 0):
@ -96,9 +96,9 @@ class PriceHandler:
is_single = self.optionHandler.option_info['is_single_option']
if not is_single:
await self.ordering_by_option_button_click()
self.ordering_by_option_button_click()
option_data, min_cost, max_cost, avg_cost, upper_avg_cost = await self.collect_product_costs_and_prices(is_single, is_group) # 수집된 옵션정보를 반환
option_data, min_cost, max_cost, avg_cost, upper_avg_cost = self.collect_product_costs_and_prices(is_single, is_group) # 수집된 옵션정보를 반환
if option_data is None:
self.logger.error("상품 옵션 정보를 수집하지 못했습니다.", exc_info=True)
return
@ -133,12 +133,12 @@ class PriceHandler:
# 5. 계산된 값 입력
self.logger.debug("계산된 값을 페이지에 입력합니다.")
await self.input_calculated_values(additional_margin, shipping_cost)
self.input_calculated_values(additional_margin, shipping_cost)
# 6. 반품비, 초도배송비, 교환비 계산 및 입력
return_fee, first_delv_fee, exchange_fee = self.calculate_claim_costs(max_cost)
self.logger.debug(f"반품비: {return_fee}, 초도배송비: {first_delv_fee}, 교환비: {exchange_fee}")
await self.input_claim_costs(return_fee, first_delv_fee, exchange_fee)
self.input_claim_costs(return_fee, first_delv_fee, exchange_fee)
# 7. 저장
# save_xpath = "//button[contains(.,'저장하기')]"
@ -149,7 +149,7 @@ class PriceHandler:
self.logger.error(f"가격 수정 중 오류 발생: {e}", exc_info=True)
async def input_claim_costs(self, return_fee, first_delv_fee, exchange_fee):
def input_claim_costs(self, return_fee, first_delv_fee, exchange_fee):
"""
계산된 반품비, 초도배송비, 교환비를 입력합니다.
@ -161,28 +161,28 @@ class PriceHandler:
"""
try:
# 반품비 수정
return_fee_element = await self.page.wait_for_selector(self.return_fee_input_locator, timeout=5000)
return_fee_element = self.page.wait_for_selector(self.return_fee_input_locator, timeout=5000)
if return_fee_element:
return_fee = self.round_to_UP(return_fee)
await return_fee_element.fill(str(return_fee)) # 기존 내용을 지우고 새 값을 입력
return_fee_element.fill(str(return_fee)) # 기존 내용을 지우고 새 값을 입력
self.logger.debug(f"반품비 수정 완료: {return_fee}")
else:
self.logger.error(f"반품비 입력 중 오류 발생: 요소를 찾을 수 없음", exc_info=True)
# 초도배송비 수정
first_delv_fee_element = await self.page.wait_for_selector(self.first_delv_fee_input_locator, timeout=5000)
first_delv_fee_element = self.page.wait_for_selector(self.first_delv_fee_input_locator, timeout=5000)
if first_delv_fee_element:
first_delv_fee = self.round_to_UP(first_delv_fee)
await first_delv_fee_element.fill(str(first_delv_fee)) # 기존 내용을 지우고 새 값을 입력
first_delv_fee_element.fill(str(first_delv_fee)) # 기존 내용을 지우고 새 값을 입력
self.logger.debug(f"초도배송비 수정 완료: {first_delv_fee}")
else:
self.logger.error(f"초도배송비 입력 중 오류 발생: 요소를 찾을 수 없음", exc_info=True)
# 교환비 수정
exchange_fee_element = await self.page.wait_for_selector(self.exchange_fee_input_locator, timeout=5000)
exchange_fee_element = self.page.wait_for_selector(self.exchange_fee_input_locator, timeout=5000)
if exchange_fee_element:
exchange_fee = self.round_to_UP(exchange_fee)
await exchange_fee_element.fill(str(exchange_fee)) # 기존 내용을 지우고 새 값을 입력
exchange_fee_element.fill(str(exchange_fee)) # 기존 내용을 지우고 새 값을 입력
self.logger.debug(f"교환비 수정 완료: {exchange_fee}")
else:
self.logger.error(f"교환비 입력 중 오류 발생: 요소를 찾을 수 없음", exc_info=True)
@ -221,7 +221,7 @@ class PriceHandler:
self.logger.error(f"Claim cost 계산 중 오류 발생: {e}", exc_info=True)
return 199000, 199000, 499000
async def input_calculated_values(self, margin, shipping_cost):
def input_calculated_values(self, margin, shipping_cost):
"""
계산된 마진, 배송비, 교환비를 옵션에 입력합니다.
@ -236,13 +236,13 @@ class PriceHandler:
shipping_cost = self.round_to_UP(shipping_cost)
# 더하기 마진 입력 (4번째 인풋박스)
margin_element = await self.page.wait_for_selector(self.plus_margin_locator, timeout=5000)
await margin_element.fill(str(margin))
margin_element = self.page.wait_for_selector(self.plus_margin_locator, timeout=5000)
margin_element.fill(str(margin))
self.logger.debug(f"더하기 마진 입력 완료: {margin}")
# 해외 배송비 입력 (5번째 인풋박스)
oversea_shipping_element = await self.page.wait_for_selector(self.oversea_shipping_locator, timeout=5000)
await oversea_shipping_element.fill(str(shipping_cost))
oversea_shipping_element = self.page.wait_for_selector(self.oversea_shipping_locator, timeout=5000)
oversea_shipping_element.fill(str(shipping_cost))
self.logger.debug(f"해외 배송비 입력 완료: {shipping_cost}")
except Exception as e:
@ -531,7 +531,7 @@ class PriceHandler:
self.logger.error(f"원가기반 가격 계산 중 중 오류 발생: {e}", exc_info=True)
return 100000 # 오류발생시 기본 가격 100,000 반환
async def get_option_count_from_text(self):
def get_option_count_from_text(self):
"""
텍스트 기반으로 옵션 수를 계산합니다. 옵션이 없는 경우 단일 상품으로 간주하여 기본값 1 반환합니다.
@ -544,10 +544,10 @@ class PriceHandler:
try:
# 요소를 기다림
option_count_text_element = await self.page.wait_for_selector(self.option_count_text_locator, timeout=5000)
option_count_text_element = self.page.wait_for_selector(self.option_count_text_locator, timeout=5000)
# 텍스트에서 숫자만 추출
option_text = await option_count_text_element.text_content()
option_text = option_count_text_element.text_content()
match = re.search(r'\d+', option_text)
if match:
@ -567,7 +567,7 @@ class PriceHandler:
return 1 # 예외 발생 시 기본값 1 반환
async def collect_product_costs_and_prices(self, is_single, is_group):
def collect_product_costs_and_prices(self, is_single, is_group):
"""
상품 원가와 판매가를 수집하여 반환합니다. 단위는 위안화
@ -586,7 +586,7 @@ class PriceHandler:
if is_single:
total_options = 1
else:
total_options = await self.get_option_count_from_text()
total_options = self.get_option_count_from_text()
self.product_costs = [] # 모든 옵션의 상품원가를 저장할 리스트
self.option_data = [] # 각 옵션의 상품원가와 기준판매가를 저장할 리스트
@ -608,20 +608,20 @@ class PriceHandler:
standard_selling_price_locator = self.standard_selling_price_for_locator_template.format(index=i+1)
# 각 선택자를 사용하여 요소 찾기
product_cost_element = await self.page.wait_for_selector(product_cost_locator, timeout=10000)
# await self.page.evaluate("(element) => element.scrollIntoView()", product_cost_element)
await product_cost_element.scroll_into_view_if_needed(timeout=3000)
product_cost_element = self.page.wait_for_selector(product_cost_locator, timeout=10000)
# self.page.evaluate("(element) => element.scrollIntoView()", product_cost_element)
product_cost_element.scroll_into_view_if_needed(timeout=3000)
standard_selling_price_element = await self.page.wait_for_selector(standard_selling_price_locator, timeout=10000)
# await self.page.evaluate("(element) => element.scrollIntoView()", standard_selling_price_element)
await standard_selling_price_element.scroll_into_view_if_needed(timeout=3000)
standard_selling_price_element = self.page.wait_for_selector(standard_selling_price_locator, timeout=10000)
# self.page.evaluate("(element) => element.scrollIntoView()", standard_selling_price_element)
standard_selling_price_element.scroll_into_view_if_needed(timeout=3000)
# cost_value = int(float(await product_cost_element.input_value().replace(",", "")))
cost_value_str = await product_cost_element.input_value()
# cost_value = int(float(product_cost_element.input_value().replace(",", "")))
cost_value_str = product_cost_element.input_value()
cost_value = int(float(cost_value_str.replace(",", "")))
self.logger.debug(f" {i}옵션의 cost_value : {cost_value}")
# price_value = int(await standard_selling_price_element.input_value().replace(",", ""))
price_value_str = await standard_selling_price_element.input_value()
# price_value = int(standard_selling_price_element.input_value().replace(",", ""))
price_value_str = standard_selling_price_element.input_value()
price_value = int(float(price_value_str.replace(",", "")))
self.logger.debug(f" {i}옵션의 price_value : {price_value}")
@ -659,7 +659,7 @@ class PriceHandler:
return self.option_data, 20000, 20000, 20000, 20000
async def get_plusmargin_and_shipping_values(self):
def get_plusmargin_and_shipping_values(self):
"""
더하기 마진과 해외 배송비 값을 가져오는 메서드입니다.
@ -671,13 +671,13 @@ class PriceHandler:
"""
try:
# 더하기 마진 값 가져오기
margin_element = await self.page.wait_for_selector(self.plus_margin_locator, timeout=5000)
margin_value = await margin_element.input_value()
margin_element = self.page.wait_for_selector(self.plus_margin_locator, timeout=5000)
margin_value = margin_element.input_value()
margin = int(margin_value.replace(",", "")) if margin_value else 0
self.logger.debug(f"더하기 마진 값: {margin}")
shipping_element = await self.page.wait_for_selector(self.oversea_shipping_locator, timeout=5000)
shipping_value = await shipping_element.input_value()
shipping_element = self.page.wait_for_selector(self.oversea_shipping_locator, timeout=5000)
shipping_value = shipping_element.input_value()
shipping_cost = int(shipping_value.replace(",", "")) if shipping_value else 0
self.logger.debug(f"해외 배송비 값: {shipping_cost}")
@ -805,10 +805,10 @@ class PriceHandler:
self.logger.debug(f"총 추가 해외배송비: {total_extra_shipping}")
return total_extra_shipping
async def ordering_by_name_button_click(self):
def ordering_by_name_button_click(self):
self.logger.debug("가격탭의 이름순 정렬 버튼을 클릭합니다.")
await self.page.click(self.ordering_by_name_locator)
self.page.click(self.ordering_by_name_locator)
async def ordering_by_option_button_click(self):
def ordering_by_option_button_click(self):
self.logger.debug("가격탭의 옵션탭 순서 정렬 버튼을 클릭합니다.")
await self.page.click(self.ordering_by_option_locator)
self.page.click(self.ordering_by_option_locator)

View File

@ -1,2 +0,0 @@
[Platforms]
WindowsArguments = dpiawareness=1

View File

@ -33,11 +33,11 @@ class ThumbnailHandler:
self.whale_translator = self.browser_controller.get_whale()
self.logger.debug(f"whale_translator 업데이트 : {self.whale_translator}")
async def process_thumbnails(self):
def process_thumbnails(self):
self.update_whale()
# 썸네일 카드 개수 수집
thumbnails = await self.page.query_selector_all("div#productMainContentContainerId div.ant-row.ant-row-bottom.css-1li46mu > div")
thumbnails = self.page.query_selector_all("div#productMainContentContainerId div.ant-row.ant-row-bottom.css-1li46mu > div")
total_thumbnails = len(thumbnails)
self.logger.debug(f"총 썸네일 카드 수집 완료: {total_thumbnails}")
@ -47,21 +47,21 @@ class ThumbnailHandler:
delete_button_selector = self.delete_button_selector_template.format(index=1) # 첫번째카드만 삭제를 반복하면 되므로
# 이미지 URL 수집
thumbnail_img_element = await self.page.query_selector(thumbnail_img_selector)
thumbnail_img_element = self.page.query_selector(thumbnail_img_selector)
if thumbnail_img_element:
image_url = await thumbnail_img_element.get_attribute("src")
image_url = thumbnail_img_element.get_attribute("src")
self.logger.debug(f"{index}번째 썸네일 이미지 URL: {image_url}")
# 이미지 번역 실행
translated_image_path = await self.translate_thumbnail_image(image_url, index)
translated_image_path = self.translate_thumbnail_image(image_url, index)
# 기존 썸네일 삭제
await self.delete_thumbnail(delete_button_selector)
self.delete_thumbnail(delete_button_selector)
# 번역된 이미지 업로드
await self.upload_translated_image(translated_image_path, thumbnails)
self.upload_translated_image(translated_image_path, thumbnails)
async def translate_thumbnail_image(self, image_url, index):
def translate_thumbnail_image(self, image_url, index):
try:
# 이미지 번역 수행 및 임시 저장 경로 반환
translated_image_path = os.path.join(self.temp_dir, f"translated_thumb_{index}.png") # 이미지 저장 경로 설정
@ -76,35 +76,35 @@ class ThumbnailHandler:
self.logger.error(f"{index}번째 썸네일 이미지 번역 중 오류 발생: {e}", exc_info=True)
return None
async def delete_thumbnail(self, delete_button_selector):
def delete_thumbnail(self, delete_button_selector):
try:
delete_button = await self.page.query_selector(delete_button_selector)
delete_button = self.page.query_selector(delete_button_selector)
if delete_button:
await delete_button.click()
delete_button.click()
self.logger.debug("썸네일 삭제 버튼 클릭 완료")
except Exception as e:
self.logger.error(f"썸네일 삭제 중 오류 발생: {e}", exc_info=True)
async def upload_translated_image(self, image_path, thumbnails):
def upload_translated_image(self, image_path, thumbnails):
try:
# 썸네일 카드 목록의 마지막 요소가 업로드 카드
last_thumbnail = thumbnails[-1]
upload_button = await last_thumbnail.query_selector("span:has-text('Upload')")
upload_button = last_thumbnail.query_selector("span:has-text('Upload')")
if upload_button:
await upload_button.click()
upload_button.click()
self.logger.debug("업로드 버튼 클릭 완료")
# 파일 업로드 수행
file_input = await self.page.query_selector(self.file_upload_button_selector)
file_input = self.page.query_selector(self.file_upload_button_selector)
if file_input:
await file_input.set_input_files(image_path)
file_input.set_input_files(image_path)
self.logger.debug("이미지 파일 업로드 완료")
# '이미지 삽입' 버튼 클릭
confirm_button = await self.page.query_selector(self.confirm_upload_button_selector)
await confirm_button.click()
confirm_button = self.page.query_selector(self.confirm_upload_button_selector)
confirm_button.click()
self.logger.debug("이미지 삽입 버튼 클릭 완료")
except Exception as e:
self.logger.error(f"이미지 업로드 중 오류 발생: {e}", exc_info=True)

View File

@ -31,7 +31,7 @@ class TitleHandler:
self.page = page1
self.logger.debug(f"page객체 업데이트 : {page1}")
async def get_product_name(self) -> str:
def get_product_name(self) -> str:
"""
노출상품명 입력칸에서 상품명을 가져오는 메서드입니다.
@ -40,15 +40,15 @@ class TitleHandler:
"""
try:
self.logger.debug("노출상품명 입력칸에서 상품명을 가져오는 중입니다.")
product_name_element = await self.page.query_selector(self.product_name_input_locator)
product_name = await product_name_element.get_attribute('value') if product_name_element else ""
product_name_element = self.page.query_selector(self.product_name_input_locator)
product_name = product_name_element.get_attribute('value') if product_name_element else ""
self.logger.debug(f"상품명: {product_name}")
return product_name
except Exception as e:
self.logger.error(f"상품명 가져오기 중 오류 발생: {e}", exc_info=True)
return ""
async def enter_product_name_suggestion(self, suggestion: str):
def enter_product_name_suggestion(self, suggestion: str):
"""
상품명 추천단어를 입력칸에 입력하는 메서드입니다.
@ -57,31 +57,31 @@ class TitleHandler:
"""
try:
self.logger.debug(f"추천 단어를 상품명 추천 입력칸에 입력 중: {suggestion}")
suggestion_input_element = await self.page.query_selector(self.suggestion_input_locator)
suggestion_input_element = self.page.query_selector(self.suggestion_input_locator)
if suggestion_input_element:
await suggestion_input_element.fill(suggestion)
suggestion_input_element.fill(suggestion)
self.logger.debug(f"추천 단어 '{suggestion}' 입력 완료.")
else:
self.logger.error("추천 입력칸 요소를 찾을 수 없습니다.")
except Exception as e:
self.logger.error(f"추천 입력 단어 입력 중 오류 발생: {e}", exc_info=True)
async def click_product_name_search_button(self):
def click_product_name_search_button(self):
"""
상품명 추천단어 입력칸의 검색 버튼을 클릭하는 메서드입니다.
"""
try:
self.logger.debug("상품명 추천단어 검색 버튼 클릭 중.")
search_button_element = await self.page.query_selector(self.search_button_locator)
search_button_element = self.page.query_selector(self.search_button_locator)
if search_button_element:
await search_button_element.click()
search_button_element.click()
self.logger.debug("검색 버튼 클릭 완료.")
else:
self.logger.error("검색 버튼 요소를 찾을 수 없습니다.")
except Exception as e:
self.logger.error(f"상품명 추천 검색 버튼 클릭 중 오류 발생: {e}", exc_info=True)
async def get_original_product_name(self) -> str:
def get_original_product_name(self) -> str:
"""
원본 상품명을 가져오는 메서드입니다.
@ -90,45 +90,45 @@ class TitleHandler:
"""
try:
self.logger.debug("원본 상품명을 가져오는 중입니다.")
original_name_element = await self.page.query_selector(self.original_product_name_locator)
original_name = await original_name_element.inner_text() if original_name_element else ""
original_name_element = self.page.query_selector(self.original_product_name_locator)
original_name = original_name_element.inner_text() if original_name_element else ""
self.logger.debug(f"원본 상품명: {original_name}")
return original_name
except Exception as e:
self.logger.error(f"원본 상품명 가져오기 중 오류 발생: {e}", exc_info=True)
return ""
async def delete_warning_word_in_product_name(self):
def delete_warning_word_in_product_name(self):
"""
상품명에서 경고 단어를 삭제하는 버튼을 클릭하는 메서드입니다.
"""
try:
self.logger.debug("경고 단어 삭제 버튼 클릭 중입니다.")
delete_button_element = await self.page.query_selector(self.delete_warning_button_locator)
delete_button_element = self.page.query_selector(self.delete_warning_button_locator)
if delete_button_element:
await delete_button_element.click()
delete_button_element.click()
self.logger.debug("경고 단어 삭제 버튼 클릭 완료.")
else:
self.logger.error("경고 단어 삭제 버튼 요소를 찾을 수 없습니다.")
except Exception as e:
self.logger.error(f"경고 단어 삭제 버튼 클릭 중 오류 발생: {e}", exc_info=True)
async def click_category_suggestion_button(self):
def click_category_suggestion_button(self):
"""
카테고리 추천받기 버튼을 클릭하는 메서드입니다.
"""
try:
self.logger.debug("카테고리 추천받기 버튼 클릭 중입니다.")
category_suggestion_button_element = await self.page.query_selector(self.category_suggestion_button_locator)
category_suggestion_button_element = self.page.query_selector(self.category_suggestion_button_locator)
if category_suggestion_button_element:
await category_suggestion_button_element.click()
category_suggestion_button_element.click()
self.logger.debug("카테고리 추천받기 버튼 클릭 완료.")
else:
self.logger.error("카테고리 추천받기 버튼 요소를 찾을 수 없습니다.")
except Exception as e:
self.logger.error(f"카테고리 추천받기 버튼 클릭 중 오류 발생: {e}", exc_info=True)
async def get_category_ori(self, market='ss') -> str:
def get_category_ori(self, market='ss') -> str:
"""
카테고리를 가져오는 메서드로 인증 필요 여부에 따라 카테고리 선택자를 다르게 처리합니다.
@ -169,7 +169,7 @@ class TitleHandler:
return ""
async def get_category(self, market='ss') -> str:
def get_category(self, market='ss') -> str:
"""
카테고리를 가져오는 메서드로 인증 필요 여부에 따라 카테고리 선택자를 다르게 처리합니다.
@ -188,11 +188,11 @@ class TitleHandler:
self.logger.debug(f"category_locator : {category_locator}")
await self.page.wait_for_selector(category_locator, timeout=5000, state="attached") # 요소가 나타날 때까지 대기
self.page.wait_for_selector(category_locator, timeout=5000, state="attached") # 요소가 나타날 때까지 대기
main_category_element = self.page.locator(category_locator) # 대기 후 동기적으로 요소 가져오기
self.logger.debug(f"main_category_element : {main_category_element}")
if not await main_category_element.count():
if not main_category_element.count():
self.logger.error("카테고리 메인 선택자를 찾을 수 없습니다.")
return ""
@ -201,23 +201,23 @@ class TitleHandler:
self.logger.debug(f"category_text_element : {category_text_element}")
if await category_text_element.count():
category_text = await category_text_element.inner_text()
if category_text_element.count():
category_text = category_text_element.inner_text()
if "인증" in category_text:
self.logger.debug(f"카테고리 인증 필요 발생 category_text = {category_text}")
category_text_certified_element = main_category_element.locator(self.category_text_locator_certified)
if await category_text_certified_element.count():
category_text = await category_text_certified_element.inner_text()
if category_text_certified_element.count():
category_text = category_text_certified_element.inner_text()
self.logger.debug(f"인증 필요 카테고리 text = {category_text}")
if "그룹상품" in category_text:
self.logger.debug(f"카테고리 그룹상품 발생 category_text = {category_text}")
category_text_certified_element = main_category_element.locator(self.category_text_locator_certified)
self.is_group_ESM = True
if await category_text_certified_element.count():
category_text = await category_text_certified_element.inner_text()
if category_text_certified_element.count():
category_text = category_text_certified_element.inner_text()
self.logger.debug(f"그룹상품 카테고리 text = {category_text}")
else:
self.logger.debug(f"카테고리 text = {category_text}")

View File

@ -327,13 +327,14 @@ class WhaleTranslator:
except Exception as e:
self.logger.error(f"'뒤로' 버튼 클릭 중 오류 발생: {e}", exc_info=True)
def close_whale_window(self):
"""웨일 창을 종료하는 메서드."""
def stop_whale_browser(self):
"""Whale 브라우저 종료 및 리소스 정리"""
try:
if self.whale_window:
self.logger.debug("웨일 브라우저 종료 중...")
self.whale_window.close()
if self.whale_app:
self.whale_app.kill()
self.logger.info("웨일 창을 성공적으로 종료했습니다.")
else:
self.logger.warning("웨일 애플리케이션이 시작되지 않았습니다.")
self.logger.debug("웨일 브라우저 종료 완료.")
except Exception as e:
self.logger.error("웨일 창을 종료하는 중 오류 발생", exc_info=True)
self.logger.error(f"웨일 종료 중 오류 발생: {e}")