WRMC_DeletePro/main.py

257 lines
12 KiB
Python

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QRadioButton, QProgressBar, QFileDialog, QCheckBox, QHBoxLayout, QSpinBox, QLabel, QSizePolicy, QMessageBox, QTextEdit
from PyQt5.QtCore import Qt, QThread, pyqtSlot, pyqtSignal
import pandas as pd
from web_action import MarketAutomation
from logger_module import setup_logger
import logging
import asyncio
class BrowserThread(QThread):
progress_updated = pyqtSignal(int) # 진행 상태 업데이트를 위한 시그널
paused = pyqtSignal(bool)
def __init__(self, url, market, deleting_data, logger):
super().__init__()
self.logger = logger
self.url = url
self.market = market
self.deleting_data = deleting_data
self._pause = False
def run(self):
self.logger.info(f"Browser thread starting for market: {self.market} with URL: {self.url}")
asyncio.run(self.perform_deletion())
async def perform_deletion(self):
automation = MarketAutomation()
await automation.start_browser()
self.logger.info("Browser started successfully.")
await automation.goto_market(self.url)
self.logger.info(f"Navigated to {self.url}")
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) # 진행 상태 업데이트를 위해 시그널 발생
await automation.close_browser()
self.logger.info("Browser closed successfully.")
def pause(self):
self._pause = True
def resume(self):
self._pause = False
class MainApp(QWidget):
def __init__(self, logger):
super().__init__()
self.logger = logger
self.deleting_data = []
self.browser_thread = None
self.paused = False # 중지 상태를 추적하는 플래그
self.initUI()
def initUI(self):
mainLayout = QVBoxLayout()
# 엑셀 파일 불러오기 및 관련 레이블 설정
topLayout = QHBoxLayout()
# 엑셀 파일 불러오기 버튼
self.loadButton = QPushButton('엑셀 파일 불러오기', self)
self.loadButton.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
self.loadButton.clicked.connect(self.load_excel)
topLayout.addWidget(self.loadButton, 30) # 30% 공간 할당
# # 총 갯수 레이블
# self.totalCountLabel = QLabel("총 갯수: 0", self)
# self.totalCountLabel.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
# topLayout.addWidget(self.totalCountLabel, 20) # 20% 공간 할당
# 엑셀에서 불러온 셀값들의 갯수 레이블
self.loadedCountLabel = QLabel("불러온 갯수: 0", self)
self.loadedCountLabel.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
topLayout.addWidget(self.loadedCountLabel, 20) # 20% 공간 할당
# 글자수 레이블
self.byteSizeLabel = QLabel("글자수", self)
self.byteSizeLabel.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
topLayout.addWidget(self.byteSizeLabel, 10) # 10% 공간 할당 (20% 중 레이블 부분)
# 글자수 스핀박스
self.byteSizeSpinBox = QSpinBox(self)
self.byteSizeSpinBox.setRange(10, 200)
self.byteSizeSpinBox.setValue(50)
self.byteSizeSpinBox.setSingleStep(1)
self.byteSizeSpinBox.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
topLayout.addWidget(self.byteSizeSpinBox, 10) # 10% 공간 할당 (20% 중 스핀박스 부분)
mainLayout.addLayout(topLayout)
# 라디오 버튼 가로 배열
radioLayout = QHBoxLayout()
self.marketButtons = {name: QRadioButton(name) for name in ["쿠팡", "스마트스토어", "11번가", "롯데온", "ESM"]}
for btn in self.marketButtons.values():
radioLayout.addWidget(btn)
mainLayout.addLayout(radioLayout)
# 버튼 레이아웃 (가로)
buttonLayout = QHBoxLayout()
# 브라우저 실행 버튼
self.startBrowserButton = QPushButton('브라우저 실행', self)
self.startBrowserButton.clicked.connect(self.start_browser_action)
buttonLayout.addWidget(self.startBrowserButton)
# 삭제 실행 버튼
self.deleteButton = QPushButton('삭제 실행', self)
self.deleteButton.clicked.connect(self.delete_products)
buttonLayout.addWidget(self.deleteButton)
# 중지 버튼
self.pauseButton = QPushButton('중지', self)
self.pauseButton.clicked.connect(self.toggle_pause_resume)
buttonLayout.addWidget(self.pauseButton)
# 버튼 레이아웃을 메인 레이아웃에 추가
mainLayout.addLayout(buttonLayout)
# 진행 상태 프로그레스바
self.progressBar = QProgressBar(self)
mainLayout.addWidget(self.progressBar)
# 데이터 표시를 위한 QTextEdit 위젯
self.dataDisplay = QTextEdit(self)
self.dataDisplay.setReadOnly(True) # 읽기 전용 설정
self.dataDisplay.setPlaceholderText("불러온 데이터가 여기에 표시됩니다...")
mainLayout.addWidget(self.dataDisplay)
# 항상위 체크박스
self.alwaysOnTopCheckbox = QCheckBox('항상위', self)
self.alwaysOnTopCheckbox.stateChanged.connect(self.set_always_on_top)
mainLayout.addWidget(self.alwaysOnTopCheckbox)
self.setLayout(mainLayout)
self.setGeometry(300, 300, 600, 200)
self.setWindowTitle('상품삭제 Automation')
def update_progress_bar(self, value):
self.progressBar.setValue(value) # 프로그레스바 업데이트
def toggle_pause_resume(self):
if self.browser_thread and self.browser_thread.isRunning():
if not self.paused:
self.browser_thread.pause()
self.pauseButton.setText('계속')
self.paused = True
else:
self.browser_thread.resume()
self.pauseButton.setText('중지')
self.paused = False
@pyqtSlot()
def load_excel_ini(self):
fileName, _ = QFileDialog.getOpenFileName(self, 'Open Excel File', '', 'Excel Files (*.xlsx *.xls)')
if fileName:
byte_size = self.byteSizeSpinBox.value()
try:
df = pd.read_excel(fileName, sheet_name=0) # 첫 번째 시트를 불러옵니다.
first_column_name = df.columns[0] # 첫 번째 열의 이름을 가져옵니다.
# 결측치 제거
non_empty_data = df[first_column_name].dropna()
# 중복 데이터 제거
unique_data = non_empty_data.drop_duplicates()
# 바이트 크기 이상의 데이터 필터링
filtered_data = [data for data in unique_data if len(str(data).encode('utf-8')) >= byte_size]
# 프로그레스바와 레이블 업데이트
self.progressBar.setMaximum(len(unique_data)) # 중복 제거된 전체 데이터 수를 프로그레스바 최대값으로 설정
self.loadedCountLabel.setText(f"불러온 갯수: {len(filtered_data)}") # 필터링된 데이터 수
# self.totalCountLabel.setText(f"총 갯수: {len(unique_data)}") # 중복 제거된 총 데이터 수 업데이트
self.deleting_data = filtered_data
self.logger.debug(f'{fileName} 파일에서 중복 제거 후 {len(filtered_data)}개의 적합한 데이터를 성공적으로 불러왔습니다.')
except Exception as e:
self.logger.error(f"파일을 불러오는 중 오류가 발생했습니다: {e}")
QMessageBox.critical(self, "오류", "파일을 불러오는 중 오류가 발생했습니다. 파일 형식을 확인해주세요.")
@pyqtSlot()
def load_excel(self):
fileName, _ = QFileDialog.getOpenFileName(self, 'Open Excel File', '', 'Excel Files (*.xlsx *.xls)')
if fileName:
byte_size = self.byteSizeSpinBox.value()
try:
df = pd.read_excel(fileName, sheet_name=0)
first_column_name = df.columns[0]
non_empty_data = df[first_column_name].dropna()
unique_data = non_empty_data.drop_duplicates()
filtered_data = [data for data in unique_data if len(str(data).encode('utf-8')) >= byte_size]
# 데이터를 QTextEdit 위젯에 표시
self.dataDisplay.clear()
self.dataDisplay.append("\n".join([str(data) for data in filtered_data]))
# 프로그레스바와 레이블 업데이트
self.progressBar.setMaximum(len(unique_data))
self.loadedCountLabel.setText(f"불러온 갯수: {len(filtered_data)}")
self.deleting_data = filtered_data
self.logger.debug(f'{fileName} 파일에서 중복 제거 후 {len(filtered_data)}개의 적합한 데이터를 성공적으로 불러왔습니다.')
except Exception as e:
self.logger.error(f"파일을 불러오는 중 오류가 발생했습니다: {e}")
QMessageBox.critical(self, "오류", "파일을 불러오는 중 오류가 발생했습니다. 파일 형식을 확인해주세요.")
@pyqtSlot()
def start_browser_action(self):
selected_market = next((btn.text() for btn in self.marketButtons.values() if btn.isChecked()), None)
if not selected_market: # 마켓이 선택되지 않았을 경우
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",
"스마트스토어": "https://accounts.commerce.naver.com/login?url=https%3A%2F%2Fsell.smartstore.naver.com%2F%23%2Flogin-callback",
"11번가": "https://login.11st.co.kr/auth/front/selleroffice/login.tmall?returnURL=https%3A%2F%2Fsoffice.11st.co.kr%2F",
"롯데온": "https://store.lotteon.com/cm/main/login_SO.wsp",
"ESM": "https://signin.esmplus.com/login"
}
url = urls.get(selected_market)
if url:
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.progress_updated.connect(self.update_progress_bar)
self.browser_thread.start()
else:
QMessageBox.critical(self, "오류", "선택한 마켓의 URL 정보가 없습니다.")
@pyqtSlot()
def delete_products(self):
if self.browser_thread:
self.browser_thread.start()
def set_always_on_top(self, checked):
if checked:
self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
else:
self.setWindowFlags(self.windowFlags() & ~Qt.WindowStaysOnTopHint)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
# 로그 레벨을 DEBUG로 설정하여 로거를 초기화합니다.
logger = setup_logger('default_logger', 'application.log', level=logging.DEBUG)
ex = MainApp(logger)
ex.show()
sys.exit(app.exec_())