현재 상태 저장
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 92 KiB |
81
gui.py
|
|
@ -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
|
|
@ -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}번째 옵션의 파일 입력 요소를 찾을 수 없습니다.")
|
||||
|
|
|
|||
84
price.py
|
|
@ -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)
|
||||
|
|
|
|||
36
thumb.py
|
|
@ -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)
|
||||
|
|
|
|||
56
title.py
|
|
@ -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}")
|
||||
|
|
|
|||
13
whale_new.py
|
|
@ -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}")
|
||||
|
|
|
|||