663 lines
36 KiB
Python
663 lines
36 KiB
Python
from playwright.async_api import async_playwright
|
|
import os, random, traceback, asyncio
|
|
from ui.two_factor_auth_dialog import TwoFactorAuthDialog
|
|
from playwright._impl._errors import TargetClosedError
|
|
|
|
class PlaywrightHelper:
|
|
def __init__(self):
|
|
self.browser = None
|
|
self.context = None
|
|
self.page = None
|
|
self.dialog = TwoFactorAuthDialog()
|
|
self.username = None
|
|
self.password = None
|
|
self.progress_bar = None
|
|
self.status_label = None
|
|
|
|
|
|
self.market_names = [
|
|
'쿠팡', '스마트스토어', 'esm', '11번가-국내', '11번가-글로벌', '롯데온', '인터파크', '위메프', '옥션1.0'
|
|
]
|
|
|
|
self.market_xpaths = {
|
|
'쿠팡': "div#rc-tabs-0-tab-cp",
|
|
'스마트스토어': "div#rc-tabs-0-tab-ss",
|
|
'esm': "div#rc-tabs-0-tab-esm",
|
|
'11번가-국내': "div#rc-tabs-0-tab-est",
|
|
'11번가-글로벌': "div#rc-tabs-0-tab-est_global",
|
|
'롯데온': "div#rc-tabs-0-tab-lotteon",
|
|
'인터파크': "div#rc-tabs-0-tab-ip",
|
|
'위메프': "div#rc-tabs-0-tab-wmp",
|
|
'옥션1.0': "div#rc-tabs-0-tab-at"
|
|
}
|
|
|
|
self.progress_steps = {
|
|
'쿠팡': 35,
|
|
'스마트스토어': 40,
|
|
'esm': 45,
|
|
'11번가-국내': 50,
|
|
'11번가-글로벌': 55,
|
|
'롯데온': 60,
|
|
'인터파크': 65,
|
|
'위메프': 70,
|
|
'옥션1.0': 75
|
|
}
|
|
|
|
|
|
# 마켓 이름과 XPath 매핑
|
|
self.market_xpath_mapping = {
|
|
'쿠팡': 'cp',
|
|
'스마트스토어': 'ss',
|
|
'esm': 'esm',
|
|
'11번가-국내': 'est',
|
|
'11번가-글로벌': 'est_global',
|
|
'롯데온': 'lotteon',
|
|
'인터파크': 'ip',
|
|
'위메프': 'wmp',
|
|
'옥션1.0': 'at'
|
|
}
|
|
|
|
self.api_key_validation_button = {
|
|
'쿠팡': "div#rc-tabs-0-panel-cp div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary",
|
|
'스마트스토어': "div#rc-tabs-0-panel-ss div.ant-row.css-1li46mu > button[type=\"button\"]:nth-child(3)",
|
|
'esm': "div#rc-tabs-0-panel-esm div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary",
|
|
'11번가-국내': "div#rc-tabs-0-panel-est div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary",
|
|
'11번가-글로벌': "div#rc-tabs-0-panel-est_global div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary",
|
|
'롯데온': "div#rc-tabs-0-panel-lotteon div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary",
|
|
'인터파크': "div#rc-tabs-0-panel-ip div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary",
|
|
'위메프': "div#rc-tabs-0-panel-wmp div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary",
|
|
'옥션1.0': "div#rc-tabs-0-panel-at div.ant-row.css-1li46mu > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary",
|
|
}
|
|
|
|
self.api_key_status = {
|
|
'쿠팡': "div#rc-tabs-0-panel-cp span:nth-child(2) > sup",
|
|
'스마트스토어': "div#rc-tabs-0-panel-ss span:nth-child(2) > sup",
|
|
'esm': "div#rc-tabs-0-panel-esm span:nth-child(2) > sup",
|
|
'11번가-국내': "div#rc-tabs-0-panel-est span:nth-child(2) > sup",
|
|
'11번가-글로벌': "div#rc-tabs-0-panel-est_global span:nth-child(2) > sup",
|
|
'롯데온': "div#rc-tabs-0-panel-lotteon span:nth-child(2) > sup",
|
|
'인터파크': "div#rc-tabs-0-panel-ip span:nth-child(2) > sup",
|
|
'위메프': "div#rc-tabs-0-panel-wmp span:nth-child(2) > sup",
|
|
'옥션1.0': "div#rc-tabs-0-panel-at sup",
|
|
}
|
|
|
|
self.order_collection_button = {
|
|
'쿠팡': "div#rc-tabs-0-panel-cp div:nth-child(5) > button[type=\"button\"]",
|
|
'스마트스토어': "#rc-tabs-0-panel-ss .css-1li46mu[role='switch'][type='button']",
|
|
'esm': "div#rc-tabs-0-panel-esm div:nth-child(3) > button[type=\"button\"]",
|
|
'11번가-국내': "div#rc-tabs-0-panel-est div > div > div:nth-child(2) > button[type=\"button\"]",
|
|
'11번가-글로벌': "div#rc-tabs-0-panel-est_global div > div > div:nth-child(2) > button[type=\"button\"]",
|
|
'롯데온': "div#rc-tabs-0-panel-lotteon div > div > div:nth-child(2) > button[type=\"button\"]",
|
|
'인터파크': "div#rc-tabs-0-panel-ip div:nth-child(25) > button[type=\"button\"]",
|
|
'위메프': "div#rc-tabs-0-panel-wmp div:nth-child(2) > button[type=\"button\"]",
|
|
'옥션1.0': "div#rc-tabs-0-panel-at div:nth-child(1) > button[type=\"button\"]",
|
|
}
|
|
|
|
self.smartstore_elements = {
|
|
'account_button': "#rc-tabs-0-panel-ss .css-1li46mu .ant-btn-background-ghost[type='button']",
|
|
'popup_login_type_button': ".Login_type_list__KPqjy .Login_type_item__2_QV8 .Login_title__3ixGa",
|
|
'popup_login_id_input': ".Login_login_area__cMnCU input[placeholder='아이디 또는 이메일 주소']",
|
|
'popup_login_pw_input': ".Login_login_item__2fOX0 [placeholder='비밀번호']",
|
|
'popup_login_button': ".Login_btn_box__22wei [type='button']",
|
|
'login_success_check': "[ng-if='vm.loginInfo'] .login-id.text-overflow",
|
|
'2fa_check': '#twoFactorAuth', # 2단계 인증 확인용 셀렉터
|
|
'2fa_complete_check': '#twoFactorAuthComplete' # 2단계 인증 완료 확인용 셀렉터
|
|
}
|
|
|
|
self.toggle_states = {
|
|
'쿠팡': 'on',
|
|
'스마트스토어': 'on',
|
|
'esm': 'on',
|
|
'11번가-국내': 'on',
|
|
'11번가-글로벌': 'on',
|
|
'롯데온': 'on',
|
|
'인터파크': 'on',
|
|
'위메프': 'off',
|
|
'옥션1.0': 'off',
|
|
}
|
|
|
|
self.market_shipping_profile_selectors = {
|
|
'쿠팡': ('cp', 'sc-enhgma.iWXfFP'),
|
|
'스마트스토어': ('ss', 'sc-ialZcF.AOoIo'),
|
|
'esm': ('esm', 'sc-DIosr.gWSxuU'),
|
|
'11번가-국내': ('est', 'sc-jwiwYR.juQnsn'),
|
|
'11번가-글로벌': ('est_global', 'sc-eLoUSf.dcgEMZ'),
|
|
'롯데온': ('lotteon', 'sc-ldRCOE.eQdovA'),
|
|
'인터파크': ('ip', 'sc-kYKKLo.bKCTmC'),
|
|
'위메프': ('wmp', 'sc-jzrJLD.jMimQL'),
|
|
'옥션1.0': ('at', 'sc-hLiTId.fFaLDf')
|
|
}
|
|
|
|
self.api_key_xpaths = {
|
|
'쿠팡': {
|
|
'쿠팡id': "div#rc-tabs-0-panel-cp div:nth-child(1) > input",
|
|
'업체코드': "div#rc-tabs-0-panel-cp div:nth-child(2) > input",
|
|
'accesskey': "div#rc-tabs-0-panel-cp div:nth-child(3) > input",
|
|
'secretkey': "div#rc-tabs-0-panel-cp div:nth-child(4) > input",
|
|
},
|
|
'스마트스토어': {
|
|
'업로드할스마트스토어계정명' : "div#rc-tabs-0-panel-ss div:nth-child(1) > input",
|
|
'업로드할스마트스토어계정id' : "div#rc-tabs-0-panel-ss div:nth-child(1) > input",
|
|
'업로드할스마트스토어계정pw' : "div#rc-tabs-0-panel-ss div:nth-child(1) > input",
|
|
'애플리케이션id': "div#rc-tabs-0-panel-ss div:nth-child(2) > input",
|
|
'애플리케이션시크릿': "div#rc-tabs-0-panel-ss div:nth-child(3) > input",
|
|
},
|
|
'esm': {
|
|
'옥션id': "div#rc-tabs-0-panel-esm div:nth-child(1) > input",
|
|
'지마켓id': "div#rc-tabs-0-panel-esm div:nth-child(2) > input",
|
|
},
|
|
'11번가-국내': {
|
|
'apikey': "div#rc-tabs-0-panel-est input",
|
|
},
|
|
'11번가-글로벌': {
|
|
'apikey': "div#rc-tabs-0-panel-est_global input",
|
|
},
|
|
'롯데온': {
|
|
'apikey': "div#rc-tabs-0-panel-lotteon input",
|
|
},
|
|
'인터파크': {
|
|
'상품상태재고수정인증키': "div#rc-tabs-0-panel-ip div:nth-child(1) > input",
|
|
'상품상태재고수정비밀키':"div#rc-tabs-0-panel-ip div:nth-child(2) > input",
|
|
'상품재고조회인증키':"div#rc-tabs-0-panel-ip div:nth-child(3) > input",
|
|
'상품재고조회비밀키':"div#rc-tabs-0-panel-ip div:nth-child(4) > input",
|
|
'상품정보조회인증':"div#rc-tabs-0-panel-ip div:nth-child(6) > input",
|
|
'상품정보조회비밀키':"div#rc-tabs-0-panel-ip div:nth-child(7) > input",
|
|
'상품수정인증키':"div#rc-tabs-0-panel-ip div:nth-child(8) > input",
|
|
'상품수정비밀키':"div#rc-tabs-0-panel-ip div:nth-child(9) > input",
|
|
'상품등록인증키':"div#rc-tabs-0-panel-ip div:nth-child(11) > input",
|
|
'상품등록비밀키':"div#rc-tabs-0-panel-ip div:nth-child(12) > input",
|
|
'반품배송지조회인증키':"div#rc-tabs-0-panel-ip div:nth-child(13) > input",
|
|
'반품배송지조회비밀키':"div#rc-tabs-0-panel-ip div:nth-child(14) > input",
|
|
'반품배송지등록인증키':"div#rc-tabs-0-panel-ip div:nth-child(16) > input",
|
|
'반품배송지등록비밀키':"div#rc-tabs-0-panel-ip div:nth-child(17) > input",
|
|
'상품qna등록인증키':"div#rc-tabs-0-panel-ip div:nth-child(18) > input",
|
|
'상품qna등록비밀키':"div#rc-tabs-0-panel-ip div:nth-child(19) > input",
|
|
'상품qna조회인증키':"div#rc-tabs-0-panel-ip div:nth-child(21) > input",
|
|
'상품qna조회비밀키':"div#rc-tabs-0-panel-ip div:nth-child(22) > input",
|
|
'인터파크업체번호':"div#rc-tabs-0-panel-ip div:nth-child(23) > input",
|
|
'공급계약일련번호':"div#rc-tabs-0-panel-ip div:nth-child(24) > input",
|
|
},
|
|
'위메프': {
|
|
'apikey': "div#rc-tabs-0-panel-wmp input",
|
|
},
|
|
'옥션1.0': {
|
|
'apikey': "div#rc-tabs-0-panel-at div:nth-child(1) > input",
|
|
'멤버id': "div#rc-tabs-0-panel-at div:nth-child(2) > input",
|
|
},
|
|
}
|
|
|
|
async def init_browser(self, isHeadless_mode = True):
|
|
try:
|
|
|
|
"""브라우저 설정 및 인스턴스 생성"""
|
|
playwright = await async_playwright().start()
|
|
# self.browser = await playwright.chromium.launch(headless=False)
|
|
|
|
# 드라이버의 경로 설정
|
|
driver_path = os.path.join(os.path.dirname(__file__), 'drivers', 'chromium-1112', 'chrome-win','chrome.exe')
|
|
|
|
# 확장 프로그램 경로
|
|
extension_path = os.path.join(os.path.dirname(__file__), 'drivers', 'extensions', '1.1.100_0')
|
|
|
|
# 사용자 데이터 디렉토리 설정
|
|
user_data_dir = os.path.join(os.path.dirname(__file__), 'drivers', 'user_data')
|
|
|
|
# 사용자 데이터 디렉토리가 존재하지 않으면 생성
|
|
if not os.path.exists(user_data_dir):
|
|
os.makedirs(user_data_dir)
|
|
print(f"{user_data_dir} 디렉토리가 생성되었습니다.")
|
|
|
|
# User agent 설정
|
|
user_agent = random.choice([
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.0.0",
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0",
|
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15",
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 OPR/85.0.0.0",
|
|
])
|
|
|
|
self.browser = await playwright.chromium.launch_persistent_context(user_data_dir,
|
|
headless=isHeadless_mode,
|
|
permissions=["geolocation", "notifications"], # 필요한 권한을 모두 허용
|
|
geolocation={"latitude": 37.5665, "longitude": 126.9780}, # 위치 정보 설정 (서울)
|
|
locale="ko-KR", # 로케일 설정
|
|
args=[
|
|
'--disable-popup-blocking', # 팝업 차단 비활성화
|
|
f'--disable-extensions-except={extension_path}',
|
|
f'--load-extension={extension_path}',
|
|
],
|
|
executable_path=driver_path,
|
|
user_agent=user_agent # user agent 설정 추가
|
|
) # headless 모드 설정
|
|
|
|
# self.page = await self.browser.new_page() # 첫 번째 페이지를 가져옴
|
|
self.page = self.browser.pages[0]
|
|
|
|
# await self.page.set_default_navigation_timeout(40000) # 기본 타임아웃(40초) 변경
|
|
|
|
except Exception as e:
|
|
print(f"브라우저 초기화 중 에러발생 : {e}")
|
|
traceback.print_exc()
|
|
|
|
async def launch_new_browser(self, isHeadless_mode = False):
|
|
try:
|
|
"""브라우저 설정 및 인스턴스 생성"""
|
|
playwright = await async_playwright().start()
|
|
# self.browser = await playwright.chromium.launch(headless=False)
|
|
|
|
# 드라이버의 경로 설정
|
|
driver_path = os.path.join(os.path.dirname(__file__), 'drivers', 'chromium-1112', 'chrome-win','chrome.exe')
|
|
|
|
# 확장 프로그램 경로
|
|
extension_path = os.path.join(os.path.dirname(__file__), 'drivers', 'extensions', '1.1.100_0')
|
|
|
|
# 사용자 데이터 디렉토리 설정
|
|
user_data_dir = os.path.join(os.path.dirname(__file__), 'drivers', 'user_data')
|
|
|
|
# 사용자 데이터 디렉토리가 존재하지 않으면 생성
|
|
if not os.path.exists(user_data_dir):
|
|
os.makedirs(user_data_dir)
|
|
print(f"{user_data_dir} 디렉토리가 생성되었습니다.")
|
|
|
|
# User agent 설정
|
|
user_agent = random.choice([
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.0.0",
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0",
|
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 12_0) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15",
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 OPR/85.0.0.0",
|
|
])
|
|
|
|
self.context = await playwright.chromium.launch_persistent_context(user_data_dir,
|
|
headless=isHeadless_mode,
|
|
permissions=["geolocation", "notifications"], # 필요한 권한을 모두 허용
|
|
geolocation={"latitude": 37.5665, "longitude": 126.9780}, # 위치 정보 설정 (서울)
|
|
locale="ko-KR", # 로케일 설정
|
|
args=[
|
|
'--disable-popup-blocking', # 팝업 차단 비활성화
|
|
f'--disable-extensions-except={extension_path}',
|
|
f'--load-extension={extension_path}',
|
|
],
|
|
executable_path=driver_path,
|
|
user_agent=user_agent # user agent 설정 추가
|
|
) # headless 모드 설정
|
|
|
|
self.page = await self.context.new_page()
|
|
await self.page.goto('https://www.percenty.co.kr')
|
|
# 브라우저 창 크기 조정
|
|
await self.page.set_viewport_size({"width": 1920, "height": 1080})
|
|
# await self.page.set_default_navigation_timeout(40000) # 기본 타임아웃(40초) 변경
|
|
|
|
except Exception as e:
|
|
print(f"브라우저 초기화 중 에러발생 : {e}")
|
|
traceback.print_exc()
|
|
|
|
async def close_browser(self):
|
|
if self.browser:
|
|
await self.browser.close()
|
|
print("self.browser 종료 실행 완료")
|
|
|
|
async def clear_cache(self):
|
|
# 브라우저 캐시를 삭제하는 메서드
|
|
if self.context:
|
|
await self.context.clear_cookies()
|
|
await self.context.clear_storage()
|
|
|
|
async def set_headless_mode(self, headless):
|
|
print(f"헤드리스 모드 : {headless}")
|
|
await self.browser.close()
|
|
print(f"기존 브라우저 닫기")
|
|
await self.launch_new_browser(headless)
|
|
# print(f"새 브라우저 headless모드 : {headless}로 초기화")
|
|
# await self.login_Process('https://percenty.co.kr', self.username, self.password, self.progress_bar, self.status_label)
|
|
print(f"브라우저 로그인")
|
|
|
|
async def login_and_fetch_api_keys(self, url, username, password, status_label, progress_bar):
|
|
try:
|
|
|
|
print('로그인 동작 실행')
|
|
await self.login_Process(url, username, password, progress_bar, status_label)
|
|
print('로그인 동작 완료')
|
|
|
|
api_keys = {}
|
|
|
|
for market in self.market_names:
|
|
print(f"self.market_xpaths[market] : {self.market_xpaths[market]}")
|
|
print(f"self.api_key_xpaths[market] : {self.api_key_xpaths[market]}")
|
|
api_keys[market] = await self.fetch_market_api_keys(self.market_xpaths[market], self.api_key_xpaths[market])
|
|
print(f"api_keys['{market}'] \n {api_keys[market]}")
|
|
progress_bar.setValue(self.progress_steps[market])
|
|
status_label.setText(f'현재 상태: {market} 가져오기 완료')
|
|
|
|
return api_keys
|
|
except Exception as e:
|
|
print(f"오류발생 : {e}")
|
|
|
|
|
|
# 각 마켓별 API 키 가져오기
|
|
async def fetch_market_api_keys(self, market_xpath, api_key_xpaths):
|
|
await self.page.click(market_xpath)
|
|
# await self.page.wait_for_timeout(1000) # 잠시 대기
|
|
await asyncio.sleep(1) # 잠시 대기
|
|
|
|
|
|
return {key: await self.page.get_attribute(xpath, 'value') for key, xpath in api_key_xpaths.items()}
|
|
|
|
async def login_check(self):
|
|
try:
|
|
print("login_check 실행")
|
|
await self.page.wait_for_load_state() # 페이지의 모든 요소가 로드될때 까지 대기
|
|
print("웹 로딩 완료")
|
|
|
|
# await self.page.wait_for_selector("div#root .ant-typography.css-1li46mu:nth-child(3) > span", timeout=2000) # 2초 내 로그인 여부 판단
|
|
await self.page.wait_for_selector("div#root .ant-space-item .ant-typography.css-1li46mu[style='color: rgba(0, 0, 0, 0.85); font-size: 14px;']", timeout=2000) # 2초 내 로그인 여부 판단
|
|
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
async def login_Process(self, url, username, password, progress_bar, status_label):
|
|
try:
|
|
self.username = username
|
|
self.password = password
|
|
await self.page.goto(url)
|
|
progress_bar.setValue(10)
|
|
status_label.setText('현재 상태: 웹페이지 접속완료')
|
|
|
|
# 현재 로그인 상태 확인
|
|
login_status = await self.login_check()
|
|
print(f"login_status : {login_status}")
|
|
|
|
if not login_status:
|
|
print('로그인 실행')
|
|
# 홈 페이지에서 로그인 버튼 클릭
|
|
|
|
# 로그인 버튼이 보이도록 가로 스크롤
|
|
await self.page.evaluate('window.scrollBy(800, 0)') # 가로 스크롤
|
|
await self.page.wait_for_selector(".signList > .ant-btn-default > span", state='visible')
|
|
print('로그인 페이지 스크롤')
|
|
|
|
await self.page.click(".signList > .ant-btn-default > span")
|
|
# div#root .css-1li46mu .hojyII .signList button.ant-btn.ant-btn-default[type='button'] span
|
|
print('로그인 페이지의 로그인 버튼 클릭')
|
|
|
|
# 로그인 페이지에서 로그인 수행
|
|
await self.page.fill(".ant-input:nth-child(4)", username)
|
|
await self.page.fill(".ant-input:nth-child(1)", password)
|
|
await self.page.click(".ant-btn-primary")
|
|
|
|
# 알림 메시지가 나타나면 닫기
|
|
try:
|
|
await self.page.wait_for_selector("#ch-shadow-root-wrapper > div.ModalLayer__ModalLayerWrapper-ch-front__sc-10u1w92-0.jImPdL > div > article > div > div > div.PopupCloseBtn__CloseButtonArea-ch-front__sc-14jjsiy-2.kSHXDH > button", timeout=2000)
|
|
await self.page.click("#ch-shadow-root-wrapper > div.ModalLayer__ModalLayerWrapper-ch-front__sc-10u1w92-0.jImPdL > div > article > div > div > div.PopupCloseBtn__CloseButtonArea-ch-front__sc-14jjsiy-2.kSHXDH > button")
|
|
print("알림 메시지를 닫았습니다.")
|
|
except Exception as e:
|
|
print("알림 메시지가 나타나지 않았습니다.")
|
|
|
|
|
|
progress_bar.setValue(20)
|
|
status_label.setText('현재 상태: 퍼센티 로그인 완료')
|
|
|
|
# 마켓 설정 페이지로 이동
|
|
await self.page.click("xpath=/html/body/div[1]/div/div/div/div/aside/div/ul/li[7]/ul/li[2]")
|
|
print("마켓 설정 페이지로 이동")
|
|
|
|
# 팝업 다이얼로그 닫기 (있는 경우)
|
|
try:
|
|
print("팝업다이알로그가 존재한다면 클릭")
|
|
await self.page.click("div.ant-modal-footer > button[type=\"button\"].ant-btn.css-1li46mu.ant-btn-primary", timeout=1000)
|
|
|
|
except:
|
|
pass
|
|
|
|
progress_bar.setValue(30)
|
|
status_label.setText('현재 상태: 마켓 설정')
|
|
except Exception as e:
|
|
print(f"오류발생 : {e}")
|
|
|
|
|
|
async def update_api_keys(self, url, username, password, api_keys, status_label, progress_bar):
|
|
try:
|
|
# 캐시 삭제
|
|
await self.clear_cache()
|
|
print('캐쉬 삭제')
|
|
|
|
await self.login_Process(url, username, password, progress_bar, status_label)
|
|
print('로그인 동작 완료')
|
|
|
|
# 각 마켓의 API 키를 업데이트
|
|
for market in self.market_xpath_mapping.keys():
|
|
if market in api_keys:
|
|
await self.update_market_api_keys(market, api_keys[market])
|
|
progress_bar.setValue(progress_bar.value() + 5)
|
|
status_label.setText(f'현재 상태: {market} 업데이트 완료')
|
|
|
|
await self.browser.close()
|
|
print('browser 종료')
|
|
return True
|
|
except Exception as e:
|
|
print(f"오류발생 : {e}")
|
|
return False
|
|
|
|
|
|
# 각 마켓별 API 키 업데이트
|
|
async def update_market_api_keys(self, market, market_api_keys):
|
|
print(f"update_market_api_keys || market : {market} 시도")
|
|
market_xpath = f"div#rc-tabs-0-tab-{self.market_xpath_mapping[market]}"
|
|
api_key_xpaths = self.api_key_xpaths[market]
|
|
print(f"update_market_api_keys || market_xpath : {market_xpath} ")
|
|
print(f"update_market_api_keys || api_key_xpaths : {api_key_xpaths} ")
|
|
|
|
await self.page.click(market_xpath)
|
|
# await self.page.wait_for_timeout(1000) # 잠시 대기
|
|
await asyncio.sleep(1) # 잠시 대기
|
|
|
|
# 배송프로필 선택
|
|
await self.select_shipping_profile(market, market_api_keys)
|
|
|
|
|
|
# 주문 수집 기능 설정
|
|
print("toggle_state 시작")
|
|
toggle_state = await self.page.get_attribute(self.order_collection_button[market], "aria-checked")
|
|
print(f"toggle_state : {toggle_state}, self.toggle_states[market] : {self.toggle_states[market]}, ")
|
|
if self.toggle_states[market] == 'on' and toggle_state == 'false':
|
|
await self.page.click(self.order_collection_button[market])
|
|
elif self.toggle_states[market] == 'off' and toggle_state == 'true':
|
|
await self.page.click(self.order_collection_button[market])
|
|
|
|
|
|
# API 키 입력
|
|
for key, value in api_key_xpaths.items():
|
|
api_key_value = market_api_keys.get(key, '')
|
|
print(f"key : {key}")
|
|
print(f"value : {value}")
|
|
print(f"api_key_value : {api_key_value}")
|
|
if key not in ['업로드할스마트스토어계정명', '업로드할스마트스토어계정id', '업로드할스마트스토어계정pw']:
|
|
print(f"key : {key}이므로 fill 실행")
|
|
await self.page.fill(value, api_key_value)
|
|
|
|
# 스마트스토어의 경우 로그인 작업 수행
|
|
if market == '스마트스토어' and key == '업로드할스마트스토어계정명':
|
|
print(f"스마트스토어 작업 시작")
|
|
|
|
# 새 창이 열리는 이벤트를 기다림
|
|
async with self.page.context.expect_page() as new_page_info: # 새 팝업이 아닌 새창
|
|
await self.page.click(self.smartstore_elements['account_button'])
|
|
print(f"page.click - account_button")
|
|
|
|
popup_page = await new_page_info.value
|
|
print(f"새 창 열림: {popup_page.url}")
|
|
try:
|
|
await popup_page.wait_for_load_state()
|
|
print("새 창 로드 완료")
|
|
except Exception as e:
|
|
print(f"새 창 로드 중 에러 발생: {e}")
|
|
traceback.print_exc()
|
|
return
|
|
|
|
await popup_page.evaluate('(selector) => document.querySelector(selector).scrollIntoViewIfNeeded()', self.smartstore_elements['popup_login_type_button'])
|
|
await popup_page.click(self.smartstore_elements['popup_login_type_button'])
|
|
print(f"page.click - popup_login_type_button")
|
|
|
|
await popup_page.evaluate('(selector) => document.querySelector(selector).scrollIntoViewIfNeeded()', self.smartstore_elements['popup_login_id_input'])
|
|
await popup_page.fill(self.smartstore_elements['popup_login_id_input'], market_api_keys.get('업로드할스마트스토어계정id', ''))
|
|
print(f"page.fill - 업로드할스마트스토어계정id")
|
|
|
|
await popup_page.fill(self.smartstore_elements['popup_login_pw_input'], market_api_keys.get('업로드할스마트스토어계정pw', ''))
|
|
print(f"page.fill - 업로드할스마트스토어계정pw")
|
|
|
|
await popup_page.evaluate('(selector) => document.querySelector(selector).scrollIntoViewIfNeeded()', self.smartstore_elements['popup_login_button'])
|
|
await popup_page.click(self.smartstore_elements['popup_login_button'])
|
|
print(f"page.click - popup_login_button")
|
|
|
|
try:
|
|
# 2단계 인증 처리
|
|
popup_page = await self.handle_two_factor_authentication(popup_page, market_api_keys)
|
|
|
|
# 팝업 페이지가 닫혀 있는지 확인
|
|
if popup_page.is_closed():
|
|
print("팝업 페이지가 닫혔습니다. 로그인 성공으로 간주합니다.")
|
|
else:
|
|
# 로그인 성공 확인
|
|
login_success = await popup_page.inner_text(self.smartstore_elements['login_success_check'])
|
|
if login_success != market_api_keys.get('업로드할스마트스토어계정id', ''):
|
|
print("스마트스토어 로그인 실패")
|
|
return
|
|
|
|
# 팝업 닫기
|
|
await popup_page.close()
|
|
except Exception as e:
|
|
print(f"로그인 팝업윈도우 처리 중 에러발생 : {e}")
|
|
traceback.print_exc()
|
|
|
|
|
|
# API 검증 버튼 클릭
|
|
await self.page.click(self.api_key_validation_button[market])
|
|
# await self.page.wait_for_timeout(2000) # 잠시 대기
|
|
await asyncio.sleep(2) # 잠시 대기
|
|
|
|
|
|
# API 상태 확인
|
|
api_status = await self.page.inner_text(self.api_key_status[market])
|
|
print(f"{market} API 상태 : {api_status}")
|
|
|
|
if "검증 완료" not in api_status:
|
|
print(f"{market} API 검증 실패")
|
|
return
|
|
|
|
|
|
|
|
async def handle_two_factor_authentication(self, popup_page, market_api_keys):
|
|
try:
|
|
print("2단계 인증 발생 확인 중...")
|
|
try:
|
|
await popup_page.wait_for_load_state()
|
|
# 2단계 인증 페이지가 있는지 확인
|
|
two_factor_element = await popup_page.wait_for_selector("div#root .Layout_layout__2COMk .Layout_inner__3QZZI .TwoStepCertify_h_title__1XYdA", timeout=2000)
|
|
# print(f"two_factor_element 존재 : {two_factor_element}")
|
|
two_factor_text = await two_factor_element.inner_text()
|
|
print(f"two_factor_text : [{two_factor_text}]")
|
|
|
|
print("2단계 인증 필요. 사용자에게 인증 번호 입력을 요청합니다.")
|
|
# PyQt5 다이얼로그를 통해 사용자 입력 받기
|
|
self.dialog.popup_page = popup_page
|
|
await self.dialog.show_dialog()
|
|
|
|
while self.dialog.isVisible():
|
|
await asyncio.sleep(1)
|
|
|
|
if not self.dialog.auth_method or not self.dialog.code:
|
|
print("사용자가 인증을 완료하지 않았습니다.")
|
|
return popup_page
|
|
|
|
# 페이지 다시 로드 및 상태 확인
|
|
await popup_page.wait_for_load_state()
|
|
print("2단계 인증 완료 후 페이지 리로드")
|
|
|
|
# 팝업 페이지가 닫혀 있는지 확인
|
|
if popup_page.is_closed():
|
|
print("팝업 페이지가 닫혔습니다. 로그인 성공으로 간주합니다.")
|
|
return popup_page
|
|
|
|
# 로그인 성공 확인
|
|
login_success = await popup_page.inner_text(self.smartstore_elements['login_success_check'])
|
|
if login_success != market_api_keys.get('업로드할스마트스토어계정id', ''):
|
|
print("스마트스토어 로그인 실패")
|
|
return
|
|
|
|
await popup_page.close()
|
|
|
|
except TargetClosedError:
|
|
print("팝업 페이지가 닫혔습니다. 로그인 성공으로 간주합니다.")
|
|
return popup_page
|
|
except TimeoutError:
|
|
print("2단계 인증 페이지 로드 시간 초과. 2단계 인증이 발생하지 않았습니다.")
|
|
return popup_page
|
|
|
|
except Exception as e:
|
|
print(f"2단계 인증 처리 중 에러 발생: {e}")
|
|
traceback.print_exc()
|
|
|
|
return popup_page
|
|
|
|
|
|
def generate_selector(self, tab_id, class_name, profile_index, state_class):
|
|
return f"#rc-tabs-0-panel-{tab_id} > div > div.{class_name} > div.ant-list.ant-list-vertical.ant-list-split.ant-list-bordered.css-1li46mu > div > div > ul > li:nth-child({profile_index}) > div > div:nth-child(2) > button.ant-btn.css-1li46mu.ant-btn-text.Body3Regular14.{state_class}"
|
|
|
|
def generate_profile_selector(self, tab_id, class_name, profile_index):
|
|
return f"#rc-tabs-0-panel-{tab_id} > div > div.{class_name} > div.ant-list.ant-list-vertical.ant-list-split.ant-list-bordered.css-1li46mu > div > div > ul > li:nth-child({profile_index}) > div > div:nth-child(1) > div.H5Medium16.CharacterPrimary85"
|
|
|
|
async def select_shipping_profile(self, market, market_api_keys):
|
|
|
|
profile_number = market_api_keys.get('배송프로필번호', '1')
|
|
profile_number = int(profile_number)
|
|
print(f"profile_number : {profile_number}")
|
|
|
|
try:
|
|
market_config = self.market_shipping_profile_selectors.get(market)
|
|
if not market_config:
|
|
# self.logger.error(f"{market} 마켓의 배송프로필 선택자는 설정되지 않았습니다.")
|
|
print(f"{market} 마켓의 배송프로필 선택자는 설정되지 않았습니다.")
|
|
return
|
|
|
|
tab_id, class_name = market_config
|
|
|
|
if profile_number < 1 or profile_number > 3:
|
|
# self.logger.warning(f"{market} 마켓에 유효하지 않은 배송프로필 번호가 선택되었습니다. 기본으로 1번 프로필을 사용합니다.")
|
|
print(f"{market} 마켓에 유효하지 않은 배송프로필 번호가 선택되었습니다. 기본으로 1번 프로필을 사용합니다.")
|
|
profile_number = 1
|
|
|
|
profile_selector = self.generate_profile_selector(tab_id, class_name, profile_number)
|
|
default_selected_selector = self.generate_selector(tab_id, class_name, profile_number, "CharacterDisabledPlaceholder25")
|
|
default_selectable_selector = self.generate_selector(tab_id, class_name, profile_number, "Primary6")
|
|
|
|
# profile_selector가 있는지 확인
|
|
is_profile_selector = await self.page.query_selector(profile_selector)
|
|
if not is_profile_selector:
|
|
# self.logger.warning(f"{market} 마켓에 배송프로필이 존재하지 않습니다. 기본으로 1번 프로필을 사용합니다.")
|
|
print(f"{market} 마켓에 배송프로필이 존재하지 않습니다. 기본으로 1번 프로필을 사용합니다.")
|
|
profile_selector = self.generate_profile_selector(tab_id, class_name, 1)
|
|
default_selected_selector = self.generate_selector(tab_id, class_name, 1, "CharacterDisabledPlaceholder25")
|
|
default_selectable_selector = self.generate_selector(tab_id, class_name, 1, "Primary6")
|
|
|
|
# 기본 프로필로 선택된 상태인지 확인
|
|
is_default_selected = await self.page.query_selector(default_selected_selector)
|
|
if is_default_selected:
|
|
# self.logger.info(f"{market} 마켓의 {profile_number}번 프로필이 이미 기본 프로필로 선택되어 있습니다.")
|
|
print(f"{market} 마켓의 {profile_number}번 프로필이 이미 기본 프로필로 선택되어 있습니다.")
|
|
return
|
|
|
|
# 기본 프로필로 선택 가능한 상태인지 확인
|
|
is_default_selectable = await self.page.query_selector(default_selectable_selector)
|
|
if is_default_selectable:
|
|
await is_default_selectable.click()
|
|
# self.logger.info(f"{market} 마켓의 {profile_number}번 프로필을 기본 프로필로 선택했습니다.")
|
|
print(f"{market} 마켓의 {profile_number}번 프로필을 기본 프로필로 선택했습니다.")
|
|
else:
|
|
# self.logger.error(f"{market} 마켓의 {profile_number}번 프로필을 기본 프로필로 선택할 수 없습니다.")
|
|
print(f"{market} 마켓의 {profile_number}번 프로필을 기본 프로필로 선택할 수 없습니다.")
|
|
except Exception as e:
|
|
# self.logger.error(f"{market} 배송프로필 선택 중 오류 발생: {e}")
|
|
print(f"{market} 배송프로필 선택 중 오류 발생: {e}")
|
|
traceback.print_exc()
|