257 lines
12 KiB
Python
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_())
|