From bf240af991f427a0dd03a56d39498e04ee3ab915 Mon Sep 17 00:00:00 2001 From: 9700X_PC <9700X_PC@gmail.com> Date: Wed, 6 Nov 2024 22:47:42 +0900 Subject: [PATCH] =?UTF-8?q?=EC=98=B5=EC=85=98=20=EB=B2=88=EC=97=AD=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.ini | 15 ++++++++---- locatorManager.py | 1 + option.py | 59 +++++++++++++++++++++++++++++++++------------ whale_translator.py | 31 +++--------------------- 4 files changed, 57 insertions(+), 49 deletions(-) diff --git a/config.ini b/config.ini index 4fffb13e..5e33e9a7 100644 --- a/config.ini +++ b/config.ini @@ -34,20 +34,25 @@ price_selector_template = '//*[@id="productMainContentContainerId"]/div[1]/div[2 # 옵션 상자 ; option_box_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc' -option_box_selector = '//*[@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[{index}]/div/div[1]/div/div[2]/div' +; option_box_selector = '//*[@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[{index}]/div/div[1]/div/div[2]/div' +option_box_selector = 'div#productMainContentContainerId li > div > div:nth-child(1) > div > div:nth-child(2) > div' + ; excluded_option_marker = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) > .sc-dfauwV.bXsMpn' excluded_option_marker = '.bXsMpn.sc-dfauwV' ; delete_button_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) span:has-text("삭제")' ; delete_button_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) div.sc-igZIGL.kQDmyq' ; delete_button_selector = '.kQDmyq.sc-igZIGL' +; delete_button_selector_template = '//*[@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[{index}]/div/div[1]/div/div[2]/div/div[2]/div[1]/div/span' +delete_button_selector_template = 'div#productMainContentContainerId li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div.ant-row.ant-row-no-wrap.ant-row-space-between.ant-row-middle.css-1li46mu > div:nth-child(1) > div > span' -delete_button_selector_template = '//*[@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[{index}]/div/div[1]/div/div[2]/div/div[2]/div[1]/div/span' fallback1_delete_button_selector_template = 'div#productMainContentContainerId li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div.ant-row.ant-row-no-wrap.ant-row-space-between.ant-row-middle.css-1li46mu > div:nth-child(1) > div > span' - -confirm_delete_button_selector = '.ant-modal.css-1li46mu.ant-modal-confirm.ant-modal-confirm-confirm button:has-text("삭제")' +delete_dialog_selector = 'div.sc-ddjGPC.jbwEYW' +confirm_delete_button_selector = 'button.ant-btn-primary.ant-btn-dangerous:has-text('삭제')' +; confirm_delete_button_selector = '.ant-modal.css-1li46mu.ant-modal-confirm.ant-modal-confirm-confirm button:has-text("삭제")' +; confirm_delete_button_selector = 'xpath=/html/body/div[8]/div/div[2]/div/div[2]/div/div/div/div[2]/button[2]' add_button_selector2 = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) > .sc-dRGYJT.hmQUGb' -add_button_selector = '//*[@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[{index}]/div/div[1]/div/div[2]/div/div/img' +add_button_selector = 'div#productMainContentContainerId li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div > img' ; add_button_selector = 'div.hmQUGb.sc-dRGYJT' ; add_button_selector = 'div#productMainContentContainerId div.lesrZh.sc-bYHUQc:nth-child({index}) > .sc-krITIZ.ckztYT' ; file_upload_button_selector = '.ant-modal-content button:has-text("클릭 or 드레그로 파일 업로드")' diff --git a/locatorManager.py b/locatorManager.py index 4118abaa..a8912579 100644 --- a/locatorManager.py +++ b/locatorManager.py @@ -92,6 +92,7 @@ class LocatorManager: 'excluded_option_marker': self.config.get('OptionLocators', 'excluded_option_marker').strip("'"), 'delete_button_selector_template': self.config.get('OptionLocators', 'delete_button_selector_template').strip("'"), 'fallback1_delete_button_selector_template': self.config.get('OptionLocators', 'fallback1_delete_button_selector_template').strip("'"), + 'delete_dialog_selector': self.config.get('OptionLocators', 'delete_dialog_selector').strip("'"), 'confirm_delete_button_selector': self.config.get('OptionLocators', 'confirm_delete_button_selector').strip("'"), 'add_button_selector': self.config.get('OptionLocators', 'add_button_selector').strip("'"), 'file_upload_button_selector': self.config.get('OptionLocators', 'file_upload_button_selector').strip("'"), diff --git a/option.py b/option.py index e5ea7748..23bd5a6e 100644 --- a/option.py +++ b/option.py @@ -36,6 +36,7 @@ class OptionHandler: self.excluded_option_marker = self.locator_manager.get_locator('OptionLocators', 'excluded_option_marker') self.delete_button_selector_template = self.locator_manager.get_locator('OptionLocators', 'delete_button_selector_template') self.fallback1_delete_button_selector_template = self.locator_manager.get_locator('OptionLocators', 'fallback1_delete_button_selector_template') + self.delete_dialog_selector = self.locator_manager.get_locator('OptionLocators', 'delete_dialog_selector') self.confirm_delete_button_selector = self.locator_manager.get_locator('OptionLocators', 'confirm_delete_button_selector') self.add_button_selector = self.locator_manager.get_locator('OptionLocators', 'add_button_selector') self.file_upload_button_selector = self.locator_manager.get_locator('OptionLocators', 'file_upload_button_selector') @@ -268,6 +269,8 @@ class OptionHandler: if toggle_states['optionAutoSelect']: self.logger.debug(f"옵션 필터링 및 조정 : {toggle_states['optionAutoSelect']}") await self.filter_and_adjust_options(max_option_count) + # 가격 낮은 순 재정렬 클릭 + await self.low_order_click() # 6. 선택된 옵션정보 재수집 if self.is_percenty_success: @@ -629,6 +632,7 @@ class OptionHandler: try: selected_count = 0 # 현재까지 선택된 옵션 수 + self.logger.info(f"최대 선택 가능 옵션수 설정값 : {max_option_count}") for i, name in enumerate(self.option_info['original_names'].values()): # 최대 옵션 수에 도달하면 더 이상 선택하지 않음 if max_option_count > 0 and selected_count >= max_option_count: @@ -716,9 +720,9 @@ class OptionHandler: try: # 모든 옵션 상자 요소 가져오기 - # option_boxes = await self.page.query_selector_all(self.option_box_selector) + option_boxes = await 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 = await 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() @@ -732,11 +736,18 @@ class OptionHandler: # 선택자에서 인덱스를 반영해 동적 선택자를 생성 add_button_selector = self.add_button_selector.format(index=index) + # 옵션 박스 내부의 텍스트 확인하여 "제외된 옵션" 포함 여부 검사 + option_text_content = await option_box.inner_text() + if "제외된 옵션" in option_text_content: + self.logger.debug(f"{index}번째 옵션은 제외된 옵션입니다. 번역을 생략합니다.") + continue # 제외된 옵션이므로 다음 옵션으로 이동 + # 옵션 이미지가 존재하는지 확인 option_image = await 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") self.logger.debug(f"{index}번째 옵션 이미지 URL: {option_image_url}") @@ -765,34 +776,47 @@ class OptionHandler: try: # 기본 선택자로 삭제 버튼 찾기 - # delete_button = option_box.query_selector(self.delete_button_selector) delete_button = self.page.locator(f'{self.delete_button_selector_template.format(index=index)}') await delete_button.wait_for(state="attached", timeout=5000) # 타임아웃 설정 - + if not await delete_button.is_visible(): # fallback으로 재시도 delete_button = self.page.locator(f'xpath={self.fallback1_delete_button_selector_template.format(index=index)}') - delete_button.set_timeout(5000) + await delete_button.wait_for(state="attached", timeout=5000) if await delete_button.is_visible(): await 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) # 다이알로그 클래스 확인 + + if dialog: + self.logger.debug(f"{index}번째 옵션의 삭제 다이알로그 확인됨") + + # 삭제 확인 버튼 찾기 + confirm_delete_button = await dialog.query_selector(self.confirm_delete_button_selector) + + if confirm_delete_button: + await confirm_delete_button.click() + self.logger.debug(f"{index}번째 옵션의 삭제 확인 버튼 클릭됨") + else: + self.logger.error(f"{index}번째 옵션의 삭제 확인 버튼이 보이지 않습니다.") + else: + self.logger.error(f"{index}번째 옵션의 삭제 다이알로그가 나타나지 않았습니다.") + except Exception as e: + self.logger.error(f"{index}번째 옵션의 삭제 다이알로그를 찾는 중 오류 발생: {e}", exc_info=True) + except Exception as e: self.logger.error(f"{index}번째 옵션의 삭제 버튼을 찾는 중 오류 발생: {e}", exc_info=True) - - if delete_button: - # await delete_button.click() - # self.logger.debug(f"{index}번째 옵션의 이미지 삭제 버튼 클릭") - confirm_delete_button = await self.page.wait_for_selector(self.confirm_delete_button_selector) - self.logger.debug(f"{index}번째 옵션의 이미지 삭제확인 버튼 가져오기") - if confirm_delete_button: - await confirm_delete_button.click() - self.logger.debug(f"{index}번째 옵션의 기존 이미지가 삭제되었습니다.") - + try: # '+ 버튼' 클릭 후 파일 업로드 self.logger.debug(f"{index}번째 옵션의 이미지추가 버튼 가져오기") - add_button = self.page.locator(add_button_selector) + add_button = await self.page.query_selector(add_button_selector) + if add_button: await add_button.click() @@ -812,6 +836,9 @@ class OptionHandler: self.logger.debug(f"{index}번째 옵션에 이미지가 업로드되었습니다.") else: self.logger.error(f"{index}번째 옵션의 파일 입력 요소를 찾을 수 없습니다.") + except Exception as e: + self.logger.error(f"{index}번째 옵션의 이미지를 추가하는 중 오류 발생: {e}", exc_info=True) + except Exception as e: self.logger.error(f"{index}번째 옵션 이미지 번역 중 오류 발생: {e}", exc_info=True) diff --git a/whale_translator.py b/whale_translator.py index 29bb3306..b82b82ef 100644 --- a/whale_translator.py +++ b/whale_translator.py @@ -9,7 +9,6 @@ import KO_EN import pyperclip # 클립보드 데이터를 확인하기 위한 라이브러리 from PIL import ImageGrab import re -# import psutil class WhaleTranslator: def __init__(self, app, logger, secret_mode=True, vd_mode=False, pixel_check_interval=0.1, timeout=10, color_tolerance=20): @@ -299,7 +298,7 @@ class WhaleTranslator: self.logger.debug(f"페이지 로딩 완료 후 웨일 창의 가운데로 마우스 커서 이동") # pyautogui.moveTo(960,580) # 마우스 센터로 이동 self.move_mouse_to_center() - time.sleep(1) # 마우스 이동 후 대기 + time.sleep(0.4) # 마우스 이동 후 대기 # original_color = self.get_mouse_position_color() # 현재 색상 가져오기 # self.colors['before'] = original_color @@ -491,32 +490,7 @@ class WhaleTranslator: time.sleep(1) # 페이지 로딩 대기 - def close_whale_window_if_exists(self): - """윈도우 핸들을 사용하여 웨일 창을 닫음""" - - try: - if not self.whale_hwnd: - self.logger.debug("웨일 창 핸들이 설정되지 않았습니다.") - return - - # 핸들이 유효한지 확인 - if win32gui.IsWindow(self.whale_hwnd): - self.logger.debug(f"웨일 창 핸들을 찾았습니다: {self.whale_hwnd}. 종료 중...") - win32gui.PostMessage(self.whale_hwnd, win32con.WM_CLOSE, 0, 0) # 창을 종료하는 메시지 전송 - time.sleep(1) # 잠시 대기하여 창 종료 확인 - if not win32gui.IsWindow(self.whale_hwnd): - self.logger.debug("웨일 창이 성공적으로 종료되었습니다.") - else: - self.logger.debug("웨일 창 종료에 실패했습니다.") - else: - self.logger.debug("유효하지 않은 웨일 창 핸들입니다.") - - except Exception as e: - self.logger.error(f"핸들을 사용한 웨일 창 종료 중 오류 발생: {e}", exc_info=True) - - - def close_whale_window_if_exists_ori(self): """웨일 브라우저 창을 프로세스 ID(pid)로 찾아 종료""" try: if not self.whale_pid: @@ -816,7 +790,8 @@ class WhaleTranslator: max_retries (int): 최대 재시도 횟수. """ retry_count = 0 - + self.move_mouse_to_center() + while retry_count < max_retries: self.logger.debug(f"마우스 오른쪽 클릭 시도 #{retry_count + 1}") pyautogui.rightClick()