...
This commit is contained in:
parent
50cc450fe8
commit
82bc3aa1c4
45
main.py
45
main.py
|
|
@ -12,13 +12,15 @@ class BrowserThread(QThread):
|
|||
progress_updated = pyqtSignal(int) # 진행 상태 업데이트를 위한 시그널
|
||||
paused = pyqtSignal(bool)
|
||||
|
||||
def __init__(self, url, market, deleting_data, logger):
|
||||
def __init__(self, url, market, deleting_data, logger, dryrunFactor, successCount):
|
||||
super().__init__()
|
||||
self.logger = logger
|
||||
self.dryrunFactor = dryrunFactor
|
||||
self.url = url
|
||||
self.market = market
|
||||
self.deleting_data = deleting_data
|
||||
self._pause = False
|
||||
self.successCount = successCount
|
||||
|
||||
def run(self):
|
||||
self.logger.info(f"Browser thread starting for market: {self.market} with URL: {self.url}")
|
||||
|
|
@ -35,12 +37,16 @@ class BrowserThread(QThread):
|
|||
for index, product in enumerate(self.deleting_data):
|
||||
while self._pause:
|
||||
await asyncio.sleep(1) # 중지 상태에서 대기
|
||||
await automation.search_and_delete_product(self.market, product)
|
||||
self.logger.debug(f"Deleted product {product} from {self.market}")
|
||||
self.progress_updated.emit(index + 1) # 진행 상태 업데이트를 위해 시그널 발생
|
||||
|
||||
result = await automation.search_and_delete_product(self.market, product, self.dryrunFactor)
|
||||
if result:
|
||||
self.logger.debug(f"상품삭제 성공 {index+1}/{len(self.deleting_data)} : {product} from {self.market}")
|
||||
self.progress_updated.emit(index + 1) # 진행 상태 업데이트를 위해 시그널 발생
|
||||
self.successCount += 1
|
||||
else:
|
||||
self.logger.debug(f"상품삭제 실패 : {product} from {self.market}")
|
||||
await automation.close_browser()
|
||||
self.logger.info("Browser closed successfully.")
|
||||
# QMessageBox.information(self, "완료", f"상품 [{self.successCount}]/[{len(self.deleting_data)}]개를 성공적으로 삭제했습니다.")
|
||||
|
||||
def pause(self):
|
||||
self._pause = True
|
||||
|
|
@ -55,6 +61,8 @@ class MainApp(QWidget):
|
|||
self.deleting_data = []
|
||||
self.browser_thread = None
|
||||
self.paused = False # 중지 상태를 추적하는 플래그
|
||||
self.dryrunFactor = False
|
||||
self.successCount = 0
|
||||
self.initUI()
|
||||
|
||||
def initUI(self):
|
||||
|
|
@ -80,7 +88,7 @@ class MainApp(QWidget):
|
|||
topLayout.addWidget(self.loadedCountLabel, 20) # 20% 공간 할당
|
||||
|
||||
# 글자수 레이블
|
||||
self.byteSizeLabel = QLabel("글자수", self)
|
||||
self.byteSizeLabel = QLabel("글자수(Byte)", self)
|
||||
self.byteSizeLabel.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
|
||||
topLayout.addWidget(self.byteSizeLabel, 10) # 10% 공간 할당 (20% 중 레이블 부분)
|
||||
|
||||
|
|
@ -109,6 +117,11 @@ class MainApp(QWidget):
|
|||
self.startBrowserButton.clicked.connect(self.start_browser_action)
|
||||
buttonLayout.addWidget(self.startBrowserButton)
|
||||
|
||||
# Dryrun 체크박스
|
||||
self.dryRunCheckbox = QCheckBox('DryRUN', self)
|
||||
self.dryRunCheckbox.stateChanged.connect(self.dryrun)
|
||||
buttonLayout.addWidget(self.dryRunCheckbox)
|
||||
|
||||
# 삭제 실행 버튼
|
||||
self.deleteButton = QPushButton('삭제 실행', self)
|
||||
self.deleteButton.clicked.connect(self.delete_products)
|
||||
|
|
@ -137,8 +150,13 @@ class MainApp(QWidget):
|
|||
self.alwaysOnTopCheckbox.stateChanged.connect(self.set_always_on_top)
|
||||
mainLayout.addWidget(self.alwaysOnTopCheckbox)
|
||||
|
||||
# # Dryrun 체크박스
|
||||
# self.dryRunCheckbox = QCheckBox('DryRUN', self)
|
||||
# self.dryRunCheckbox.stateChanged.connect(self.dryrun)
|
||||
# mainLayout.addWidget(self.dryRunCheckbox)
|
||||
|
||||
self.setLayout(mainLayout)
|
||||
self.setGeometry(300, 300, 600, 200)
|
||||
self.setGeometry(300, 300, 400, 600)
|
||||
self.setWindowTitle('상품삭제 Automation')
|
||||
|
||||
def update_progress_bar(self, value):
|
||||
|
|
@ -155,6 +173,13 @@ class MainApp(QWidget):
|
|||
self.pauseButton.setText('중지')
|
||||
self.paused = False
|
||||
|
||||
def dryrun(self, checked):
|
||||
if checked:
|
||||
self.dryrunFactor = True
|
||||
else:
|
||||
self.dryrunFactor = False
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def load_excel_ini(self):
|
||||
fileName, _ = QFileDialog.getOpenFileName(self, 'Open Excel File', '', 'Excel Files (*.xlsx *.xls)')
|
||||
|
|
@ -214,6 +239,10 @@ class MainApp(QWidget):
|
|||
QMessageBox.warning(self, "경고", "마켓을 선택해주세요.")
|
||||
return # 경고창을 표시하고 함수를 더 이상 진행하지 않음
|
||||
|
||||
if not self.deleting_data: # 마켓이 선택되지 않았을 경우
|
||||
QMessageBox.warning(self, "경고", "삭제대상이 없습니다.")
|
||||
return # 경고창을 표시하고 함수를 더 이상 진행하지 않음
|
||||
|
||||
if selected_market:
|
||||
urls = {
|
||||
"쿠팡": "https://xauth.coupang.com/auth/realms/seller/protocol/openid-connect/auth?response_type=code&client_id=wing&redirect_uri=https%3A%2F%2Fwing.coupang.com%2Fsso%2Flogin?returnUrl%3D%252F&state=794abcff-4d9d-4460-86d8-e0efba1b97c2&login=true&scope=openid",
|
||||
|
|
@ -227,7 +256,7 @@ class MainApp(QWidget):
|
|||
if self.browser_thread and self.browser_thread.isRunning():
|
||||
QMessageBox.warning(self, "경고", "이미 실행 중인 작업이 있습니다.")
|
||||
else:
|
||||
self.browser_thread = BrowserThread(url, selected_market, self.deleting_data, self.logger)
|
||||
self.browser_thread = BrowserThread(url, selected_market, self.deleting_data, self.logger, self.dryrunFactor, self.successCount)
|
||||
self.browser_thread.progress_updated.connect(self.update_progress_bar)
|
||||
self.browser_thread.start()
|
||||
else:
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,5 +1,5 @@
|
|||
from playwright.async_api import async_playwright
|
||||
import logging, random, os, time
|
||||
import logging, random, os, time, re
|
||||
|
||||
# 로거 인스턴스 가져오기
|
||||
logger = logging.getLogger('default_logger')
|
||||
|
|
@ -44,8 +44,10 @@ class MarketAutomation:
|
|||
except Exception as e:
|
||||
logger.error("웹 페이지 로드 실패", exc_info=True)
|
||||
|
||||
async def search_and_delete_product(self, market, product_name):
|
||||
async def search_and_delete_product(self, market, product_name, dryrunFactor):
|
||||
try:
|
||||
product_name = self.normalize_space(product_name)
|
||||
|
||||
if market == "ESM":
|
||||
await self.page.fill('textarea#txtKeyword', product_name)
|
||||
await self.page.click('a[href="javascript:;"] img[src*="btn_search1.gif"]')
|
||||
|
|
@ -58,30 +60,57 @@ class MarketAutomation:
|
|||
|
||||
elif market == "쿠팡":
|
||||
await self.page.fill('input[data-v-671ac22c][type="text"]', product_name)
|
||||
# await self.page.fill('#searchContainer > dd > div > dl.search-condition > dd.searchWordDD > span > table > tr.table-row-focused > td:nth-child(2) > input', product_name)
|
||||
|
||||
await self.page.click('button.searchBtn')
|
||||
print("검색버큰 클릭")
|
||||
# await self.page.wait_for_selector('#rootContainer > div:nth-child(6) > div.search-result-section > div.table-wrapper-container > div.table-wrapper.border-right.hide-see-auto-option > table > thead > tr > th:nth-child(2) > span > input[type=checkbox]') # 검색 결과 로드 대기
|
||||
# await self.page.wait_for_selector('#rootContainer > div:nth-child(6) > div.search-result-section > div.table-wrapper-container > div.table-wrapper.border-right.hide-see-auto-option > table > thead > tr > th:nth-child(2) > span > input[type=checkbox]') # 검색 결과 로드 대기
|
||||
time.sleep(2)
|
||||
# 검색된상품의 텍스트가 검색상품 텍스트와 일치하는지 검사.
|
||||
await self.page.wait_for_selector('#rootContainer > div:nth-child(6) > div.search-result-section > div.table-wrapper-container > div.table-wrapper.border-right.hide-see-auto-option > table > tbody > tr > td.fixed-col.left-align.small-horizontal-padding.middle-vertical-padding.border-right-double.editable-cell.editable-popup > div > span')
|
||||
print("검색버튼 클릭")
|
||||
|
||||
# await self.page.locator('span.sc-common-check input[type="checkbox"]:nth-child(12)').click()
|
||||
await self.page.click('#rootContainer > div:nth-child(6) > div.search-result-section > div.table-wrapper-container > div.table-wrapper.border-right.hide-see-auto-option > table > thead > tr > th:nth-child(2) > span > input[type=checkbox]')
|
||||
print("체크박스 클릭")
|
||||
# 검색 결과 로드 대기
|
||||
await self.page.wait_for_selector('#rootContainer > div:nth-child(6) > div.search-result-section')
|
||||
|
||||
# "검색 결과가 없습니다." 메시지 확인
|
||||
no_results_selector = '#rootContainer > div:nth-child(6) > div.search-result-section > div.table-wrapper-container > div.table-wrapper.border-right.hide-see-auto-option > table > tbody > tr > td > span'
|
||||
no_results_text = await self.page.text_content(no_results_selector)
|
||||
|
||||
# time.sleep(100)
|
||||
await self.page.wait_for_selector('button[data-v-29314869]', state='attached')
|
||||
print("삭제버튼 로드 기다림")
|
||||
# CSS selector 사용
|
||||
# await self.page.locator('button.wing-web-component.wuic-button:has-text("삭제")').click()
|
||||
await self.page.locator('button.wing-web-component.wuic-button:has-text("삭제"):nth-child(4)').click()
|
||||
print("삭제버튼 클릭")
|
||||
time.sleep(100)
|
||||
# Xpath 사용 (권장하지 않음)
|
||||
# await self.page.locator("xpath=/html/body/div[1]/div[2]/div/section/section/div/div/div[1]/div[6]/div[2]/div[2]/div[3]/div[1]/button[3]").click()
|
||||
await self.page.wait_for_load_state('networkidle') # 다음 동작 전 페이지 로딩 완료 대기
|
||||
if "검색 결과가 없습니다." in no_results_text:
|
||||
logger.debug("검색 결과가 없습니다.")
|
||||
logger.debug(f"{no_results_text}")
|
||||
|
||||
# 여기서 추가 작업 처리 가능 (예: 사용자에게 알림, 다른 작업 수행 등)
|
||||
return # 더 이상의 작업을 진행하지 않고 함수 종료
|
||||
|
||||
# 검색된 첫 번째 상품의 상품명 가져오기
|
||||
first_product_name = await self.page.text_content('#rootContainer > div:nth-child(6) > div.search-result-section > div.table-wrapper-container > div.table-wrapper.border-right.hide-see-auto-option > table > tbody > tr:nth-child(1) > td.fixed-col.left-align.small-horizontal-padding.middle-vertical-padding.border-right-double.editable-cell.editable-popup > div > span')
|
||||
first_product_name = self.normalize_space(first_product_name)
|
||||
|
||||
# first_product_name = await self.page.text_content('#rootContainer > div:nth-child(6) > div.search-result-section > div.table-wrapper-container > div.table-wrapper.border-right.hide-see-auto-option > table > tbody > tr:nth-child(1) > td.fixed-col.left-align.small-horizontal-padding.middle-vertical-padding.border-right-double.editable-cell.editable-popup > div > span')
|
||||
logger.debug(f"product_name : {product_name}")
|
||||
logger.debug(f"first_product_name : {first_product_name}")
|
||||
# 상품명이 검색 키워드와 일치하는지 확인
|
||||
if first_product_name == product_name:
|
||||
logger.debug("상품명 일치")
|
||||
# 해당 상품의 체크박스 선택
|
||||
await self.page.check('#rootContainer > div:nth-child(6) > div.search-result-section > div.table-wrapper-container > div.table-wrapper.border-right.hide-see-auto-option > table > tbody > tr:nth-child(1) > td:nth-child(2) > span > input[type="checkbox"]')
|
||||
time.sleep(5)
|
||||
logger.debug("체크박스 체크")
|
||||
|
||||
await self.page.click('.wing-web-component:nth-child(10)') # 삭제 버튼
|
||||
|
||||
await self.page.wait_for_selector('button.confirm.alert-confirm') # 삭제 확인 버튼 대기
|
||||
|
||||
if not dryrunFactor:
|
||||
await self.page.click('button.confirm.alert-confirm') # 삭제 확인 버튼 클릭
|
||||
logger.debug("삭제확인 클릭")
|
||||
else:
|
||||
logger.debug("DryrunFactor 설정됨")
|
||||
await self.page.click('.showSweetAlert .cancel') # 삭제 취소 버튼 클릭
|
||||
#container-wing-v2 > div:nth-child(5) > div.sweet-alert.showSweetAlert.visible > div.alert-buttons > button.cancel
|
||||
first_product_name = None # 검색된 상품 변수 초기화
|
||||
await self.page.wait_for_load_state('networkidle') # 삭제 후 페이지 로딩 대기
|
||||
else:
|
||||
logger.warning("검색된 상품이 없거나 일치하지 않습니다.")
|
||||
|
||||
# await self.page.wait_for_load_state('networkidle') # 다음 동작 전 페이지 로딩 완료 대기
|
||||
|
||||
|
||||
elif market == "스스":
|
||||
|
|
@ -111,9 +140,15 @@ class MarketAutomation:
|
|||
await self.page.click('a[href="javascript:fnListDelete(\'U\');"]')
|
||||
await self.page.wait_for_load_state('networkidle') # 다음 동작 전 페이지 로딩 완료 대기
|
||||
|
||||
logger.debug(f"{product_name} 상품 삭제 완료")
|
||||
if first_product_name:
|
||||
logger.debug(f"{product_name} 상품 삭제 완료")
|
||||
return True
|
||||
else:
|
||||
logger.debug(f"{product_name} 상품 삭제 실패")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"{market}에서 상품 검색 및 삭제 중 오류 발생", exc_info=True)
|
||||
return False
|
||||
|
||||
async def close_browser(self):
|
||||
try:
|
||||
|
|
@ -121,3 +156,8 @@ class MarketAutomation:
|
|||
logger.debug("브라우저가 성공적으로 닫혔습니다.")
|
||||
except Exception as e:
|
||||
logger.error("브라우저 종료 중 오류 발생", exc_info=True)
|
||||
|
||||
def normalize_space(self, text):
|
||||
# 연속된 공백을 하나의 공백으로 치환하고, 양쪽 끝의 공백을 제거합니다.
|
||||
text = re.sub(r'\s+', ' ', text).strip()
|
||||
return text
|
||||
|
|
|
|||
Loading…
Reference in New Issue