토글추가

This commit is contained in:
Envy_PC 2024-10-11 09:25:09 +09:00
parent fa474c9a68
commit 6f7a6249eb
11 changed files with 1870 additions and 453 deletions

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -33,8 +33,7 @@ class BrowserController:
self.product_name_template = self.locator_manager.get_locator('BrowserControl', 'product_name_template')
self.product_price_template = self.locator_manager.get_locator('BrowserControl', 'product_price_template')
self.product_image_template = self.locator_manager.get_locator('BrowserControl', 'product_image_template')
self.next_page_button_template = self.locator_manager.get_locator('BrowserControl', 'next_page_button_template')
self.new_product_page_locator = self.locator_manager.get_locator('BrowserControl', 'new_product_page_locator')
self.product_edit_button = self.locator_manager.get_locator('BrowserControl', 'product_edit_button')
self.current_page = self.locator_manager.get_locator('BrowserControl', 'current_page')
self.next_page_button_template = self.locator_manager.get_locator('BrowserControl', 'next_page_button_template')
self.new_product_page_locator = self.locator_manager.get_locator('BrowserControl', 'new_product_page_locator')
@ -42,6 +41,8 @@ class BrowserController:
self.source_button_locator = self.locator_manager.get_locator('BrowserControl', 'source_button_locator')
self.ck_source_editing_area_locator = self.locator_manager.get_locator('BrowserControl', 'ck_source_editing_area_locator')
self.option_input_field_locator = self.locator_manager.get_locator('BrowserControl', 'option_input_field_locator')
self.text_templates = self.locator_manager.selectors.get('DetailPageTextTemplates', {})
self.product_name_template_xpath = self.locator_manager.get_locator('BrowserControl', 'product_name_template_xpath')
def get_page(self):
@ -86,19 +87,19 @@ class BrowserController:
if is_admin:
# 관리자 로그인 처리
await self.page.fill(self.login_email_locator, admin_id) # 관리자 ID 입력
await self.page.fill(self.login_password_locator, admin_password) # 관리자 비밀번호 입력
await self.page.click(self.login_button_locator) # 관리자 로그인 버튼 클릭
await self.page.fill(self.login_email_locator, admin_id)
await self.page.fill(self.login_password_locator, admin_password)
await self.page.click(self.login_button_locator)
else:
# 관리자 토글 버튼을 클릭해서 직원 로그인 화면 활성화
admin_toggle = self.page.locator(self.admin_toggle_locator)
if await admin_toggle.get_attribute("aria-checked") == "true":
await admin_toggle.click() # 관리자 모드에서 직원 모드로 전환
await self.page.fill(self.login_email_locator, admin_id) # 관리자 ID 입력
await self.page.fill(self.staff_id_locator, user_id) # 직원 ID 입력
await self.page.fill(self.login_password_locator, user_password) # 직원 비밀번호 입력
await self.page.click(self.staff_login_button_locator) # 직원 로그인 버튼 클릭
await self.page.fill(self.login_email_locator, admin_id)
await self.page.fill(self.staff_id_locator, user_id)
await self.page.fill(self.login_password_locator, user_password)
await self.page.click(self.staff_login_button_locator)
self.logger.debug(f'로그인 완료: {"관리자" if is_admin else "직원"} 계정')
@ -159,16 +160,19 @@ class BrowserController:
index : 상품명을 수집하는 인덱스
selector : 수집방법 (css 또는 xpath)
"""
try:
if selector == 'xpath':
product_name_xpath = f"//div[{index}]/div/li/div/div/div[2]/div/div/div[1]/div[1]/span[2]"
product_name_element = await self.page.query_selector(product_name_xpath)
# config.ini에서 설정된 선택자에 인덱스를 적용하여 가져옴
# product_name_selector = self.product_name_template.format(index=index)
# self.logger.debug(f"사용된 선택자: {product_name_selector}") # 선택자 출력
if selector == 'css':
product_name_css = f'div#root div:nth-child({index}) > div > li > div > div > div:nth-child(2) > div > div > div.ant-col.css-1li46mu > div.sc-ktPPKK.ezbvYT > span.sc-ecPEgm.gmiQgL.Body3Regular14.CharacterPrimary85'
# 상품명을 포함한 span 요소를 찾기 위한 선택자 수정
product_name_element = await self.page.locator(product_name_css).element_handle()
product_name_xpath_selector = self.product_name_template_xpath.format(index=index)
self.logger.debug(f"사용된 선택자: {product_name_xpath_selector}") # 선택자 출력
# XPath 기반으로 요소 검색
product_name_element = await self.page.locator(f"xpath={product_name_xpath_selector}").element_handle()
# product_name_element = await self.page.query_selector(product_name_selector)
# product_name_element가 None인지 확인
if product_name_element is None:
@ -182,6 +186,8 @@ class BrowserController:
self.logger.error(f"상품명 수집 중 오류: {e}")
return "수집 오류 발생"
def fetch_image_urls(self, html_content):
"""
HTML 콘텐츠에서 모든 <img> 태그의 URL을 순서대로 추출하고 중복 제거.
@ -241,20 +247,21 @@ class BrowserController:
async def get_product_edit_buttons(self):
"""현재 페이지의 세부사항 수정 및 업로드 버튼을 찾기"""
try:
# 페이지 로딩을 기다림
# await self.page.wait_for_load_state('networkidle', timeout=10000) # 네트워크 요청이 끝난 후 대기
# 버튼 선택자를 가져옴
edit_button_selector = self.product_edit_button
# 페이지 끝까지 스크롤하여 모든 동적 요소 로드
await self.scroll_page_to_bottom()
await self.scroll_page_to_top()
if not edit_button_selector:
self.logger.debug("상품 수정 버튼의 선택자를 찾을 수 없습니다.")
return []
# 선택자를 사용해 버튼 객체를 찾음
buttons = self.page.locator(edit_button_selector)
# # 스크롤하여 모든 버튼을 화면에 표시 (가장 하단까지 스크롤)
# self.page.evaluate("""window.scrollTo(0, document.body.scrollHeight);""")
# self.logger.debug("페이지를 아래로 스크롤했습니다.")
# 버튼이 존재하는지 확인
if await buttons.count() == 0:
self.logger.debug("세부사항 수정 및 업로드 버튼을 찾을 수 없습니다.")
return []
# 버튼 선택 (확실한 선택자를 사용하여 확인)
buttons = self.page.locator('button:has-text("세부사항 수정 및 업로드")')
count = await buttons.count()
self.logger.debug(f"수정할 상품 개수: {count}")
@ -265,6 +272,51 @@ class BrowserController:
self.logger.debug(f"상품 수정 버튼을 찾는 중 오류: {e}", exc_info=True)
return []
async def get_product_edit_buttons_by_templete(self):
"""현재 페이지의 세부사항 수정 및 업로드 버튼을 찾기"""
try:
# 버튼 선택자 설정
edit_button_selector_template = f'//button[span[text()="세부사항 수정 및 업로드"]]'
# 선택자를 사용해 버튼 객체를 찾음
buttons = self.page.locator(edit_button_selector_template)
# 버튼이 존재하는지 확인
button_count = await buttons.count()
if button_count == 0:
self.logger.debug("세부사항 수정 및 업로드 버튼을 찾을 수 없습니다.")
return []
self.logger.debug(f"수정할 상품 개수: {button_count}")
# 모든 버튼을 리스트로 반환
return [buttons.nth(i) for i in range(button_count)]
except Exception as e:
self.logger.debug(f"상품 수정 버튼을 찾는 중 오류: {e}", exc_info=True)
return []
async def click_modify_button_by_text(self, index):
"""인덱스에 해당하는 '세부사항 수정 및 업로드' 버튼 클릭"""
try:
# config.ini에서 선택자 가져오기
button_template = self.locator_manager.get_locator('BrowserControl', 'product_edit_button_template')
button_selector = f'({button_template})[{index}]'
button = await self.page.query_selector(button_selector)
# 버튼이 화면에 보이도록 스크롤 후 클릭
if button:
await button.scroll_into_view_if_needed()
await self.page.evaluate('arguments[0].click();', button)
self.logger.debug(f'{index}번째 상품의 수정 버튼 클릭 완료')
else:
self.logger.debug(f'{index}번째 상품의 수정 버튼을 찾지 못했습니다.')
except Exception as e:
self.logger.debug(f'{index}번째 상품의 수정 버튼 클릭 중 오류: {str(e)}')
async def open_product_edit_dialog(self, button):
"""상품 수정 다이얼로그 열기"""
try:
@ -281,7 +333,8 @@ class BrowserController:
async def click_detail_tab(self):
"""상세페이지 탭 클릭"""
try:
await self.page.click('div.ant-tabs-tab:has-text("상세페이지")')
detail_tab_locator = self.locator_manager.get_locator('BrowserControl', 'detail_tab_locator')
await self.page.click(detail_tab_locator)
self.logger.debug("상세페이지 탭 클릭 완료.")
except Exception as e:
self.logger.debug(f"상세페이지 탭 클릭 중 오류: {e}", exc_info=True)
@ -289,7 +342,8 @@ class BrowserController:
async def click_option_tab(self):
"""상세페이지 탭 클릭"""
try:
await self.page.click('div.ant-tabs-tab:has-text("옵션")')
option_tab_locator = self.locator_manager.get_locator('BrowserControl', 'option_tab_locator')
await self.page.click(option_tab_locator)
self.logger.debug("옵션 탭 클릭 완료.")
except Exception as e:
self.logger.debug(f"옵션 탭 클릭 중 오류: {e}", exc_info=True)
@ -297,7 +351,8 @@ class BrowserController:
async def click_price_tab(self):
"""상세페이지 탭 클릭"""
try:
await self.page.click('div.ant-tabs-tab:has-text("가격")')
price_tab_locator = self.locator_manager.get_locator('BrowserControl', 'price_tab_locator')
await self.page.click(price_tab_locator)
self.logger.debug("가격 탭 클릭 완료.")
except Exception as e:
self.logger.debug(f"가격 탭 클릭 중 오류: {e}", exc_info=True)
@ -349,16 +404,13 @@ class BrowserController:
input_field = await self.page.wait_for_selector(input_field_locator)
# 선두부 텍스트 입력
await input_field.type('---')
await input_field.press('Enter')
pyautogui.typewrite('## > 안녕하세요 혜리수샵입니다.')
await input_field.press('Enter')
await input_field.type('# 마켓정책으로 인해 모든 옵션이 노출되지 않을수도 있습니다.')
await input_field.type('**반드시 옵션사진과 옵션이름을 확인하시고 구매하시기 바랍니다.**')
await input_field.press('Enter')
await input_field.type('---')
await input_field.press('Enter')
for key in sorted(self.text_templates.keys()):
leading_text = self.text_templates[key]
if 'leading_text' in key and leading_text: # leading_text 항목만 가져오기
await input_field.type(leading_text)
await input_field.press('Enter')
await input_field.press('Enter')
self.logger.debug(f"{key} 텍스트 입력 완료: {leading_text}")
# 각 옵션을 한 줄씩 입력
for i, option in enumerate(option_data, start=1):
@ -368,7 +420,8 @@ class BrowserController:
option_text = option
# 옵션을 A. B. 등으로 표시하며 입력
option_prefix = f"{chr(64 + i)}. " # A, B, C...
# option_prefix = f"{chr(64 + i)}. " # A, B, C...
option_prefix = f"- {chr(64 + i)}. " # 마크다운 목록 A, B, C...
await input_field.type(option_prefix + option_text)
await input_field.press('Enter') # 엔터 키를 입력하여 줄바꿈
@ -409,8 +462,9 @@ class BrowserController:
async def save_and_ecs_product_edit(self):
"""상품 수정 후 저장 버튼 클릭"""
try:
await self.page.click('button:has-text("저장하기")')
await self.page.keyboard.press("Escape") # ESC로 다이얼로그 닫기
save_button_locator = self.locator_manager.get_locator('BrowserControl', 'save_button_locator')
await self.page.click(save_button_locator)
await self.page.keyboard.press("Escape")
self.logger.debug("상품 수정 내용 저장 및 ECS 완료.")
except Exception as e:
self.logger.debug(f"저장 버튼 클릭 중 오류: {e}", exc_info=True)
@ -418,7 +472,8 @@ class BrowserController:
async def save_product_edit(self):
"""상품 수정 후 저장 버튼 클릭"""
try:
await self.page.click('button:has-text("저장하기")')
save_button_locator = self.locator_manager.get_locator('BrowserControl', 'save_button_locator')
await self.page.click(save_button_locator)
self.logger.debug("상품 수정 내용 저장 완료.")
except Exception as e:
self.logger.debug(f"저장 버튼 클릭 중 오류: {e}", exc_info=True)
@ -589,22 +644,6 @@ class BrowserController:
self.logger.error(f"상품 정보 수집 중 오류 발생: {e}", exc_info=True)
return []
async def click_modify_button_by_text(self, index):
"""인덱스에 해당하는 '세부사항 수정 및 업로드' 버튼 클릭"""
try:
button_selector = f'(//button[span[text()="세부사항 수정 및 업로드"]])[{index}]'
# 버튼이 화면에 보이도록 스크롤 후 클릭
button = await self.page.query_selector(button_selector)
if button:
await button.scroll_into_view_if_needed()
await self.page.evaluate('arguments[0].click();', button)
self.logger.debug(f'{index}번째 상품의 수정 버튼 클릭 완료')
else:
self.logger.debug(f'{index}번째 상품의 수정 버튼을 찾지 못했습니다.')
except Exception as e:
self.logger.debug(f'{index}번째 상품의 수정 버튼 클릭 중 오류: {str(e)}')
async def scroll_page_to_bottom(self, pause_time=0.2):
"""페이지의 맨 아래까지 스크롤하여 모든 동적 요소를 로드"""

