수정중

This commit is contained in:
Envy_PC 2024-11-13 17:08:37 +09:00
parent 0dca9937ef
commit d114b33fd7
51 changed files with 454 additions and 338 deletions

BIN
1products.db Normal file

Binary file not shown.

View File

View File

@ -9,24 +9,28 @@ class DatabaseManager:
self._create_tables() self._create_tables()
def _create_tables(self): def _create_tables(self):
# 상품 테이블과 검색 결과 테이블 생성
try: try:
with self.conn: with self.conn:
# products 테이블 생성
self.conn.execute(''' self.conn.execute('''
CREATE TABLE IF NOT EXISTS products ( CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT, name TEXT,
image_url TEXT, image_url TEXT,
tag TEXT, tag TEXT,
price INTER, price INTEGER,
category TEXT, category TEXT,
percent_category TEXT, percenty_category TEXT,
selected_search_result_id INTEGER, -- 선택된 search_results ID selected_search_result_id INTEGER,
saved_img_path TEXT, -- 이미지 저장 경로 필드 추가
FOREIGN KEY(selected_search_result_id) REFERENCES search_results(id) FOREIGN KEY(selected_search_result_id) REFERENCES search_results(id)
) )
''') ''')
# search_results 테이블에 고유 ID 추가
self.conn.execute(''' self.conn.execute('''
CREATE TABLE IF NOT EXISTS search_results ( CREATE TABLE IF NOT EXISTS search_results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_id INTEGER, product_id INTEGER,
title TEXT, title TEXT,
source TEXT, source TEXT,
@ -34,7 +38,8 @@ class DatabaseManager:
imgurl TEXT, imgurl TEXT,
encrypted_url TEXT, encrypted_url TEXT,
original_url TEXT, original_url TEXT,
is_selected BOOLEAN DEFAULT 0, -- 선택 여부 필드 saved_img_path TEXT, -- 이미지 저장 경로 필드 추가
is_selected BOOLEAN DEFAULT 0,
FOREIGN KEY (product_id) REFERENCES products (id) FOREIGN KEY (product_id) REFERENCES products (id)
) )
''') ''')
@ -49,11 +54,10 @@ class DatabaseManager:
with self.conn: with self.conn:
for product in products: for product in products:
cursor = self.conn.execute(''' cursor = self.conn.execute('''
INSERT INTO products (name, image_url, tag, price, category, percent_category) INSERT INTO products (name, image_url, tag, price, category, percenty_category, saved_img_path)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
''', (product['name'], product['image_url'], product['tag'], product['price'], product['category'], product['percent_category'])) ''', (product['name'], product['image_url'], product['tag'], product['price'], product['category'], product['percenty_category'], product.get('saved_img_path')))
# 각 삽입된 행의 product_id 추출
product_id = cursor.lastrowid product_id = cursor.lastrowid
product_ids.append(product_id) product_ids.append(product_id)
self.logger.debug(f"Inserted product with ID: {product_id}") self.logger.debug(f"Inserted product with ID: {product_id}")
@ -64,36 +68,49 @@ class DatabaseManager:
self.logger.error(f"Error inserting products: {e}", exc_info=True) self.logger.error(f"Error inserting products: {e}", exc_info=True)
return [] return []
def update_product_image_path(self, product_id, image_path):
"""특정 상품의 saved_img_path 필드만 업데이트"""
try:
with self.conn:
self.conn.execute('''
UPDATE products
SET saved_img_path = ?
WHERE id = ?
''', (image_path, product_id))
self.logger.info(f"Updated saved_img_path for Product ID [{product_id}]")
except Exception as e:
self.logger.error(f"Error updating saved_img_path for Product ID [{product_id}]: {e}", exc_info=True)
def insert_search_results(self, product_id, search_results): def insert_search_results(self, product_id, search_results):
# 검색 결과 중 상위 5개만 DB에 저장 # 검색 결과 중 상위 5개만 DB에 저장
try: try:
top_results = search_results[:5] # 상위 5개로 제한 top_results = search_results[:5] # 상위 5개로 제한
with self.conn: with self.conn:
self.conn.executemany(''' self.conn.executemany('''
INSERT INTO search_results (product_id, title, source, price, imgurl, encrypted_url, original_url) INSERT INTO search_results (product_id, title, source, price, imgurl, saved_img_path, encrypted_url, original_url)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', [(product_id, result['title'], result['source'], result['price'], result['imgurl'], result['encrypted_url'], result['original_url']) for result in top_results]) ''', [(product_id, result['title'], result['source'], result['price'], result['imgurl'], result['saved_img_path'], result['encrypted_url'], result['original_url']) for result in top_results])
self.logger.info(f"Product ID [{product_id}]: {len(top_results)} search results inserted into database.") self.logger.info(f"Product ID [{product_id}]: {len(top_results)} search results inserted into database.")
except Exception as e: except Exception as e:
self.logger.error(f"Error inserting search results for Product ID [{product_id}]: {e}", exc_info=True) self.logger.error(f"Error inserting search results for Product ID [{product_id}]: {e}", exc_info=True)
def select_search_result_for_product(self, product_id, search_result_id): def select_search_result_for_product(self, product_id, search_result_id):
# products 테이블에 선택된 search_result_id 업데이트
try: try:
with self.conn: with self.conn:
# products 테이블의 selected_search_result_id 업데이트
self.conn.execute(''' self.conn.execute('''
UPDATE products UPDATE products
SET selected_search_result_id = ? SET selected_search_result_id = ?
WHERE id = ? WHERE id = ?
''', (search_result_id, product_id)) ''', (search_result_id, product_id))
# search_results 테이블의 is_selected 업데이트 # search_results 테이블의 선택된 항목 업데이트
self.conn.execute(''' self.conn.execute('''
UPDATE search_results UPDATE search_results
SET is_selected = 1 SET is_selected = CASE WHEN id = ? THEN 1 ELSE 0 END
WHERE id = ? WHERE product_id = ?
''', (search_result_id,)) ''', (search_result_id, product_id))
self.logger.info(f"Product ID [{product_id}] selected search result ID [{search_result_id}].") self.logger.info(f"Product ID [{product_id}] selected search result ID [{search_result_id}].")
except Exception as e: except Exception as e:
self.logger.error(f"Error selecting search result for product ID [{product_id}]: {e}", exc_info=True) self.logger.error(f"Error selecting search result for product ID [{product_id}]: {e}", exc_info=True)
@ -123,7 +140,7 @@ class DatabaseManager:
try: try:
query = ''' query = '''
SELECT p.name as 상품명, sr.original_url as 원본링크, sr.price as 가격, SELECT p.name as 상품명, sr.original_url as 원본링크, sr.price as 가격,
sr.source as 출처, p.percent_category as 퍼센티카테고리, sr.source as 출처, p.percenty_category as 퍼센티카테고리,
sr.title as 이미지검색결과의상품명, sr.imgurl as 이미지URL sr.title as 이미지검색결과의상품명, sr.imgurl as 이미지URL
FROM products p FROM products p
LEFT JOIN search_results sr ON p.id = sr.product_id LEFT JOIN search_results sr ON p.id = sr.product_id

