수정중

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()
def _create_tables(self):
# 상품 테이블과 검색 결과 테이블 생성
try:
with self.conn:
# products 테이블 생성
self.conn.execute('''
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
image_url TEXT,
tag TEXT,
price INTER,
price INTEGER,
category TEXT,
percent_category TEXT,
selected_search_result_id INTEGER, -- 선택된 search_results ID
percenty_category TEXT,
selected_search_result_id INTEGER,
saved_img_path TEXT, -- 이미지 저장 경로 필드 추가
FOREIGN KEY(selected_search_result_id) REFERENCES search_results(id)
)
''')
# search_results 테이블에 고유 ID 추가
self.conn.execute('''
CREATE TABLE IF NOT EXISTS search_results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_id INTEGER,
title TEXT,
source TEXT,
@ -34,7 +38,8 @@ class DatabaseManager:
imgurl TEXT,
encrypted_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)
)
''')
@ -49,11 +54,10 @@ class DatabaseManager:
with self.conn:
for product in products:
cursor = self.conn.execute('''
INSERT INTO products (name, image_url, tag, price, category, percent_category)
VALUES (?, ?, ?, ?, ?, ?)
''', (product['name'], product['image_url'], product['tag'], product['price'], product['category'], product['percent_category']))
INSERT INTO products (name, image_url, tag, price, category, percenty_category, saved_img_path)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', (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_ids.append(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)
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):
# 검색 결과 중 상위 5개만 DB에 저장
try:
top_results = search_results[:5] # 상위 5개로 제한
with self.conn:
self.conn.executemany('''
INSERT INTO search_results (product_id, title, source, price, imgurl, encrypted_url, original_url)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', [(product_id, result['title'], result['source'], result['price'], result['imgurl'], result['encrypted_url'], result['original_url']) for result in top_results])
INSERT INTO search_results (product_id, title, source, price, imgurl, saved_img_path, encrypted_url, original_url)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', [(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.")
except Exception as e:
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):
# products 테이블에 선택된 search_result_id 업데이트
try:
with self.conn:
# products 테이블의 selected_search_result_id 업데이트
self.conn.execute('''
UPDATE products
SET selected_search_result_id = ?
WHERE id = ?
''', (search_result_id, product_id))
# search_results 테이블의 is_selected 업데이트
# search_results 테이블의 선택된 항목 업데이트
self.conn.execute('''
UPDATE search_results
SET is_selected = 1
WHERE id = ?
''', (search_result_id,))
SET is_selected = CASE WHEN id = ? THEN 1 ELSE 0 END
WHERE product_id = ?
''', (search_result_id, product_id))
self.logger.info(f"Product ID [{product_id}] selected search result ID [{search_result_id}].")
except Exception as e:
self.logger.error(f"Error selecting search result for product ID [{product_id}]: {e}", exc_info=True)
@ -123,7 +140,7 @@ class DatabaseManager:
try:
query = '''
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
FROM products p
LEFT JOIN search_results sr ON p.id = sr.product_id

View File

@ -19,8 +19,6 @@ class ImageDownloader:
"Cache-Control": "max-age=0"
}
def download_image(self, image_url, product_id):
# 임시 폴더에 이미지 다운로드
try:
@ -35,3 +33,19 @@ class ImageDownloader:
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
from bs4 import BeautifulSoup
from playwright.sync_api import sync_playwright
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.image_downloader = image_downloader
self.logger = logger
self.browser = None
self.page = None
@ -35,6 +35,8 @@ class BaiduImageSearcher:
"Cache-Control": "max-age=0"
})
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]'
@ -82,18 +84,15 @@ class BaiduImageSearcher:
# 첫 번째 검색과 이후 검색의 선택자를 다르게 설정
# if self.is_first_search:
# if self.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_input_xpath = '//*[@id="app"]/div/div[1]/div[7]/div/div/div[2]/div[2]/div/form/input'
# # self.is_first_search = False # 이후 검색에서는 일반 선택자를 사용
self.logger.info("is_first_search")
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.is_first_search = False # 이후 검색에서는 일반 선택자를 사용
# else:
# 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_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.click(upload_button_xpath)
@ -129,16 +128,14 @@ class BaiduImageSearcher:
return False
return True
def extract_product_data(self):
def extract_product_data(self, product_id):
# 검색 결과 페이지에서 JSON 데이터 추출
if self.check_capcha():
return False
content = None
self.logger.info("검색 결과 페이지에서 JSON 데이터 추출")
content = self.page.content()
self.page.go_back() # 뒤로 가기
soup = BeautifulSoup(content, 'html.parser')
script_tag = soup.select_one("html > head > script:nth-of-type(2)")
@ -154,7 +151,7 @@ class BaiduImageSearcher:
product_info = []
# 필터링된 출처에 따라 데이터 추출
for card in data:
for idx, card in enumerate(data):
if card.get("cardName") == "product":
products = card["tplData"]["list"]
for product in products:
@ -167,16 +164,21 @@ class BaiduImageSearcher:
# 출처 필터링
if source in self.filtered_sources:
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({
"title": title,
"source": source,
"price": price,
"imgurl": imgurl,
"saved_img_path": saved_img_path,
"encrypted_url": buyurl,
"original_url": original_url
})
self.logger.info("product_info 추출 완료")
self.logger.info(f"추출된 정보 \n{product_info}")
self.logger.info(f"{product_info}")
return product_info
except json.JSONDecodeError as e:

13
main.py
View File

@ -3,19 +3,28 @@ from mainProcessor import MainProcessor
from logger_module import setup_logger
import logging
from PySide6.QtWidgets import QApplication, QLabel, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QDialog
import sys
if __name__ == "__main__":
# 로그 설정
logger = setup_logger(log_file='app.log', log_level=logging.DEBUG)
# 폴더 및 파일 경로 설정
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')
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.")
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()
logger.info("process_all_products Completed.")

View File

@ -3,16 +3,36 @@ from xlsReader import ExcelReader
from databaseManager import DatabaseManager
from imageDownloader import ImageDownloader
from imgSearcher import BaiduImageSearcher
from resultDiag import ProductViewer
import pandas as pd
from datetime import datetime
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.excel_reader = ExcelReader(excel_folder, logger)
self.db_manager = DatabaseManager(db_path, logger)
self.image_downloader = ImageDownloader(temp_folder, logger)
self.image_searcher = BaiduImageSearcher(sources=['淘宝', 'tmall', '1688'], logger=logger)
self.logger.debug("1")
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.")
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):
# 모든 엑셀 파일을 읽어와 DB에 저장
products = self.excel_reader.read_excel_files()
@ -20,30 +40,6 @@ class MainProcessor:
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):
try:
@ -53,6 +49,9 @@ class MainProcessor:
# 이미지 다운로드 및 검색 실행
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
@ -82,7 +81,7 @@ class MainProcessor:
continue # 재시도 시 루프를 다시 시작
# 검색 결과 추출
search_results = self.image_searcher.extract_product_data()
search_results = self.image_searcher.extract_product_data(product_id)
# if search_results == []: # 빈 리스트일 경우만 실패로 간주
# attempt += 1
# 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.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}")
time.sleep(1)
@ -112,16 +112,47 @@ class MainProcessor:
def show_results(self):
# 검색 결과 출력
try:
with self.db_manager.conn as conn:
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.resultViewer.show()
self.logger.debug(f"show_results Completed")
except Exception as e:
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.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_value = QLabel("")
@ -68,7 +69,7 @@ class ProductCard(QWidget):
self.cat_value.setText("")
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.price_value.setText(str(price))
@ -78,14 +79,19 @@ class ProductCard(QWidget):
self.cat_value.setText(cat)
# self.price_value.setText(str(price))
# 이미지 설정
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()
pixmap = QPixmap(product_img_path)
self.image_label.setPixmap(pixmap.scaled(self.image_label.size(), Qt.KeepAspectRatio))
# # 이미지 설정
# 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):
"""이미지 URL에서 데이터를 다운로드합니다."""

Binary file not shown.

View File

@ -5,10 +5,16 @@ import sys
from productCard import ProductCard # ProductCard 클래스 임포트
from searchResultCard import SearchResultCard # SearchResultCard 클래스 임포트
import pandas as pd
from datetime import datetime
class ProductViewer(QWidget):
def __init__(self, db_path):
super().__init__()
self.conn = sqlite3.connect(db_path)
self.cursor = self.conn.cursor() # 여기에 cursor를 올바르게 초기화
self.product_index = 0
self.products = self.load_products()
self.total_products = len(self.products)
@ -16,7 +22,7 @@ class ProductViewer(QWidget):
# UI 초기화
self.init_ui()
self.load_product_data()
# self.load_product_data()
def init_ui(self):
self.setWindowTitle("Product Viewer")
@ -30,7 +36,7 @@ class ProductViewer(QWidget):
self.middle_layout = QHBoxLayout()
self.search_result_cards = [SearchResultCard(i+1) for i in range(4)]
for card in self.search_result_cards:
self.middle_layout.addWidget(card, 2)
self.middle_layout.addWidget(card,2)
# card.setFixedHeight(300) # 각 카드의 크기를 고정
main_layout.addLayout(self.middle_layout)
@ -55,34 +61,42 @@ class ProductViewer(QWidget):
def load_products(self):
# DB에서 products 테이블 데이터를 로드
cursor = self.conn.cursor()
cursor.execute("SELECT * FROM products")
products = cursor.fetchall()
self.cursor.execute("SELECT * FROM products")
products = self.cursor.fetchall()
return products
def load_product_data(self):
# 현재 상품 데이터를 로드하여 ProductCard에 설정
product = self.products[self.product_index]
product_id = product[0] # products 테이블의 ID
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_price = product[4]
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 초기화 및 데이터 로드
self.reset_search_result_cards() # 이전 카드 데이터 리셋
search_results = self.load_search_results(product[0])
# SearchResultCard 초기화 및 데이터 설정
search_results = self.load_search_results(product_id)
for i, result in enumerate(search_results):
if i < len(self.search_result_cards):
title = result[1]
price = result[3]
source = result[2]
img_url = result[4]
is_selected = result[5] # DB에서 is_selected 값을 가져옴
self.search_result_cards[i].set_data(name=title, price=price, source=source, img_url=img_url, is_selected=is_selected, cursor=self.cursor)
title, source, price, saved_img_path, is_selected, search_result_id = result[1], result[2], result[3], result[4], result[5], result[0]
self.search_result_cards[i].set_data(
name=title, price=price, source=source, saved_img_path=saved_img_path, is_selected=is_selected
)
self.search_result_cards[i].product_id = product_id
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}")
@ -94,17 +108,21 @@ class ProductViewer(QWidget):
def load_search_results(self, product_id):
# 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()
def previous_product(self):
if self.product_index > 0:
self.product_index -= 1
self.reset_search_result_cards()
self.load_product_data()
def next_product(self):
if self.product_index < self.total_products - 1:
self.product_index += 1
self.reset_search_result_cards()
self.load_product_data()
def show_product_dialog(self):
@ -127,13 +145,19 @@ class ProductViewer(QWidget):
elif event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter:
self.show_product_dialog()
def select_card(self, index, cursor):
def select_card(self, index):
# 선택된 카드의 UI 갱신 및 데이터베이스 업데이트
print(f"{index}번째 카드 선택")
for i, card in enumerate(self.search_result_cards):
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:
card.set_selected(False)
print(f"i = {i} | index = {index} - False")
print(f"card.index : {card.index}")
card.update_selection(False) # 선택 해제 상태로 업데이트
class ProductDialog(QDialog):
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
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__()
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.layout = QGridLayout(self)
# UI 요소 초기화
self.index_label = QLabel(str(index))
self.index_label = QLabel(str(self.index))
self.index_label.setAlignment(Qt.AlignCenter)
self.index_label.setStyleSheet("font-weight: bold; font-size: 16px;")
@ -51,25 +59,29 @@ class SearchResultCard(QWidget):
self.price_value.setText("")
self.source_value.setText("")
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.price_value.setText(f"{price}")
self.price_value.setText(f"{price}")
self.source_value.setText(source)
self.load_selection_from_db(is_selected) # 초기 선택 상태 설정
# 이미지 설정
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()
pixmap = QPixmap(saved_img_path)
self.image_label.setPixmap(pixmap.scaled(self.image_label.size(), Qt.KeepAspectRatio))
# 선택 상태에 따라 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):
"""이미지 URL에서 데이터를 다운로드합니다."""
@ -81,9 +93,8 @@ class SearchResultCard(QWidget):
print(f"Image download error: {e}")
return None
def set_selected(self, selected, cursor):
"""카드의 선택 상태를 설정하고 데이터베이스를 업데이트합니다."""
# 선택 상태에 따라 스타일 및 텍스트 변경
def update_selection(self, selected):
"""사용자의 선택을 반영하여 UI와 데이터베이스를 업데이트합니다."""
if selected:
self.select_label.setText("선택")
self.select_label.setStyleSheet("font-weight: bold; color: red;")
@ -94,9 +105,27 @@ class SearchResultCard(QWidget):
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:
cursor.execute("UPDATE search_results SET is_selected = ? WHERE id = ?", (1 if selected else 0, self.index))
cursor.connection.commit() # 변경 사항 커밋
self.cursor.execute(
"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:
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.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):
# 엑셀 파일을 순회하며 모든 데이터 수집
try:
@ -26,7 +66,7 @@ class ExcelReader:
'태그': 'tag',
'가격': 'price',
'카테고리': 'category',
'퍼센티카테고리': 'percent_category'
'퍼센티카테고리': 'percenty_category'
}).replace({np.nan: None})
data.extend(df.to_dict('records'))
self.logger.info("read_excel_files successfully.")
@ -34,3 +74,4 @@ class ExcelReader:
return data
except Exception as e:
self.logger.error(f"Error read_excel_files: {e}", exc_info=True)