Skip to content

Commit 78f537a

Browse files
authored
Merge pull request #62 from pirogramming/main-test
[feat] oliveyoung 인기상품 추천 기능 구현
2 parents 5790f16 + 5b9e0d5 commit 78f537a

9 files changed

Lines changed: 567 additions & 123 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Django CI/CD
1+
name: Moodico Server
22

33
on:
44
workflow_dispatch:

moodico/moodtest/color_analyzer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
def product_result_by_mood(mood):
88
try:
99
mood_zones_path = 'static/data/mood_zones.json'
10-
products_path = 'static/data/all_products_hex_update_tempk=4_2_1_1.json'
10+
products_path = 'static/data/all_products.json'
1111

1212
with open(mood_zones_path, 'r', encoding='utf-8') as f:
1313
mood_zones = json.load(f)
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import json
2+
import os
3+
from django.conf import settings
4+
from django.core.management.base import BaseCommand
5+
from selenium import webdriver
6+
from selenium.webdriver.common.by import By
7+
from selenium.webdriver.chrome.service import Service
8+
from selenium.webdriver.support.ui import WebDriverWait
9+
from selenium.webdriver.support import expected_conditions as EC
10+
from webdriver_manager.chrome import ChromeDriverManager
11+
12+
13+
class Command(BaseCommand):
14+
help = "Scrape products and save JSON under STATIC_ROOT/data/advertise_products.json"
15+
16+
def handle(self, *args, **options):
17+
chrome_options = webdriver.ChromeOptions()
18+
chrome_options.add_argument("--headless")
19+
chrome_options.add_argument("--no-sandbox")
20+
chrome_options.add_argument("--disable-dev-shm-usage")
21+
chrome_options.add_argument("--disable-gpu")
22+
chrome_options.add_argument(
23+
'user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36'
24+
)
25+
26+
driver = webdriver.Chrome(
27+
service=Service(ChromeDriverManager().install()),
28+
options=chrome_options
29+
)
30+
31+
try:
32+
url = "https://www.oliveyoung.co.kr/store/main/getBestList.do?dispCatNo=900000100100001&fltDispCatNo=10000010002&pageIdx=1&rowsPerPage=10"
33+
driver.get(url)
34+
wait = WebDriverWait(driver, 15)
35+
36+
# Wait for <ul class="cate_prd_list"> to appear (parent of products)
37+
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'ul.cate_prd_list')))
38+
# Now get all <li class="flag">
39+
products = driver.find_elements(By.CSS_SELECTOR, 'li.flag')
40+
41+
all_products = []
42+
for item in products:
43+
try:
44+
prd_info = item.find_element(By.CSS_SELECTOR, 'div.prd_info')
45+
46+
# Main image and link
47+
link_tag = prd_info.find_element(By.CSS_SELECTOR, 'a.prd_thumb')
48+
product_url = link_tag.get_attribute('href')
49+
img_tag = link_tag.find_element(By.TAG_NAME, 'img')
50+
image_src = img_tag.get_attribute('src')
51+
image_alt = img_tag.get_attribute('alt')
52+
53+
# Brand and product name
54+
prd_name_box = prd_info.find_element(By.CSS_SELECTOR, 'div.prd_name')
55+
brand_name = ''
56+
product_name = ''
57+
try:
58+
brand_name = prd_name_box.find_element(By.CSS_SELECTOR, 'span.tx_brand').text.strip()
59+
except Exception:
60+
pass
61+
try:
62+
product_name = prd_name_box.find_element(By.CSS_SELECTOR, 'p.tx_name').text.strip()
63+
except Exception:
64+
pass
65+
66+
# Price
67+
price_original = ''
68+
price_current = ''
69+
try:
70+
price_box = prd_info.find_element(By.CSS_SELECTOR, 'p.prd_price')
71+
price_original_elem = price_box.find_elements(By.CSS_SELECTOR, 'span.tx_org')
72+
price_original = price_original_elem[0].text.strip() if price_original_elem else ''
73+
price_current_elem = price_box.find_elements(By.CSS_SELECTOR, 'span.tx_cur')
74+
price_current = price_current_elem.text.strip() if price_current_elem else ''
75+
except Exception:
76+
pass
77+
78+
# Flags
79+
flags = []
80+
try:
81+
prd_flag_box = prd_info.find_element(By.CSS_SELECTOR, 'p.prd_flag')
82+
flag_spans = prd_flag_box.find_elements(By.CSS_SELECTOR, 'span.icon_flag')
83+
flags = [flag.text.strip() for flag in flag_spans]
84+
except Exception:
85+
pass
86+
87+
# Review score
88+
review_score = None
89+
try:
90+
prd_point_box = prd_info.find_element(By.CSS_SELECTOR, 'p.prd_point_area')
91+
review_point_elem = prd_point_box.find_elements(By.CSS_SELECTOR, 'span.review_point')
92+
if review_point_elem:
93+
score_text = review_point_elem[0].text.strip()
94+
if '10점만점에' in score_text and '점' in score_text:
95+
review_score = score_text.replace('10점만점에', '').replace('점', '').strip()
96+
except Exception:
97+
pass
98+
99+
product_data = {
100+
'product_url': product_url,
101+
'brand_name': brand_name,
102+
'product_name': product_name,
103+
'image_src': image_src,
104+
'image_alt': image_alt,
105+
'price_original': price_original,
106+
'price_current': price_current,
107+
'flags': flags,
108+
'review_score': review_score
109+
}
110+
111+
all_products.append(product_data)
112+
except Exception as e:
113+
self.stderr.write(f"Error scraping item: {e}")
114+
continue
115+
116+
SAVE_PATH = os.path.join(settings.BASE_DIR, 'static', 'data', 'advertise_products.json')
117+
os.makedirs(os.path.dirname(SAVE_PATH), exist_ok=True)
118+
with open(SAVE_PATH, 'w', encoding='utf-8') as f:
119+
json.dump(all_products, f, ensure_ascii=False, indent=2)
120+
print(f"\nSaved {len(all_products)} products to {SAVE_PATH}")
121+
122+
finally:
123+
driver.quit()

