This commit is contained in:
9700X_PC 2025-02-14 08:07:25 +09:00
parent c67e2b3564
commit f4d75d5f87
6 changed files with 684 additions and 3 deletions

View File

@ -1,7 +1,7 @@
import sys
import logging
from PySide6.QtWidgets import QApplication
from src.gui import TaobaoScraperApp
from src.mainWindow import MainWindow
from src.databaseManager import DatabaseManager
from src.loggerModule import Logger
from src.user_info_dialog import UserInfoDialog
@ -26,12 +26,12 @@ if __name__ == "__main__":
logger = Logger(log_file="Scrapper2.log", logger_name="Scrapper_Logger", level=logging.INFO)
db_manager = DatabaseManager(logger) # 데이터베이스 매니저 인스턴스 생성
window = TaobaoScraperApp(logger, db_manager)
window = MainWindow(logger, db_manager)
# 로그인 다이얼로그 실행
login_dialog = UserInfoDialog()
if login_dialog.exec(): # 로그인 성공 시
window = TaobaoScraperApp(logger, db_manager)
window = MainWindow(logger, db_manager)
window.show()
sys.exit(app.exec()) # 메인 UI 실행
else: # 로그인 실패 시

40
main.py.bak Normal file
View File

@ -0,0 +1,40 @@
import sys
import logging
from PySide6.QtWidgets import QApplication
from src.gui import TaobaoScraperApp
from src.databaseManager import DatabaseManager
from src.loggerModule import Logger
from src.user_info_dialog import UserInfoDialog
import ctypes
# COM 초기화 (멀티스레드 모드)
def initialize_com():
COINIT_MULTITHREADED = 0x0
ctypes.windll.ole32.CoInitializeEx(None, COINIT_MULTITHREADED)
# COM 해제
def uninitialize_com():
ctypes.windll.ole32.CoUninitialize()
if __name__ == "__main__":
initialize_com() # COM 초기화
app = QApplication(sys.argv)
logger = Logger(log_file="Scrapper2.log", logger_name="Scrapper_Logger", level=logging.INFO)
db_manager = DatabaseManager(logger) # 데이터베이스 매니저 인스턴스 생성
window = TaobaoScraperApp(logger, db_manager)
# 로그인 다이얼로그 실행
login_dialog = UserInfoDialog()
if login_dialog.exec(): # 로그인 성공 시
window = TaobaoScraperApp(logger, db_manager)
window.show()
sys.exit(app.exec()) # 메인 UI 실행
else: # 로그인 실패 시
sys.exit(0) # 프로그램 종료
uninitialize_com() # COM 해제

237
src/Scrapper1.py Normal file
View File

