수정중
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
|
@ -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
|
|
@ -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.")
|
||||||
|
|
|
||||||
105
mainProcessor.py
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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에서 데이터를 다운로드합니다."""
|
||||||
|
|
|
||||||
BIN
products.db
|
|
@ -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):
|
||||||
208
resultGUI.py
|
|
@ -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())
|
|
||||||
|
|
@ -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;")
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 465 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
|
@ -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("경계를 찾을 수 없습니다.")
|
||||||
|
|
@ -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")
|
||||||
|
|
@ -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")
|
||||||
43
xlsReader.py
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||