moodico/products/management/commands/generate_clusters.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
from sklearn.metrics import silhouette_score
1111
from sklearn.preprocessing import StandardScaler
1212

13-
14-
# -------- helpers (kept as in your script) --------
1513
def hex_to_rgb(hex_code):
1614
h = hex_code.lstrip('#')
1715
if len(h) == 3:

moodico/products/views.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
# Create your views here.
1818
def color_matrix_explore(request):
1919
"""색상 매트릭스 페이지 뷰"""
20-
media_cluster = os.path.join(settings.BASE_DIR, 'static','data', 'products_clustered.json')
20+
static_cluster = os.path.join(settings.BASE_DIR, 'static','data', 'products_clustered.json')
2121
static_all = os.path.join(settings.BASE_DIR, 'static', 'data', 'all_products.json')
2222

2323
product_path = None
24-
if os.path.exists(media_cluster):
25-
product_path = media_cluster
24+
if os.path.exists(static_cluster):
25+
product_path = static_cluster
2626
else:
2727
product_path = static_all
2828

@@ -725,7 +725,6 @@ def get_top_liked_products(limit=10, include_unliked=True, exclude_brands=None):
725725

726726
# 3) 정렬
727727
products_with_likes.sort(key=lambda x: (-x['like_count'], x['product_name']))
728-
729728
return products_with_likes[:limit]
730729

731730

moodico/recommendation/views.py

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,55 @@
66
import logging
77
logger = logging.getLogger(__name__)
88
from sklearn.metrics.pairwise import cosine_similarity
9-
from moodico.products.views import get_top_liked_products
109

1110
# Create your views here.
12-
def my_item_recommendation(request):
13-
# Get recommended or default products
14-
search_results = get_top_liked_products(limit=10)
15-
recommended_items = [] # Set this if you want a separate recommended section
16-
17-
return render(
18-
request,
19-
'upload/upload.html',
11+
# def my_item_recommendation(request):
12+
# # Get recommended or default products
13+
# search_results = get_top_liked_products(limit=10)
14+
# recommended_items = [] # Set this if you want a separate recommended section
15+
# print("....",search_results)
16+
17+
# return render(
18+
# request,
19+
# 'upload/upload.html',
20+
# {
21+
# 'search_results': search_results,
22+
# 'recommended_items': recommended_items
23+
# }
24+
# )
25+
26+
def get_recommendation_list():
27+
# JSON 데이터를 파싱 (실제로는 DB나 API에서 받아올 수 있음)
28+
products_path = 'static/data/advertise_products.json'
29+
with open(products_path, 'r', encoding='utf-8') as f:
30+
raw_data = json.load(f)
31+
32+
# 태그 추출 규칙 예시 (첫번째 flag 사용 or None)
33+
def get_tag(flags):
34+
for tag in ['글로시', 'matte', 'glossy', '증정', '세일', '쿠폰', '오늘드림']:
35+
if tag in flags:
36+
return tag
37+
return flags[0] if flags else '-'
38+
39+
search_results = [
2040
{
21-
'search_results': search_results,
22-
'recommended_items': recommended_items
41+
"brand": item["brand_name"],
42+
"name": item["product_name"],
43+
"image": item["image_src"],
44+
"price": item["price_original"].replace("~", ""),
45+
"tag": get_tag(item.get("flags", [])),
46+
"url": item["product_url"],
2347
}
24-
)
48+
for item in raw_data
49+
]
50+
return search_results
51+
52+
def my_item_recommendation(request):
53+
search_results = get_recommendation_list()
54+
return render(request, 'upload/upload.html', {
55+
"search_results": search_results
56+
})
57+
2558

2659
@csrf_exempt
2760
def recommend_by_color(request):

moodico/upload/views.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
import requests
33
from django.http import HttpResponse
44
from moodico.upload.forms import UploadForm
5+
from moodico.recommendation.views import get_recommendation_list
56

67
# Create your views here.
78
# 이미지 업로드
89
def upload_color_image(request):
10+
search_results = get_recommendation_list()
911
if request.method == 'POST':
1012
form = UploadForm(request.POST, request.FILES)
1113
if form.is_valid():
@@ -19,11 +21,12 @@ def upload_color_image(request):
1921
upload.save()
2022
return render(request, 'upload/upload.html', {
2123
'form': UploadForm(),
22-
'uploaded_image_url': upload.image_path.url
24+
'uploaded_image_url': upload.image_path.url,
25+
'search_results':search_results,
2326
})
2427
else:
2528
form = UploadForm()
26-
return render(request, 'upload/upload.html', {'form': form})
29+
return render(request, 'upload/upload.html', {'form': form, 'search_results': search_results})
2730

2831
# upload.html에서 검색한 제품의 이미지를 가져옴
2932
def proxy_image(request):

0 commit comments

Comments
 (0)