View File

@ -19,8 +19,6 @@ class ImageDownloader:
"Cache-Control": "max-age=0" "Cache-Control": "max-age=0"
} }
def download_image(self, image_url, product_id): def download_image(self, image_url, product_id):
# 임시 폴더에 이미지 다운로드 # 임시 폴더에 이미지 다운로드
try: try:
@ -35,3 +33,19 @@ class ImageDownloader:
self.logger.warning(f"Failed to download image: {e}", exc_info=True) self.logger.warning(f"Failed to download image: {e}", exc_info=True)
def download_image_for_searchResult(self, img_url, product_id, index):
"""이미지 URL에서 이미지를 다운로드하고 로컬 경로를 반환합니다."""
try:
response = requests.get(img_url, stream=True)
if response.status_code == 200:
file_path = os.path.join(self.temp_folder, f"{product_id}_{index}.jpg")
with open(file_path, "wb") as file:
for chunk in response.iter_content(1024):
file.write(chunk)
self.logger.info(f"Downloaded image for product ID {product_id} at index {index}")
return file_path
else:
self.logger.warning(f"Failed to download image from {img_url}, status code {response.status_code}")
except Exception as e:
self.logger.error(f"Error downloading image for search result: {e}")
return None

BIN
img/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 KiB

BIN
img/10.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

BIN
img/10_0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
img/10_3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
img/1_1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
img/1_2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
img/1_3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
img/2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
img/3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
img/4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

BIN
img/4_2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
img/5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

BIN
img/5_0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
img/5_2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
img/5_3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
img/6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
img/6_1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
img/6_2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
img/6_3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

BIN
img/7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

BIN
img/7_0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
img/7_1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
img/7_2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
img/7_3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
img/8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
img/8_1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
img/9.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
img/9_0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
img/9_1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
img/9_2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
img/9_3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -4,10 +4,10 @@ import re
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from playwright.sync_api import sync_playwright from playwright.sync_api import sync_playwright
class BaiduImageSearcher: class BaiduImageSearcher:
def __init__(self, sources=None, logger=None): def __init__(self, sources=None, image_downloader=None, logger=None):
self.filtered_sources = set(sources) if sources else {'淘宝', 'tmall', '1688'} self.filtered_sources = set(sources) if sources else {'淘宝', 'tmall', '1688'}
self.image_downloader = image_downloader
self.logger = logger self.logger = logger
self.browser = None self.browser = None
self.page = None self.page = None
@ -35,6 +35,8 @@ class BaiduImageSearcher:
"Cache-Control": "max-age=0" "Cache-Control": "max-age=0"
}) })
self.initial_url = 'https://graph.baidu.com/pcpage/index?tpl_from=pc' self.initial_url = 'https://graph.baidu.com/pcpage/index?tpl_from=pc'
# upload_button_xpath = '//*[@id="app"]/div/div[1]/div[7]/div/span[1]/span[1]' # upload_button_xpath = '//*[@id="app"]/div/div[1]/div[7]/div/span[1]/span[1]'
@ -82,18 +84,15 @@ class BaiduImageSearcher:
# 첫 번째 검색과 이후 검색의 선택자를 다르게 설정 # 첫 번째 검색과 이후 검색의 선택자를 다르게 설정
# if self.is_first_search: # if self.is_first_search:
# if self.is_first_search: # if self.is_first_search:
# self.logger.info("is_first_search") self.logger.info("is_first_search")
# upload_button_xpath = '//*[@id="app"]/div/div[1]/div[7]/div/span[1]/span[1]' upload_button_xpath = '//*[@id="app"]/div/div[1]/div[7]/div/span[1]/span[1]'
# upload_input_xpath = '//*[@id="app"]/div/div[1]/div[7]/div/div/div[2]/div[2]/div/form/input' upload_input_xpath = '//*[@id="app"]/div/div[1]/div[7]/div/div/div[2]/div[2]/div/form/input'
# # self.is_first_search = False # 이후 검색에서는 일반 선택자를 사용 self.is_first_search = False # 이후 검색에서는 일반 선택자를 사용
# else: # else:
# self.logger.info("another search") # self.logger.info("another search")
# upload_button_xpath = '//*[@id="app"]/div/div[1]/div/div[1]/div/div/div[1]/span[1]/span[1]' # upload_button_xpath = '//*[@id="app"]/div/div[1]/div/div[1]/div/div/div[1]/span[1]/span[1]'
# upload_input_xpath = '//*[@id="app"]/div/div[1]/div/div[1]/div/div/div[1]/div/div[2]/div[2]/div/form/input' # upload_input_xpath = '//*[@id="app"]/div/div[1]/div/div[1]/div/div/div[1]/div/div[2]/div[2]/div/form/input'
upload_button_xpath = '//*[@id="app"]/div/div[1]/div[7]/div/span[1]/span[1]'
upload_input_xpath = '//*[@id="app"]/div/div[1]/div[7]/div/div/div[2]/div[2]/div/form/input'
# 이미지 업로드 버튼 클릭 및 파일 업로드 # 이미지 업로드 버튼 클릭 및 파일 업로드
self.page.wait_for_selector(upload_button_xpath) self.page.wait_for_selector(upload_button_xpath)
self.page.click(upload_button_xpath) self.page.click(upload_button_xpath)
@ -129,16 +128,14 @@ class BaiduImageSearcher:
return False return False
return True return True
def extract_product_data(self): def extract_product_data(self, product_id):
# 검색 결과 페이지에서 JSON 데이터 추출 # 검색 결과 페이지에서 JSON 데이터 추출
if self.check_capcha(): if self.check_capcha():
return False return False
content = None content = None
self.logger.info("검색 결과 페이지에서 JSON 데이터 추출") self.logger.info("검색 결과 페이지에서 JSON 데이터 추출")
content = self.page.content() content = self.page.content()
self.page.go_back() # 뒤로 가기
soup = BeautifulSoup(content, 'html.parser') soup = BeautifulSoup(content, 'html.parser')
script_tag = soup.select_one("html > head > script:nth-of-type(2)") script_tag = soup.select_one("html > head > script:nth-of-type(2)")
@ -154,7 +151,7 @@ class BaiduImageSearcher:
product_info = [] product_info = []
# 필터링된 출처에 따라 데이터 추출 # 필터링된 출처에 따라 데이터 추출
for card in data: for idx, card in enumerate(data):
if card.get("cardName") == "product": if card.get("cardName") == "product":
products = card["tplData"]["list"] products = card["tplData"]["list"]
for product in products: for product in products:
@ -167,16 +164,21 @@ class BaiduImageSearcher:
# 출처 필터링 # 출처 필터링
if source in self.filtered_sources: if source in self.filtered_sources:
original_url = self.get_original_url(buyurl) original_url = self.get_original_url(buyurl)
# 이미지 다운로드 및 saved_img_path 설정
saved_img_path = self.image_downloader.download_image_for_searchResult(imgurl, product_id=product_id, index=products.index(product))
product_info.append({ product_info.append({
"title": title, "title": title,
"source": source, "source": source,
"price": price, "price": price,
"imgurl": imgurl, "imgurl": imgurl,
"saved_img_path": saved_img_path,
"encrypted_url": buyurl, "encrypted_url": buyurl,
"original_url": original_url "original_url": original_url
}) })
self.logger.info("product_info 추출 완료") self.logger.info("product_info 추출 완료")
self.logger.info(f"추출된 정보 \n{product_info}") self.logger.info(f"{product_info}")
return product_info return product_info
except json.JSONDecodeError as e: except json.JSONDecodeError as e:

13
main.py
View File

@ -3,19 +3,28 @@ from mainProcessor import MainProcessor
from logger_module import setup_logger from logger_module import setup_logger
import logging import logging
from PySide6.QtWidgets import QApplication, QLabel, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QDialog
import sys
if __name__ == "__main__": if __name__ == "__main__":
# 로그 설정 # 로그 설정
logger = setup_logger(log_file='app.log', log_level=logging.DEBUG) logger = setup_logger(log_file='app.log', log_level=logging.DEBUG)
# 폴더 및 파일 경로 설정 # 폴더 및 파일 경로 설정
excel_folder = os.path.join(os.getcwd(), 'xls') excel_folder = os.path.join(os.getcwd(), 'xls')
img_folder = os.path.join(os.getcwd(), 'img')
db_path = os.path.join(os.getcwd(), 'products.db') db_path = os.path.join(os.getcwd(), 'products.db')
temp_folder = os.path.join(os.getcwd(), 'temp')
# products.db 파일이 이미 존재할 경우 삭제
if os.path.exists(db_path):
os.remove(db_path)
print(f"{db_path} 파일이 삭제되었습니다.")
# 메인 프로세서 실행 # 메인 프로세서 실행
logger.info("Starting main process.") logger.info("Starting main process.")
app = QApplication(sys.argv)
processor = MainProcessor(excel_folder, db_path, temp_folder, logger) processor = MainProcessor(excel_folder, db_path, img_folder, logger)
processor.process_all_products() processor.process_all_products()
logger.info("process_all_products Completed.") logger.info("process_all_products Completed.")

View File

@ -3,16 +3,36 @@ from xlsReader import ExcelReader
from databaseManager import DatabaseManager from databaseManager import DatabaseManager
from imageDownloader import ImageDownloader from imageDownloader import ImageDownloader
from imgSearcher import BaiduImageSearcher from imgSearcher import BaiduImageSearcher
from resultDiag import ProductViewer
import pandas as pd
from datetime import datetime
class MainProcessor: class MainProcessor:
def __init__(self, excel_folder, db_path, temp_folder, logger): def __init__(self, excel_folder, db_path, img_folder, logger):
self.logger = logger self.logger = logger
self.excel_reader = ExcelReader(excel_folder, logger) self.excel_reader = ExcelReader(excel_folder, logger)
self.db_manager = DatabaseManager(db_path, logger) self.db_manager = DatabaseManager(db_path, logger)
self.image_downloader = ImageDownloader(temp_folder, logger) self.logger.debug("1")
self.image_searcher = BaiduImageSearcher(sources=['淘宝', 'tmall', '1688'], logger=logger) self.resultViewer = ProductViewer(db_path)
self.logger.debug("2")
self.image_downloader = ImageDownloader(img_folder, logger)
self.image_searcher = BaiduImageSearcher(sources=['淘宝', 'tmall', '1688'], image_downloader=self.image_downloader, logger=logger)
self.logger.info("MainProcessor initialized.") self.logger.info("MainProcessor initialized.")
def clean_up_files(self):
img_folder = os.path.join(os.getcwd(), 'img')
xls_folder = os.path.join(os.getcwd(), 'xls')
for folder in [img_folder, xls_folder]:
for filename in os.listdir(folder):
file_path = os.path.join(folder, filename)
try:
if os.path.isfile(file_path):
os.remove(file_path)
self.logger.info(f"Deleted file: {file_path}")
except Exception as e:
self.logger.error(f"Error deleting file {file_path}: {e}")
def process_all_products(self): def process_all_products(self):
# 모든 엑셀 파일을 읽어와 DB에 저장 # 모든 엑셀 파일을 읽어와 DB에 저장
products = self.excel_reader.read_excel_files() products = self.excel_reader.read_excel_files()
@ -20,30 +40,6 @@ class MainProcessor:
self.image_searcher.start_browser() self.image_searcher.start_browser()
# # 각 상품에 대해 이미지 검색 수행
# for product, product_id in zip(products, product_ids):
# try:
# if product_id is None:
# self.logger.error("Failed to insert product into database.")
# continue
# # 이미지 다운로드 및 검색 실행
# image_path = self.image_downloader.download_image(product['image_url'], product_id)
# is_success_upload_image = self.image_searcher.upload_image(image_path)
# is_success_expand_results = self.image_searcher.expand_results()
# # 검색 결과를 추출하여 DB에 저장
# search_results = self.image_searcher.extract_product_data()
# self.db_manager.insert_search_results(product_id, search_results)
# os.remove(image_path)
# self.logger.debug(f"Processed product ID: {product_id}")
# time.sleep(1)
# except Exception as e:
# self.logger.warning(f"Failed to process product ID: {product_id} - {e}", exc_info=True)
# 각 상품에 대해 이미지 검색 수행 # 각 상품에 대해 이미지 검색 수행
for product, product_id in zip(products, product_ids): for product, product_id in zip(products, product_ids):
try: try:
@ -53,6 +49,9 @@ class MainProcessor:
# 이미지 다운로드 및 검색 실행 # 이미지 다운로드 및 검색 실행
image_path = self.image_downloader.download_image(product['image_url'], product_id) image_path = self.image_downloader.download_image(product['image_url'], product_id)
product['saved_img_path'] = image_path # 이미지 경로 설정
# self.db_manager.insert_products([product])
self.db_manager.update_product_image_path(product_id, image_path)
# 재시도 횟수 설정 # 재시도 횟수 설정
max_retries = 3 max_retries = 3
@ -82,7 +81,7 @@ class MainProcessor:
continue # 재시도 시 루프를 다시 시작 continue # 재시도 시 루프를 다시 시작
# 검색 결과 추출 # 검색 결과 추출
search_results = self.image_searcher.extract_product_data() search_results = self.image_searcher.extract_product_data(product_id)
# if search_results == []: # 빈 리스트일 경우만 실패로 간주 # if search_results == []: # 빈 리스트일 경우만 실패로 간주
# attempt += 1 # attempt += 1
# self.logger.warning(f"Extract product data failed for Product ID [{product_id}]. Retry {attempt}/{max_retries}") # self.logger.warning(f"Extract product data failed for Product ID [{product_id}]. Retry {attempt}/{max_retries}")
@ -101,8 +100,9 @@ class MainProcessor:
self.logger.debug(f"Insert DB: {product_id}") self.logger.debug(f"Insert DB: {product_id}")
self.db_manager.insert_search_results(product_id, search_results) self.db_manager.insert_search_results(product_id, search_results)
# input("로그를 확인한 후 아무 키나 눌러서 계속하세요...")
os.remove(image_path) # os.remove(image_path)
self.logger.debug(f"Processed product ID: {product_id}") self.logger.debug(f"Processed product ID: {product_id}")
time.sleep(1) time.sleep(1)
@ -112,16 +112,47 @@ class MainProcessor:
def show_results(self): def show_results(self):
# 검색 결과 출력 # 검색 결과 출력
try: try:
with self.db_manager.conn as conn: self.resultViewer.show()
cursor = conn.execute('''
SELECT p.name, s.title, s.source, s.price, s.original_url
FROM products p
JOIN search_results s ON p.id = s.product_id
''')
for row in cursor:
print(f"상품명: {row[0]}, 검색 결과: {row[1]}, 출처: {row[2]}, 가격: {row[3]}, 원본 링크: {row[4]}")
self.logger.debug(f"show_results Completed") self.logger.debug(f"show_results Completed")
except Exception as e: except Exception as e:
self.logger.warning(f"Failed to show_results - {e}", exc_info=True) self.logger.warning(f"Failed to show_results - {e}", exc_info=True)
def export_to_xls(self):
# 현재 날짜와 시간을 사용하여 파일 이름 생성
date_str = datetime.now().strftime('%Y%m%d')
file_index = 1
try:
with self.db_manager.conn as conn:
cursor = conn.execute('''
SELECT s.original_url, p.name, p.tag, p.percenty_category
FROM products p
JOIN search_results s ON p.id = s.product_id
''')
data = cursor.fetchall()
# 50개씩 데이터를 나누어 출력
for i in range(0, len(data), 50):
# 50개 데이터를 추출하여 DataFrame으로 변환
chunk = data[i:i + 50]
df = pd.DataFrame(chunk, columns=["original_url", "name", "tag", "percenty_category"])
# 엑셀 파일 생성
excel_filename = f"출력데이터_{date_str}_{file_index}.xlsx"
with pd.ExcelWriter(excel_filename) as writer:
df.to_excel(writer, index=False, startrow=3, startcol=1, sheet_name="multi_ss")
# 셀 배치
worksheet = writer.sheets['multi_ss']
for row_idx, row_data in enumerate(chunk, start=4):
worksheet[f"B{row_idx}"] = row_data[0] # original_url
worksheet[f"C{row_idx}"] = row_data[1] # name
worksheet[f"F{row_idx}"] = row_data[2] # tag
worksheet[f"G{row_idx}"] = row_data[3] # percent_category
self.logger.info(f"{excel_filename} 파일에 데이터 50개 저장 완료.")
file_index += 1
except Exception as e:
self.logger.error(f"Error exporting to Excel: {e}", exc_info=True)

View File

@ -28,7 +28,8 @@ class ProductCard(QWidget):
self.info2_label.setStyleSheet("font-weight: bold; font-size: 16px;") self.info2_label.setStyleSheet("font-weight: bold; font-size: 16px;")
self.image_label = QLabel() self.image_label = QLabel()
self.image_label.setFixedSize(250, 250) # self.image_label.setFixedWidth(250)
self.image_label.setFixedSize(200, 250)
self.name_label = QLabel("상품명:") self.name_label = QLabel("상품명:")
self.name_value = QLabel("") self.name_value = QLabel("")
@ -68,7 +69,7 @@ class ProductCard(QWidget):
self.cat_value.setText("") self.cat_value.setText("")
self.image_label.clear() self.image_label.clear()
def set_data(self, name, price, tag, cat, img_url): def set_data(self, name, price, tag, cat, product_img_path):
"""카드에 상품 데이터를 설정합니다.""" """카드에 상품 데이터를 설정합니다."""
self.name_value.setText(name) self.name_value.setText(name)
# self.price_value.setText(str(price)) # self.price_value.setText(str(price))
@ -78,14 +79,19 @@ class ProductCard(QWidget):
self.cat_value.setText(cat) self.cat_value.setText(cat)
# self.price_value.setText(str(price)) # self.price_value.setText(str(price))
# 이미지 설정
pixmap = QPixmap() pixmap = QPixmap(product_img_path)
img_data = self.download_image_data(img_url) self.image_label.setPixmap(pixmap.scaled(self.image_label.size(), Qt.KeepAspectRatio))
if img_data:
pixmap.loadFromData(img_data)
self.image_label.setPixmap(pixmap.scaled(self.image_label.size(), Qt.KeepAspectRatio)) # # 이미지 설정
else: # pixmap = QPixmap()
self.image_label.clear() # img_data = self.download_image_data(img_url)
# if img_data:
# pixmap.loadFromData(img_data)
# self.image_label.setPixmap(pixmap.scaled(self.image_label.size(), Qt.KeepAspectRatio))
# else:
# self.image_label.clear()
def download_image_data(self, img_url): def download_image_data(self, img_url):
"""이미지 URL에서 데이터를 다운로드합니다.""" """이미지 URL에서 데이터를 다운로드합니다."""

Binary file not shown.

View File

@ -5,10 +5,16 @@ import sys
from productCard import ProductCard # ProductCard 클래스 임포트 from productCard import ProductCard # ProductCard 클래스 임포트
from searchResultCard import SearchResultCard # SearchResultCard 클래스 임포트 from searchResultCard import SearchResultCard # SearchResultCard 클래스 임포트
import pandas as pd
from datetime import datetime
class ProductViewer(QWidget): class ProductViewer(QWidget):
def __init__(self, db_path): def __init__(self, db_path):
super().__init__() super().__init__()
self.conn = sqlite3.connect(db_path) self.conn = sqlite3.connect(db_path)
self.cursor = self.conn.cursor() # 여기에 cursor를 올바르게 초기화
self.product_index = 0 self.product_index = 0
self.products = self.load_products() self.products = self.load_products()
self.total_products = len(self.products) self.total_products = len(self.products)
@ -16,7 +22,7 @@ class ProductViewer(QWidget):
# UI 초기화 # UI 초기화
self.init_ui() self.init_ui()
self.load_product_data() # self.load_product_data()
def init_ui(self): def init_ui(self):
self.setWindowTitle("Product Viewer") self.setWindowTitle("Product Viewer")
@ -30,7 +36,7 @@ class ProductViewer(QWidget):
self.middle_layout = QHBoxLayout() self.middle_layout = QHBoxLayout()
self.search_result_cards = [SearchResultCard(i+1) for i in range(4)] self.search_result_cards = [SearchResultCard(i+1) for i in range(4)]
for card in self.search_result_cards: for card in self.search_result_cards:
self.middle_layout.addWidget(card, 2) self.middle_layout.addWidget(card,2)
# card.setFixedHeight(300) # 각 카드의 크기를 고정 # card.setFixedHeight(300) # 각 카드의 크기를 고정
main_layout.addLayout(self.middle_layout) main_layout.addLayout(self.middle_layout)
@ -55,34 +61,42 @@ class ProductViewer(QWidget):
def load_products(self): def load_products(self):
# DB에서 products 테이블 데이터를 로드 # DB에서 products 테이블 데이터를 로드
cursor = self.conn.cursor()
cursor.execute("SELECT * FROM products") self.cursor.execute("SELECT * FROM products")
products = cursor.fetchall() products = self.cursor.fetchall()
return products return products
def load_product_data(self): def load_product_data(self):
# 현재 상품 데이터를 로드하여 ProductCard에 설정 # 현재 상품 데이터를 로드하여 ProductCard에 설정
product = self.products[self.product_index] product = self.products[self.product_index]
product_id = product[0] # products 테이블의 ID
product_name = product[1] product_name = product[1]
product_img_url = product[2] product_img_path = product[8]
# print(f"product_img_path:{product_img_path}")
product_tag = product[3] product_tag = product[3]
product_price = product[4] product_price = product[4]
product_cat = product[5] product_cat = product[5]
self.product_card.set_data(name=product_name, price=product_price, tag=product_tag, cat=product_cat, img_url=product_img_url) self.product_card.set_data(
name=product_name,
price=product_price,
tag=product_tag,
cat=product_cat,
product_img_path=product_img_path
)
# SearchResultCard 초기화 및 데이터 로드 # SearchResultCard 초기화 및 데이터 설정
self.reset_search_result_cards() # 이전 카드 데이터 리셋 search_results = self.load_search_results(product_id)
search_results = self.load_search_results(product[0])
for i, result in enumerate(search_results): for i, result in enumerate(search_results):
if i < len(self.search_result_cards): if i < len(self.search_result_cards):
title = result[1] title, source, price, saved_img_path, is_selected, search_result_id = result[1], result[2], result[3], result[4], result[5], result[0]
price = result[3] self.search_result_cards[i].set_data(
source = result[2] name=title, price=price, source=source, saved_img_path=saved_img_path, is_selected=is_selected
img_url = result[4] )
is_selected = result[5] # DB에서 is_selected 값을 가져옴 self.search_result_cards[i].product_id = product_id
self.search_result_cards[i].set_data(name=title, price=price, source=source, img_url=img_url, is_selected=is_selected, cursor=self.cursor) self.search_result_cards[i].search_result_id = search_result_id
self.search_result_cards[i].cursor = self.cursor
# 페이지 버튼 텍스트 업데이트 # 페이지 버튼 텍스트 업데이트
self.page_button.setText(f"{self.product_index + 1}/{self.total_products}") self.page_button.setText(f"{self.product_index + 1}/{self.total_products}")
@ -94,17 +108,21 @@ class ProductViewer(QWidget):
def load_search_results(self, product_id): def load_search_results(self, product_id):
# DB에서 search_results 테이블의 데이터를 로드 # DB에서 search_results 테이블의 데이터를 로드
self.cursor.execute("SELECT * FROM search_results WHERE product_id=?", (product_id,)) # self.cursor.execute("SELECT * FROM search_results WHERE product_id=?", (product_id,))
self.cursor.execute("SELECT id, title, source, price, saved_img_path, is_selected FROM search_results WHERE product_id=?", (product_id,))
return self.cursor.fetchall() return self.cursor.fetchall()
def previous_product(self): def previous_product(self):
if self.product_index > 0: if self.product_index > 0:
self.product_index -= 1 self.product_index -= 1
self.reset_search_result_cards()
self.load_product_data() self.load_product_data()
def next_product(self): def next_product(self):
if self.product_index < self.total_products - 1: if self.product_index < self.total_products - 1:
self.product_index += 1 self.product_index += 1
self.reset_search_result_cards()
self.load_product_data() self.load_product_data()
def show_product_dialog(self): def show_product_dialog(self):
@ -127,13 +145,19 @@ class ProductViewer(QWidget):
elif event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter: elif event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
self.show_product_dialog() self.show_product_dialog()
def select_card(self, index, cursor): def select_card(self, index):
# 선택된 카드의 UI 갱신 및 데이터베이스 업데이트 # 선택된 카드의 UI 갱신 및 데이터베이스 업데이트
print(f"{index}번째 카드 선택")
for i, card in enumerate(self.search_result_cards): for i, card in enumerate(self.search_result_cards):
if i == index: if i == index:
card.set_selected(True) print(f"i = {i} | index = {index} - True")
print(f"card.index : {card.index}")
card.update_selection(True) # 선택 상태로 업데이트
else: else:
card.set_selected(False) print(f"i = {i} | index = {index} - False")
print(f"card.index : {card.index}")
card.update_selection(False) # 선택 해제 상태로 업데이트
class ProductDialog(QDialog): class ProductDialog(QDialog):
def __init__(self, parent=None): def __init__(self, parent=None):

View File