View File

@ -9,60 +9,77 @@ product_cost_locator = //*[@id='productMainContentContainerId']/div/div[2]/div/d
standard_selling_price_locator = //*[@id='productMainContentContainerId']/div/div[2]/div/div/div[5]/div[1]/div/div/div/div/div[2]/table/tbody/tr[{i}]/td[4]/div/div/div[1]/div/div[2]/input
[OptionLocators]
option_input_locator = //*[@id='optionMainContainerId']/div/div[2]/table/tbody/tr[{i}]/td[2]/input
option_price_locator = //*[@id='optionMainContainerId']/div/div[2]/table/tbody/tr[{i}]/td[4]/input
# 옵션 관련 선택자
option_excluded_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[{i}]/div/div[1]/div/div[2]/div/div[3]
option_input_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[{i}]/div/div[1]/div/div[3]/div[2]/div[1]/span/input
single_option_locator = //div[@id="productMainContentContainerId"]//label[contains(@class, 'ant-radio-button-wrapper-checked') and contains(., '단일 상품등록')]
option_product_locator = //div[@id="productMainContentContainerId"]//label[contains(@class, 'ant-radio-button-wrapper-checked') and contains(., '옵션 상품등록')]
total_options_selector = '#productMainContentContainerId label.ant-checkbox-wrapper'
original_name_selector_template = 'div#productMainContentContainerId li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(3) > span'
edit_field_selector_template = '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'
checkbox_selector_template = '#productMainContentContainerId li:nth-child({i}) input[type="checkbox"]'
image_selector_template = '#productMainContentContainerId li:nth-child({i}) img.sc-gbvfcU.ezktkd'
price_selector_template = '#productMainContentContainerId li:nth-child({i}) sup'
delete_button_selector_template = '#productMainContentContainerId > div.sc-TOgAA.fZvEqY > div:nth-child(2) > div > div > div:nth-child(2) > div > div.sc-cFShuL.dbIeho > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-fGdiLE.iyXMeU > div.ant-list.ant-list-split.css-1li46mu > div > div > ul > li:nth-child({i}) > 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'
confirm_delete_button_locator = 'body > div:nth-child(18) > div > div.ant-modal-wrap.ant-modal-confirm-centered.ant-modal-centered > div > div.sc-ddjGPC.jbwEYW > div > div > div > div.ant-modal-confirm-btns > button.ant-btn.css-1li46mu.ant-btn-primary.ant-btn-dangerous'
add_button_selector_template = '#productMainContentContainerId > div.sc-TOgAA.fZvEqY > div:nth-child(2) > div > div > div:nth-child(2) > div > div.sc-cFShuL.dbIeho > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-fGdiLE.iyXMeU > div.ant-list.ant-list-split.css-1li46mu > div > div > ul > li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div > img'
file_input_locator = input[type="file"]
[DetailLocators]
product_detail_input_locator = //*[@id='detailMainContainerId']/div/div/div[{i}]/textarea
product_image_locator = //*[@id='detailMainContainerId']/div/div/div[{i}]/img
[DetailPageTextTemplates]
leading_text_1 = "---"
leading_text_2 = "# > 안녕하세요 혜리수샵입니다."
leading_text_3 = " "
leading_text_4 = " "
leading_text_5 = "### 마켓정책으로 인해 모든 옵션이 노출되지 않을수도 있습니다."
leading_text_6 = "**반드시 옵션사진과 옵션이름을 확인하시고 구매하시기 바랍니다.**"
leading_text_7 = "---"
# 필요한 만큼 추가 가능
[ProductNameLocators]
product_name_input_locator = //*[@id='productMainContentContainerId']/div/div[1]/div/div/div[1]/input
[BrowserControl]
# 크롬 창 이름
chrome_window_name = 퍼센티 - 셀러들을 위한 AI 구매대행 솔루션 - Chrome
# 로그인 관련 선택자
admin_id_input = input[placeholder="이메일 주소 입력"]
admin_password_input = input[placeholder="영문/숫자/특수문자의 조합 (6~15자리)"]
admin_login_button = button:has-text("로그인 하기")
user_id_input = input[placeholder="직원 아이디 입력"]
user_password_input = input[placeholder="영문/숫자/특수문자의 조합 (6~15자리)"]
user_login_button = button:has-text("직원 로그인 하기")
admin_toggle_button = button[role="switch"]
# 관리자 로그인 관련 선택자
login_email_locator = input[placeholder="이메일 주소 입력"]
login_password_locator = input[placeholder="영문/숫자/특수문자의 조합 (6~15자리)"]
login_button_locator = button:has-text("로그인 하기")
# 광고 다이얼로그
ad_dialog_selector = div.ant-modal-wrap.ant-modal-centered
ad_close_button_selector = div.ant-modal-footer > div > div > button[type='button'].ant-btn.css-1li46mu.ant-btn-default
# 직원 로그인 관련 선택자
staff_id_locator = input[placeholder="직원 아이디 입력"]
staff_login_button_locator = button:has-text("직원 로그인 하기")
admin_toggle_locator = button[role="switch"]
# 상품 수 관련 선택자
total_product_count = '#root > div > div > div > div > main > div > div.sc-ezreuY.kYrYVh > div.sc-dChVcU.cRrUlt > div.sc-izQBue.dxiUJm > div > div:nth-child(1) > label > span:nth-child(2)'
# 광고 다이얼로그 관련 선택자
close_ad_dialog_locator = div.ant-modal-wrap.ant-modal-centered
close_ad_button_locator = div.ant-modal-footer > div > div > button[type='button'].ant-btn.css-1li46mu.ant-btn-default
# 상품명 관련 선택자
product_name_xpath = //div[{index}]/div/li/div/div/div[2]/div/div/div[1]/div[1]/span[2]
product_name_css = 'div#root div:nth-child({index}) > div > li > div > div > div:nth-child(2) > div > div > div.ant-col.css-1li46mu > div.sc-ktPPKK.ezbvYT > span.sc-ecPEgm.gmiQgL.Body3Regular14.CharacterPrimary85'
# 상품 관련 선택자
product_name_template_xpath = /html/body/div[1]/div/div/div/div/main/div/div[2]/div[2]/div[3]/div/div/ul/div[{index}]/div/li/div/div/div[2]/div/div/div[1]/div[1]/span[2]
product_name_template = 'div#root div:nth-child({index}) > div > li > div > div > div:nth-child(2) > div > div > div.ant-col.css-1li46mu > div.sc-ktPPKK.ezbvYT > span.sc-ecPEgm.gmiQgL.Body3Regular14.CharacterPrimary85'
product_price_template = 'div#root div:nth-child({index}) > div > li > div > div > div:nth-child(2) > div > div > div.ant-col.css-1li46mu > span.price'
product_image_template = 'div#root div:nth-child({index}) > div > li > div > div > div:nth-child(2) > div > img'
# 상품 수정을 위한 버튼
# 상품 편집 및 페이지 이동 관련 선택자
product_edit_button = button:has-text("세부사항 수정 및 업로드")
product_edit_button_template = //button[span[text()="세부사항 수정 및 업로드"]]
# 탭 관련 선택자
detail_tab = div.ant-tabs-tab:has-text("상세페이지")
option_tab = div.ant-tabs-tab:has-text("옵션")
price_tab = div.ant-tabs-tab:has-text("가격")
# 페이지 이동 관련 선택자
next_page_button_template = li.ant-pagination-item[title="{page_number}"]
new_product_page_locator = span.ant-menu-title-content:has-text("신규 상품 등록")
current_page_locator = li.ant-pagination-item.ant-pagination-item-active
# 이미지 URL 추출 관련 선택자
source_button = button[data-cke-tooltip-text="소스"]
source_editing_area = div.ck-source-editing-area
# 상세페이지 소스 관련 선택자
source_button_locator = button[data-cke-tooltip-text="소스"]
ck_source_editing_area_locator = div.ck-source-editing-area
# 옵션 입력 필드
option_input_field_locator = 'div#productMainContentContainerId > div > div > div:nth-child(2) > div:nth-child(2) > div:nth-child(2) > div'
[CategoryMargins]

