diff --git a/config.ini b/config.ini index 3468734a..bdd6d631 100644 --- a/config.ini +++ b/config.ini @@ -39,9 +39,39 @@ leading_text_6 = "**반드시 옵션사진과 옵션이름을 확인하시고 leading_text_7 = "---" # 필요한 만큼 추가 가능 -[ProductNameLocators] -product_name_input_locator = //*[@id='productMainContentContainerId']/div/div[1]/div/div/div[1]/input +[TitleLocators] +# 상품명 관련 선택자 +product_name_input_locator = //*[@id='productMainContentContainerId']/div/div[1]/div[5]/div[1]/span/input +product_name_input_css_path = 'div#productMainContentContainerId div:nth-child(5) > div:nth-child(1) > span > input' + +# 상품명 추천단어 입력칸 선택자 +product_name_suggestion_input_locator = //*[@id="productMainContentContainerId"]/div/div[1]/div[2]/div[2]/div/span/span/span[1]/input +product_name_suggestion_input_css_path = 'div#productMainContentContainerId div:nth-child(2) > div:nth-child(2) > div > span > span > span.ant-input-affix-wrapper.css-1li46mu.ant-input-outlined > input' + +# 상품명 추천단어 입력 검색 버튼 선택자 +product_name_search_button_locator = //*[@id="productMainContentContainerId"]/div/div[1]/div[2]/div[2]/div/span/span/span[2]/button +product_name_search_button_css_path = 'div#productMainContentContainerId div:nth-child(2) > div:nth-child(2) > div > span > span > span.ant-input-group-addon > button[type="button"]' + +# 원본 상품명 선택자 +original_product_name_locator = //*[@id="productMainContentContainerId"]/div/div[1]/div[6]/div[1]/div/span +original_product_name_css_path = 'div#productMainContentContainerId div.sc-aNeao.tNLFa > div.ant-flex.css-1li46mu.ant-flex-align-stretch.ant-flex-vertical > div:nth-child(1) > div > span' + +# 상품명의 경고단어 삭제 버튼 선택자 +product_name_warning_delete_button_locator = //*[@id="productMainContentContainerId"]/div/div[1]/div[6]/div[3]/div[2]/div/button +product_name_warning_delete_button_css_path = 'div#productMainContentContainerId div:nth-child(2) > div > button[type="button"]' + +# 카테고리 관련 선택자 +category_suggestion_button_locator = //*[@id='productMainContentContainerId']/div/div[1]/div[5]/div[2]/button +category_suggestion_button_css_path = 'div#productMainContentContainerId div:nth-child(2) > button[type="button"]' + +# 카테고리 선택자 - 인증 여부에 따른 분기 +category_main_selector_with_cp = '#productMainContentContainerId .ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow:nth-of-type(1)' +category_main_selector_with_ss = '#productMainContentContainerId .ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow:nth-of-type(2)' +category_main_selector_with_esm = '#productMainContentContainerId .ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow:nth-of-type(3)' +category_certified_text_locator = div.ant-col.css-1li46mu:nth-child(1) +category_text_with_certification_locator = div.ant-col.css-1li46mu:nth-child(2) +category_text_without_certification_locator = div.ant-col.css-1li46mu:nth-child(1) [BrowserControl] # 크롬 창 이름 diff --git a/gui.py b/gui.py index dafe1090..8b98c44f 100644 --- a/gui.py +++ b/gui.py @@ -716,11 +716,11 @@ class TranslationApp(QWidget): self.logger.debug(f'{index}/{len(product_buttons)}: 세부사항 수정 작업 중...') - # 상품명 수집 및 수집 오류 처리 - product_name = await self.browser_controller.get_product_name(index, 'css') - if product_name == "수집 오류 발생": - self.logger.debug('상품 수집 오류, 다음 상품으로 넘어갑니다.') - continue + # # 상품명 수집 및 수집 오류 처리 + # product_name = await self.browser_controller.get_product_name(index, 'css') + # if product_name == "수집 오류 발생": + # self.logger.debug('상품 수집 오류, 다음 상품으로 넘어갑니다.') + # continue # 상품 수정 다이얼로그 열기 await self.browser_controller.open_product_edit_dialog(button) diff --git a/test/ele_test.py b/test/ele_test.py new file mode 100644 index 00000000..e51309ce --- /dev/null +++ b/test/ele_test.py @@ -0,0 +1,146 @@ +import asyncio +from playwright.async_api import async_playwright +from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget, QMessageBox +from qasync import QEventLoop +import sys + +class CategoryHandler: + def __init__(self, page): + self.page = page + + async def handle_category_action(self): + # #productMainContentContainerId 내부에서 클래스 이름 "ant-select ant-select-outlined css-1li46mu ant-select-single ant-select-show-arrow"를 포함한 요소 중 두 번째 요소 찾기 + print("[DEBUG] handle_category_action: Locating category container element...") + category_locator = "div#productMainContentContainerId div.ant-select.ant-select-outlined.css-1li46mu.ant-select-single.ant-select-show-arrow >> nth=1" + + try: + await self.page.wait_for_selector(category_locator, timeout=5000) + except Exception as e: + print(f"[ERROR] handle_category_action: Timed out waiting for category container element. Error: {e}") + QMessageBox.information(None, "결과", f"카테고리 컨테이너 요소를 찾는 데 실패했습니다: {e}") + return + + category_element = self.page.locator(category_locator) + count = await category_element.count() + print(f"[DEBUG] handle_category_action: Number of elements found with locator '{category_locator}': {count}") + if count == 0: + print(f"[ERROR] handle_category_action: Category container element not found using locator '{category_locator}'!") + QMessageBox.information(None, "결과", "카테고리 컨테이너 요소를 찾을 수 없습니다.") + return + + # "인증필요"와 카테고리 텍스트 추출 + print("[DEBUG] handle_category_action: Extracting '인증필요' and category text...") + certification_text = "" + category_text = "" + + try: + cert_needed_locator = category_element.locator("div.ant-col.css-1li46mu:nth-child(1)") + certification_text = await cert_needed_locator.inner_text() + print(f"[DEBUG] handle_category_action: Certification text found - '{certification_text}'") + if "인증필요" in certification_text: + # 인증필요가 있는 경우 두 번째 요소가 카테고리 텍스트 + category_text_locator = category_element.locator("div.ant-col.css-1li46mu:nth-child(2)") + category_text = await category_text_locator.inner_text() + else: + # 인증필요가 없는 경우 첫 번째 요소가 카테고리 텍스트 + category_text = certification_text + certification_text = "" # 인증필요가 없으므로 초기화 + except Exception: + # 인증필요가 없는 경우 첫 번째 요소가 카테고리 텍스트 + print("[DEBUG] handle_category_action: Certification text not found. Assuming first element is category text.") + category_text_locator = category_element.locator("div.ant-col.css-1li46mu:nth-child(1)") + category_text = await category_text_locator.inner_text() + + full_text = f"{certification_text} {category_text}".strip() + print(f"[DEBUG] handle_category_action: Full text - '{full_text}'") + QMessageBox.information(None, "검색 결과", f"카테고리 텍스트: {full_text}") + + # 카테고리 텍스트에 '인증'이라는 단어가 포함되어 있는지 검사 + if "인증" in full_text: + print("[INFO] 인증 필요 카테고리입니다. 인증 절차를 진행합니다.") + # 인증이 필요한 경우 수행할 작업 + await self.perform_certification_action() + else: + print("[INFO] 인증이 필요하지 않은 카테고리입니다.") + # 인증이 필요하지 않은 경우 수행할 작업 + await self.perform_standard_action() + + async def perform_certification_action(self): + # 인증 절차를 진행하는 코드 작성 + print("[DEBUG] perform_certification_action: Starting certification process...") + # 예시: 특정 버튼 클릭하기 + await self.page.click("button:has-text('인증 시작')") + print("[INFO] perform_certification_action: Certification process completed.") + + async def perform_standard_action(self): + # 인증이 필요하지 않은 경우의 일반적인 작업 코드 작성 + print("[DEBUG] perform_standard_action: Performing standard action...") + # 예시: 다음 단계로 이동 + await self.page.click("button:has-text('다음 단계')") + print("[INFO] perform_standard_action: Standard action completed.") + +async def run_playwright(): + print("[DEBUG] run_playwright: Launching Playwright...") + playwright = await async_playwright().start() + browser = await playwright.chromium.launch(headless=False) + page = await browser.new_page() + print("[DEBUG] run_playwright: Navigating to https://www.percenty.co.kr...") + await page.goto("https://www.percenty.co.kr") # 실제 페이지 URL로 변경하세요 + print("[INFO] run_playwright: Page loaded successfully.") + return page, browser, playwright + +class MainWindow(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("Playwright 테스트") + self.setGeometry(100, 100, 400, 300) + self.page = None + self.browser = None + self.playwright = None + + # 버튼 생성 + self.init_button = QPushButton("Playwright 실행") + self.init_button.clicked.connect(self.run_playwright_button) + + self.check_button = QPushButton("요소 검사 및 메시지 출력") + self.check_button.clicked.connect(self.check_category_button) + self.check_button.setEnabled(False) + + # 레이아웃 설정 + layout = QVBoxLayout() + layout.addWidget(self.init_button) + layout.addWidget(self.check_button) + self.setLayout(layout) + + def run_playwright_button(self): + print("[DEBUG] run_playwright_button: Playwright 실행 버튼 클릭됨.") + asyncio.create_task(self.init_playwright()) + + async def init_playwright(self): + print("[DEBUG] init_playwright: Initializing Playwright...") + self.page, self.browser, self.playwright = await run_playwright() + self.check_button.setEnabled(True) + print("[INFO] init_playwright: Playwright initialized and check button enabled.") + + def check_category_button(self): + if self.page: + print("[DEBUG] check_category_button: 요소 검사 버튼 클릭됨.") + asyncio.create_task(self.handle_category_action()) + + async def handle_category_action(self): + print("[DEBUG] handle_category_action: Handling category action...") + handler = CategoryHandler(self.page) + await handler.handle_category_action() + print("[INFO] handle_category_action: Category check completed.") + +if __name__ == "__main__": + print("[DEBUG] Main: Starting application...") + app = QApplication(sys.argv) + loop = QEventLoop(app) + asyncio.set_event_loop(loop) + + window = MainWindow() + window.show() + + with loop: + sys.exit(loop.run_forever()) \ No newline at end of file diff --git a/title.py b/title.py new file mode 100644 index 00000000..ec7b2917 --- /dev/null +++ b/title.py @@ -0,0 +1,164 @@ +class TitleHandler: + """ + TitleHandler 클래스는 상품명과 카테고리 관련 정보를 처리하는 역할을 합니다. + 다양한 선택자를 사용하여 웹 페이지에서 상품명, 추천 단어, 카테고리 등을 수집하고 입력하는 기능을 제공합니다. + + Attributes: + page (Page): Playwright의 페이지 객체로, 브라우저와의 상호작용을 담당합니다. + logger (Logger): 로깅을 위한 Logger 객체입니다. + """ + def __init__(self, page, logger, locator_manager): + self.page = page + self.logger = logger + self.locator_manager = locator_manager + + # 선택자 로드 + self.product_name_input_locator = self.locator_manager.get_locator('TitleLocators', 'product_name_input_locator') + self.suggestion_input_locator = self.locator_manager.get_locator('TitleLocators', 'suggestion_input_locator') + self.search_button_locator = self.locator_manager.get_locator('TitleLocators', 'search_button_locator') + self.original_name_locator = self.locator_manager.get_locator('TitleLocators', 'original_name_locator') + self.delete_warning_button_locator = self.locator_manager.get_locator('TitleLocators', 'delete_warning_button_locator') + self.category_suggestion_button_locator = self.locator_manager.get_locator('TitleLocators', 'category_suggestion_button_locator') + self.main_category_locator_with_cp = self.locator_manager.get_locator('TitleLocators', 'category_main_selector_with_cp') + self.main_category_locator_with_ss = self.locator_manager.get_locator('TitleLocators', 'category_main_selector_with_ss') + self.main_category_locator_with_esm = self.locator_manager.get_locator('TitleLocators', 'category_main_selector_with_esm') + self.certified_text_locator = self.locator_manager.get_locator('TitleLocators', 'certified_text_locator') + self.category_text_locator = self.locator_manager.get_locator('TitleLocators', 'category_text_locator') + self.category_text_locator_certified = self.locator_manager.get_locator('TitleLocators', 'category_text_locator_certified') + + async def get_product_name(self) -> str: + """ + 노출상품명 입력칸에서 상품명을 가져오는 메서드입니다. + + Returns: + str: 상품명 텍스트 + """ + 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 "" + 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): + """ + 상품명 추천단어를 입력칸에 입력하는 메서드입니다. + + Args: + suggestion (str): 입력할 추천 단어 + """ + try: + self.logger.debug(f"추천 단어를 상품명 추천 입력칸에 입력 중: {suggestion}") + suggestion_input_element = await self.page.query_selector(self.suggestion_input_locator) + if suggestion_input_element: + await 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): + """ + 상품명 추천단어 입력칸의 검색 버튼을 클릭하는 메서드입니다. + """ + try: + self.logger.debug("상품명 추천단어 검색 버튼 클릭 중.") + search_button_element = await self.page.query_selector(self.search_button_locator) + if search_button_element: + await 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: + """ + 원본 상품명을 가져오는 메서드입니다. + + Returns: + str: 원본 상품명 텍스트 + """ + try: + self.logger.debug("원본 상품명을 가져오는 중입니다.") + original_name_element = await self.page.query_selector(self.original_name_locator) + original_name = await 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): + """ + 상품명에서 경고 단어를 삭제하는 버튼을 클릭하는 메서드입니다. + """ + try: + self.logger.debug("경고 단어 삭제 버튼 클릭 중입니다.") + delete_button_element = await self.page.query_selector(self.delete_warning_button_locator) + if delete_button_element: + await 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): + """ + 카테고리 추천받기 버튼을 클릭하는 메서드입니다. + """ + try: + self.logger.debug("카테고리 추천받기 버튼 클릭 중입니다.") + category_suggestion_button_element = await self.page.query_selector(self.category_suggestion_button_locator) + if category_suggestion_button_element: + await 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(self, market) -> str: + """ + 카테고리를 가져오는 메서드로 인증 필요 여부에 따라 카테고리 선택자를 다르게 처리합니다. + + Returns: + str: 카테고리 텍스트 + """ + try: + self.logger.debug("카테고리 텍스트를 가져오는 중입니다.") + if market == 'ss': + main_category_element = await self.page.query_selector(self.main_category_locator_with_ss) + self.logger.debug(f"선택 마켓 : 스마트스토어") + elif market == 'cp': + main_category_element = await self.page.query_selector(self.main_category_locator_with_cp) + self.logger.debug(f"선택 마켓 : 쿠팡") + elif market == 'esm': + main_category_element = await self.page.query_selector(self.main_category_locator_with_esm) + self.logger.debug(f"선택 마켓 : ESM") + if not main_category_element: + self.logger.error("카테고리 메인 선택자를 찾을 수 없습니다.") + return "" + + certified_text_element = await main_category_element.query_selector(self.certified_text_locator) + if certified_text_element: + certified_text = await certified_text_element.inner_text() + if "인증" in certified_text: + category_text_element = await main_category_element.query_selector(self.category_text_locator_certified) + self.logger.debug(f"카테고리 인증 필요 발생: {category_text}") + else: + category_text_element = certified_text_element + category_text = await category_text_element.inner_text() if category_text_element else "" + self.logger.debug(f"카테고리 텍스트: {category_text}") + return category_text + else: + self.logger.error("카테고리 인증 요소를 찾을 수 없습니다.") + return "" + except Exception as e: + self.logger.error(f"카테고리 텍스트 가져오기 중 오류 발생: {e}", exc_info=True) + return "" \ No newline at end of file diff --git a/title.txt b/title.txt new file mode 100644 index 00000000..db6f6ff6 --- /dev/null +++ b/title.txt @@ -0,0 +1,47 @@ +상품명 / 카테고리 + +상품명 추천단어 입력칸의 요소 + +상품명 추천단어 입력칸의 css +#productMainContentContainerId > div > div.sc-aNeao.tNLFa > div:nth-child(2) > div:nth-child(2) > div > span > span > span.ant-input-affix-wrapper.css-1li46mu.ant-input-outlined > input +css path +"div#productMainContentContainerId div:nth-child(2) > div:nth-child(2) > div > span > span > span.ant-input-affix-wrapper.css-1li46mu.ant-input-outlined > input" +xpath +//*[@id="productMainContentContainerId"]/div/div[1]/div[2]/div[2]/div/span/span/span[1]/input + +상품명 추천단어 입력 검색버튼 요소 + +상품명 추천단어 입력 검색버튼 css +#productMainContentContainerId > div > div.sc-aNeao.tNLFa > div:nth-child(2) > div:nth-child(2) > div > span > span > span.ant-input-group-addon > button +상품명 추천단어 입력 검색버튼 css path +"div#productMainContentContainerId div:nth-child(2) > div:nth-child(2) > div > span > span > span.ant-input-group-addon > button[type=\"button\"]" +상품명 추천단어 입력 검색버튼 xpath +//*[@id="productMainContentContainerId"]/div/div[1]/div[2]/div[2]/div/span/span/span[2]/button + +카테고리 추천받기 버튼 요소 + +카테고리 추천받기 버튼 css +#productMainContentContainerId > div > div.sc-aNeao.tNLFa > div:nth-child(5) > div:nth-child(2) > button +카테고리 추천받기 버튼 css path +"div#productMainContentContainerId div:nth-child(2) > button[type=\"button\"]" +카테고리 추천받기 버튼 xpath +//*[@id="productMainContentContainerId"]/div/div[1]/div[5]/div[2]/button + + + +노출상품명 입력칸 +//*[@id="productMainContentContainerId"]/div/div[1]/div[5]/div[1]/span/input +"div#productMainContentContainerId div:nth-child(5) > div:nth-child(1) > span > input" + + +원본상품명 +//*[@id="productMainContentContainerId"]/div/div[1]/div[6]/div[1]/div/span +"div#productMainContentContainerId div.sc-aNeao.tNLFa > div.ant-flex.css-1li46mu.ant-flex-align-stretch.ant-flex-vertical > div:nth-child(1) > div > span" +回力联名玉桂狗女童鞋2024春秋季限量卡通款小孩儿童运动舒适板鞋 + +상품명의 경고단어 삭제 버튼 +//*[@id="productMainContentContainerId"]/div/div[1]/div[6]/div[3]/div[2]/div/button +"div#productMainContentContainerId div:nth-child(2) > div > button[type=\"button\"]" + + +