Merge branch 'pyside6' of ssh://cckb9998.synology.me:30022/ckh08045/autoTrans into pyside6

This commit is contained in:
R5600U_PC 2024-10-01 12:04:45 +09:00
commit 723c186dc0
16 changed files with 7541 additions and 133 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -130,47 +130,48 @@ class BrowserController:
return 0
async def get_total_product_count1(self):
"""총 상품 개수를 반환"""
try:
await self.page.wait_for_load_state('domcontentloaded') # 페이지 로딩 완료 대기
# async def get_total_product_count1(self):
# """총 상품 개수를 반환"""
# try:
# await self.page.wait_for_load_state('domcontentloaded') # 페이지 로딩 완료 대기
total_count_css_selector = '//label/span[contains(text(),"")]'
total_element = await self.page.wait_for_selector('//*[@id="root"]/div/div/div/div/main/div/div[2]/div[3]/div[2]/div/div[1]/label/span[2]', timeout=5000)
total_element2 = await self.page.query_selector(total_count_css_selector)
# total_count_css_selector = '//label/span[contains(text(),"총")]'
# total_element = await self.page.wait_for_selector('//*[@id="root"]/div/div/div/div/main/div/div[2]/div[3]/div[2]/div/div[1]/label/span[2]', timeout=5000)
# total_element2 = await self.page.query_selector(total_count_css_selector)
# JavaScript로 해당 요소의 텍스트를 가져옴
total_count_text3 = await self.page.evaluate('''() => {
let element = document.querySelector('#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)');
return element ? element.innerText : null;
}''')
# # JavaScript로 해당 요소의 텍스트를 가져옴
# total_count_text3 = await self.page.evaluate('''() => {
# let element = document.querySelector('#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)');
# return element ? element.innerText : null;
# }''')
self.logger.debug(f'{total_element.inner_text()}')
self.logger.debug(f'{total_element2.inner_text()}')
self.logger.debug(f'{total_count_text3}')
# self.logger.debug(f'{total_element.inner_text()}')
# self.logger.debug(f'{total_element2.inner_text()}')
# self.logger.debug(f'{total_count_text3}')
total_count_text = 1
total_count = int(re.findall(r'\d+', total_count_text)[0])
self.logger.debug(f'총 상품수 : {total_count}')
return total_count
except Exception as e:
self.logger.debug(f"총 상품 개수 수집 중 오류 발생: {e}", exc_info=True)
return 0
# total_count_text = 1
# total_count = int(re.findall(r'\d+', total_count_text)[0])
# self.logger.debug(f'총 상품수 : {total_count}개')
# return total_count
# except Exception as e:
# self.logger.debug(f"총 상품 개수 수집 중 오류 발생: {e}", exc_info=True)
# return 0
async def get_product_name_ori(self, index):
"""해당 상품의 이름을 가져옵니다. 오류 발생 시 '수집 오류 발생' 반환"""
try:
product_name_xpath = f"//div[{index}]/li/div/div/div[2]/div/div/div[1]/div[1]/span[2]"
product_name_element = await self.page.query_selector(product_name_xpath)
return await product_name_element.inner_text().strip()
except Exception as e:
self.logger.debug(f"상품명 수집 중 오류: {e}", exc_info=True)
return "수집 오류 발생"
# async def get_product_name_ori(self, index):
# """해당 상품의 이름을 가져옵니다. 오류 발생 시 '수집 오류 발생' 반환"""
# try:
# product_name_xpath = f"//div[{index}]/li/div/div/div[2]/div/div/div[1]/div[1]/span[2]"
# product_name_element = await self.page.query_selector(product_name_xpath)
# return await product_name_element.inner_text().strip()
# except Exception as e:
# self.logger.debug(f"상품명 수집 중 오류: {e}", exc_info=True)
# return "수집 오류 발생"
async def get_product_name(self, index):
"""상품명을 수집하는 메서드"""
try:
product_name_element = await self.page.locator(f'div.product-name:nth-child({index})').element_handle()
# 상품명을 포함한 span 요소를 찾기 위한 선택자 수정
product_name_element = await self.page.locator(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').element_handle()
# product_name_element가 None인지 확인
if product_name_element is None:
@ -184,6 +185,23 @@ class BrowserController:
self.logger.error(f"상품명 수집 중 오류: {e}")
return "수집 오류 발생"
# async def get_product_name_ori2(self, index):
# """상품명을 수집하는 메서드"""
# try:
# product_name_element = await self.page.locator(f'div.product-name:nth-child({index})').element_handle()
# # product_name_element가 None인지 확인
# if product_name_element is None:
# self.logger.error(f"상품명 요소를 찾을 수 없습니다: index {index}")
# return "수집 오류 발생"
# # 요소가 존재할 경우, inner_text 수집
# product_name = await product_name_element.inner_text()
# return product_name.strip()
# except Exception as e:
# self.logger.error(f"상품명 수집 중 오류: {e}")
# return "수집 오류 발생"
async def extract_image_urls(self):
"""HTML에서 이미지 URL 추출 및 img 태그 삭제 후 소스 버튼 다시 클릭"""
self.logger.debug('이미지 URL을 추출 중...')
@ -357,7 +375,7 @@ class BrowserController:
data_value = await textarea.get_attribute("data-value")
# HTML 소스에서 이미지 URL 추출
image_urls = await self.fetch_image_urls(data_value)
image_urls = self.fetch_image_urls(data_value)
self.logger.debug(f'추출된 이미지 URL 수: {len(image_urls)}')
# HTML 소스에서 이미지 URL 삭제
@ -387,7 +405,7 @@ class BrowserController:
self.logger.debug(f"이미지 URL 추출 중 오류: {e}", exc_info=True)
return []
def paste_image_in_chrome(self, clipboardImageManager, url):
async def paste_image_in_chrome(self, clipboardImageManager, url):
"""크롬으로 포커스를 옮기고 클립보드의 이미지를 붙여넣고 엔터 입력"""
try:
self.switch_to_chrome() # 크롬으로 포커스 이동

155
gui.py
View File

@ -15,6 +15,7 @@ class TranslationApp(QWidget):
super().__init__()
self.initUI()
self.logger = logger
self.debug = False
key_path = 'leensoo1nt.json'
self.settings = QSettings("WhenRideMycar", "TranslationApp") # QSettings 초기화
self.browser_controller = BrowserController(self, self.logger)
@ -24,8 +25,9 @@ class TranslationApp(QWidget):
self.vertexAI = VertexAITranslator(self.logger, key_path)
self.optionHandler = None
self.clipboardImageManager = ClipboardImageManager(self, logger, self.browser_controller, debug=False)
self.clipboardImageManager = ClipboardImageManager(self, logger, self.browser_controller, self.debug)
self.optionHandler = OptionHandler(self.browser_controller.page, self.whale_translator, self.logger, self.vertexAI, self.debug)
self.running = False
# 변수 설정
@ -103,7 +105,7 @@ class TranslationApp(QWidget):
def initUI(self):
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.setGeometry(QRect(1740, 500, 180, 400))
self.setGeometry(QRect(940, 500, 180, 400))
self.setWindowTitle('이미지 번역 도구')
# 로그
@ -229,6 +231,7 @@ class TranslationApp(QWidget):
self.pause_button.clicked.connect(self.pause_translation)
self.exit_button.clicked.connect(self.close)
self.exit_button.clicked.connect(self.on_close_button_clicked)
async def run_async_tasks(self):
"""비동기 작업을 실행"""
@ -259,6 +262,12 @@ class TranslationApp(QWidget):
# 비동기 함수 실행을 위해 asyncio.create_task 사용
asyncio.create_task(self.start_browser())
def on_close_button_clicked(self):
"""크롬 실행 버튼 클릭 시 호출"""
self.logger.debug('크롬 실행 버튼 클릭됨')
# 비동기 함수 실행을 위해 asyncio.create_task 사용
asyncio.create_task(self.close())
async def start_browser(self):
"""크롬 브라우저 실행 후 로그인"""
self.logger.debug('크롬 브라우저를 실행합니다...')
@ -284,9 +293,12 @@ class TranslationApp(QWidget):
# 로그인 정보 저장
self.save_settings()
# 옵션 핸들러 새로 초기화
self.optionHandler = OptionHandler(self.browser_controller.page, self.logger, self.vertexAI)
# "신규 상품 등록" 페이지로 이동
self.logger.debug('신규 상품 등록 페이지로 이동 중...')
await self.browser_controller.go_to_new_product_page()
# 옵션핸들러에 초기화된 page 객체 전달.
self.optionHandler.update_page(self.browser_controller.page)
def save_settings(self):
"""QSettings에 사용자 정보 저장"""
@ -334,18 +346,17 @@ class TranslationApp(QWidget):
# 비동기 함수 실행을 위해 asyncio.create_task 사용
asyncio.create_task(self.start_translation())
async def start_translation(self):
self.logger.debug('번역 작업을 시작합니다...')
self.running = True # 번역 작업이 시작됨
try:
# 1. "신규 상품 등록" 페이지로 이동
self.logger.debug('신규 상품 등록 페이지로 이동 중...')
await self.browser_controller.go_to_new_product_page()
# # 1. "신규 상품 등록" 페이지로 이동
# self.logger.debug('신규 상품 등록 페이지로 이동 중...')
# await self.browser_controller.go_to_new_product_page()
# 2. 총 상품 수 수집
await self.browser_controller.scroll_page_to_bottom()
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('수집할 상품이 없습니다. 작업을 종료합니다.')
@ -358,20 +369,21 @@ class TranslationApp(QWidget):
page_number = 1
while self.running or completed_count < total_products:
# 4. 현재 페이지의 모든 "세부사항 수정 및 업로드" 버튼 찾기
# 3. 총 상품 수만큼 반복 작업 수행
while self.running and completed_count < total_products:
self.logger.debug(f'현재 페이지: {page_number}')
product_buttons = await self.browser_controller.get_product_edit_buttons()
# 4. 현재 페이지의 모든 "세부사항 수정 및 업로드" 버튼 찾기
product_buttons = await self.browser_controller.get_product_edit_buttons()
if not product_buttons:
self.logger.debug('수정할 상품이 없습니다. 번역 작업을 종료합니다.')
self.logger.debug('수정할 상품이 없습니다. 작업을 종료합니다.')
break
# 5. 각 상품에 대해 번역 작업 수행
for index, button in enumerate(product_buttons, start=1):
if not self.running or completed_count >= total_products:
if not self.running:
self.logger.debug('번역 작업이 중단되었습니다.')
break
return
self.logger.debug(f'{index}/{len(product_buttons)}: 세부사항 수정 작업 중...')
@ -386,7 +398,7 @@ class TranslationApp(QWidget):
# 옵션 수정
self.start_stage(0)
await self.edit_option()
await self.edit_option(product_name)
self.complete_stage(0)
# 상세페이지 수정
@ -402,10 +414,9 @@ class TranslationApp(QWidget):
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
return
# 6. 다음 페이지로 이동 (있으면)
if not await self.browser_controller.go_to_next_page():
@ -421,15 +432,102 @@ 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 # 번역 작업 중단
def close(self):
async def close(self):
self.logger.debug('프로그램을 종료합니다...')
self.save_settings()
self.browser_controller.close_browser() # 브라우저 종료
self.whale_translator.close_all_virtual_desktops()
await self.browser_controller.close_browser() # 브라우저 종료
await self.whale_translator.close_all_virtual_desktops()
super().close()
async def detail_trans(self):
@ -439,7 +537,8 @@ class TranslationApp(QWidget):
self.detail_progress_bar.setVisible(True)
# 이미지 URL 추출
image_urls = self.browser_controller.extract_image_urls()
# image_urls = self.browser_controller.extract_image_urls()
image_urls = await self.browser_controller.extract_image_urls() # 코루틴 실행
total_images = len(image_urls)
self.logger.debug(f"현재 상품의 총 이미지 수 : {total_images}")
@ -453,7 +552,7 @@ class TranslationApp(QWidget):
self.logger.debug('번역 작업이 중단되었습니다.')
break
self.whale_translator.translate_image(url)
await self.whale_translator.translate_image(url)
await self.browser_controller.paste_image_in_chrome(self.clipboardImageManager, url)
self.update_detail_progress(i,total_images)
@ -461,22 +560,22 @@ class TranslationApp(QWidget):
# 수정 후 저장
self.logger.debug('상품 세부사항 저장 중...')
await self.browser_controller.save_product_edit()
# await self.browser_controller.save_product_edit()
self.detail_progress_bar.setVisible(False)
self.detail_progress_bar.setValue(0)
async def edit_option(self):
async def edit_option(self, product_name):
# 상세페이지 탭 클릭
await self.browser_controller.click_option_tab()
self.detail_progress_bar.setVisible(True)
# 옵션 최대선택갯수
max_option_count = 10
self.optionHandler.process_options(max_option_count)
await self.optionHandler.process_options(product_name, max_option_count)
# 수정 후 저장
self.optionHandler.save_option()
await self.optionHandler.save_option()
self.detail_progress_bar.setVisible(False)

View File

@ -58,6 +58,7 @@ async def main():
finally:
# 앱 종료 시 절전모드 방지 해제
allow_sleep()
await window.close() # TranslationApp의 close() 비동기 호출
if __name__ == '__main__':
asyncio.run(main()) # 비동기 함수는 asyncio.run()으로 실행

221
option.py
View File

@ -1,8 +1,14 @@
import os
import tempfile
from datetime import datetime
class OptionHandler:
def __init__(self, page, logger, vertexAI):
def __init__(self, page, whale_translator, logger, vertexAI, debug=False):
self.page = page
self.logger = logger
self.debug = debug
self.vertexAItranslator = vertexAI
self.whale_translator = whale_translator
self.option_info = {
'original_names': {},
'edit_fields': {},
@ -11,10 +17,20 @@ class OptionHandler:
'prices': {} # 가격 정보 추가
}
def update_page(self, page1):
self.page = page1
self.logger.debug(f"page객체 업데이트 : {page1}")
def process_options(self, max_option_count=10):
"""옵션 상품을 처리하는 메서드"""
async def process_options(self, product_name, max_option_count=10):
"""
옵션 처리 로직. 옵션을 번역하고 이미지를 업데이트함.
:param product_name: 상품명 (str 형태).
:param max_option_count: 최대 옵션 갯수 (기본값 10).
"""
try:
self.logger.debug(f"상품명: {product_name}에 대한 옵션을 처리 중...")
# 1. 단일 옵션인지 판단
if self.is_single_option():
self.logger.debug("단일 옵션 상품입니다. 옵션 수정 과정을 생략합니다.")
@ -26,29 +42,34 @@ class OptionHandler:
return
# 3. 가격 낮은 순 정렬 클릭
self.low_order_click()
await self.low_order_click()
# 4. 옵션 정보 수집 및 번역
option_info = self.collect_options_info()
self.option_info = await 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}")
translated_options = await self.vertexAItranslator.translate_options(self.option_info['original_names'], product_name)
self.logger.debug(f"번역된 옵션: {translated_options}")
# 5. 번역된 옵션명 편집칸에 입력
self.logger.debug("번역된 옵션명을 입력합니다.")
self.apply_translated_options(translated_options, self.option_info['edit_fields'])
await self.apply_translated_options(translated_options, self.option_info['edit_fields'])
# 6. 옵션 선택 및 제한 처리
self.adjust_options(self.option_info['checkboxes'], max_option_count)
# 6. 옵션 이미지 업데이트 (옵션 이미지가 있는 경우)
self.logger.debug("옵션 이미지 업데이트 (옵션 이미지가 있는 경우)")
for index, option_image_url in enumerate(self.option_info.get('option_images', []), start=1):
option_name = translated_options.get(f'trans_option_{index}', f'옵션_{index}')
await self.update_option_image(index, option_image_url, product_name, option_name, self.debug)
# 7. 정리된 옵션을 다시한번 더 가격 낮은 순으로 정렬 클릭
self.low_order_click()
# 7. 옵션 선택 및 제한 처리
await self.adjust_options(self.option_info['checkboxes'], max_option_count)
# 8. 저장 버튼 클릭
# 8. 정리된 옵션을 다시한번 더 가격 낮은 순으로 정렬 클릭
await self.low_order_click()
# 9. 저장 버튼 클릭
self.logger.debug("저장 버튼을 클릭합니다.")
self.page.click('button:has-text("저장하기")')
await self.page.click('button:has-text("저장하기")')
self.logger.debug("옵션 처리 완료.")
@ -90,7 +111,7 @@ class OptionHandler:
self.logger.error(f"전체 옵션 체크박스 확인 중 오류 발생: {e}", exc_info=True)
return False
def collect_options_info(self):
async def collect_options_info(self):
"""옵션 정보를 수집 (이미지, 옵션명, 편집 필드, 가격, 체크박스 정보 포함)"""
# option_info = {
# 'original_names': {},
@ -103,9 +124,9 @@ class OptionHandler:
try:
# 총 옵션 갯수 수집
total_options_selector = '#productMainContentContainerId label.ant-checkbox-wrapper'
total_options_element = self.page.query_selector(total_options_selector)
total_options_element = await self.page.query_selector(total_options_selector)
if total_options_element:
total_options_text = total_options_element.inner_text()
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 # 옵션 갯수를 찾지 못할 경우 기본값
@ -117,8 +138,8 @@ class OptionHandler:
try:
# 원본옵션명 수집
original_name_selector = f"div#productMainContentContainerId li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(3) > span"
original_name_element = self.page.query_selector(original_name_selector)
original_name = original_name_element.inner_text() if original_name_element else None
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:
# 옵션명 기준으로 수집 항목 구성
@ -127,7 +148,7 @@ class OptionHandler:
# 옵션 편집 필드 수집
edit_field_selector = f"div#productMainContentContainerId li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(3) > div:nth-child(2) > div:nth-child(1) > span > input"
edit_field_element = self.page.query_selector(edit_field_selector)
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}")
@ -138,7 +159,7 @@ class OptionHandler:
# 옵션 체크박스 수집
checkbox_selector = f'#productMainContentContainerId li:nth-child({i}) input[type="checkbox"]'
# f"div#productMainContentContainerId li:nth-child({i}) > div > div:nth-child(1) > div > div:nth-child(1) > label > span > input"
checkbox_element = self.page.query_selector(checkbox_selector)
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}")
@ -149,9 +170,9 @@ class OptionHandler:
image_selector = f'#productMainContentContainerId li:nth-child({i}) img.sc-gbvfcU.ezktkd'
# "div#productMainContentContainerId li:nth-child(1) > div > div:nth-child(1) > div > div:nth-child(2) > div > img"
# "div#productMainContentContainerId li:nth-child(2) > div > div:nth-child(1) > div > div:nth-child(2) > div > img"
image_element = self.page.query_selector(image_selector)
image_element = await self.page.query_selector(image_selector)
if image_element:
image_url = image_element.get_attribute('src')
image_url = await image_element.get_attribute('src')
self.option_info['images'][original_name] = image_url
self.logger.debug(f"{i}번째 옵션 이미지 수집 완료 : {image_element}")
else:
@ -160,9 +181,11 @@ class OptionHandler:
# 가격 정보 수집
price_selector = f'#productMainContentContainerId li:nth-child({i}) sup'
price_element = self.page.query_selector(price_selector)
price_element = await self.page.query_selector(price_selector)
if price_element:
price_text = price_element.inner_text().replace(",", "").replace("", "").strip()
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:
@ -182,9 +205,10 @@ class OptionHandler:
return self.option_info
def apply_translated_options(self, translated_options, edit_fields):
async def apply_translated_options(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}")
@ -197,7 +221,7 @@ class OptionHandler:
self.logger.debug(f"{key}번째 번역옵션 필드 : {edit_field}")
if edit_field:
edit_field.fill(translated_name) # 필드에 번역된 옵션명 입력
await edit_field.fill(translated_name) # 필드에 번역된 옵션명 입력
self.logger.debug(f"{key}번째 translated_name : [{translated_name}] 입력 완료")
else:
self.logger.debug(f"{key}번째 옵션 필드가 없습니다.")
@ -206,28 +230,28 @@ class OptionHandler:
except Exception as e:
self.logger.error(f"번역된 옵션명을 입력하는 중 오류 발생: {e}", exc_info=True)
def adjust_options(self, checkboxes, max_option_count):
async def adjust_options(self, checkboxes, max_option_count):
"""옵션 체크 상태 조정"""
try:
if len(checkboxes) > 3:
self.logger.debug("옵션이 3개 이상이므로 가장 낮은 옵션을 체크 해제합니다.")
checkboxes[0].click()
await 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()
await checkboxes[i].click()
except Exception as e:
self.logger.error(f"옵션 체크 조정 중 오류 발생: {e}", exc_info=True)
def check_options(self, option_info):
async 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)
total_checkbox_element = await self.page.query_selector(total_checkbox_selector)
if total_checkbox_element:
total_checkbox_element.click()
await total_checkbox_element.click()
self.logger.debug("모든 옵션 체크 해제 완료")
# 옵션 갯수에 따라 선택 로직 진행
@ -243,21 +267,138 @@ class OptionHandler:
# 선택된 옵션들 체크
for checkbox in options_to_check:
checkbox.click()
await checkbox.click()
self.logger.debug(f"옵션 체크 완료: {checkbox}")
except Exception as e:
self.logger.error(f"옵션 체크 중 오류 발생: {e}", exc_info=True)
def low_order_click(self):
async def low_order_click(self):
self.logger.debug("가격 낮은 순 정렬을 클릭합니다.")
self.page.click('button:has-text("가격 낮은 순")')
self.page.wait_for_load_state('domcontentloaded')
await self.page.click('button:has-text("가격 낮은 순")')
await self.page.wait_for_load_state('domcontentloaded')
def save_option(self):
async def save_option(self):
"""옵션 수정 후 저장 버튼 클릭"""
try:
self.page.click('button:has-text("저장하기")')
await self.page.click('button:has-text("저장하기")')
self.logger.debug("옵션 수정 내용 저장 완료.")
except Exception as e:
self.logger.debug(f"옵션수정 후 저장 버튼 클릭 중 오류: {e}", exc_info=True)
# async def update_option_image(self, option_index, option_image_url, product_name, option_name, debug=False):
# """옵션 이미지가 존재할 경우 삭제 후, 새로운 번역된 이미지를 추가하는 메서드"""
# try:
# image_css = 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({option_index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > img"
# delete_button_css = 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({option_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"
# add_button_css = 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({option_index}) > div > div:nth-child(1) > div > div:nth-child(2) > div > div > img"
# # 옵션 이미지가 있는지 확인하고, 있으면 삭제
# option_image_element = await self.page.locator(image_css).element_handle()
# if option_image_element:
# self.logger.debug(f"{option_index}번째 옵션 이미지가 존재합니다. 삭제 버튼을 클릭합니다.")
# delete_button_element = await self.page.locator(delete_button_css).element_handle()
# if delete_button_element:
# await delete_button_element.click()
# self.logger.debug(f"{option_index}번째 옵션 이미지 삭제 버튼 클릭 완료.")
# # 삭제 확인 버튼 클릭
# confirm_button_css = '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_button = await self.page.locator(confirm_button_css).element_handle()
# if confirm_button:
# await confirm_button.click()
# self.logger.debug(f"{option_index}번째 옵션 이미지 삭제 확인 완료.")
# else:
# self.logger.debug(f"{option_index}번째 옵션 이미지는 존재하지 않습니다. 바로 추가 작업을 진행합니다.")
# # 이미지 추가 버튼 클릭
# add_button_element = await self.page.locator(add_button_css).element_handle()
# if add_button_element:
# await add_button_element.click()
# self.logger.debug(f"{option_index}번째 옵션 이미지 추가 버튼 클릭 완료.")
# # 파일 저장 경로 생성
# current_time = datetime.now().strftime('%Y%m%d_%H%M%S')
# tmp_image_dir = os.path.join(os.getcwd(), 'tmp_image')
# os.makedirs(tmp_image_dir, exist_ok=True) # 디렉토리 생성
# temp_image_path = os.path.join(tmp_image_dir, f'{product_name}_{option_name}_{current_time}.png')
# # 옵션 이미지 번역 후 파일 추가
# translated_image_path = await self.whale_translator.translate_image(option_image_url, path=temp_image_path)
# self.logger.debug(f"{option_index}번째 옵션 번역된 이미지 경로: {translated_image_path}")
# # 파일 선택 다이얼로그에 번역된 이미지 경로 입력
# await self.page.set_input_files('input[type="file"]', translated_image_path)
# self.logger.debug(f"{option_index}번째 옵션에 번역된 이미지 파일이 추가되었습니다.")
# # 번역된 이미지가 성공적으로 업로드되었으면 임시 파일 삭제
# if not debug:
# if os.path.exists(translated_image_path):
# os.remove(translated_image_path)
# self.logger.debug(f"임시 파일 {translated_image_path}이(가) 삭제되었습니다.")
# except Exception as e:
# self.logger.error(f"{option_index}번째 옵션 이미지 처리 중 오류 발생: {e}", exc_info=True)
async def update_option_image(self, index, option_image_url, product_name, option_name, debug=False):
"""
옵션 이미지가 존재할 경우, 기존 이미지를 삭제하고 번역된 이미지를 추가하는 메서드.
:param index: 옵션 인덱스.
:param option_image_url: 옵션 이미지 URL.
:param product_name: 상품명.
:param option_name: 옵션명.
:param debug: 디버그 모드일 경우 이미지를 삭제하지 않음 (기본값 False).
"""
try:
# 이미지가 없을 경우 메서드 종료
if not option_image_url:
self.logger.debug(f"{index}번째 옵션의 이미지가 존재하지 않아 작업을 종료합니다.")
return
self.logger.debug(f"{index}번째 옵션의 이미지를 업데이트합니다.")
# 기존 이미지 삭제 (삭제 버튼이 존재할 경우)
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()
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()
if confirm_delete_button:
await confirm_delete_button.click()
self.logger.debug(f"{index}번째 옵션의 이미지가 삭제되었습니다.")
else:
self.logger.debug(f"삭제 확인 버튼을 찾을 수 없습니다.")
# 이미지 번역 후 추가
translated_image_path = await self.whale_translator.translate_image(option_image_url, f"tmp_image/{product_name}-{option_name}-{index}.png")
# 이미지 업로드 버튼 클릭 (옵션 이미지가 없는 경우)
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()
if add_button:
await add_button.click()
# 파일 선택 다이얼로그에서 번역된 이미지 파일 입력
file_input = await self.page.wait_for_selector('input[type="file"]')
await file_input.set_input_files(translated_image_path)
self.logger.debug(f"{index}번째 옵션에 번역된 이미지가 추가되었습니다.")
# 디버그 모드가 아닐 경우, 성공적으로 업로드 후 임시 파일 삭제
if not debug:
import os
if os.path.exists(translated_image_path):
os.remove(translated_image_path)
self.logger.debug(f"번역된 이미지 파일이 삭제되었습니다: {translated_image_path}")
except Exception as e:
self.logger.error(f"{index}번째 옵션 이미지 업데이트 중 오류 발생: {e}", exc_info=True)