106
gui.py
View File

@ -1,4 +1,4 @@
from PySide6.QtWidgets import QWidget, QPushButton, QVBoxLayout, QGridLayout, QTextEdit, QLabel, QLineEdit, QHBoxLayout, QProgressBar, QSizePolicy
from PySide6.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QGridLayout, QTextEdit, QLabel, QLineEdit, QHBoxLayout, QProgressBar, QSizePolicy
from PySide6.QtCore import Qt, QRect, QSettings, QTimer
from toggleSwitch import ToggleSwitch
from browser_control import BrowserController
@ -421,7 +421,7 @@ class TranslationApp(QWidget):
self.translate_button.clicked.connect(self.on_start_translation_button_clicked)
self.pause_button.clicked.connect(self.pause_translation)
self.exit_button.clicked.connect(self.close)
# self.exit_button.clicked.connect(self.close)
self.exit_button.clicked.connect(self.on_close_button_clicked)
async def run_async_tasks(self):
@ -581,12 +581,17 @@ class TranslationApp(QWidget):
# 비동기 함수 실행을 위해 asyncio.create_task 사용
asyncio.create_task(self.start_browser())
# async def on_close_button_clicked(self):
# """크롬 실행 버튼 클릭 시 호출"""
# self.logger.debug('크롬 실행 버튼 클릭됨')
# # 비동기 함수 실행을 위해 asyncio.create_task 사용
# task = asyncio.create_task(self.close())
# await task # 작업이 완료될 때까지 대기
async def on_close_button_clicked(self):
"""크롬 실행 버튼 클릭 시 호출"""
self.logger.debug('크롬 실행 버튼 클릭됨')
# 비동기 함수 실행을 위해 asyncio.create_task 사용
task = asyncio.create_task(self.close())
await task # 작업이 완료될 때까지 대기
QApplication.quit() # QApplication을 직접 종료
async def start_browser(self):
"""크롬 브라우저 실행 후 로그인"""
@ -698,7 +703,7 @@ class TranslationApp(QWidget):
self.logger.debug(f'1페이지가 아니므로 동적로딩을 위해 휠 스크롤 업')
# 4. 현재 페이지의 모든 "세부사항 수정 및 업로드" 버튼 찾기
product_buttons = await self.browser_controller.get_product_edit_buttons()
product_buttons = await self.browser_controller.get_product_edit_buttons_by_templete()
if not product_buttons:
self.logger.debug('수정할 상품이 없습니다. 작업을 종료합니다.')
break
@ -712,7 +717,7 @@ class TranslationApp(QWidget):
self.logger.debug(f'{index}/{len(product_buttons)}: 세부사항 수정 작업 중...')
# 상품명 수집 및 수집 오류 처리
product_name = await self.browser_controller.get_product_name(index, 'xpath')
product_name = await self.browser_controller.get_product_name(index, 'css')
if product_name == "수집 오류 발생":
self.logger.debug('상품 수집 오류, 다음 상품으로 넘어갑니다.')
continue
@ -761,93 +766,6 @@ class TranslationApp(QWidget):
self.logger.debug(f"번역 작업 중 오류 발생: {e}", exc_info=True)
self.running = False
# async def start_translation_ori(self):
# self.logger.debug('번역 작업을 시작합니다...')
# self.running = True # 번역 작업이 시작됨
# try:
# # # 1. "신규 상품 등록" 페이지로 이동
# # self.logger.debug('신규 상품 등록 페이지로 이동 중...')
# # await self.browser_controller.go_to_new_product_page()
# # 2. 총 상품 수 수집
# await self.browser_controller.scroll_page_to_bottom()
# total_products = await self.browser_controller.get_total_product_count()
# if total_products == 0:
# self.logger.debug('수집할 상품이 없습니다. 작업을 종료합니다.')
# return
# self.total_progress_bar.setMaximum(total_products)
# self.total_progress_bar.setValue(0)
# completed_count = 0
# self.update_total_progress(completed_count, total_products)
# page_number = 1
# while self.running or completed_count < total_products:
# # 4. 현재 페이지의 모든 "세부사항 수정 및 업로드" 버튼 찾기
# self.logger.debug(f'현재 페이지: {page_number}')
# product_buttons = await self.browser_controller.get_product_edit_buttons()
# if not product_buttons:
# self.logger.debug('수정할 상품이 없습니다. 번역 작업을 종료합니다.')
# break
# # 5. 각 상품에 대해 번역 작업 수행
# for index, button in enumerate(product_buttons, start=1):
# if not self.running or completed_count >= total_products:
# self.logger.debug('번역 작업이 중단되었습니다.')
# break
# self.logger.debug(f'{index}/{len(product_buttons)}: 세부사항 수정 작업 중...')
# # 상품명 수집 및 수집 오류 처리
# product_name = await self.browser_controller.get_product_name(index)
# if product_name == "수집 오류 발생":
# self.logger.debug('상품 수집 오류, 다음 상품으로 넘어갑니다.')
# continue
# # 상품 수정 다이얼로그 열기
# await self.browser_controller.open_product_edit_dialog(button)
# # 옵션 수정
# self.start_stage(0)
# await self.edit_option()
# self.complete_stage(0)
# # 상세페이지 수정
# self.start_stage(1)
# await self.detail_trans()
# self.complete_stage(1)
# # 수정 후 저장
# self.logger.debug('상품 세부사항 저장 중...')
# await self.browser_controller.save_product_edit()
# completed_count += 1
# self.update_total_progress(completed_count, total_products)
# self.logger.debug(f'{completed_count}/[{total_products}]개 상품 수정 완료.')
# # 모든 상품이 완료되었는지 체크
# if completed_count >= total_products:
# self.logger.debug('모든 상품이 완료되었습니다.')
# break
# # 6. 다음 페이지로 이동 (있으면)
# if not await self.browser_controller.go_to_next_page():
# self.logger.debug('더 이상 페이지가 없습니다. 작업을 종료합니다.')
# break
# page_number += 1
# if self.running:
# self.logger.debug('모든 상품 번역 및 저장 완료.')
# self.running = False # 작업 종료 후 상태를 False로 전환
# except Exception as e:
# self.logger.debug(f"번역 작업 중 오류 발생: {e}", exc_info=True)
# self.running = False
def pause_translation(self):
self.logger.debug('번역 작업을 중단합니다...')
self.running = False # 번역 작업 중단