@ -0,0 +1,237 @@
import os
import sys
import random
import asyncio
import logging
import re
from PySide6.QtCore import QThread, Signal
from playwright.async_api import async_playwright, Page
import time
class Scrapper1(QThread):
data_collected = Signal(bool, str)
progress_signal = Signal(int) # 진행률을 전달하는 시그널
login_complete = Signal() # 로그인 완료 시 MainWindow에 알림
def __init__(self, logger, db_manager):
super().__init__()
self.logger = logger
self.db_manager = db_manager
self.search_query = ""
self.login_detected = False
async def collect_data(self):
try:
async with async_playwright() as p:
# 브라우저 경로 설정
browser_path = None
if getattr(sys, 'frozen', False):
browser_path = os.path.join(os.path.dirname(sys.executable), 'browsers', 'chromium-1112', 'chrome-win', 'chrome.exe')
else:
browser_path = os.path.join(os.path.dirname(__file__), 'browsers', 'chromium-1112', 'chrome-win', 'chrome.exe')
self.logger.log(f"브라우저 경로: {browser_path}", level=logging.DEBUG)
# 사용자 에이전트 설정
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.logger.log(f"user_agent: {user_agent}", level=logging.DEBUG)
# 브라우저 시작 (headless 모드)
browser = await p.chromium.launch(
headless=True, # headless 모드로 설정
executable_path=browser_path,
args=[
'--disable-popup-blocking',
'--start-maximized',
'--window-size=1920,1080'
]
)
# 시크릿 브라우저 컨텍스트 생성
context = await browser.new_context(
user_agent=user_agent,
geolocation={"latitude": 37.5665, "longitude": 126.9780},
locale="ko-KR",
permissions=["geolocation", "notifications"]
)
# 페이지 열기
page = await context.new_page()
# await page.goto("https://world.taobao.com")
await page.goto("https://s.taobao.com/search?commend=all&ie=utf8page=1&q=%E5%A5%B3%E5%A3%AB%E8%A5%BF%E6%9C%8D&search_type=item")
self.logger.log(f"접속 완료.", level=logging.INFO)
# 페이지 로딩 확인 및 pagedown
# await page.wait_for_selector(".tb-pick-content-item") # 상품 카드 로딩 대기
await page.wait_for_selector("doubleCard--gO3Bz6bu") # 상품 카드 로딩 대기
self.logger.log(f"페이지 로딩 완료", level=logging.INFO)
await page.keyboard.press("PageDown")
await page.wait_for_timeout(1000) # 1초 대기
await page.keyboard.press("PageDown")
await page.wait_for_timeout(2000) # 추가 2초 대기 (상품 로딩)
# 상품 수집
items_data = await self.scrape_items(page)
if items_data:
# 중복 필터링 후 새 상품만 추가
self.db_manager.insert_items(items_data)
self.data_collected.emit(True, "데이터 수집 완료")
else:
self.data_collected.emit(False, "데이터 수집 실패")
await context.close()
await browser.close()
except Exception as e:
self.logger.log(f"브라우저 작업 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
self.data_collected.emit(False, f"오류 발생: {e}", exec=True)
async def perform_qr_login(self):
try:
async with async_playwright() as p:
# 1. 로그인 페이지 접속
if getattr(os, 'frozen', False):
browser_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'browsers', 'chromium-1112', 'chrome-win', 'chrome.exe')
else:
browser_path = os.path.join(os.path.dirname(__file__), 'browsers', 'chromium-1112', 'chrome-win', 'chrome.exe')
user_agent = random.choice([
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...",
# ... (기타 user agent)
])
browser = await p.chromium.launch(
headless=False, # QR 로그인은 창이 보여야 함
executable_path=browser_path,
args=[
'--disable-popup-blocking',
'--start-maximized',
'--window-size=1920,1080'
]
)
context = await browser.new_context(
user_agent=user_agent,
geolocation={"latitude": 37.5665, "longitude": 126.9780},
locale="ko-KR",
permissions=["geolocation", "notifications"]
)
page = await context.new_page()
login_url = "https://login.taobao.com/member/login.jhtml"
await page.goto(login_url)
# 2. QR 페이지 스크린샷 캡쳐 후 저장
await page.wait_for_selector("div#qrcode-img canvas", timeout=10000)
await asyncio.sleep(2)
qr_canvas = await page.query_selector("div#qrcode-img canvas")
if not qr_canvas:
self.logger.log("QR 코드 캔버스 찾지 못함", level=30)
await browser.close()
return None, None, None
img_bytes = await qr_canvas.screenshot()
qr_path = os.path.join(os.getcwd(), "temp_qr.png")
with open(qr_path, "wb") as f:
f.write(img_bytes)
self.logger.log("QR 코드 캡쳐 및 저장 완료", level=20)
return browser, page, login_url, qr_path
except Exception as e:
self.logger.log(f"QR 로그인 수행 중 오류: {e}", level=40)
return None, None, None, None
async def monitor_login(self, page, login_url, timeout=90):
"""
1초마다 현재 URL을 감시하여, QR 페이지 URL과 달라지면 로그인 완료로 판단.
"""
start_time = time.time()
while time.time() - start_time < timeout:
current_url = page.url
if current_url != login_url:
self.logger.log(f"로그인 완료 감지: {current_url}", level=20)
self.login_detected = True
break
await asyncio.sleep(1)
return self.login_detected
async def scrape_items(self, page: Page):
try:
items = await page.query_selector_all("div#ice-container div.tb-pick-feeds-container > div")
total_items = len(items)
self.logger.log(f"{len(items)}개의 상품 카드가 발견되었습니다.", level=logging.DEBUG)
items_data = []
for idx, item in enumerate(items):
self.logger.log(f"{idx + 1}번째 상품 카드 처리 중 - XPath 확인", level=logging.DEBUG)
# a 태그를 명시적으로 선택하여 href 가져오기
link_element = await item.query_selector("a.item-link")
if link_element:
item_href = await link_element.get_attribute("href")
self.logger.log(f"{idx + 1}번째 상품 href: {item_href}", level=logging.DEBUG)
if item_href:
# 숫자만 추출하고 9~12자리 필터링
item_id_match = re.search(r'(\d{9,12})', item_href)
if item_id_match:
item_id = item_id_match.group(1)
pc_url = f"https://item.taobao.com/item.htm?id={item_id}"
self.logger.log(f"{idx + 1}번째 상품 ID: {item_id}, 상품 URL: {pc_url}", level=logging.DEBUG)
else:
self.logger.log(f"{idx + 1}번째 상품 ID를 올바르게 추출할 수 없습니다.", level=logging.WARNING)
continue
else:
self.logger.log(f"{idx + 1}번째 상품 ID를 가져올 수 없습니다.", level=logging.WARNING)
continue
else:
self.logger.log(f"{idx + 1}번째 상품의 a 태그를 찾을 수 없습니다.", level=logging.WARNING)
continue
name_element = await item.query_selector(".info-wrapper-title-text")
name = await name_element.inner_text() if name_element else "N/A"
self.logger.log(f"{idx + 1}번째 상품명: {name}", level=logging.DEBUG)
price_element = await item.query_selector(".price-value")
price = await price_element.inner_text() if price_element else "0"
self.logger.log(f"{idx + 1}번째 상품 가격: {price}", level=logging.DEBUG)
image_element = await item.query_selector(".img-wrapper")
image_style = await image_element.get_attribute("style") if image_element else ""
# image_url = image_style.split("url(")[-1].strip('")') if image_style else "N/A"
image_url = (image_style.split("url(")[-1].strip('");').strip("'").replace("//", "https://", 1) if image_style else "N/A")
self.logger.log(f"{idx + 1}번째 상품 이미지 URL: {image_url}", level=logging.DEBUG)
sales_element = await item.query_selector(".month-sale")
sales = await sales_element.inner_text() if sales_element else "0"
self.logger.log(f"{idx + 1}번째 상품 판매량: {sales}", level=logging.DEBUG)
items_data.append((item_id, pc_url, name, float(price), image_url, sales))
# 프로그레스바 업데이트
progress = int(((idx + 1) / total_items) * 100)
self.progress_signal.emit(progress)
self.logger.log(f"수집된 상품 수 : {len(items_data)}", level=logging.DEBUG)
return items_data
except Exception as e:
self.logger.log(f"데이터 수집 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
return None
async def wait_for_user(self):
await asyncio.sleep(2)
def run(self):
asyncio.run(self.collect_data())

View File

@ -8,3 +8,11 @@ API_KEY = X9Tz3JqC/JcCwxnNewA6qdloIN6QFIitVBgS1a2KVDYk1AmddaDTvzr6+t3dyLZV3gh2TP
banned_tags = 오늘출발, 오늘발송
banned_words = 금지어1, 금지어2, 금지어3
disallowed_words = 비허용단어1, 비허용단어2, 비허용단어3
[SearchCategory]
패션 = 여성의류:女装, 남성의류:男装, 아동의류:童装
가전 = 스마트폰:手机, 노트북:笔记本电脑, 태블릿:平板电脑
생활 = 주방용품:厨房用品, 청소기:吸尘器, 가구:家具
도서 = 소설:小说, 자기계발:自我发展, 역사:历史
음식 = 한식:韩国料理, 중식:中国料理, 일식:日本料理

396
src/mainWindow.py Normal file
View File

@ -0,0 +1,396 @@
import sys, os, configparser, logging
from PySide6.QtWidgets import (QApplication, QWidget, QVBoxLayout, QPushButton, QLabel, QMessageBox,
QTextBrowser, QDialog, QProgressBar, QTextEdit, QHBoxLayout, QMenuBar, QMenu, QComboBox)
from PySide6.QtGui import QAction, QPixmap
from PySide6.QtCore import Qt, Slot, Signal, QThread
from src.Scrapper1 import Scrapper1
from src.excel_export import ExcelExporter
from src.post_processor import PostProcessor
from src.xlsProcessingThread import XLSProcessingThread
from src.ProcessingThread_for_DB import ProcessingThread
from src.keyword.keyword_manager import KeywordManager
from src.categoryManager import CategoryManager
from src.user_info_dialog import UserInfoDialog
import os, asyncio, qasync
class MainWindow(QWidget):
def __init__(self, logger, db_manager):
super().__init__()
self.logger = logger
self.logger.set_gui_logger(self.update_log_window)
self.is_logged_in = False # 로그인 상태 플래그
self.db_manager = db_manager
self.setWindowTitle("내차는 언제타냐 - 역소싱기")
self.layout = QVBoxLayout()
# 메뉴바 생성
self.menu_bar = QMenuBar(self)
# 설정 메뉴
self.settings_menu = QMenu("설정", self)
self.menu_bar.addMenu(self.settings_menu)
# 설정 메뉴에 금지어 관리 추가
self.manage_keywords_action = QAction("금지어 관리", self)
self.manage_keywords_action.triggered.connect(self.manage_keywords)
self.settings_menu.addAction(self.manage_keywords_action)
# 설정 메뉴에 금지 카테고리 관리 추가
self.manage_category_action = QAction("금지 카테고리 관리", self)
self.manage_category_action.triggered.connect(self.manage_cat)
self.settings_menu.addAction(self.manage_category_action)
# 도움말 메뉴
self.help_menu = QMenu("도움말", self)
self.menu_bar.addMenu(self.help_menu)
# 도움말 메뉴에 항목 추가
self.help_action = QAction("도움말 보기", self)
self.help_action.triggered.connect(self.show_help_dialog)
self.help_menu.addAction(self.help_action)
# 사용자 정보 메뉴
self.user_menu = QMenu("사용자 정보", self)
self.menu_bar.addMenu(self.user_menu)
# 사용자 정보 메뉴에 항목 추가
self.user_info_action = QAction("사용자 정보 보기", self)
self.user_info_action.triggered.connect(self.show_user_info_dialog)
self.user_menu.addAction(self.user_info_action)
# 로그창 추가
self.log_text_edit = QTextEdit()
self.log_text_edit.setReadOnly(True)
self.log_text_edit.setMinimumHeight(200)
# 프로그레스바 추가
self.progress_bar = QProgressBar()
self.progress_bar.setAlignment(Qt.AlignCenter)
self.progress_bar.setValue(0)
# 상품분류 콤보박스 (대분류와 소분류)
self.category_combo = QComboBox()
self.subcategory_combo = QComboBox()
category_layout = QHBoxLayout()
category_layout.addWidget(QLabel("상품 대분류:"))
category_layout.addWidget(self.category_combo)
category_layout.addWidget(QLabel("상품 소분류:"))
category_layout.addWidget(self.subcategory_combo)
# 시작, 엑셀출력, 후처리, 닫기 버튼
# 로그인 버튼, 시작 버튼 등
self.login_button = QPushButton("로그인")
self.login_button.clicked.connect(self.on_login_button_clicked)
self.start_button = QPushButton("시작")
self.start_button.setEnabled(False)
self.start_button.clicked.connect(self.start_scraping)
self.excel_button = QPushButton("엑셀출력")
self.excel_button.clicked.connect(self.save_to_excel)
self.post_db__button = QPushButton("후처리")
self.post_db__button.clicked.connect(self.post_process_by_DB)
self.close_button = QPushButton("닫기")
self.close_button.clicked.connect(self.close)
# 버튼 레이아웃
button_layout = QHBoxLayout()
button_layout.addWidget(self.login_button)
button_layout.addWidget(self.start_button)
button_layout.addWidget(self.post_db__button)
button_layout.addWidget(self.excel_button)
button_layout.addWidget(self.close_button)
# 전체 레이아웃 구성
self.layout.addWidget(self.menu_bar)
self.layout.addLayout(category_layout) # 상품분류 선택 영역 추가
self.layout.addLayout(button_layout)
self.layout.addWidget(QLabel("진행 상황"))
self.layout.addWidget(self.progress_bar)
self.layout.addWidget(QLabel("로그 출력"))
self.layout.addWidget(self.log_text_edit)
self.setLayout(self.layout)
# QR 이미지 표시를 위한 QLabel (초기에는 숨김)
self.qr_label = QLabel()
self.qr_label.setVisible(False)
self.layout.addWidget(self.qr_label)
# base_dir 경로 가져오기
base_dir = self.get_base_dir()
self.xls_file_path = os.path.join(base_dir, "baseXLS_Percenty.xlsx")
config_path = os.path.join(base_dir, "config.ini")
self.config = self.load_config(config_path)
# 콤보박스 초기화 (config.ini의 [Category] 섹션 사용)
self.category_dict = {} # 대분류 -> (소분류_표시, 검색어) 리스트
self.populate_category_combobox()
# PlaywrightThread에 전달할 검색어를 초기화하기 위해 빈 값 설정
self.search_query = ""
self.scrapper1 = Scrapper1(self.logger, self.db_manager)
self.scrapper1.data_collected.connect(self.on_data_collected)
self.scrapper1.progress_signal.connect(self.update_progress_bar)
self.scrapper1.login_complete.connect(self.on_login_complete)
self.categoryManager = CategoryManager(self.logger, self.xls_file_path)
self.excel_exporter = ExcelExporter(logger=self.logger, db_manager=self.db_manager)
self.excel_exporter.progress_signal.connect(self.update_progress_bar)
self.postProcessor = PostProcessor(self.logger, self.db_manager, self.config, self.categoryManager)
self.keyword_manager = KeywordManager(logger=self.logger, config=self.config, parent=self)
@Slot()
def on_login_button_clicked(self):
# qasync를 사용하여 비동기 로그인 수행
qasync.ensure_future(self.perform_login())
async def perform_login(self):
from playwright.async_api import async_playwright
self.logger.log("QR 로그인 시작", level=20)
async with async_playwright() as p:
browser, page, login_url, qr_path = await self.scrapper1.perform_qr_login()
if not qr_path:
QMessageBox.warning(self, "오류", "QR 코드 캡쳐 실패")
return
# QR 이미지 파일 경로를 받아서 표시
pixmap = QPixmap(qr_path)
self.qr_label.setPixmap(pixmap)
self.qr_label.setVisible(True)
# 90초 동안 QR 표시, 이후 자동으로 닫힘
self.logger.log("QR 코드 표시 (최대 90초)", level=20)
# 동시에 로그인 모니터링 시작
login_task = asyncio.create_task(self.scrapper1.monitor_login(page, login_url, timeout=90))
# 90초 후 자동 QR 창 닫기
await asyncio.sleep(90)
self.qr_label.setVisible(False)
if await login_task:
self.logger.log("로그인 완료 감지됨", level=20)
self.start_button.setEnabled(True)
self.login_button.setEnabled(False)
else:
self.logger.log("로그인 시간 초과", level=30)
QMessageBox.warning(self, "로그인 실패", "로그인 시간 초과")
await browser.close()
@Slot()
def start_scraping(self):
# 사용자가 로그인 완료 후 시작 버튼을 누르면,
# 기존 로그인 세션이 유지된 상태에서 검색 페이지로 이동하여 데이터 수집 시작
self.logger.log("시작 버튼 클릭 - 데이터 수집 시작", level=20)
# 예를 들어, Scrapper1.collect_data() 실행
self.scrapper1.start()
@Slot()
def on_login_complete(self):
# Scrapper1에서 로그인 완료 시 발생시키는 시그널 처리 (필요 시)
self.logger.log("MainWindow: 로그인 완료 알림 받음", level=20)
def load_config(self, file_path: str) -> configparser.ConfigParser:
"""
config.ini 파일을 읽어서 ConfigParser 객체로 반환
"""
config = configparser.ConfigParser()
try:
config.read(file_path, encoding="utf-8") # UTF-8 인코딩으로 설정 파일 읽기
self.logger.log(f"Config 파일 '{file_path}' 로드 성공", level=logging.INFO)
except Exception as e:
self.logger.log(f"Config 파일 로드 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
return config
def get_base_dir(self):
"""
실행 환경에 따라 base_dir을 설정하는 메서드.
"""
if getattr(sys, 'frozen', False): # 패키징된 경우
base_dir = os.path.dirname(sys.executable)
internal_dir = os.path.join(base_dir, '_internal') # _internal 디렉토리 포함
if os.path.exists(internal_dir):
return internal_dir
else:
base_dir = os.path.dirname(os.path.abspath(__file__))
return base_dir
def populate_category_combobox(self):
"""
config.ini의 [SearchCategory] 섹션을 읽어 대분류와 소분류 콤보박스 초기화
"""
if "SearchCategory" not in self.config:
self.logger.log("config.ini에 [SearchCategory] 섹션이 없습니다.", level=logging.ERROR)
return
self.category_combo.clear()
self.category_dict.clear()
# 대분류 키들을 추가
for major in self.config["SearchCategory"]:
self.category_combo.addItem(major)
# 값은 "소분류1:중국어, 소분류2:중국어, ..." 형태
items = self.config["SearchCategory"][major].split(",")
sub_list = []
for item in items:
# 좌우 공백 제거 후 ':' 기준 분리
parts = item.strip().split(":")
if len(parts) == 2:
display, search = parts
sub_list.append((display.strip(), search.strip()))
self.category_dict[major] = sub_list
# 초기 대분류 선택 시 소분류 콤보박스 업데이트
self.category_combo.currentIndexChanged.connect(self.populate_subcategory_combobox)
self.populate_subcategory_combobox(0)
def populate_subcategory_combobox(self, index):
"""
대분류 선택에 따라 소분류 콤보박스를 업데이트
"""
self.subcategory_combo.clear()
if index < 0:
return
major = self.category_combo.itemText(index)
sub_list = self.category_dict.get(major, [])
for display, _ in sub_list:
self.subcategory_combo.addItem(display)
# 자동 선택 첫 소분류의 검색어 저장
if sub_list:
self.search_query = sub_list[0][1]
else:
self.search_query = ""
# 연결: 소분류 선택 변경 시 검색어 업데이트
self.subcategory_combo.currentIndexChanged.connect(self.update_search_query)
def update_search_query(self, index):
"""
소분류 선택이 변경되면 해당 중국어 검색어를 업데이트
"""
major = self.category_combo.currentText()
sub_list = self.category_dict.get(major, [])
if 0 <= index < len(sub_list):
self.search_query = sub_list[index][1]
self.logger.log(f"선택된 검색어: {self.search_query}", level=logging.DEBUG)
else:
self.search_query = ""
@Slot()
def start_scraping(self):
"""
시작 버튼 클릭 , 선택된 검색어를 playwright_thread에 전달 크롤링 시작
"""
if not self.search_query:
QMessageBox.warning(self, "오류", "상품분류를 올바르게 선택해주세요.")
return
self.logger.log(f"선택된 검색어 '{self.search_query}'로 Playwright 스레드 시작.", level=logging.INFO)
# playwright_thread에 검색어 전달
self.scrapper1.search_query = self.search_query
self.progress_bar.setValue(1)
self.scrapper1.start()
@Slot()
def post_process_by_DB(self):
self.logger.log(f"DB로 후처리 작업을 시작합니다.", level=logging.INFO)
self.progress_bar.setValue(1)
self.db_thread = ProcessingThread(self.postProcessor, self.keyword_manager)
self.db_thread.progress.connect(self.on_DB_progress)
self.db_thread.start()
@Slot(str)
def on_DB_progress(self, message):
self.logger.log(message, level=logging.INFO)
@Slot(str)
def manage_keywords(self):
self.logger.log("금지어 관리 버튼 클릭됨", level=logging.DEBUG)
self.keyword_manager.exec()
@Slot()
def manage_cat(self):
try:
if not os.path.exists(self.xls_file_path):
QMessageBox.warning(self, "파일 없음", f"'{self.xls_file_path}' 파일이 존재하지 않습니다.")
self.logger.log(f"'{self.xls_file_path}' 파일이 존재하지 않습니다.", level=logging.ERROR)
return
os.startfile(self.xls_file_path)
QMessageBox.information(
self,
"카테고리 관리",
"금지할 카테고리 옆에 False를 입력하면 금지, 비어있거나 True면 허용"
)
self.logger.log(f"'{self.xls_file_path}' 파일이 실행되었습니다.", level=logging.INFO)
except Exception as e:
QMessageBox.critical(self, "오류", f"엑셀 파일 열기 중 오류 발생: {e}")
self.logger.log(f"엑셀 파일 열기 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
@Slot(str)
def on_folder_selected(self, selected_folder):
self.logger.log(f"선택된 폴더: {selected_folder}", level=logging.INFO)
self.xls_thread = XLSProcessingThread(self.postProcessor, selected_folder)
self.xls_thread.progress.connect(self.on_xls_progress)
self.xls_thread.start()
@Slot(str)
def on_xls_progress(self, message):
self.logger.log(message, level=logging.INFO)
@Slot()
def save_to_excel(self):
self.progress_bar.setValue(1)
success = self.excel_exporter.save_to_excel()
if success:
QMessageBox.information(self, "엑셀 출력", "엑셀 파일로 저장 완료")
else:
QMessageBox.critical(self, "오류", "엑셀 저장 오류")
@Slot(bool, str)
def on_data_collected(self, success, message):
if success:
QMessageBox.information(self, "수집 완료", message)
else:
QMessageBox.warning(self, "수집 실패", message)
@Slot(int)
def update_progress_bar(self, value):
self.progress_bar.setValue(value)
@Slot(str)
def update_log_window(self, message):
self.log_text_edit.append(message)
@Slot()
def show_help_dialog(self):
self.logger.log("도움말 메뉴 클릭됨", level=logging.INFO)
dialog = QDialog(self)
dialog.setWindowTitle("도움말")
dialog.setMinimumSize(400, 300)
dialog_layout = QVBoxLayout()
help_text_browser = QTextBrowser()
help_text_browser.setText("""
<h3>Taobao Scraper 도움말</h3>
<p> 프로그램은 Taobao 데이터를 스크래핑하고 엑셀 파일로 출력하거나, DB를 후처리하는 기능을 제공합니다.</p>
<ul>
<li><b>시작:</b> 선택한 상품분류에 따라 데이터를 스크래핑 시작</li>
<li><b>엑셀 출력:</b> 데이터를 엑셀 파일로 저장</li>
<li><b>후처리:</b> DB의 데이터를 후처리</li>
<li><b>설정 > 금지어 관리:</b> 금지어 목록을 관리</li>
<li><b>설정 > 금지 카테고리 관리:</b> 금지된 카테고리를 설정</li>
</ul>
<p>추가적인 문의 사항이 있으면 개발자에게 문의하세요.</p>
""")
dialog_layout.addWidget(help_text_browser)
dialog.setLayout(dialog_layout)
dialog.exec()
@Slot()
def show_user_info_dialog(self):
self.logger.log("사용자 정보 메뉴 클릭됨", level=logging.INFO)
dialog = UserInfoDialog(self)
dialog.exec()