스파르타 코딩클럽 내일배움캠프 AI 웹개발자양성과정 3회차
2022.11.07. 47일차 - TIL
1.프로젝트 진행
1) 추천 시스템 완성하기(백엔드)
일단 크롤링해서 csv파일로 식당 관련 정보를 가져왔다. 더보기 버튼이나 페이지 번호 버튼을 눌러야 현재에서 더 많은 정보가 보이는지라 어떻게 가져올까 고민했는데 다른 팀원께서 페이지 변경 시 url이 일정방식으로 변하는 것을 발견하시고 자료를 참고해 각 url 별 내용을 크롤링하셨다.
# 크롤링을 위한 코드
import requests
import pandas as pd
from bs4 import BeautifulSoup
import csv
filename = 'jeju_restaurants.csv'
f = open(filename, 'w', encoding='utf-8-sig', newline='')
writer = csv.writer(f)
titles = ['storeId', 'store_name', 'store_image', 'store_rating', 'store_category', 'store_address', 'store_view', 'store_review']
writer.writerow(titles)
store_id = 0
for page_num in range(10):
# range를 이용하면 0부터 인덱스가 시작되므로 page_num에 1을 더해준 url을 이용
keyword = "제주"
url = f'https://www.mangoplate.com/search/{keyword}?keyword={keyword}&page={page_num+1}'
headers = {'User-Agent': 'Chrome'}
response = requests.get(url, headers=headers)
print('응답 : ', response)
soup = BeautifulSoup(response.text, 'html.parser')
data = soup.select("li.server_render_search_result_item > div.list-restaurant-item")
for item in data:
image= item.select_one('img').get('data-original')
title = item.select_one('h2.title').text.replace('\n', '')
rating =item.select_one('strong.search_point').text
category = item.select_one('span').text
address = item.select_one('p').text.split('-')[0]
view = item.select_one('span.view_count').text
review = item.select_one('span.review_count').text
store_data = [store_id, title, image, rating, category, address, view, review]
store_id += 1
writer.writerow(store_data)
그리고 사용자별 추천을 위해 우리 시스템 상에 동작한 내용이 없는데 어떻게 유사도를 뽑아낼까 고민했는데 튜터님께서 그냥 임의의 사용자가 식당에 대해 랜덤하게 평점을 부여하는 csv파일로 유사도를 구하는 방법을 추천해주셔서 그렇게 진행했다.
# 사용자별 임의의 식당에 대한 평점 데이터 생성을 위한 코드
import csv
import requests
import pandas as pd
import random
from datetime import datetime
now = datetime.now()
filename = 'ratings.csv'
f = open(filename, 'w', encoding='utf-8-sig', newline='')
writer = csv.writer(f)
titles = ['userId', 'storeId', 'rating', 'timestamp']
writer.writerow(titles)
for i in range(100):
storeid_set = random.sample(range(200), 150)
storeid_set.sort()
userid = i
for j in range(150):
storeid = storeid_set[j]
rating = random.randrange(1, 6)
nowtime = now.timestamp()
rating_data = [userid, storeid, rating, nowtime]
writer.writerow(rating_data)
그리고 우리 시스템에서 식당 정보를 보여주기 위해 db에 미리 필요한 정보를 저장하는게 좋을 거 같아 csv내용을 db에 저장하는 코드를 만들었다. 이 과정에서 식당 모델이 필요할 것이라고 생각해 아예 recommend 앱을 만들어 식당 모델을 만들었다.
# 식당 csv를 db에 저장하기 위한 코드
import os
import sys
import csv
import django
#환경변수 세팅(뒷부분은 프로젝트명.settings로 설정한다.)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "drf_tambang.settings")
django.setup()
# model import
from recommend.models import *
#읽어들일 csv 디렉토리를 각 변수에 담는다.
STORE = 'jeju_restaurants.csv'
#함수 정의하기 (row부분엔 해당 table의 row명을 적어준다.)
def store():
with open(STORE, 'rt', encoding='UTF8') as csv_file:
data_reader = csv.reader(csv_file)
next(data_reader, None)
for row in data_reader:
if row[0]:
store_name = row[1]
store_image = row[2]
store_rating = row[3]
store_category = row[4]
store_address = row[5]
store_view = row[6]
store_review = row[7]
print(store_name, store_image, store_rating, store_category, store_address, store_view, store_review)
Restaurant.objects.create(
store_name = store_name,
store_image = store_image,
store_rating = store_rating,
store_category = store_category,
store_address = store_address,
store_view = store_view,
store_review = store_review
)
print('PRODUCT DATA UPLOADED SUCCESSFULY!')
# 함수 실행
store()
코드들이 준비가 되었으니 사용자별 추천 식당 목록을 추출하는 코드를 view로 작성했다.
# 사용자 유사도를 뽑아내는 코드
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework import status
from rest_framework.response import Response
from recommend.serializers import RecommendSerializer
from users.models import User
import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from recommend.models import Restaurant
from rest_framework.generics import get_object_or_404
# Create your views here.
class RecommendView(APIView):
def get(self, request):
userid = request.user.id # 현재 사용자 pk(지금은 5번)
print(userid)
# csv 파일읽기
restaurants = pd.read_csv('jeju_restaurants.csv')
ratings = pd.read_csv('ratings.csv')
# 상점번호(storeId)를 기준으로 ratings와 restaurants를 결합
restaurant_ratings = pd.merge(ratings, restaurants, on='storeId')
# user별로 식당에 부여한 rating 값을 볼 수 있도록 pivot table 사용
title_user = restaurant_ratings.pivot_table('rating', index='userId', columns='storeId')
# 평점을 부여안한 식당 그냥 0이라고 부여
title_user = title_user.fillna(0)
# 유저 0~99 번과 유저 0~99 번 간의 코사인 유사도를 구함
user_based_collab = cosine_similarity(title_user, title_user)
# 위는 그냥 numpy 행렬이니까, 이를 데이터프레임으로 변환
user_based_collab = pd.DataFrame(user_based_collab, index=title_user.index, columns=title_user.index)
# 현재 유저와 가장 비슷한 유저를 뽑고,
sim_user = user_based_collab[userid].sort_values(ascending=False).loc[:10].index[1]
# 유사한 유저가 좋아했던 식당 평점 내림차순으로 출력
result = title_user.query(f"userId == {sim_user}").sort_values(ascending=False, by=sim_user, axis=1)
result_me = title_user.query(f"userId == {userid}").sort_values(ascending=True, by=userid, axis=1)
# 현재 유저(접속자)와 가장 유사한 유저의 좋은 평점 중 1번 사용자가 가지 않은 식당 고르기(5개?)
# recommend_list에 추천할 식당번호가 저장
count = 0
recommend_list = []
for i in range(200):
if count < 5:
if result_me[result.columns[i]].values[0] == 0.0:
recommend_list.append(result.columns[i])
count += 1
else:
break
print(recommend_list)
# 해당 id값의 식당 db 정보 가져오기
store_result=[]
for i in recommend_list:
try:
store = Restaurant.objects.get(id=i+1)
store_result.append(store)
except:
pass
print(store_result)
serializer = RecommendSerializer(store_result, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
# recommends = Article.objects.filler(id=duskkkhsf)
# serializer = ArticleSerializer(articles, many=True)
# return Response(serializer.data, status=status.HTTP_200_OK)
TIP) 후에 튜터님 피드백을 들으니 추천마다 csv 파일을 읽어 유사도를 뽑아내는 것은 서버에 부담을 많이 준다고 한다. 그래서 이런 접근을 최소화할 것을 유념하자.
2) 추천 목록 보여주기(프론트)
몰라.. Vanilla JS 하나도 몰라서 튜터님 강의들으면서 따라했다. 머리로는 이렇게 하자 이러면서 방법을 모르겠어서...
# 추천받기 버튼을 누르면 실행되는 recommend.js 코드
async function loadRecommends() {
recommends = await handleRecommend()
console.log(recommends)
const recommend_list = document.getElementById("recommend_container")
recommends.forEach(recommend => {
const newRecommend = document.createElement("div")
newRecommend.classList.add('grid-product')
const recommendImage = document.createElement("img")
const bodyRecommend = document.createElement("div")
bodyRecommend.classList.add('grid-detail')
const bodyName = document.createElement("p")
const bodyContent = document.createElement("p")
recommendImage.setAttribute("src", `${recommend.store_image}`)
newRecommend.setAttribute("id", recommend.id)
bodyName.innerText = recommend.store_name
bodyContent.innerText = recommend.store_rating
bodyContent.innerText = recommend.store_address
bodyRecommend.appendChild(bodyName)
bodyRecommend.appendChild(bodyContent)
newRecommend.appendChild(bodyRecommend)
newRecommend.appendChild(recommendImage)
newRecommend.setAttribute("onclick", "")
recommend_list.appendChild(newRecommend)
});
}
그리고 fetch를 위해 api.js에 작성한 코드이다.
//추천가게 받아오기
async function handleRecommend() {
const response = await fetch("http://127.0.0.1:8000/recommends/", {
headers: {
Authorization: "Bearer " + localStorage.getItem("access"),
},
method: "GET",
});
response_json = await response.json();
return response_json;
}
TIP) 코드 정리가 좀 필요하다. 기능별로 묶어놓고, 변수 컨벤션 잘 지키기
3) 다른 프론트 기능 완성하기(게시글 작성, 리스트, 수정, 삭제, 댓글 작성)
코드 정리의 중요성... 너무 중구난방이라 팀원들과 결국 Live share 틀어놓고 함께 작성했다. 진짜 일일이 찾으면서 콘솔 다 찍어놓고, 어디서 에러가 나는지 분석했다... 그리고 다시 깨닫게 되는 postman의 중요성.. 이게 있어야 백엔드의 문제인지 프론트에서의 문제인지 구분이 가능하다... 처음에 백엔드를 구현 다하고 postman으로 확인을 다하니 백엔드 다했어!! 라는 마음으로 프론트만 잘 연결하면 된다고 생각했다. 하지만 프로젝트가 진행될수록 백엔드도 다양한 부분이 추가가 되었고, 이것을 확인하는 절차를 잊어버렸다...(나중에 튜터님께 질문하러 가서 postman으로 확인하고 다시 질문드리겠습니다 라는 사태가....;;; 창피하다...;;;;) 전체 코드는 내일 깃헙으로 공유 예정...
'개발일지 > AI 캠프' 카테고리의 다른 글
내일배움캠프 AI - 49일차 TIL, 2022.11.09 (0) | 2022.11.10 |
---|---|
내일배움캠프 AI - 48일차 TIL, 2022.11.08 (1) | 2022.11.08 |
내일배움캠프 AI - 10주차 WIL (0) | 2022.11.08 |
내일배움캠프 AI - 46일차 TIL, 2022.11.04 (0) | 2022.11.08 |
내일배움캠프 AI - 45일차 TIL, 2022.11.03 (0) | 2022.11.04 |