View File

@ -4,34 +4,69 @@ class LocatorManager:
def __init__(self, config_file='config.ini'):
self.config_file = config_file
self.selectors = {}
self.config = configparser.ConfigParser()
self.load_locators_from_config()
def load_locators_from_config(self):
"""
config.ini 파일에서 선택자를 불러와 self.selectors에 저장
"""
config = configparser.ConfigParser()
with open(self.config_file, 'r', encoding='utf-8') as config_file:
config.read_file(config_file)
self.config.read_file(config_file)
# PriceLocators 섹션
self.selectors['PriceLocators'] = {
'return_fee_input_locator': config.get('PriceLocators', 'return_fee_input_locator'),
'first_delv_fee_input_locator': config.get('PriceLocators', 'first_delv_fee_input_locator'),
'exchange_fee_input_locator': config.get('PriceLocators', 'exchange_fee_input_locator'),
'plus_margin_locator': config.get('PriceLocators', 'plus_margin_locator'),
'oversea_shipping_locator': config.get('PriceLocators', 'oversea_shipping_locator'),
'option_count_text_locator': config.get('PriceLocators', 'option_count_text_locator'),
'product_cost_locator': config.get('PriceLocators', 'product_cost_locator'),
'standard_selling_price_locator': config.get('PriceLocators', 'standard_selling_price_locator')
'return_fee_input_locator': self.config.get('PriceLocators', 'return_fee_input_locator'),
'first_delv_fee_input_locator': self.config.get('PriceLocators', 'first_delv_fee_input_locator'),
'exchange_fee_input_locator': self.config.get('PriceLocators', 'exchange_fee_input_locator'),
'plus_margin_locator': self.config.get('PriceLocators', 'plus_margin_locator'),
'oversea_shipping_locator': self.config.get('PriceLocators', 'oversea_shipping_locator'),
'option_count_text_locator': self.config.get('PriceLocators', 'option_count_text_locator'),
'product_cost_locator': self.config.get('PriceLocators', 'product_cost_locator'),
'standard_selling_price_locator': self.config.get('PriceLocators', 'standard_selling_price_locator')
}
# BrowserControl 섹션
self.selectors['BrowserControl'] = {
'login_email_locator': self.config.get('BrowserControl', 'login_email_locator'),
'login_password_locator': self.config.get('BrowserControl', 'login_password_locator'),
'login_button_locator': self.config.get('BrowserControl', 'login_button_locator'),
'admin_toggle_locator': self.config.get('BrowserControl', 'admin_toggle_locator'),
'staff_id_locator': self.config.get('BrowserControl', 'staff_id_locator'),
'staff_login_button_locator': self.config.get('BrowserControl', 'staff_login_button_locator'),
'close_ad_dialog_locator': self.config.get('BrowserControl', 'close_ad_dialog_locator'),
'close_ad_button_locator': self.config.get('BrowserControl', 'close_ad_button_locator'),
'product_name_template': self.config.get('BrowserControl', 'product_name_template'),
'product_price_template': self.config.get('BrowserControl', 'product_price_template'),
'product_image_template': self.config.get('BrowserControl', 'product_image_template'),
'new_product_page_locator': self.config.get('BrowserControl', 'new_product_page_locator'),
'current_page_locator': self.config.get('BrowserControl', 'current_page_locator'),
'next_page_button_template': self.config.get('BrowserControl', 'next_page_button_template'),
'source_button_locator': self.config.get('BrowserControl', 'source_button_locator'),
'ck_source_editing_area_locator': self.config.get('BrowserControl', 'ck_source_editing_area_locator'),
'option_input_field_locator': self.config.get('BrowserControl', 'option_input_field_locator')
}
# OptionLocators 섹션
self.selectors['OptionLocators'] = {
'option_input_locator': config.get('OptionLocators', 'option_input_locator'),
'option_price_locator': config.get('OptionLocators', 'option_price_locator')
'option_excluded_selector_template': self.config.get('OptionLocators', 'option_excluded_selector_template'),
'option_input_selector_template': self.config.get('OptionLocators', 'option_input_selector_template'),
'single_option_locator': self.config.get('OptionLocators', 'single_option_locator'),
'option_product_locator': self.config.get('OptionLocators', 'option_product_locator'),
'total_options_selector': self.config.get('OptionLocators', 'total_options_selector'),
'original_name_selector_template': self.config.get('OptionLocators', 'original_name_selector_template'),
'edit_field_selector_template': self.config.get('OptionLocators', 'edit_field_selector_template'),
'checkbox_selector_template': self.config.get('OptionLocators', 'checkbox_selector_template'),
'image_selector_template': self.config.get('OptionLocators', 'image_selector_template'),
'price_selector_template': self.config.get('OptionLocators', 'price_selector_template'),
'delete_button_selector_template': self.config.get('OptionLocators', 'delete_button_selector_template'),
'confirm_delete_button_locator': self.config.get('OptionLocators', 'confirm_delete_button_locator'),
'add_button_selector_template': self.config.get('OptionLocators', 'add_button_selector_template'),
'file_input_locator': self.config.get('OptionLocators', 'file_input_locator')
}
def get_locator(self, section, key):
"""
섹션과 키를 받아서 해당 선택자를 반환

14
main.py
View File

@ -19,9 +19,9 @@ def allow_sleep():
"""절전모드 방지 설정을 해제"""
ctypes.windll.kernel32.SetThreadExecutionState(ES_CONTINUOUS)
async def process_qt_events(app):
async def process_qt_events(app, stop_event):
"""PySide6의 이벤트를 처리하는 비동기 함수"""
while True:
while not stop_event.done():
app.processEvents()
await asyncio.sleep(0.01) # 10ms마다 Qt 이벤트 처리
@ -35,6 +35,8 @@ async def main():
app = None
window = None # window 변수를 None으로 초기화하여 finally 블록에서 참조 가능하도록 함
stop_event = asyncio.Future() # 종료 이벤트 생성
try:
# PySide6 앱 실행
app = QApplication([])
@ -58,14 +60,16 @@ async def main():
# asyncio와 PySide6 이벤트 루프를 통합
await asyncio.gather(
process_qt_events(app), # PySide6 이벤트 처리
window.run_async_tasks() # 비동기 작업
process_qt_events(app, stop_event), # PySide6 이벤트 처리, stop_event 추가
window.run_async_tasks(), # 비동기 작업
)
finally:
# 앱 종료 시 절전모드 방지 해제
allow_sleep()
await window.close() # TranslationApp의 close() 비동기 호출
stop_event.set_result(True) # 종료 이벤트 설정 (process_qt_events 종료)
if window: # window가 생성되었을 경우에만 close() 호출
await window.close() # window.close()를 finally 블록으로 이동
if __name__ == '__main__':
asyncio.run(main()) # 비동기 함수는 asyncio.run()으로 실행

412
option.py
View File

@ -15,6 +15,22 @@ class OptionHandler:
self.whale_translator = whale_translator
self.init_option_info()
# 선택자 로드
self.option_excluded_selector_template = self.locator_manager.get_locator('OptionLocators', 'option_excluded_selector_template')
self.option_input_selector_template = self.locator_manager.get_locator('OptionLocators', 'option_input_selector_template')
self.single_option_locator = self.locator_manager.get_locator('OptionLocators', 'single_option_locator')
self.option_product_locator = self.locator_manager.get_locator('OptionLocators', 'option_product_locator')
self.total_options_selector = self.locator_manager.get_locator('OptionLocators', 'total_options_selector')
self.original_name_selector_template = self.locator_manager.get_locator('OptionLocators', 'original_name_selector_template')
self.edit_field_selector_template = self.locator_manager.get_locator('OptionLocators', 'edit_field_selector_template')
self.checkbox_selector_template = self.locator_manager.get_locator('OptionLocators', 'checkbox_selector_template')
self.image_selector_template = self.locator_manager.get_locator('OptionLocators', 'image_selector_template')
self.price_selector_template = self.locator_manager.get_locator('OptionLocators', 'price_selector_template')
self.delete_button_selector_template = self.locator_manager.get_locator('OptionLocators', 'delete_button_selector_template')
self.confirm_delete_button_locator = self.locator_manager.get_locator('OptionLocators', 'confirm_delete_button_locator')
self.add_button_selector_template = self.locator_manager.get_locator('OptionLocators', 'add_button_selector_template')
self.file_input_locator = self.locator_manager.get_locator('OptionLocators', 'file_input_locator')
def update_page(self, page1):
self.page = page1
self.logger.debug(f"page객체 업데이트 : {page1}")
@ -99,28 +115,20 @@ class OptionHandler:
await self.low_order_click()
for i in range(1, total_options_count + 1):
# 옵션이 제외되지 않았는지 확인
option_excluded_selector = f"//*[@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[{i}]/div/div[1]/div/div[2]/div/div[3]"
option_excluded_selector = self.option_excluded_selector_template.format(i)
option_input_selector = self.option_input_selector_template.format(i)
option_excluded_element = await self.page.query_selector(option_excluded_selector)
# 옵션이 제외되지 않은 경우에만 처리
if not option_excluded_element:
# 입력 필드에서 value 값 수집
option_input_selector = f"//*[@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[{i}]/div/div[1]/div/div[3]/div[2]/div[1]/span/input"
option_input_element = await self.page.query_selector(option_input_selector)
if option_input_element:
option_name_value_attrib = await option_input_element.get_attribute('value')
option_name_value = option_name_value_attrib.strip()
option_name_value = (await option_input_element.get_attribute('value')).strip()
selected_translated_options.append(
(option_name_value, self.option_info['prices'].get(option_name_value, {}).get('low_price', 0))
)
selected_translated_options.append((option_name_value, self.option_info['prices'].get(option_name_value, {}).get('low_price', 0)))
# 정렬된 옵션명만 추출하여 저장
# sorted_options = [option[0] for option in selected_translated_options]
# 클래스 변수에 저장
self.option_info['selected_translated_options'] = selected_translated_options
self.logger.debug(f"현재 페이지에서 가격 낮은 순으로 정렬된 선택된 옵션 저장 완료: {selected_translated_options}")
self.logger.debug(f"선택된 옵션 저장 완료: {selected_translated_options}")
except Exception as e:
self.logger.error(f"선택된 옵션 저장 중 오류 발생: {e}", exc_info=True)
@ -234,12 +242,9 @@ class OptionHandler:
"""단일 상품 상태 여부를 확인하는 메서드"""
try:
# 단일 상품 등록 버튼이 선택되었는지 확인
single_option_xpath = "//div[@id='productMainContentContainerId']//label[contains(@class, 'ant-radio-button-wrapper-checked') and contains(., '단일 상품등록')]"
single_option_checked = await self.page.query_selector(single_option_xpath) is not None
single_option_checked = await self.page.query_selector(self.single_option_locator) is not None
# 옵션 상품 등록 버튼이 선택되었는지 확인
option_product_xpath = "//div[@id='productMainContentContainerId']//label[contains(@class, 'ant-radio-button-wrapper-checked') and contains(., '옵션 상품등록')]"
option_product_checked = await self.page.query_selector(option_product_xpath) is not None
option_product_checked = await self.page.query_selector(self.option_product_locator) is not None
# 두 요소의 상태를 기반으로 단일 상품 여부 결정
is_single = single_option_checked and not option_product_checked
@ -253,7 +258,7 @@ class OptionHandler:
async def is_all_options_checked(self):
"""전체 옵션 체크박스 상태를 확인 (전체 체크 여부)"""
try:
checkbox = await self.page.query_selector('#productMainContentContainerId .ant-checkbox-wrapper-checked')
checkbox = await self.page.query_selector(self.total_options_selector)
if checkbox:
self.logger.debug("전체 옵션이 체크되어 있음")
return True
@ -267,202 +272,149 @@ class OptionHandler:
async def collect_options_info(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)
if total_options_element:
total_options_text = await total_options_element.inner_text()
total_options_count = int(''.join(filter(str.isdigit, total_options_text))) # 숫자만 추출
else:
total_options_count = 0 # 옵션 갯수를 찾지 못할 경우 기본값
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
self.logger.debug(f"총 옵션 갯수: {total_options_count}")
# 옵션 정보를 비동기로 수집 (각 항목 병렬 처리)
for i in range(1, total_options_count + 1):
try:
tasks = []
original_name_selector = self.original_name_selector_template.format(i)
edit_field_selector = self.edit_field_selector_template.format(i)
checkbox_selector = self.checkbox_selector_template.format(i)
image_selector = self.image_selector_template.format(i)
price_selector = self.price_selector_template.format(i)
# 원본 옵션명 수집
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"
tasks.append(self.page.query_selector(original_name_selector))
tasks = [
self.page.query_selector(original_name_selector),
self.page.query_selector(edit_field_selector),
self.page.query_selector(checkbox_selector),
self.page.query_selector(image_selector),
self.page.query_selector(price_selector)
]
# 편집 필드 수집
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"
tasks.append(self.page.query_selector(edit_field_selector))
# 체크박스 수집
checkbox_selector = f'#productMainContentContainerId li:nth-child({i}) input[type="checkbox"]'
tasks.append(self.page.query_selector(checkbox_selector))
# 이미지 수집
image_selector = f'#productMainContentContainerId li:nth-child({i}) img.sc-gbvfcU.ezktkd'
tasks.append(self.page.query_selector(image_selector))
# 가격 정보 수집
price_selector = f'#productMainContentContainerId li:nth-child({i}) sup'
tasks.append(self.page.query_selector(price_selector))
# 각 태스크를 병렬로 처리
elements = await asyncio.gather(*tasks)
original_name_element, edit_field_element, checkbox_element, image_element, price_element = elements
# 원본 옵션명 수집
original_name_element = elements[0]
original_name = await original_name_element.inner_text() if original_name_element else None
if original_name:
self.logger.debug(f"{i}번째 옵션명 수집완료. 나머지 필드 수집중...")
if original_name_element:
original_name = await 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)
# 편집 필드 수집
edit_field_element = elements[1]
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_element = elements[2]
if checkbox_element:
self.option_info['checkboxes'].append(checkbox_element)
is_checked = await checkbox_element.is_checked()
self.option_info['checked_states'][original_name] = is_checked
self.logger.debug(f"{i}번째 옵션 체크 상태: {is_checked}")
else:
self.logger.debug(f"{i}번째 옵션 체크박스 수집 실패▣ checkbox_element : {checkbox_element}")
# 이미지 수집
image_element = elements[3]
if image_element:
image_url = await 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
self.logger.debug(f"{i}번째 옵션 이미지 수집 실패▣ image_element : {image_element}")
# 가격 정보 수집
price_element = elements[4]
self.option_info['images'][original_name] = await image_element.get_attribute('src')
if price_element:
price_text = await price_element.inner_text()
price_text = price_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}")
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
async def collect_options_info_ori(self):
"""옵션 정보를 수집 (이미지, 옵션명, 편집 필드, 가격, 체크박스 정보 포함)"""
self.init_option_info()
# async 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)
if total_options_element:
total_options_text = await total_options_element.inner_text()
total_options_count = int(''.join(filter(str.isdigit, total_options_text))) # 숫자만 추출
else:
total_options_count = 0 # 옵션 갯수를 찾지 못할 경우 기본값
# try:
# # 총 옵션 갯수 수집
# total_options_selector = '#productMainContentContainerId label.ant-checkbox-wrapper'
# total_options_element = await self.page.query_selector(total_options_selector)
# if total_options_element:
# total_options_text = await 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}")
# 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 = await self.page.query_selector(original_name_selector)
original_name = await original_name_element.inner_text() if original_name_element else None
# # 옵션 정보를 수집 (총 옵션 갯수만큼 반복)
# 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 = await self.page.query_selector(original_name_selector)
# original_name = await 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
# 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 = await 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}")
# # 옵션 편집 필드 수집
# 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)
# 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 = await self.page.query_selector(checkbox_selector)
if checkbox_element:
self.option_info['checkboxes'].append(checkbox_element)
self.logger.debug(f"{i}번째 옵션 체크박스 수집 완료 : {checkbox_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 = await 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()
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}")
# # 체크 상태 수집
# self.logger.debug(f"{i}번째 옵션 체크박스 상태 수집")
# is_checked = await 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}")
else:
self.logger.debug(f"{i}번째 옵션 체크박스 수집 실패▣ checkbox_element : {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 = await self.page.query_selector(image_selector)
if image_element:
image_url = await 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}")
# # 옵션 이미지 수집
# 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)
# if image_element:
# image_url = await 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 = await self.page.query_selector(price_selector)
if price_element:
price_text = await price_element.inner_text()
price_text = price_text.replace(",", "").replace("", "").strip()
# # 가격 정보 수집
# price_selector = f'#productMainContentContainerId li:nth-child({i}) sup'
# price_element = await self.page.query_selector(price_selector)
# if price_element:
# price_text = await price_element.inner_text()
# price_text = price_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}
# 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}")
# 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"{i}번째 옵션 수집 중 오류 발생: {e}", exc_info=True)
except Exception as e:
self.logger.error(f"옵션 정보 수집 중 오류 발생: {e}", exc_info=True)
# except Exception as e:
# self.logger.error(f"옵션 정보 수집 중 오류 발생: {e}", exc_info=True)
return self.option_info
# return self.option_info
async def apply_translated_options(self, translated_options, edit_fields):
"""번역된 옵션명을 편집 필드에 입력하고 selected_translated_options을 한 번에 업데이트"""
@ -500,31 +452,6 @@ class OptionHandler:
self.logger.error(f"번역된 옵션명을 입력하는 중 오류 발생: {e}", exc_info=True)
async def apply_translated_options_ori(self, translated_options, edit_fields):
"""번역된 옵션명을 편집 필드에 입력"""
try:
# translated_options = await translated_options # 코루틴 실행 후 결과를 저장
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:
await 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)
async def filter_and_adjust_options(self, max_option_count):
"""가격 필터링을 적용하고 옵션을 조정"""
try:
@ -549,12 +476,12 @@ class OptionHandler:
# 필터링된 옵션명만 추출
filtered_option_names = {option['name'] for option in filtered_options}
# 필터링된 옵션들만 체크박스에서 남기고 나머지 체크박스 해제
checkboxes = [
self.option_info['checkboxes'][i]
for i, name in enumerate(self.option_info['original_names'].values())
if name in filtered_option_names
]
# # 필터링된 옵션들만 체크박스에서 남기고 나머지 체크박스 해제
# checkboxes = [
# self.option_info['checkboxes'][i]
# for i, name in enumerate(self.option_info['original_names'].values())
# if name in filtered_option_names
# ]
# 체크박스 상태 조정
await self.adjust_options(filtered_option_names, max_option_count)
@ -573,52 +500,40 @@ class OptionHandler:
try:
# 옵션 체크 상태를 수집한 정보에서 필터링된 옵션들만 체크 상태로 유지
for i, name in enumerate(self.option_info['original_names'].values()):
checkbox = self.option_info['checkboxes'][i]
checkbox_selector = self.checkbox_selector_template.format(i+1)
checkbox_element = await self.page.query_selector(checkbox_selector)
is_checked = self.option_info['checked_states'].get(name, False)
# 필터링된 옵션에 속하는 경우 체크 상태 유지, 그렇지 않으면 체크 해제
if name in filtered_option_names:
if not is_checked: # 원래 체크되지 않았으면 체크
self.logger.debug(f"{name} 옵션 체크")
await checkbox.click()
self.option_info['checked_states'][name] = True # 체크 상태 업데이트
else:
if is_checked: # 원래 체크된 경우 체크 해제
self.logger.debug(f"{name} 옵션 체크 해제")
await checkbox.click()
self.option_info['checked_states'][name] = False # 체크 상태 업데이트
# # 필터링된 옵션 중 max_option_count가 넘지 않도록 체크 상태 유지
# if len(filtered_option_names) > max_option_count:
# self.logger.debug(f"필터링된 옵션이 {max_option_count}개 이상이므로 초과된 옵션을 체크 해제합니다.")
# filtered_options_sorted = sorted(filtered_option_names) # 정렬된 옵션 리스트
# for i, name in enumerate(filtered_options_sorted[max_option_count:], start=max_option_count):
# checkbox = self.option_info['checkboxes'][i]
# if self.option_info['checked_states'][name]: # 이미 체크된 경우만 해제
# self.logger.debug(f"{name} 옵션 체크 해제 (초과된 옵션)")
# await checkbox.click()
# self.option_info['checked_states'][name] = False # 체크 상태 업데이트
if checkbox_element:
if name in filtered_option_names:
if not is_checked:
await checkbox_element.click()
self.option_info['checked_states'][name] = True
else:
if is_checked:
await checkbox_element.click()
self.option_info['checked_states'][name] = False
self.logger.debug(f"옵션 체크 상태 조정 완료.")
except Exception as e:
self.logger.error(f"옵션 체크 상태 조정 중 오류 발생: {e}", exc_info=True)
async def AtoZ_button_click(self):
self.logger.debug("가격 낮은 순 정렬을 클릭합니다.")
await self.page.click('button:has-text("A-Z")')
# atoz_button_xpath = '//*[@id="productMainContentContainerId"]/div[1]/div[2]/div/div/div[2]/div/div[1]/div/div/div[2]/div/div/div[4]/div[1]/div[1]/div/div[1]/button'
async def AtoZ_button_click(self):
AtoZ_button_locator = self.locator_manager.get_locator('OptionLocators', 'AtoZ_button_locator')
self.logger.debug("A-Z 버튼을 클릭합니다.")
await self.page.click(AtoZ_button_locator)
async def low_order_click(self):
low_order_button_locator = self.locator_manager.get_locator('OptionLocators', 'low_order_button_locator')
self.logger.debug("가격 낮은 순 정렬을 클릭합니다.")
await self.page.click('button:has-text("가격 낮은 순")')
# await self.page.wait_for_load_state('domcontentloaded')
await self.page.click(low_order_button_locator)
async def save_option(self):
"""옵션 수정 후 저장 버튼 클릭"""
save_button_locator = self.locator_manager.get_locator('OptionLocators', 'save_button_locator')
try:
await self.page.click('button:has-text("저장하기")')
await self.page.click(save_button_locator)
self.logger.debug("옵션 수정 내용 저장 완료.")
except Exception as e:
self.logger.debug(f"옵션수정 후 저장 버튼 클릭 중 오류: {e}", exc_info=True)
@ -641,17 +556,20 @@ class OptionHandler:
self.logger.debug(f"{index}번째 옵션의 이미지를 업데이트합니다.")
delete_button_selector = self.delete_button_selector_template.format(index)
confirm_delete_button_locator = self.confirm_delete_button_locator
add_button_selector = self.add_button_selector_template.format(index)
file_input_locator = self.file_input_locator
# 기존 이미지 삭제 (삭제 버튼이 존재할 경우)
delete_button_selector = f"#productMainContentContainerId > div.sc-TOgAA.fZvEqY > div:nth-child(2) > div > div > div:nth-child(2) > div > div.sc-cFShuL.dbIeho > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-fGdiLE.iyXMeU > div.ant-list.ant-list-split.css-1li46mu > div > div > ul > 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"
delete_button = await self.page.locator(delete_button_selector).element_handle()
delete_button = await self.page.query_selector(delete_button_selector)
if delete_button:
self.logger.debug(f"{index}번째 옵션의 기존 이미지를 삭제합니다.")
await delete_button.click()
# 삭제 확인 버튼 클릭
confirm_delete_button_selector = "body > div:nth-child(18) > div > div.ant-modal-wrap.ant-modal-confirm-centered.ant-modal-centered > div > div.sc-ddjGPC.jbwEYW > div > div > div > div.ant-modal-confirm-btns > button.ant-btn.css-1li46mu.ant-btn-primary.ant-btn-dangerous"
confirm_delete_button = await self.page.locator(confirm_delete_button_selector).element_handle()
confirm_delete_button = await self.page.query_selector(confirm_delete_button_locator)
if confirm_delete_button:
self.logger.debug(f"삭제 확인버튼 클릭")
await confirm_delete_button.click()
@ -664,7 +582,6 @@ class OptionHandler:
# 디렉토리가 존재하지 않으면 생성
self.logger.debug("이미지 저장 경로 설정")
translated_image_path = f"tmp_image/{product_name}-{option_name}-{index}.png"
if not os.path.exists('tmp_image'):
os.makedirs('tmp_image')
self.logger.debug("이미지 임시저장폴더가 존재하지 않아 생성.")
@ -674,14 +591,13 @@ class OptionHandler:
self.clipboardImageManager.process_clipboard(option_image_url, translated_image_path)
# 이미지 업로드 버튼 클릭 (옵션 이미지가 없는 경우)
add_button_selector = f"#productMainContentContainerId > div.sc-TOgAA.fZvEqY > div:nth-child(2) > div > div > div:nth-child(2) > div > div.sc-cFShuL.dbIeho > div > div > div.ant-collapse-content.ant-collapse-content-active > div > div > div.sc-fGdiLE.iyXMeU > div.ant-list.ant-list-split.css-1li46mu > div > div > ul > li:nth-child({index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div > img"
add_button = await self.page.locator(add_button_selector).element_handle()
add_button = await self.page.query_selector(add_button_selector)
if add_button:
await add_button.click()
# 파일 선택 다이얼로그에서 번역된 이미지 파일 입력
file_input = await self.page.wait_for_selector('input[type="file"]')
file_input = await self.page.wait_for_selector(file_input_locator)
await file_input.set_input_files(translated_image_path)
self.logger.debug(f"{index}번째 옵션에 번역된 이미지가 추가되었습니다.")
@ -705,7 +621,7 @@ class OptionHandler:
original_image.save(original_image_path)
# 저장된 원본 이미지를 다시 업로드
file_input = await self.page.wait_for_selector('input[type="file"]')
file_input = await self.page.wait_for_selector(file_input_locator)
await file_input.set_input_files(original_image_path)
self.logger.debug(f"{index}번째 옵션에 원본 이미지가 업로드되었습니다.")
else: