From 82bc3aa1c4de57cda1df743b41844fd756002908 Mon Sep 17 00:00:00 2001 From: Envy_PC Date: Tue, 14 May 2024 07:39:17 +0900 Subject: [PATCH] ... --- main.py | 47 ++++++++++++++++++++----- requirements.txt | Bin 0 -> 546 bytes web_action.py | 88 ++++++++++++++++++++++++++++++++++------------- 3 files changed, 102 insertions(+), 33 deletions(-) create mode 100644 requirements.txt diff --git a/main.py b/main.py index a9766c0..c663a05 100644 --- a/main.py +++ b/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,13 +37,17 @@ 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): @@ -154,6 +172,13 @@ class MainApp(QWidget): self.browser_thread.resume() 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): @@ -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: diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..b137bd4fcaefdaeecc799d7bd1775be74796615f GIT binary patch literal 546 zcmZvZ-3o$06oltG=ur$)3%ckX>Ip)~exPj0mDGo?X3knxMC@Mp&77G%J?;fOA~ZPT zgdL8&3oQAYVqz!87$fX)<@W-mac_#0ikK%gMTwd-9NM=#!JITRdLfZpyx|Nb$gh;d z6b?E0mUm?|F*48-NsAkM9NFf;)~}YCO$t3VUp)i+e#44vP2bGu%+kXhyaSK^>O#*? zRmCo@&r;v%PZQ6c7rrX8cX{@znXetIpBxJw?a)zIkqxi#=z3L5WoFa$PbjnLGnJ?v i2bbsj9BIk@Y?#$>o^~#WT7 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') + # 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.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.click('button.searchBtn') + 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) + + if "검색 결과가 없습니다." in no_results_text: + logger.debug("검색 결과가 없습니다.") + logger.debug(f"{no_results_text}") + + # 여기서 추가 작업 처리 가능 (예: 사용자에게 알림, 다른 작업 수행 등) + return # 더 이상의 작업을 진행하지 않고 함수 종료 - # 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') # 다음 동작 전 페이지 로딩 완료 대기 + # 검색된 첫 번째 상품의 상품명 가져오기 + 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