...
This commit is contained in:
parent
3ab88bef1e
commit
89fdd21058
|
|
@ -636,6 +636,8 @@ class Scrapper1(QThread):
|
|||
실제 스크래핑 로직은 필요에 따라 구현하세요.
|
||||
"""
|
||||
try:
|
||||
self.logger.log(f"프로그레스바 초기화")
|
||||
self.progress_signal.emit(0)
|
||||
self.logger.log("검색 페이지 접속 중...", level=20)
|
||||
import urllib.parse
|
||||
encoded_query = urllib.parse.quote(self.search_query)
|
||||
|
|
|
|||
|
|
@ -27,73 +27,147 @@ class ExcelExporter(QObject):
|
|||
self.logger.log(f"DB에서 데이터 로드 중 오류 발생: {e}", level=logging.ERROR, exc_info=True)
|
||||
return pd.DataFrame()
|
||||
|
||||
def save_to_excel(self, output_path="output.xlsx"):
|
||||
# def save_to_excel(self, output_path="output.xlsx"):
|
||||
# df = self.fetch_data_from_db()
|
||||
|
||||
# if df.empty:
|
||||
# self.logger.log(f"DB에서 불러온 데이터가 없습니다.", level=logging.WARNING)
|
||||
# return False # 성공 여부 반환
|
||||
|
||||
# # 조건에 맞는 데이터 필터링
|
||||
# filtered_df = df[(df['is_valid'] == 1) & (df['is_export'] == 0)]
|
||||
# if filtered_df.empty:
|
||||
# self.logger.log(f"조건에 맞는 데이터가 없습니다.", level=logging.WARNING)
|
||||
# return False # 성공 여부 반환
|
||||
|
||||
|
||||
# app = xw.App(visible=False)
|
||||
# self.logger.log(f"xlwings 시작", level=logging.DEBUG)
|
||||
|
||||
# try:
|
||||
# total_rows = len(df)
|
||||
# for i in range(0, total_rows, 50):
|
||||
# df_subset = df.iloc[i:i+50]
|
||||
# self.logger.log(f"{i}번째 출력할 데이터:\n{df_subset}", level=logging.DEBUG) # 데이터 검증 로그 추가
|
||||
|
||||
# part_file_name = output_path.replace('.xlsx', f'_part{i//50 + 1}.xlsx')
|
||||
# shutil.copy(self.base_excel_path, part_file_name)
|
||||
# self.logger.log(f"기본 엑셀 파일 '{self.base_excel_path}' 복사 완료", level=logging.DEBUG)
|
||||
|
||||
# wb = xw.Book(part_file_name)
|
||||
# ws = wb.sheets['multi_ss']
|
||||
|
||||
# for index, row in df_subset.iterrows():
|
||||
# row_num = 4 + (index % 50)
|
||||
# self.logger.log(f"{index + 1}번째 행 기록 시작: B{row_num}, C{row_num}, D{row_num}, F{row_num}, G{row_num}, H{row_num}", level=logging.DEBUG) # 셀 위치 로그 추가
|
||||
# ws.range(f'B{row_num}').value = row['pc_url']
|
||||
# ws.range(f'C{row_num}').value = row['generated_Title']
|
||||
# ws.range(f'D{row_num}').value = row['margin_price']
|
||||
# ws.range(f'F{row_num}').value = row['tags']
|
||||
# ws.range(f'G{row_num}').value = row['category_code']
|
||||
# ws.range(f'H{row_num}').value = row['memo']
|
||||
|
||||
# # 데이터베이스 업데이트
|
||||
# self.db_manager.update_item({
|
||||
# 'id': row['id'],
|
||||
# 'generated_Title': row.get('generated_Title', None),
|
||||
# 'category_code': row['category_code'],
|
||||
# 'tags': row['tags'],
|
||||
# 'margin_price': row.get('margin_price', None),
|
||||
# 'memo': row['memo'],
|
||||
# 'is_valid': row['is_valid'],
|
||||
# 'is_export': 1 # is_export를 1로 설정
|
||||
# })
|
||||
|
||||
# self.logger.log(f"{index + 1}번째 행 기록 완료", level=logging.DEBUG)
|
||||
|
||||
# # 프로그레스바 업데이트
|
||||
# progress = int((index + 1) / total_rows * 100)
|
||||
# self.progress_signal.emit(progress)
|
||||
|
||||
# wb.save(part_file_name) # SaveCopyAs 대신 save 사용
|
||||
# wb.close()
|
||||
# self.saved_files.append(part_file_name)
|
||||
# self.logger.log(f"파일 '{part_file_name}'에 데이터가 저장되었습니다.", level=logging.INFO)
|
||||
# return True # 성공 여부 반환
|
||||
|
||||
# except Exception as e:
|
||||
# self.logger.log(f"엑셀 저장 중 예외 발생: {e}", level=logging.ERROR, exc_info=True)
|
||||
# return False # 실패 시 False 반환
|
||||
# finally:
|
||||
# app.quit()
|
||||
# self.logger.log(f"xlwings 종료", level=logging.DEBUG)
|
||||
|
||||
def save_to_excel(self, output_path="output.xlsx", options=None):
|
||||
"""
|
||||
options: dict with keys:
|
||||
- shoppinglens (bool)
|
||||
- title_generation (bool)
|
||||
- margin_price (bool)
|
||||
- category_input (bool)
|
||||
"""
|
||||
if options is None:
|
||||
options = {"shoppinglens": False, "title_generation": False, "margin_price": False, "category_input": False}
|
||||
|
||||
df = self.fetch_data_from_db()
|
||||
|
||||
if df.empty:
|
||||
self.logger.log(f"DB에서 불러온 데이터가 없습니다.", level=logging.WARNING)
|
||||
return False # 성공 여부 반환
|
||||
self.logger.log("DB에서 불러온 데이터가 없습니다.", level=logging.WARNING)
|
||||
return False
|
||||
|
||||
# 조건에 맞는 데이터 필터링
|
||||
# 기본 필터: 유효한 데이터(is_valid=1, is_export=0)
|
||||
filtered_df = df[(df['is_valid'] == 1) & (df['is_export'] == 0)]
|
||||
if filtered_df.empty:
|
||||
self.logger.log(f"조건에 맞는 데이터가 없습니다.", level=logging.WARNING)
|
||||
return False # 성공 여부 반환
|
||||
|
||||
self.logger.log("조건에 맞는 데이터가 없습니다.", level=logging.WARNING)
|
||||
return False
|
||||
|
||||
app = xw.App(visible=False)
|
||||
self.logger.log(f"xlwings 시작", level=logging.DEBUG)
|
||||
|
||||
self.logger.log("xlwings 시작", level=logging.DEBUG)
|
||||
try:
|
||||
total_rows = len(df)
|
||||
total_rows = len(filtered_df)
|
||||
for i in range(0, total_rows, 50):
|
||||
df_subset = df.iloc[i:i+50]
|
||||
self.logger.log(f"{i}번째 출력할 데이터:\n{df_subset}", level=logging.DEBUG) # 데이터 검증 로그 추가
|
||||
|
||||
df_subset = filtered_df.iloc[i:i+50]
|
||||
part_file_name = output_path.replace('.xlsx', f'_part{i//50 + 1}.xlsx')
|
||||
shutil.copy(self.base_excel_path, part_file_name)
|
||||
self.logger.log(f"기본 엑셀 파일 '{self.base_excel_path}' 복사 완료", level=logging.DEBUG)
|
||||
|
||||
wb = xw.Book(part_file_name)
|
||||
ws = wb.sheets['multi_ss']
|
||||
|
||||
# 각 행에 대해 처리
|
||||
for index, row in df_subset.iterrows():
|
||||
row_num = 4 + (index % 50)
|
||||
self.logger.log(f"{index + 1}번째 행 기록 시작: B{row_num}, C{row_num}, D{row_num}, F{row_num}, G{row_num}, H{row_num}", level=logging.DEBUG) # 셀 위치 로그 추가
|
||||
# 무조건 pc_url 출력 (B열)
|
||||
ws.range(f'B{row_num}').value = row['pc_url']
|
||||
ws.range(f'C{row_num}').value = row['generated_Title']
|
||||
ws.range(f'D{row_num}').value = row['price']
|
||||
ws.range(f'F{row_num}').value = row['tags']
|
||||
ws.range(f'G{row_num}').value = row['category_code']
|
||||
# 기본적으로 memo는 H열에 기록
|
||||
ws.range(f'H{row_num}').value = row['memo']
|
||||
|
||||
# 데이터베이스 업데이트
|
||||
|
||||
if options.get("shoppinglens"):
|
||||
# 쇼핑렌즈 수집이 활성화된 경우 옵션에 따라 다른 컬럼에 기록
|
||||
if options.get("title_generation"):
|
||||
ws.range(f'C{row_num}').value = row.get('generated_Title', "")
|
||||
if options.get("margin_price"):
|
||||
ws.range(f'D{row_num}').value = row.get('margin_price', "")
|
||||
if options.get("category_input"):
|
||||
ws.range(f'G{row_num}').value = row.get('category_code', "")
|
||||
# DB 업데이트 시에도 해당 컬럼들을 함께 저장합니다.
|
||||
self.db_manager.update_item({
|
||||
'id': row['id'],
|
||||
'generated_Title': row.get('generated_Title', None),
|
||||
'category_code': row['category_code'],
|
||||
'tags': row['tags'],
|
||||
'category_code': row.get('category_code', ""),
|
||||
'tags': row.get('tags', ""),
|
||||
'margin_price': row.get('margin_price', None),
|
||||
'memo': row['memo'],
|
||||
'is_valid': row['is_valid'],
|
||||
'is_export': 1 # is_export를 1로 설정
|
||||
'is_export': 1
|
||||
})
|
||||
|
||||
self.logger.log(f"{index + 1}번째 행 기록 완료", level=logging.DEBUG)
|
||||
|
||||
# 프로그레스바 업데이트
|
||||
progress = int((index + 1) / total_rows * 100)
|
||||
self.progress_signal.emit(progress)
|
||||
|
||||
wb.save(part_file_name) # SaveCopyAs 대신 save 사용
|
||||
wb.save(part_file_name)
|
||||
wb.close()
|
||||
self.saved_files.append(part_file_name)
|
||||
self.logger.log(f"파일 '{part_file_name}'에 데이터가 저장되었습니다.", level=logging.INFO)
|
||||
return True # 성공 여부 반환
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.log(f"엑셀 저장 중 예외 발생: {e}", level=logging.ERROR, exc_info=True)
|
||||
return False # 실패 시 False 반환
|
||||
return False
|
||||
finally:
|
||||
app.quit()
|
||||
self.logger.log(f"xlwings 종료", level=logging.DEBUG)
|
||||
self.logger.log("xlwings 종료", level=logging.DEBUG)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from src.keyword.keyword_manager import KeywordManager
|
|||
from src.keyword.kiprisAPI import Kipris_API
|
||||
from src.categoryManager import CategoryManager
|
||||
from src.user_info_dialog import UserInfoDialog
|
||||
from src.toggleSwitch import ToggleSwitch
|
||||
from login.qr_dialog import QRDialog, LoginSuccessDialog, LoginInProgressDialog
|
||||
|
||||
class MainWindow(QWidget):
|
||||
|
|
@ -120,6 +121,50 @@ class MainWindow(QWidget):
|
|||
self.close_button = QPushButton("닫기")
|
||||
self.close_button.clicked.connect(self.close)
|
||||
|
||||
# 토글레이아웃 추가 (쇼핑렌즈 관련 옵션)
|
||||
self.toggle_shoppinglens_label = QLabel("쇼핑렌즈 수집")
|
||||
self.toggle_shoppinglens = ToggleSwitch()
|
||||
self.toggle_shoppinglens.setToolTip("쇼핑렌즈 데이터를 수집합니다.\n(Free 등급은 사용 불가)")
|
||||
self.toggle_shoppinglens.setChecked(False)
|
||||
self.toggle_shoppinglens.stateChanged.connect(self.on_shoppinglens_toggle_changed)
|
||||
self.toggle_title_generation_label = QLabel("상품명 생성")
|
||||
self.toggle_title_generation = ToggleSwitch()
|
||||
self.toggle_title_generation.setToolTip("활성화 시, title_manager.generate_product_name 실행")
|
||||
self.toggle_title_generation.setChecked(False)
|
||||
self.toggle_margin_price_label = QLabel("마진계산")
|
||||
|
||||
self.toggle_margin_price = ToggleSwitch()
|
||||
self.toggle_margin_price.setToolTip("활성화 시, calculate_additional_margin 실행")
|
||||
self.toggle_margin_price.setChecked(False)
|
||||
self.toggle_category_input_label = QLabel("카테고리 입력")
|
||||
self.toggle_category_input = ToggleSwitch()
|
||||
self.toggle_category_input.setToolTip("활성화 시, categoryManager.find_category_code 실행")
|
||||
self.toggle_category_input.setChecked(False)
|
||||
|
||||
# 초기에는 쇼핑렌즈 토글이 꺼져 있으므로 나머지 옵션은 비활성화
|
||||
self.toggle_title_generation.setEnabled(False)
|
||||
self.toggle_margin_price.setEnabled(False)
|
||||
self.toggle_category_input.setEnabled(False)
|
||||
|
||||
# 회원 등급에 따라 쇼핑렌즈 토글 활성 여부 결정
|
||||
membership_level = self.user_info.get('membership_level', '').lower()
|
||||
if membership_level == "free":
|
||||
self.toggle_shoppinglens.setEnabled(False)
|
||||
self.logger.log("회원 등급이 free이므로 쇼핑렌즈 기능 사용 불가", level=logging.INFO)
|
||||
else:
|
||||
self.toggle_shoppinglens.setEnabled(True)
|
||||
|
||||
# 토글레이아웃에 추가
|
||||
self.toggle_layout = QHBoxLayout()
|
||||
self.toggle_layout.addWidget(self.toggle_shoppinglens_label)
|
||||
self.toggle_layout.addWidget(self.toggle_shoppinglens)
|
||||
self.toggle_layout.addWidget(self.toggle_title_generation_label)
|
||||
self.toggle_layout.addWidget(self.toggle_title_generation)
|
||||
self.toggle_layout.addWidget(self.toggle_margin_price_label)
|
||||
self.toggle_layout.addWidget(self.toggle_margin_price)
|
||||
self.toggle_layout.addWidget(self.toggle_category_input_label)
|
||||
self.toggle_layout.addWidget(self.toggle_category_input)
|
||||
|
||||
# 버튼 레이아웃
|
||||
button_layout = QHBoxLayout()
|
||||
button_layout.addWidget(self.login_button)
|
||||
|
|
@ -132,12 +177,15 @@ class MainWindow(QWidget):
|
|||
self.layout.addWidget(self.menu_bar)
|
||||
self.layout.addLayout(category_layout) # 상품분류 선택 영역 추가
|
||||
self.layout.addLayout(button_layout)
|
||||
self.layout.addWidget(QLabel("후처리 옵션"))
|
||||
self.layout.addLayout(self.toggle_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)
|
||||
|
|
@ -364,12 +412,12 @@ class MainWindow(QWidget):
|
|||
self.logger.log(f"기본 선택된 검색어: {self.selected_search_query}", level=logging.DEBUG)
|
||||
self.subcategory_combo.blockSignals(False)
|
||||
|
||||
# # 만약 서브카테고리 콤보박스에 한 번만 연결되어 있지 않다면, 한 번만 연결하도록 처리
|
||||
# try:
|
||||
# self.subcategory_combo.currentIndexChanged.disconnect()
|
||||
# except Exception:
|
||||
# pass
|
||||
# self.subcategory_combo.currentIndexChanged.connect(self.update_search_query)
|
||||
# 만약 서브카테고리 콤보박스에 한 번만 연결되어 있지 않다면, 한 번만 연결하도록 처리
|
||||
try:
|
||||
self.subcategory_combo.currentIndexChanged.disconnect()
|
||||
except Exception:
|
||||
pass
|
||||
self.subcategory_combo.currentIndexChanged.connect(self.update_search_query)
|
||||
|
||||
def update_search_query(self, index):
|
||||
major = self.category_combo.currentText()
|
||||
|
|
@ -386,6 +434,9 @@ class MainWindow(QWidget):
|
|||
def post_process_by_DB(self):
|
||||
self.logger.log(f"DB로 후처리 작업을 시작합니다.", level=logging.INFO)
|
||||
self.progress_bar.setValue(1)
|
||||
# 토글 상태 업데이트 후 옵션 전달
|
||||
self.update_toggle_options()
|
||||
self.postProcessor.options = self.toggle_options
|
||||
self.db_thread = ProcessingThread(self.postProcessor, self.keyword_manager)
|
||||
self.db_thread.progress.connect(self.on_DB_progress)
|
||||
self.db_thread.start()
|
||||
|
|
@ -425,6 +476,26 @@ class MainWindow(QWidget):
|
|||
self.xls_thread.progress.connect(self.on_xls_progress)
|
||||
self.xls_thread.start()
|
||||
|
||||
def on_shoppinglens_toggle_changed(self, state):
|
||||
# 쇼핑렌즈 토글이 켜져 있어야 나머지 옵션 활성화
|
||||
enabled = (state == 2)
|
||||
self.toggle_title_generation.setEnabled(enabled)
|
||||
self.toggle_margin_price.setEnabled(enabled)
|
||||
self.toggle_category_input.setEnabled(enabled)
|
||||
# 토글 상태 업데이트
|
||||
self.update_toggle_options()
|
||||
|
||||
def update_toggle_options(self):
|
||||
# 현재 각 토글의 상태를 딕셔너리에 저장
|
||||
self.toggle_options = {
|
||||
"shoppinglens": self.toggle_shoppinglens.isChecked(),
|
||||
"title_generation": self.toggle_title_generation.isChecked(),
|
||||
"margin_price": self.toggle_margin_price.isChecked(),
|
||||
"category_input": self.toggle_category_input.isChecked()
|
||||
}
|
||||
self.logger.log(f"토글 옵션 업데이트: {self.toggle_options}", level=logging.DEBUG)
|
||||
|
||||
|
||||
@Slot(str)
|
||||
def on_xls_progress(self, message):
|
||||
self.logger.log(message, level=logging.INFO)
|
||||
|
|
@ -432,7 +503,8 @@ class MainWindow(QWidget):
|
|||
@Slot()
|
||||
def save_to_excel(self):
|
||||
self.progress_bar.setValue(1)
|
||||
success = self.excel_exporter.save_to_excel()
|
||||
self.update_toggle_options()
|
||||
success = self.excel_exporter.save_to_excel(options=self.toggle_options)
|
||||
if success:
|
||||
QMessageBox.information(self, "엑셀 출력", "엑셀 파일로 저장 완료")
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ class PostProcessor(QObject):
|
|||
self.xlThread = XlsSerachThread(self.logger, self.db_manager)
|
||||
self.banned_words = None
|
||||
self.disallowed_words = None
|
||||
# 기본 옵션 설정 (나중에 MainWindow에서 덮어쓰기 가능)
|
||||
self.options = {"shoppinglens": False, "title_generation": False, "margin_price": False, "category_input": False}
|
||||
|
||||
def set_keyword_manager(self, keyword_manager):
|
||||
self.banned_words = keyword_manager.get_ban_list()
|
||||
|
|
@ -94,57 +96,6 @@ class PostProcessor(QObject):
|
|||
self.logger.log(f"URL 데이터 가져오기 오류: {url}, 오류: {e}", level=logging.ERROR, exc_info=True)
|
||||
return None, None
|
||||
|
||||
def process_products(self, products: List[Dict]):
|
||||
total_products = len(products)
|
||||
if total_products == 0:
|
||||
self.logger.log("처리할 상품이 없습니다.", level=logging.WARNING)
|
||||
self.progress_signal.emit(100)
|
||||
return
|
||||
|
||||
self.wh_con.start_whale_Browser()
|
||||
|
||||
for idx, product in enumerate(products, start=1):
|
||||
try:
|
||||
self.logger.log(f"상품 {product['product_id']} 쇼핑렌즈 검색 시작", level=logging.DEBUG)
|
||||
scraped_data = self.wh_con.search_and_parse(image_url=product['image_url'])
|
||||
if not scraped_data:
|
||||
self.logger.log(f"상품 {product['product_id']} 쇼핑렌즈 데이터 없음, 스킵", level=logging.WARNING)
|
||||
continue
|
||||
|
||||
most_common_category = self.categoryManager.find_most_common_category(scraped_data)
|
||||
self.logger.log(f"결정된 카테고리: {most_common_category}", level=logging.DEBUG)
|
||||
category_code = self.categoryManager.find_category_code(most_common_category)
|
||||
self.logger.log(f"결정된 카테고리 코드: {category_code}", level=logging.DEBUG)
|
||||
|
||||
if scraped_data:
|
||||
isvalid_category = self.categoryManager.is_category_allowed(most_common_category)
|
||||
self.logger.log(f"isvalid_category: {isvalid_category}", level=logging.DEBUG)
|
||||
product['is_valid'] = 1 if isvalid_category else 0
|
||||
|
||||
titles = [item["title"] for item in scraped_data if "title" in item]
|
||||
final_title = self.title_manager.generate_product_name(titles, product['name'], self.banned_words, self.disallowed_words)
|
||||
self.logger.log(f"상품명 생성 완료: {final_title}", level=logging.DEBUG)
|
||||
tags = self.filter_and_merge_tags(scraped_data)
|
||||
additional_margin = self.calculate_additional_margin(scraped_data)
|
||||
self.logger.log(f"더하기 마진: {additional_margin}", level=logging.DEBUG)
|
||||
|
||||
product.update({
|
||||
"generated_Title": final_title,
|
||||
"category_code": category_code,
|
||||
"tags": tags,
|
||||
"margin_price": additional_margin,
|
||||
"memo": self.generate_memo(scraped_data)
|
||||
})
|
||||
self.db_manager.update_item(product)
|
||||
self.logger.log(f"상품 {product['product_id']} 처리 완료", level=logging.DEBUG)
|
||||
progress = int((idx / total_products) * 100)
|
||||
self.progress_signal.emit(progress)
|
||||
except Exception as e:
|
||||
self.logger.log(f"상품 {product['product_id']} 처리 중 오류: {e}", level=logging.ERROR, exc_info=True)
|
||||
|
||||
self.progress_signal.emit(100)
|
||||
self.logger.log("모든 상품 처리가 완료되었습니다.", level=logging.INFO)
|
||||
|
||||
def filter_and_merge_tags(self, scraped_data) -> str:
|
||||
try:
|
||||
manu_tags = [item.get("manuTag") for item in scraped_data if item.get("manuTag")]
|
||||
|
|
@ -185,6 +136,130 @@ class PostProcessor(QObject):
|
|||
self.logger.log(f"메모 생성 오류: {e}", level=logging.ERROR, exc_info=True)
|
||||
return "메모 없음"
|
||||
|
||||
|
||||
# def process_products(self, products: List[Dict]):
|
||||
# total_products = len(products)
|
||||
# if total_products == 0:
|
||||
# self.logger.log("처리할 상품이 없습니다.", level=logging.WARNING)
|
||||
# self.progress_signal.emit(100)
|
||||
# return
|
||||
|
||||
# self.wh_con.start_whale_Browser()
|
||||
|
||||
# for idx, product in enumerate(products, start=1):
|
||||
# try:
|
||||
# self.logger.log(f"상품 {product['product_id']} 쇼핑렌즈 검색 시작", level=logging.DEBUG)
|
||||
# scraped_data = self.wh_con.search_and_parse(image_url=product['image_url'])
|
||||
# if not scraped_data:
|
||||
# self.logger.log(f"상품 {product['product_id']} 쇼핑렌즈 데이터 없음, 스킵", level=logging.WARNING)
|
||||
# continue
|
||||
|
||||
# most_common_category = self.categoryManager.find_most_common_category(scraped_data)
|
||||
# self.logger.log(f"결정된 카테고리: {most_common_category}", level=logging.DEBUG)
|
||||
# category_code = self.categoryManager.find_category_code(most_common_category)
|
||||
# self.logger.log(f"결정된 카테고리 코드: {category_code}", level=logging.DEBUG)
|
||||
|
||||
# if scraped_data:
|
||||
# isvalid_category = self.categoryManager.is_category_allowed(most_common_category)
|
||||
# self.logger.log(f"isvalid_category: {isvalid_category}", level=logging.DEBUG)
|
||||
# product['is_valid'] = 1 if isvalid_category else 0
|
||||
|
||||
# titles = [item["title"] for item in scraped_data if "title" in item]
|
||||
# final_title = self.title_manager.generate_product_name(titles, product['name'], self.banned_words, self.disallowed_words)
|
||||
# self.logger.log(f"상품명 생성 완료: {final_title}", level=logging.DEBUG)
|
||||
# tags = self.filter_and_merge_tags(scraped_data)
|
||||
# additional_margin = self.calculate_additional_margin(scraped_data)
|
||||
# self.logger.log(f"더하기 마진: {additional_margin}", level=logging.DEBUG)
|
||||
|
||||
# product.update({
|
||||
# "generated_Title": final_title,
|
||||
# "category_code": category_code,
|
||||
# "tags": tags,
|
||||
# "margin_price": additional_margin,
|
||||
# "memo": self.generate_memo(scraped_data)
|
||||
# })
|
||||
# self.db_manager.update_item(product)
|
||||
# self.logger.log(f"상품 {product['product_id']} 처리 완료", level=logging.DEBUG)
|
||||
# progress = int((idx / total_products) * 100)
|
||||
# self.progress_signal.emit(progress)
|
||||
# except Exception as e:
|
||||
# self.logger.log(f"상품 {product['product_id']} 처리 중 오류: {e}", level=logging.ERROR, exc_info=True)
|
||||
|
||||
# self.progress_signal.emit(100)
|
||||
# self.logger.log("모든 상품 처리가 완료되었습니다.", level=logging.INFO)
|
||||
|
||||
|
||||
|
||||
def process_products(self, products: List[Dict]):
|
||||
total_products = len(products)
|
||||
if total_products == 0:
|
||||
self.logger.log("처리할 상품이 없습니다.", level=logging.WARNING)
|
||||
self.progress_signal.emit(100)
|
||||
return
|
||||
|
||||
self.wh_con.start_whale_Browser()
|
||||
|
||||
for idx, product in enumerate(products, start=1):
|
||||
try:
|
||||
# 만약 쇼핑렌즈 수집 옵션이 비활성화되었다면 후처리 작업을 생략합니다.
|
||||
if not self.options.get("shoppinglens"):
|
||||
self.logger.log(f"상품 {product['product_id']} 후처리 생략 (쇼핑렌즈 수집 비활성)", level=logging.DEBUG)
|
||||
continue
|
||||
|
||||
self.logger.log(f"상품 {product['product_id']} 쇼핑렌즈 검색 시작", level=logging.DEBUG)
|
||||
scraped_data = self.wh_con.search_and_parse(image_url=product['image_url'])
|
||||
if not scraped_data:
|
||||
self.logger.log(f"상품 {product['product_id']} 쇼핑렌즈 데이터 없음, 스킵", level=logging.WARNING)
|
||||
continue
|
||||
|
||||
most_common_category = self.categoryManager.find_most_common_category(scraped_data)
|
||||
self.logger.log(f"결정된 카테고리: {most_common_category}", level=logging.DEBUG)
|
||||
# 카테고리 입력 옵션에 따라 처리
|
||||
category_code = ""
|
||||
if self.options.get("category_input"):
|
||||
category_code = self.categoryManager.find_category_code(most_common_category)
|
||||
self.logger.log(f"결정된 카테고리 코드: {category_code}", level=logging.DEBUG)
|
||||
|
||||
# 기본적으로 유효성 체크
|
||||
if scraped_data:
|
||||
isvalid_category = self.categoryManager.is_category_allowed(most_common_category)
|
||||
self.logger.log(f"isvalid_category: {isvalid_category}", level=logging.DEBUG)
|
||||
product['is_valid'] = 1 if isvalid_category else 0
|
||||
|
||||
# 상품명 생성 옵션
|
||||
final_title = ""
|
||||
if self.options.get("title_generation"):
|
||||
titles = [item["title"] for item in scraped_data if "title" in item]
|
||||
final_title = self.title_manager.generate_product_name(titles, product['name'], self.banned_words, self.disallowed_words)
|
||||
self.logger.log(f"상품명 생성 완료: {final_title}", level=logging.DEBUG)
|
||||
# 마진 계산 옵션
|
||||
additional_margin = 0
|
||||
if self.options.get("margin_price"):
|
||||
additional_margin = self.calculate_additional_margin(scraped_data)
|
||||
self.logger.log(f"추가 마진 계산 결과: {additional_margin}", level=logging.DEBUG)
|
||||
tags = self.filter_and_merge_tags(scraped_data)
|
||||
product.update({
|
||||
"generated_Title": final_title,
|
||||
"category_code": category_code,
|
||||
"tags": tags,
|
||||
"margin_price": additional_margin,
|
||||
"memo": self.generate_memo(scraped_data)
|
||||
})
|
||||
self.db_manager.update_item(product)
|
||||
self.logger.log(f"상품 {product['product_id']} 처리 완료", level=logging.DEBUG)
|
||||
progress = int((idx / total_products) * 100)
|
||||
self.progress_signal.emit(progress)
|
||||
except Exception as e:
|
||||
self.logger.log(f"상품 {product['product_id']} 처리 중 오류: {e}", level=logging.ERROR, exc_info=True)
|
||||
|
||||
self.progress_signal.emit(100)
|
||||
self.logger.log("모든 상품 처리가 완료되었습니다.", level=logging.INFO)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 독립 실행 시 (테스트용)
|
||||
# if __name__ == "__main__":
|
||||
# from src.loggerModule import Logger
|
||||
|
|
|
|||
|
|
@ -0,0 +1,188 @@
|
|||
from PySide6.QtCore import Qt, QRect, QPropertyAnimation, Property, Signal, QPoint
|
||||
from PySide6.QtGui import QPainter, QColor, QFont
|
||||
from PySide6.QtWidgets import QWidget, QLabel
|
||||
|
||||
class ToggleSwitch(QWidget):
|
||||
"""
|
||||
범용 토글 스위치 위젯
|
||||
QCheckBox와 유사한 인터페이스를 제공합니다.
|
||||
"""
|
||||
# clicked 시그널 (bool 타입)
|
||||
clicked = Signal(bool)
|
||||
|
||||
# QCheckBox 호환용 stateChanged 시그널 (int 타입)
|
||||
# QCheckBox는 stateChanged에 0/2를 보내므로 변환합니다.
|
||||
stateChanged = Signal(int)
|
||||
|
||||
THEMES = {
|
||||
"default": (QColor("#CCCCCC"), QColor("#00B16A"), QColor("#E6E6E6")),
|
||||
"dark": (QColor("#444444"), QColor("#00B16A"), QColor("#888888")),
|
||||
"light": (QColor("#FFFFFF"), QColor("#007ACC"), QColor("#CCCCCC")),
|
||||
"blue": (QColor("#D0E7FF"), QColor("#005F99"), QColor("#A6C8FF")),
|
||||
}
|
||||
|
||||
def __init__(self, label: str = "", parent=None, width=50, height=25, animation_duration=250, theme="default"):
|
||||
super().__init__(parent)
|
||||
self._width = width
|
||||
self._height = height
|
||||
self.setFixedSize(self._width, self._height)
|
||||
self._checked = False
|
||||
|
||||
self._animation_duration = animation_duration
|
||||
|
||||
self._background_color = QColor("#CCCCCC")
|
||||
self._circle_color_checked = QColor("#00B16A")
|
||||
self._circle_color_unchecked = QColor("#E6E6E6")
|
||||
self.setTheme(theme)
|
||||
|
||||
self._circle_diameter = self._height
|
||||
self._circle_pos = QPoint(0, 0)
|
||||
self.animation = QPropertyAnimation(self, b"circle_pos")
|
||||
self.animation.setDuration(self._animation_duration)
|
||||
self._init_position()
|
||||
|
||||
# QCheckBox 호환: stateChanged 시그널은 클릭시 clicked 신호를 int형으로 변환하여 방출
|
||||
# (checked=True -> 2, False -> 0)
|
||||
# 아래처럼 clicked 시그널에 연결하여 stateChanged도 함께 방출하도록 합니다.
|
||||
self.clicked.connect(lambda state: self.stateChanged.emit(2 if state else 0))
|
||||
|
||||
# 라벨 텍스트 (선택 사항)
|
||||
self.label = label
|
||||
if self.label:
|
||||
self._label_widget = QLabel(self.label, self)
|
||||
self._label_widget.setAlignment(Qt.AlignCenter)
|
||||
font = QFont()
|
||||
font.setPointSize(10)
|
||||
self._label_widget.setFont(font)
|
||||
self._label_widget.setStyleSheet("background: transparent;")
|
||||
self._label_widget.setGeometry(0, 0, self._width, self._height)
|
||||
else:
|
||||
self._label_widget = None
|
||||
|
||||
@Property(QPoint)
|
||||
def circle_pos(self):
|
||||
return self._circle_pos
|
||||
|
||||
@circle_pos.setter
|
||||
def circle_pos(self, pos):
|
||||
self._circle_pos = pos
|
||||
self.update()
|
||||
|
||||
def _init_position(self):
|
||||
if self._checked:
|
||||
self._circle_pos.setX(self._width - self._circle_diameter)
|
||||
else:
|
||||
self._circle_pos.setX(0)
|
||||
self._circle_pos.setY(0)
|
||||
|
||||
def setTheme(self, theme_name):
|
||||
if isinstance(theme_name, str):
|
||||
theme = self.THEMES.get(theme_name, self.THEMES["default"])
|
||||
self._background_color, self._circle_color_checked, self._circle_color_unchecked = theme
|
||||
elif isinstance(theme_name, dict):
|
||||
self._background_color = theme_name.get("background", self._background_color)
|
||||
self._circle_color_checked = theme_name.get("checked", self._circle_color_checked)
|
||||
self._circle_color_unchecked = theme_name.get("unchecked", self._circle_color_unchecked)
|
||||
else:
|
||||
self._background_color, self._circle_color_checked, self._circle_color_unchecked = self.THEMES["default"]
|
||||
self.update()
|
||||
|
||||
def setAnimationDuration(self, duration):
|
||||
self._animation_duration = duration
|
||||
self.animation.setDuration(self._animation_duration)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == Qt.LeftButton:
|
||||
self._checked = not self._checked
|
||||
self.clicked.emit(self._checked)
|
||||
self._update_animation()
|
||||
self.update()
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def _update_animation(self):
|
||||
if self._checked:
|
||||
start = QPoint(0, 0)
|
||||
end = QPoint(self._width - self._circle_diameter, 0)
|
||||
else:
|
||||
start = QPoint(self._width - self._circle_diameter, 0)
|
||||
end = QPoint(0, 0)
|
||||
self.animation.stop()
|
||||
self.animation.setStartValue(start)
|
||||
self.animation.setEndValue(end)
|
||||
self.animation.start()
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
painter.setPen(Qt.NoPen)
|
||||
|
||||
# 위젯이 활성화(enabled) 상태인지 확인
|
||||
if not self.isEnabled():
|
||||
# 비활성화 상태일 때는 배경색과 원의 색상을 어둡게 처리
|
||||
background = self._background_color.darker(150)
|
||||
circle_color = (self._circle_color_checked if self._checked else self._circle_color_unchecked).darker(150)
|
||||
else:
|
||||
background = self._background_color
|
||||
circle_color = self._circle_color_checked if self._checked else self._circle_color_unchecked
|
||||
|
||||
# 배경 그리기
|
||||
painter.setBrush(background)
|
||||
painter.drawRoundedRect(QRect(0, 0, self._width, self._height), self._height / 2, self._height / 2)
|
||||
|
||||
# 원(circle) 그리기
|
||||
painter.setBrush(circle_color)
|
||||
painter.drawEllipse(self._circle_pos.x(), self._circle_pos.y(), self._circle_diameter, self._circle_diameter)
|
||||
|
||||
def setChecked(self, checked: bool):
|
||||
if self._checked != checked:
|
||||
self._checked = checked
|
||||
self._update_animation()
|
||||
self.update()
|
||||
|
||||
def isChecked(self) -> bool:
|
||||
return self._checked
|
||||
|
||||
def setState(self, state: bool):
|
||||
if self._checked != state:
|
||||
self._checked = state
|
||||
self._update_animation()
|
||||
self.clicked.emit(self._checked)
|
||||
self.update()
|
||||
|
||||
def toggle(self):
|
||||
self.setState(not self._checked)
|
||||
|
||||
|
||||
|
||||
# 사용 예시
|
||||
# from PySide6.QtWidgets import QApplication, QVBoxLayout, QWidget
|
||||
# from toggle_switch import ToggleSwitch
|
||||
# import sys
|
||||
|
||||
# app = QApplication(sys.argv)
|
||||
|
||||
# window = QWidget()
|
||||
# layout = QVBoxLayout(window)
|
||||
|
||||
# # 기본 테마 (default)
|
||||
# toggle1 = ToggleSwitch()
|
||||
# toggle1.clicked.connect(lambda state: print("Toggle 1:", state))
|
||||
# layout.addWidget(toggle1)
|
||||
|
||||
# # 다크 테마, 크기 및 애니메이션 지속시간 변경
|
||||
# toggle2 = ToggleSwitch(width=60, height=30, animation_duration=300, theme="dark")
|
||||
# toggle2.clicked.connect(lambda state: print("Toggle 2:", state))
|
||||
# layout.addWidget(toggle2)
|
||||
|
||||
# # 사용자 지정 테마
|
||||
# custom_theme = {
|
||||
# "background": QColor("#F0F0F0"),
|
||||
# "checked": QColor("#FF5722"),
|
||||
# "unchecked": QColor("#BDBDBD")
|
||||
# }
|
||||
# toggle3 = ToggleSwitch(theme=custom_theme)
|
||||
# toggle3.clicked.connect(lambda state: print("Toggle 3:", state))
|
||||
# layout.addWidget(toggle3)
|
||||
|
||||
# window.show()
|
||||
# sys.exit(app.exec())
|
||||
BIN
taobao_items.db
BIN
taobao_items.db
Binary file not shown.
BIN
temp_qr.png
BIN
temp_qr.png
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 29 KiB |
Loading…
Reference in New Issue