@ -1,208 +0,0 @@
from PySide6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QLabel, QPushButton, QDialog, QMessageBox
)
from PySide6.QtGui import QPixmap, QKeyEvent
from PySide6.QtCore import Qt
import sys
class ProductSelectionApp(QWidget):
def __init__(self, products, search_results, logger):
super().__init__()
self.products = products # products 테이블의 데이터를 포함한 리스트
self.search_results = search_results # search_results 테이블의 데이터를 포함한 리스트
self.logger = logger
self.current_product_index = 0
self.selected_card_index = None
self.setWindowTitle("상품 선택 GUI")
self.resize(800, 600)
self.init_ui()
self.update_ui()
def init_ui(self):
# 전체 레이아웃
self.layout = QVBoxLayout()
# 상단 레이아웃 - 제품 정보
self.top_layout = QHBoxLayout()
self.image_label = QLabel() # 이미지 표시용
self.product_info_label = QLabel() # 상품 정보 표시용
self.top_layout.addWidget(self.image_label)
self.top_layout.addWidget(self.product_info_label)
self.layout.addLayout(self.top_layout, stretch=45)
# 중간 레이아웃 - 검색 결과
self.middle_layout = QHBoxLayout()
self.result_cards = []
for i in range(5): # 최대 5개의 검색 결과
card_layout = QGridLayout()
card_widget = QWidget()
card_widget.setLayout(card_layout)
# 상품 카드에 표시할 위젯 구성
card_number = QLabel(f"{i + 1}")
card_number.setAlignment(Qt.AlignCenter)
card_image = QLabel() # 이미지 표시용
card_title = QLabel("상품명:")
card_title_value = QLabel()
card_price = QLabel("가격:")
card_price_value = QLabel()
card_url = QLabel("원본URL:")
card_url_value = QLabel()
card_status = QLabel("미선택")
card_status.setAlignment(Qt.AlignCenter)
# QGridLayout에 위젯 배치
card_layout.addWidget(card_number, 0, 0, 1, 2)
card_layout.addWidget(card_image, 1, 0, 1, 2)
card_layout.addWidget(card_title, 2, 0)
card_layout.addWidget(card_title_value, 2, 1)
card_layout.addWidget(card_price, 3, 0)
card_layout.addWidget(card_price_value, 3, 1)
card_layout.addWidget(card_url, 4, 0)
card_layout.addWidget(card_url_value, 4, 1)
card_layout.addWidget(card_status, 5, 0, 1, 2)
# 카드 레이아웃 추가
self.middle_layout.addWidget(card_widget)
self.result_cards.append({
"widget": card_widget,
"number": card_number,
"image": card_image,
"title": card_title_value,
"price": card_price_value,
"url": card_url_value,
"status": card_status
})
self.layout.addLayout(self.middle_layout, stretch=45)
# 하단 레이아웃 - 탐색 버튼 및 상태
self.bottom_layout = QHBoxLayout()
self.prev_button = QPushButton("")
self.prev_button.clicked.connect(self.prev_product)
self.status_button = QPushButton("1/1")
self.status_button.clicked.connect(self.show_db_dialog)
self.next_button = QPushButton("")
self.next_button.clicked.connect(self.next_product)
self.bottom_layout.addWidget(self.prev_button)
self.bottom_layout.addWidget(self.status_button)
self.bottom_layout.addWidget(self.next_button)
self.layout.addLayout(self.bottom_layout, stretch=10)
self.setLayout(self.layout)
def update_ui(self):
# 제품 정보 업데이트
product = self.products[self.current_product_index]
self.product_info_label.setText(f"상품명: {product['name']}\n가격: {product['price']}\n카테고리: {product['category']}\n태그: {product['tag']}")
# 상단 이미지 설정
pixmap = QPixmap() # 실제 이미지를 로드해서 사용
self.image_label.setPixmap(pixmap)
# 중간 검색 결과 카드 설정
results = self.search_results.get(product['id'], [])
for i, card in enumerate(self.result_cards):
if i < len(results):
result = results[i]
card["title"].setText(result['title'])
card["price"].setText(result['price'])
card["url"].setText(result['original_url'])
pixmap = QPixmap() # 실제 imgurl 로드해서 사용
card["image"].setPixmap(pixmap)
card["status"].setText("미선택")
card["status"].setStyleSheet("color: black; font-weight: bold;")
else:
card["widget"].hide()
self.status_button.setText(f"{self.current_product_index + 1}/{len(self.products)}")
self.update_navigation_buttons()
def update_navigation_buttons(self):
self.prev_button.setEnabled(self.current_product_index > 0)
self.next_button.setEnabled(self.current_product_index < len(self.products) - 1)
def prev_product(self):
if self.current_product_index > 0:
self.current_product_index -= 1
self.update_ui()
def next_product(self):
if self.current_product_index < len(self.products) - 1:
self.current_product_index += 1
self.update_ui()
def show_db_dialog(self):
dialog = QDialog(self)
dialog.setWindowTitle("DB 조회")
dialog.setGeometry(100, 100, 400, 300)
layout = QVBoxLayout()
layout.addWidget(QLabel("DB 내용 표시"))
dialog.setLayout(layout)
dialog.exec_()
def keyPressEvent(self, event: QKeyEvent):
if event.key() in [Qt.Key_1, Qt.Key_2, Qt.Key_3, Qt.Key_4, Qt.Key_5]:
index = event.key() - Qt.Key_1
if index < len(self.result_cards) and self.result_cards[index]["widget"].isVisible():
self.select_card(index)
elif event.key() == Qt.Key_Left:
self.prev_product()
elif event.key() == Qt.Key_Right:
self.next_product()
else:
QMessageBox.warning(self, "경고", "유효하지 않은 키입니다.", QMessageBox.Ok, QMessageBox.Ok)
def select_card(self, index):
# 선택된 카드 업데이트
for i, card in enumerate(self.result_cards):
if i == index:
card["status"].setText("선택")
card["status"].setStyleSheet("color: red; font-weight: bold;")
self.selected_card_index = index
else:
card["status"].setText("미선택")
card["status"].setStyleSheet("color: black; font-weight: bold;")
if __name__ == "__main__":
app = QApplication(sys.argv)
# 샘플 데이터
products = [
{"id": 1, "name": "앞유리 썬팅 필름", "price": 26800, "category": "생활/건강-자동차용품", "tag": "썬팅필름"},
{"id": 2, "name": "집게형 속눈썹 고데기", "price": 26900, "category": "화장품/미용-뷰티소품", "tag": "아이소품"},
{"id": 3, "name": "실험실 주사기 펌프", "price": 118200, "category": "의료용품", "tag": "주사기펌프"},
{"id": 4, "name": "대파 절단기", "price": 49200, "category": "주방용품", "tag": "절단기"},
{"id": 5, "name": "여성용 스포츠 바지", "price": 18900, "category": "패션/잡화", "tag": "스포츠 바지"},
]
# 샘플 search_results 데이터
search_results = {
1: [
{"title": "UV 차단 썬팅 필름", "price": "¥44.50", "original_url": "https://item.taobao.com/item.htm?id=834618258372", "imgurl": "https://imgurl1.com/suntint.jpg"},
{"title": "자동차 창문 썬팅 필름", "price": "¥52.30", "original_url": "https://item.taobao.com/item.htm?id=925497238601", "imgurl": "https://imgurl2.com/autotint.jpg"},
],
2: [
{"title": "내추럴 속눈썹 고데기", "price": "¥33.99", "original_url": "https://item.taobao.com/item.htm?id=817709004314", "imgurl": "https://imgurl1.com/eyelash.jpg"},
],
3: [
{"title": "마이크로 스테핑기", "price": "¥1,182.00", "original_url": "https://item.taobao.com/item.htm?id=675573208809", "imgurl": "https://imgurl1.com/micropump.jpg"},
{"title": "페달 소형 실린지펌프", "price": "¥1,250.00", "original_url": "https://item.taobao.com/item.htm?id=834618254321", "imgurl": "https://imgurl2.com/pedalpump.jpg"},
{"title": "정밀 실린지펌프", "price": "¥1,320.00", "original_url": "https://item.taobao.com/item.htm?id=763797457223", "imgurl": "https://imgurl3.com/precisepump.jpg"},
],
4: [
{"title": "대파 슬라이서", "price": "¥49.50", "original_url": "https://item.taobao.com/item.htm?id=735171561855", "imgurl": "https://imgurl1.com/slicer.jpg"},
{"title": "다용도 야채 절단기", "price": "¥53.80", "original_url": "https://item.taobao.com/item.htm?id=817809254314", "imgurl": "https://imgurl2.com/vegcutter.jpg"},
],
5: [
{"title": "운동용 여성 바지", "price": "¥18.90", "original_url": "https://item.taobao.com/item.htm?id=731571561855", "imgurl": "https://imgurl1.com/sportspants.jpg"},
{"title": "여성 스포츠 쇼츠", "price": "¥21.50", "original_url": "https://item.taobao.com/item.htm?id=871709004314", "imgurl": "https://imgurl2.com/sportshorts.jpg"},
{"title": "헬스용 레깅스", "price": "¥24.30", "original_url": "https://item.taobao.com/item.htm?id=871739004318", "imgurl": "https://imgurl3.com/leggings.jpg"},
],
}
window = ProductSelectionApp(products, search_results, logger=None)
window.show()
sys.exit(app.exec())