View File

@ -1,5 +1,6 @@
{
"option_prompt_template": "질문은 아래와 같아.\n\n가공방법\n1. 특수문자가 있을 경우 제거해줘.\n2. 원본 상품명을 참고해서, 각 옵션의 이름을 최대한 간결하게, 각 옵션의 특징(제품의 스펙을 나타내는 크기,무게,용량,전압,전류,상품코드등)만 남겨줘.\n3. 간결하게 만들어진 각 옵션명을 한국어로 일관되게 번역해줘.\n4. 번역된 옵션 이름 중 같은 이름의 옵션이 있을 경우, 해당옵션들만 원본옵션명에서 특징들을 다시 추출해서 추가해줘.\n5. 가격문의, 문의사항, 견적, 견적문의, 예약금, 선결제, 고객센터 연락 등 옵션명을 바로 알려주지 않고 고객에게 연락을 유도하는 옵션명은 삭제해야 해.\n6. 번역된 옵션명들은 'trans_option_1': '', 'trans_option_2': '', 'trans_option_3': '', 'trans_option_4': '' 와 같은 형식(json)으로 반환해줘.\n\n원본 데이터\n{options}",
"option_prompt_template": "질문은 아래와 같아.\n\n가공방법\n1. 특수문자가 있을 경우 제거해줘.\n2. 원본 상품명 '{product_name}'을 참고해서, 각 옵션의 이름을 최대한 간결하게, 각 옵션의 특징(제품의 스펙을 나타내는 크기,무게,용량,전압,전류,상품코드등)만 남겨줘.\n3. 간결하게 만들어진 각 옵션명을 한국어로 일관되게 번역해줘.\n4. 번역된 옵션 이름 중 같은 이름의 옵션이 있을 경우, 해당옵션들만 원본옵션명에서 특징들을 다시 추출해서 추가해줘.\n5. 가격문의, 문의사항, 견적, 견적문의, 예약금, 선결제, 고객센터 연락 등 옵션명을 바로 알려주지 않고 고객에게 연락을 유도하는 옵션명은 삭제해야 해.\n6. 짧은 단어로 대체할수 있는 단어들은 (예시 : 디스플레시 > 화면)의미를 유지한체 짧은 단어로 대체해줘.\n7. 번역된 옵션명들은 'trans_option_1': '', 'trans_option_2': '', 'trans_option_3': '', 'trans_option_4': '' 와 같은 형식(json)으로 반환해줘.\n\n원본 데이터\n 원본옵션명{options}",
"option_prompt_template_old": "질문은 아래와 같아.\n\n가공방법\n1. 특수문자가 있을 경우 제거해줘.\n2. 원본 상품명을 참고해서, 각 옵션의 이름을 최대한 간결하게, 각 옵션의 특징(제품의 스펙을 나타내는 크기,무게,용량,전압,전류,상품코드등)만 남겨줘.\n3. 간결하게 만들어진 각 옵션명을 한국어로 일관되게 번역해줘.\n4. 번역된 옵션 이름 중 같은 이름의 옵션이 있을 경우, 해당옵션들만 원본옵션명에서 특징들을 다시 추출해서 추가해줘.\n5. 가격문의, 문의사항, 견적, 견적문의, 예약금, 선결제, 고객센터 연락 등 옵션명을 바로 알려주지 않고 고객에게 연락을 유도하는 옵션명은 삭제해야 해.\n6. 짧은 단어로 대체할수 있는 단어들은 (예시 : 디스플레시 > 화면)의미를 유지한체 짧은 단어로 대체해줘.\n7. 번역된 옵션명들은 'trans_option_1': '', 'trans_option_2': '', 'trans_option_3': '', 'trans_option_4': '' 와 같은 형식(json)으로 반환해줘.\n\n원본 데이터\n원본상품명'{product_name}'\n 원본옵션명{options}",
"detail_page_prompt_template": "상세 페이지 번역 요청: {detail_page}",
"title_prompt_template": "제목 번역 요청: {title}",
"price_prompt_template": "가격 가공 요청: {price}",

View File

@ -13,7 +13,7 @@ class VertexAITranslator:
:param key_path: Google Application Credentials의 파일 경로.
"""
self.logger = logger
# GOOGLE_APPLICATION_CREDENTIALS 환경 변수 설정
self.logger.debug(f"GOOGLE_APPLICATION_CREDENTIALS 환경 변수를 설정: {key_path}")
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = key_path
@ -26,7 +26,10 @@ class VertexAITranslator:
self.logger.debug("prompt.json 파일을 불러옵니다.")
self.prompt_data = self.load_prompt()
async def load_prompt(self):
self.allowed_chars = self.prompt_data['allowed_special_chars']
self.replacements = self.prompt_data['special_char_replacements']
def load_prompt(self):
"""
prompt.json 파일을 읽어와 파싱하는 메서드.
@ -35,7 +38,7 @@ class VertexAITranslator:
try:
prompt_path = os.path.join(os.path.dirname(__file__), 'prompt.json')
self.logger.debug(f"프롬프트 파일 경로: {prompt_path}")
async with open(prompt_path, 'r', encoding='utf-8') as file:
with open(prompt_path, 'r', encoding='utf-8') as file:
prompt_data = json.load(file)
self.logger.debug("prompt.json 파일이 성공적으로 로드되었습니다.")
return prompt_data
@ -46,7 +49,7 @@ class VertexAITranslator:
self.logger.error(f"prompt.json 파일 파싱 중 오류 발생: {e}", exc_info=True)
raise e
async def clean_special_chars(self, text):
def clean_special_chars(self, text):
"""
텍스트에서 허용되지 않는 특수 문자를 제거하고,
필요한 특수 문자를 대체하는 메서드.
@ -56,15 +59,13 @@ class VertexAITranslator:
"""
self.logger.debug(f"텍스트에서 특수 문자를 정리 중: {text}")
allowed_chars = self.prompt_data['allowed_special_chars']
replacements = self.prompt_data['special_char_replacements']
cleaned_text = []
for char in text:
if char in replacements:
cleaned_text.append(replacements[char]) # 대체 문자 추가
self.logger.debug(f"문자 '{char}'를 대체 문자로 변경: {replacements[char]}")
elif char not in allowed_chars and not char.isalnum() and not char.isspace():
if char in self.replacements:
cleaned_text.append(self.replacements[char]) # 대체 문자 추가
self.logger.debug(f"문자 '{char}'를 대체 문자로 변경: {self.replacements[char]}")
elif char not in self.allowed_chars and not char.isalnum() and not char.isspace():
self.logger.debug(f"허용되지 않은 문자 제거: {char}")
continue # 특수 문자 제거
else:
@ -75,27 +76,71 @@ class VertexAITranslator:
self.logger.debug(f"정리된 텍스트: {cleaned_text_str}")
return cleaned_text_str
async def translate_options(self, original_data):
# async def translate_options(self, original_data):
# """
# 주어진 옵션 데이터를 Vertex AI 모델을 통해 번역하는 메서드.
# :param original_data: 원본 옵션 데이터 (dict 형태).
# :return: 번역된 옵션명 (파이썬의 dict 형태).
# """
# self.logger.debug(f"옵션 데이터를 번역 중: {original_data}")
# # 데이터 정리
# cleaned_data = {key: self.clean_special_chars(value) for key, value in original_data.items()}
# self.logger.debug(f"정리된 옵션 데이터: {cleaned_data}")
# # 원본 데이터를 프롬프트 템플릿에 넣는다.
# option_prompt_template = self.prompt_data['option_prompt_template']
# prompt = option_prompt_template.format(options=json.dumps(cleaned_data, ensure_ascii=False))
# self.logger.debug(f"생성된 프롬프트: {prompt}")
# # Vertex AI 모델에 프롬프트 전달하여 응답 받기
# self.logger.debug("Vertex AI 모델에 프롬프트를 전달하여 응답을 기다리는 중...")
# response = self.model.generate_content(prompt)
# self.logger.debug(f"모델 응답: {response.text}")
# # 응답 데이터에서 JSON 형식 추출
# start = response.text.find('{')
# end = response.text.rfind('}') + 1
# if start != -1 and end != -1:
# json_text = response.text[start:end]
# self.logger.debug(f"응답에서 추출된 JSON 텍스트: {json_text}")
# try:
# translated_data = json.loads(json_text)
# self.logger.debug(f"번역된 데이터: {translated_data}")
# except json.JSONDecodeError as e:
# self.logger.error(f"응답 데이터를 파싱하는 중 오류 발생: {e}", exc_info=True)
# raise ValueError(f"응답 데이터를 파이썬의 딕셔너리로 파싱하는 중 오류 발생: {e}", exc_info=True)
# else:
# self.logger.error("응답 데이터에서 유효한 JSON 형식을 찾을 수 없습니다.")
# raise ValueError("응답 데이터에서 유효한 JSON 형식을 찾을 수 없습니다.")
# return translated_data
async def translate_options(self, original_data, product_name):
"""
주어진 옵션 데이터를 Vertex AI 모델을 통해 번역하는 메서드.
:param original_data: 원본 옵션 데이터 (dict 형태).
:param product_name: 상품명 (str 형태).
:return: 번역된 옵션명 (파이썬의 dict 형태).
"""
self.logger.debug(f"옵션 데이터를 번역 중: {original_data}")
# 데이터 정리
cleaned_data = {key: await self.clean_special_chars(value) for key, value in original_data.items()}
cleaned_data = {key: self.clean_special_chars(value) for key, value in original_data.items()}
self.logger.debug(f"정리된 옵션 데이터: {cleaned_data}")
# 원본 데이터를 프롬프트 템플릿에 넣는다.
option_prompt_template = self.prompt_data['option_prompt_template']
prompt = option_prompt_template.format(options=json.dumps(cleaned_data, ensure_ascii=False))
# 상품명과 옵션 데이터를 함께 전달
prompt = option_prompt_template.format(product_name=product_name, options=json.dumps(cleaned_data, ensure_ascii=False))
self.logger.debug(f"생성된 프롬프트: {prompt}")
# Vertex AI 모델에 프롬프트 전달하여 응답 받기
self.logger.debug("Vertex AI 모델에 프롬프트를 전달하여 응답을 기다리는 중...")
response = await self.model.generate_content(prompt)
response = self.model.generate_content(prompt) # 비동기로 변경
self.logger.debug(f"모델 응답: {response.text}")
# 응답 데이터에서 JSON 형식 추출
@ -109,7 +154,7 @@ class VertexAITranslator:
self.logger.debug(f"번역된 데이터: {translated_data}")
except json.JSONDecodeError as e:
self.logger.error(f"응답 데이터를 파싱하는 중 오류 발생: {e}", exc_info=True)
raise ValueError(f"응답 데이터를 파이썬의 딕셔너리로 파싱하는 중 오류 발생: {e}", exc_info=True)
raise ValueError(f"응답 데이터를 파이썬의 딕셔너리로 파싱하는 중 오류 발생: {e}")
else:
self.logger.error("응답 데이터에서 유효한 JSON 형식을 찾을 수 없습니다.")
raise ValueError("응답 데이터에서 유효한 JSON 형식을 찾을 수 없습니다.")

View File

@ -44,6 +44,12 @@ class WhaleTranslator:
if self.vd_mode:
self.return_to_virtual_desktop_1() # 가상 데스크탑 1로 복귀
def find_whale_window(self):
"""프로세스 ID를 기반으로 웨일 창 핸들을 찾는 메서드"""
if not self.whale_hwnd:
self.whale_hwnd = self.find_window_by_pid(self.whale_pid)
return self.whale_hwnd
def find_window_by_pid(self, pid):
"""프로세스 ID를 기반으로 창 핸들을 찾는 메서드"""
@ -55,12 +61,6 @@ class WhaleTranslator:
hwnd_list = []
win32gui.EnumWindows(enum_windows_callback, hwnd_list)
return hwnd_list[0] if hwnd_list else None
def find_whale_window(self):
"""프로세스 ID를 기반으로 웨일 창 핸들을 찾는 메서드"""
if not self.whale_hwnd:
self.whale_hwnd = self.find_window_by_pid(self.whale_pid)
return self.whale_hwnd
# def find_whale_window(self):
# """웨일 창 핸들을 찾는 메서드"""
@ -124,9 +124,14 @@ class WhaleTranslator:
except Exception as e:
self.logger.debug(f"가상 데스크톱 전환 중 오류 발생: {e}", exc_info=True)
async def translate_image(self, url):
async def translate_image(self, url, path=None):
if self.vd_mode:
await self.switch_to_virtual_desktop_2()
self.switch_to_virtual_desktop_2()
if not self.find_whale_window(): # 웨일 창을 찾지 못할 경우
self.logger.debug("웨일 창을 찾을 수 없습니다. 새 웨일 창을 생성합니다.")
await self.create_and_update_whale_window() # 새 창 생성
if self.find_whale_window():
win32gui.ShowWindow(self.whale_hwnd, win32con.SW_RESTORE) # 웨일 창 활성화
@ -138,11 +143,6 @@ class WhaleTranslator:
self.enter_url(url)
await asyncio.sleep(1) # 페이지 로딩 대기
# pyautogui.rightClick()
# await asyncio.sleep(0.2) # 페이지 로딩 대기
# pyautogui.press('c') # 원본 이미지 클립보드에 복사
# await asyncio.sleep(1) # 페이지 로딩 대기
pyautogui.rightClick()
await asyncio.sleep(0.2) # 페이지 로딩 대기
pyautogui.press('r') # 번역 클릭
@ -157,10 +157,39 @@ class WhaleTranslator:
self.logger.debug(f'번역 완료: {url}')
if self.vd_mode:
await self.return_to_virtual_desktop_1()
self.return_to_virtual_desktop_1()
# 경로를 인자로 받을경우 해당경로에 파일 저장
if path:
pass # 클립보드의 이미지를 path의 파일로 저장하고 저장경로를 리턴하는 메서드
# path에는 현재 폴더의 tmp_img폴더에 상품명-옵션명 형태로 제공됨
else:
self.logger.debug('웨일 창을 찾을 수 없습니다.')
async def create_and_update_whale_window(self):
"""
웨일 창을 찾지 못했을 경우 웨일 창을 생성하고 핸들을 업데이트하는 메서드.
"""
self.logger.debug("웨일 창을 찾지 못해 새 웨일 창을 생성합니다.")
# 웨일 브라우저 실행
whale_path = r"C:\\Program Files\\Naver\\Naver Whale\\Application\\whale.exe"
process = subprocess.Popen([whale_path, '--incognito'])
self.whale_pid = process.pid
self.logger.debug(f"새 웨일 브라우저 실행, PID: {self.whale_pid}")
await asyncio.sleep(2) # 브라우저가 실행될 때까지 대기
# 창 핸들 업데이트
self.whale_hwnd = self.find_whale_window()
if self.whale_hwnd:
self.logger.debug(f"새로 생성된 웨일 창 핸들: {self.whale_hwnd}")
win32gui.ShowWindow(self.whale_hwnd, win32con.SW_NORMAL)
win32gui.SetWindowPos(self.whale_hwnd, None, 0, 0, 1920, 1080, win32con.SWP_NOZORDER)
else:
self.logger.debug("새로 생성된 웨일 창을 찾지 못했습니다.")
def switch_language(self):
# Shift 키 누름
pyautogui.keyDown('shift')
@ -232,7 +261,7 @@ class WhaleTranslator:
except Exception as e:
self.logger.error(f"웨일 창을 종료하는 중 오류 발생: {e}", exc_info=True)
def close_all_virtual_desktops(self):
async def close_all_virtual_desktops(self):
"""모든 가상 데스크톱을 종료"""
try:
desktops = get_virtual_desktops()