import pyautogui class OptionHandler: def __init__(self, page, logger, vertexAI): self.page = page self.logger = logger self.vertexAItranslator = vertexAI self.option_info = { 'original_names': {}, 'edit_fields': {}, 'checkboxes': [], 'images': {}, 'prices': {} # 가격 정보 추가 } def process_options(self, max_option_count=10): """옵션 상품을 처리하는 메서드""" try: # 1. 단일 옵션인지 판단 if self.is_single_option(): self.logger.debug("단일 옵션 상품입니다. 옵션 수정 과정을 생략합니다.") return # 2. 전체 옵션 체크박스 상태 확인 if not self.is_all_options_checked(): self.logger.debug("옵션이 일부만 체크된 상태입니다. 옵션 수정이 완료된 상품으로 판단하여 패스합니다.") return # 3. 가격 낮은 순 정렬 클릭 self.low_order_click() # 4. 옵션 정보 수집 및 번역 option_info = self.collect_options_info() # Vertex AI를 통해 옵션명을 번역 self.logger.debug(f"수집된 원본 옵션 정보: {self.option_info['original_names']}") try: translated_options = self.vertexAItranslator.translate_options(self.option_info['original_names']) self.logger.debug(f"번역된 옵션 정보: {translated_options}") # 5. 번역된 옵션명 편집칸에 입력 self.logger.debug("번역된 옵션명을 입력합니다.") self.apply_translated_options(translated_options, self.option_info['edit_fields']) except ValueError as ve: # 안전 필터로 차단된 경우 if "SAFETY" in str(ve): self.logger.error(f"안전 필터에 의해 번역 요청이 차단되었습니다. {ve}") self.logger.debug("퍼센티 자체 AI옵션을 실행(Alt+Q)") # pyautogui를 통해 Alt+Q 키보드 입력을 보냄 pyautogui.hotkey('alt', 'q') # 대체 옵션 처리 종료 후 바로 리턴하지 않고, 다음 동작으로 넘어감. # 번역이 차단된 경우에도 나머지 프로세스를 그대로 진행 # 6. 옵션 선택 및 제한 처리 self.adjust_options(self.option_info['checkboxes'], max_option_count) # 7. 정리된 옵션을 다시 한번 더 가격 낮은 순으로 정렬 클릭 self.low_order_click() # 8. 저장 버튼 클릭 self.logger.debug("저장 버튼을 클릭합니다.") self.page.click('button:has-text("저장하기")') self.logger.debug("옵션 처리 완료.") except Exception as e: self.logger.debug(f"옵션 처리 중 오류 발생: {e}", exc_info=True) return def process_options_ori(self, max_option_count=10): """옵션 상품을 처리하는 메서드""" try: # 1. 단일 옵션인지 판단 if self.is_single_option(): self.logger.debug("단일 옵션 상품입니다. 옵션 수정 과정을 생략합니다.") return # 2. 전체 옵션 체크박스 상태 확인 if not self.is_all_options_checked(): self.logger.debug("옵션이 일부만 체크된 상태입니다. 옵션 수정이 완료된 상품으로 판단하여 패스합니다.") return # 3. 가격 낮은 순 정렬 클릭 self.low_order_click() # 4. 옵션 정보 수집 및 번역 option_info = self.collect_options_info() # Vertex AI를 통해 옵션명을 번역 self.logger.debug(f"수집된 원본 옵션 정보: {self.option_info['original_names']}") translated_options = self.vertexAItranslator.translate_options(self.option_info['original_names']) self.logger.debug(f"번역된 옵션 정보: {translated_options}") # 5. 번역된 옵션명 편집칸에 입력 self.logger.debug("번역된 옵션명을 입력합니다.") self.apply_translated_options(translated_options, self.option_info['edit_fields']) # 6. 옵션 선택 및 제한 처리 self.adjust_options(self.option_info['checkboxes'], max_option_count) # 7. 정리된 옵션을 다시한번 더 가격 낮은 순으로 정렬 클릭 self.low_order_click() # 8. 저장 버튼 클릭 self.logger.debug("저장 버튼을 클릭합니다.") self.page.click('button:has-text("저장하기")') self.logger.debug("옵션 처리 완료.") except Exception as e: self.logger.debug(f"옵션 처리 중 오류 발생: {e}", exc_info=True) return def is_single_option(self): """단일 상품 상태 여부를 확인하는 메서드""" try: # 단일 상품 등록 버튼이 선택되었는지 확인 single_option_xpath = "//div[@id='productMainContentContainerId']//label[contains(@class, 'ant-radio-button-wrapper-checked') and contains(., '단일 상품등록')]" single_option_checked = self.page.query_selector(single_option_xpath) is not None # 옵션 상품 등록 버튼이 선택되었는지 확인 option_product_xpath = "//div[@id='productMainContentContainerId']//label[contains(@class, 'ant-radio-button-wrapper-checked') and contains(., '옵션 상품등록')]" option_product_checked = self.page.query_selector(option_product_xpath) is not None # 두 요소의 상태를 기반으로 단일 상품 여부 결정 is_single = single_option_checked and not option_product_checked self.logger.debug(f"단일 상품 여부: {'단일 상품입니다' if is_single else '옵션 상품입니다'}") return is_single except Exception as e: self.logger.error(f"단일 옵션 확인 중 예외 발생: {e}", exc_info=True) return False def is_all_options_checked(self): """전체 옵션 체크박스 상태를 확인 (전체 체크 여부)""" try: checkbox = self.page.query_selector('#productMainContentContainerId .ant-checkbox-wrapper-checked') if checkbox: self.logger.debug("전체 옵션이 체크되어 있음") return True checkbox_partial = self.page.query_selector('#productMainContentContainerId .ant-checkbox-indeterminate') self.logger.debug("일부 옵션이 체크되어 있으므로 수정완료 상품으로 판단.") return checkbox_partial is None # 일부 체크 시 False except Exception as e: self.logger.error(f"전체 옵션 체크박스 확인 중 오류 발생: {e}", exc_info=True) return False def collect_options_info(self): """옵션 정보를 수집 (이미지, 옵션명, 편집 필드, 가격, 체크박스 정보 포함)""" # option_info = { # 'original_names': {}, # 'edit_fields': {}, # 'checkboxes': [], # 'images': {}, # 'prices': {} # 가격 정보 추가 # } try: # 총 옵션 갯수 수집 total_options_selector = '#productMainContentContainerId label.ant-checkbox-wrapper' total_options_element = self.page.query_selector(total_options_selector) if total_options_element: total_options_text = total_options_element.inner_text() total_options_count = int(''.join(filter(str.isdigit, total_options_text))) # 숫자만 추출 else: total_options_count = 0 # 옵션 갯수를 찾지 못할 경우 기본값 self.logger.debug(f"총 옵션 갯수: {total_options_count}") # 옵션 정보를 수집 (총 옵션 갯수만큼 반복) for i in range(1, total_options_count + 1): 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 = self.page.query_selector(original_name_selector) original_name = original_name_element.inner_text() if original_name_element else None if original_name: # 옵션명 기준으로 수집 항목 구성 self.logger.debug(f"{i}번째 옵션명 수집완료. 나머지 필드 수집중...") self.option_info['original_names'][f'origin_option_{i}'] = original_name # 옵션 편집 필드 수집 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 = 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}") else: self.logger.debug(f"{i}번째 옵션편집필드 수집 실패▣ edit_field_element : {edit_field_element}") # 옵션 체크박스 수집 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 = self.page.query_selector(checkbox_selector) if checkbox_element: self.option_info['checkboxes'].append(checkbox_element) self.logger.debug(f"{i}번째 옵션 체크박스 수집 완료 : {checkbox_element}") else: self.logger.debug(f"{i}번째 옵션 체크박스 수집 실패▣ checkbox_element : {checkbox_element}") # 옵션 이미지 수집 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 = self.page.query_selector(image_selector) if image_element: image_url = image_element.get_attribute('src') self.option_info['images'][original_name] = image_url self.logger.debug(f"{i}번째 옵션 이미지 수집 완료 : {image_element}") else: self.option_info['images'][original_name] = None # 이미지가 없으면 None. self.logger.debug(f"{i}번째 옵션 이미지 수집 실패▣ image_element : {image_element}") # 가격 정보 수집 price_selector = f'#productMainContentContainerId li:nth-child({i}) sup' price_element = self.page.query_selector(price_selector) if price_element: price_text = price_element.inner_text().replace(",", "").replace("원", "").strip() if " - " in price_text: low_price, high_price = map(int, price_text.split(" - ")) else: low_price = high_price = int(price_text) self.option_info['prices'][original_name] = {'low_price': low_price, 'high_price': high_price} self.logger.debug(f"{i}번째 옵션 가격정보 수집 완료 : {low_price} - {high_price}") else: self.logger.debug(f"{i}번째 옵션 가격정보 수집 실패▣ price_element : {price_element}") 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 apply_translated_options(self, translated_options, edit_fields): """번역된 옵션명을 편집 필드에 입력""" try: for key, translated_name in translated_options.items(): self.logger.debug(f"{key}번째 translated_name : {translated_name}") # 원본 옵션명을 기준으로 참조 origin_option_key = key.replace('trans_', 'origin_') # 'trans_option_1'을 'origin_option_1'로 변환 original_name = self.option_info['original_names'].get(origin_option_key) if original_name: edit_field = edit_fields.get(original_name) # 원본 옵션명으로 필드 참조 self.logger.debug(f"{key}번째 번역옵션 필드 : {edit_field}") if edit_field: edit_field.fill(translated_name) # 필드에 번역된 옵션명 입력 self.logger.debug(f"{key}번째 translated_name : [{translated_name}] 입력 완료") else: self.logger.debug(f"{key}번째 옵션 필드가 없습니다.") else: self.logger.debug(f"원본 옵션명을 찾을 수 없습니다: {origin_option_key}") except Exception as e: self.logger.error(f"번역된 옵션명을 입력하는 중 오류 발생: {e}", exc_info=True) def adjust_options(self, checkboxes, max_option_count): """옵션 체크 상태 조정""" try: if len(checkboxes) > 3: self.logger.debug("옵션이 3개 이상이므로 가장 낮은 옵션을 체크 해제합니다.") checkboxes[0].click() if len(checkboxes) > max_option_count: self.logger.debug("옵션이 10개 이상이므로 초과 옵션을 체크 해제합니다.") for i in range(max_option_count, len(checkboxes)): checkboxes[i].click() except Exception as e: self.logger.error(f"옵션 체크 조정 중 오류 발생: {e}", exc_info=True) def check_options(self, option_info): """옵션 체크 로직: 모든 옵션 체크 해제 후 다시 선택""" try: # 전체 옵션 체크박스 체크 해제 total_checkbox_selector = '#productMainContentContainerId label.ant-checkbox-wrapper' total_checkbox_element = self.page.query_selector(total_checkbox_selector) if total_checkbox_element: total_checkbox_element.click() self.logger.debug("모든 옵션 체크 해제 완료") # 옵션 갯수에 따라 선택 로직 진행 total_options_count = len(self.option_info['original_names']) self.logger.debug(f"선택 가능한 옵션 수: {total_options_count}") if total_options_count > 2: # 3개 이상인 경우: 1번째 옵션을 제외하고 최대 10개까지만 체크 options_to_check = self.option_info['checkboxes'][1:self.max_selected_options + 1] else: # 2개 이하인 경우: 모두 체크 options_to_check = self.option_info['checkboxes'] # 선택된 옵션들 체크 for checkbox in options_to_check: checkbox.click() self.logger.debug(f"옵션 체크 완료: {checkbox}") except Exception as e: self.logger.error(f"옵션 체크 중 오류 발생: {e}", exc_info=True) def low_order_click(self): self.logger.debug("가격 낮은 순 정렬을 클릭합니다.") self.page.click('button:has-text("가격 낮은 순")') self.page.wait_for_load_state('domcontentloaded') def save_option(self): """옵션 수정 후 저장 버튼 클릭""" try: self.page.click('button:has-text("저장하기")') self.logger.debug("옵션 수정 내용 저장 완료.") except Exception as e: self.logger.debug(f"옵션수정 후 저장 버튼 클릭 중 오류: {e}", exc_info=True)