View File

@ -4,13 +4,21 @@ from PySide6.QtCore import Qt
import requests import requests
class SearchResultCard(QWidget): class SearchResultCard(QWidget):
def __init__(self, index=1): def __init__(self, index=1, product_id=None, search_result_id=None, is_selected=False, cursor=None):
super().__init__() super().__init__()
self.index = index # 인덱스 속성 추가
self.product_id = product_id # 연결된 product의 ID
self.search_result_id = search_result_id # search_results 테이블의 고유 ID
self.cursor = cursor # DB 커서
self.init_ui()
self.load_selection_from_db(is_selected) # 초기 선택 상태 반영
def init_ui(self):
self.setFixedSize(160, 300) self.setFixedSize(160, 300)
self.layout = QGridLayout(self) self.layout = QGridLayout(self)
# UI 요소 초기화 # UI 요소 초기화
self.index_label = QLabel(str(index)) self.index_label = QLabel(str(self.index))
self.index_label.setAlignment(Qt.AlignCenter) self.index_label.setAlignment(Qt.AlignCenter)
self.index_label.setStyleSheet("font-weight: bold; font-size: 16px;") self.index_label.setStyleSheet("font-weight: bold; font-size: 16px;")
@ -51,25 +59,29 @@ class SearchResultCard(QWidget):
self.price_value.setText("") self.price_value.setText("")
self.source_value.setText("") self.source_value.setText("")
self.image_label.clear() self.image_label.clear()
self.set_selected(False, None) # 기본 선택 해제 상태로 초기화 self.select_label.setText("미선택")
self.select_label.setStyleSheet("font-weight: bold; color: black;")
self.setStyleSheet("border: 1px solid grey;")
def set_data(self, name, price, source, img_url, is_selected, cursor): def set_data(self, name, price, source, saved_img_path, is_selected):
"""카드에 검색 결과 데이터를 설정하고 선택 상태를 적용합니다.""" """카드에 검색 결과 데이터를 설정하고 선택 상태를 적용합니다."""
self.name_value.setText(name) self.name_value.setText(name)
self.price_value.setText(f"{price}") self.price_value.setText(f"{price}")
self.source_value.setText(source) self.source_value.setText(source)
self.load_selection_from_db(is_selected) # 초기 선택 상태 설정
# 이미지 설정 pixmap = QPixmap(saved_img_path)
pixmap = QPixmap() self.image_label.setPixmap(pixmap.scaled(self.image_label.size(), Qt.KeepAspectRatio))
img_data = self.download_image_data(img_url)
if img_data:
pixmap.loadFromData(img_data)
self.image_label.setPixmap(pixmap.scaled(self.image_label.size(), Qt.KeepAspectRatio))
else:
self.image_label.clear()
# 선택 상태에 따라 UI 업데이트
self.set_selected(is_selected, cursor) # # 이미지 설정
# pixmap = QPixmap()
# img_data = self.download_image_data(img_url)
# if img_data:
# pixmap.loadFromData(img_data)
# self.image_label.setPixmap(pixmap.scaled(self.image_label.size(), Qt.KeepAspectRatio))
# else:
# self.image_label.clear()
def download_image_data(self, img_url): def download_image_data(self, img_url):
"""이미지 URL에서 데이터를 다운로드합니다.""" """이미지 URL에서 데이터를 다운로드합니다."""
@ -81,9 +93,8 @@ class SearchResultCard(QWidget):
print(f"Image download error: {e}") print(f"Image download error: {e}")
return None return None
def set_selected(self, selected, cursor): def update_selection(self, selected):
"""카드의 선택 상태를 설정하고 데이터베이스를 업데이트합니다.""" """사용자의 선택을 반영하여 UI와 데이터베이스를 업데이트합니다."""
# 선택 상태에 따라 스타일 및 텍스트 변경
if selected: if selected:
self.select_label.setText("선택") self.select_label.setText("선택")
self.select_label.setStyleSheet("font-weight: bold; color: red;") self.select_label.setStyleSheet("font-weight: bold; color: red;")
@ -94,9 +105,27 @@ class SearchResultCard(QWidget):
self.setStyleSheet("border: 1px solid grey;") self.setStyleSheet("border: 1px solid grey;")
# 데이터베이스에 선택 상태 업데이트 # 데이터베이스에 선택 상태 업데이트
if cursor is not None: print(f"선택카드 DB 업데이트")
print(f"self.cursor = {self.cursor}, self.search_result_id = {self.search_result_id}")
# if self.cursor is not None and self.product_id is not None:
if self.cursor and self.search_result_id is not None:
try: try:
cursor.execute("UPDATE search_results SET is_selected = ? WHERE id = ?", (1 if selected else 0, self.index)) self.cursor.execute(
cursor.connection.commit() # 변경 사항 커밋 "UPDATE search_results SET is_selected = ? WHERE id = ?",
(1 if selected else 0, self.search_result_id)
)
self.cursor.connection.commit() # 변경 사항 커밋
print(f"Updated selection in DB for search_result_id: {self.search_result_id}, selected: {selected}")
except Exception as e: except Exception as e:
print(f"Error updating selection in database: {e}") print(f"Error updating selection in database: {e}", exc_info=True)
def load_selection_from_db(self, is_selected):
"""DB에서 가져온 선택 상태를 UI에만 반영합니다."""
if is_selected:
self.select_label.setText("선택")
self.select_label.setStyleSheet("font-weight: bold; color: red;")
self.setStyleSheet("border: 2px solid red;")
else:
self.select_label.setText("미선택")
self.select_label.setStyleSheet("font-weight: bold; color: black;")
self.setStyleSheet("border: 1px solid grey;")

BIN
test/ip/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

BIN
test/ip/1_cropped.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
test/ip/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

48
test/ip/ip.py Normal file
View File

@ -0,0 +1,48 @@
import cv2
import numpy as np
# 이미지 로드
image_path = '1.png'
image = cv2.imread(image_path)
# 이미지 로드 체크
if image is None:
print(f"Error: 이미지 파일을 찾을 수 없습니다. 경로를 확인하세요: {image_path}")
else:
# Step 1: GrabCut을 위한 초기 설정
mask = np.zeros(image.shape[:2], np.uint8)
bgdModel = np.zeros((1, 65), np.float64)
fgdModel = np.zeros((1, 65), np.float64)
# 이미지 가장자리에서 10 픽셀씩 떨어진 사각형을 전경 영역으로 지정
height, width = image.shape[:2]
rect = (10, 10, width - 20, height - 20) # 가장자리 10px을 배경으로 설정
# Step 2: GrabCut 알고리즘 적용
cv2.grabCut(image, mask, rect, bgdModel, fgdModel, 5, cv2.GC_INIT_WITH_RECT)
# 확정된 전경 및 배경 마스크 설정
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
result = image * mask2[:, :, np.newaxis]
# Step 3: Contour Detection을 통해 객체 경계 찾기
# 그레이스케일 변환
gray = cv2.cvtColor(result, cv2.COLOR_BGR2GRAY)
# 이진화 처리 (전경 객체만 남김)
_, thresh = cv2.threshold(gray, 1, 255, cv2.THRESH_BINARY)
# 윤곽선 찾기
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 가장 큰 윤곽선 기준으로 경계 영역 잘라내기
if contours:
cnt = max(contours, key=cv2.contourArea)
x, y, w, h = cv2.boundingRect(cnt)
final_cropped = result[y:y+h, x:x+w]
# 최종 결과 저장
cv2.imwrite('D:/py/baidu_web/test/ip/image_final_cropped.png', final_cropped)
print("이미지 처리 완료: 최종 결과가 저장되었습니다.")
else:
print("경계를 찾을 수 없습니다.")

51
test/rename.py Normal file
View File

@ -0,0 +1,51 @@
import sqlite3
def add_id_column_to_search_results(db_path):
# 데이터베이스 연결
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
try:
# 기존 테이블 이름 변경
cursor.execute("ALTER TABLE search_results RENAME TO search_results_old")
# 새로운 테이블 생성 (id 필드 추가)
cursor.execute('''
CREATE TABLE search_results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_id INTEGER,
title TEXT,
source TEXT,
price TEXT,
imgurl TEXT,
encrypted_url TEXT,
original_url TEXT,
is_selected BOOLEAN DEFAULT 0,
FOREIGN KEY (product_id) REFERENCES products(id)
)
''')
# 기존 테이블의 데이터를 새 테이블로 복사
cursor.execute('''
INSERT INTO search_results (product_id, title, source, price, imgurl, encrypted_url, original_url, is_selected)
SELECT product_id, title, source, price, imgurl, encrypted_url, original_url, is_selected
FROM search_results_old
''')
# 변경 사항 저장
conn.commit()
# 기존 테이블 삭제
cursor.execute("DROP TABLE search_results_old")
conn.commit()
print("Successfully added 'id' column to search_results.")
except Exception as e:
print(f"An error occurred: {e}")
finally:
# 연결 종료
conn.close()
# 데이터베이스 파일 경로를 지정하고 함수 호출
add_id_column_to_search_results("products.db")

52
test/rename1.py Normal file
View File

@ -0,0 +1,52 @@
import sqlite3
def rename_column(db_path):
# 데이터베이스에 연결
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
try:
# 기존 테이블을 새로운 테이블로 변경
cursor.execute('''
ALTER TABLE products RENAME TO products_old;
''')
# 새로운 테이블 생성, 필드 이름을 변경한 상태로 만듦
cursor.execute('''
CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
image_url TEXT,
tag TEXT,
price INTEGER,
category TEXT,
percenty_category TEXT,
selected_search_result_id INTEGER,
FOREIGN KEY(selected_search_result_id) REFERENCES search_results(id)
);
''')
# 기존 테이블에서 데이터를 복사
cursor.execute('''
INSERT INTO products (id, name, image_url, tag, price, category, percenty_category, selected_search_result_id)
SELECT id, name, image_url, tag, price, category, percent_category, selected_search_result_id
FROM products_old;
''')
# 기존 테이블 삭제
cursor.execute('''
DROP TABLE products_old;
''')
conn.commit()
print("Column renamed successfully.")
except Exception as e:
print(f"An error occurred: {e}")
conn.rollback()
finally:
conn.close()
# 실행
rename_column("products.db")

View File

@ -8,6 +8,46 @@ class ExcelReader:
self.logger = logger self.logger = logger
self.logger.info("ExcelReader initialized.") self.logger.info("ExcelReader initialized.")
def read_excel_files_for(self):
# 엑셀 파일을 순회하며 모든 데이터 수집
try:
data = []
seen_items = set() # 중복을 확인할 집합
for file_name in os.listdir(self.folder_path):
if file_name.endswith('.xlsx') or file_name.endswith('.xls'):
file_path = os.path.join(self.folder_path, file_name)
self.logger.debug(f"Reading file: {file_path}")
xls = pd.ExcelFile(file_path)
for sheet_name in xls.sheet_names:
df = pd.read_excel(xls, sheet_name=sheet_name)
# 컬럼 이름을 영문으로 변경하고 'NaN'을 None으로 변환
df = df.rename(columns={
'상품명': 'name',
'이미지URL': 'image_url',
'태그': 'tag',
'가격': 'price',
'카테고리': 'category',
'퍼센티카테고리': 'percenty_category'
}).replace({np.nan: None})
for record in df.to_dict('records'):
# 상품명과 이미지 URL을 이용해 중복 체크
unique_key = (record['name'], record['image_url'])
if unique_key not in seen_items:
seen_items.add(unique_key)
data.append(record)
else:
self.logger.debug(f"Duplicate found and skipped: {unique_key}")
self.logger.info("read_excel_files successfully.")
self.logger.debug(f"{data}")
return data
except Exception as e:
self.logger.error(f"Error in read_excel_files: {e}", exc_info=True)
def read_excel_files(self): def read_excel_files(self):
# 엑셀 파일을 순회하며 모든 데이터 수집 # 엑셀 파일을 순회하며 모든 데이터 수집
try: try:
@ -26,7 +66,7 @@ class ExcelReader:
'태그': 'tag', '태그': 'tag',
'가격': 'price', '가격': 'price',
'카테고리': 'category', '카테고리': 'category',
'퍼센티카테고리': 'percent_category' '퍼센티카테고리': 'percenty_category'
}).replace({np.nan: None}) }).replace({np.nan: None})
data.extend(df.to_dict('records')) data.extend(df.to_dict('records'))
self.logger.info("read_excel_files successfully.") self.logger.info("read_excel_files successfully.")
@ -34,3 +74,4 @@ class ExcelReader:
return data return data
except Exception as e: except Exception as e:
self.logger.error(f"Error read_excel_files: {e}", exc_info=True) self.logger.error(f"Error read_excel_files: {e}", exc_info=True)