2주차에도 저는 데이터확보를 위한 크롤링을 계속했습니다.
csv파일을 업로드하고, 시스 템프롬프트를 입력하면 동작하는 챗봇형태라서, 데이터확보와 시스템프롬프트를 만드는 것이 중요했습니다.
일단 크롤링으로 데이터를 csv로 만들었습니다
GPTs를 만들어서 csv파일을 knowledge에 넣고 , 어떤지침을 넣어야 좋은 답변이 나오는지 계속 테스트를 해보는 단계입니다.
ChatGPT - K-Food restaurant for foreigner 을 만들어었고,
지침은 아래와 같이 GPT와 대화를 하며, 결과물을 확인해가면서 작성했습니다.
아직 LLM의 특성상 GPTs에 데이터를 넣어도, 만족스러운 결과는 나오지 않았지만, 계속테스트를 하며 Fine Tuning 예정입니다.
You are a guide specializing in helping foreigners visiting Korea find, choose, and enjoy restaurants seamlessly. Your role is to provide step-by-step guidance through the entire dining process, ensuring that users feel confident and informed every step of the way. Your tone should be friendly, informative, and culturally sensitive, encouraging users to embrace and explore Korean culinary culture. Here's how you should operate:1. Provide Basic Information: Educate users about the types of Korean food, their characteristics, and cultural dining norms. Offer insights on regional specialties and basic dining etiquette, including commonly used Korean phrases for dining. Additionally, provide explanations on how foreigners from different linguistic backgrounds can search for Korean restaurants effectively, using examples in English, Chinese, Japanese, Spanish, Arabic, and French.
2. Assist with Finding Restaurants: Help users locate restaurants using popular platforms like Google Maps, MangoPlate, or local apps like Naver Map and KakaoMap. Provide curated recommendations based on the user's preferences (e.g., vegetarian options, halal-certified restaurants, or budget-friendly places). Highlight popular and trendy spots, such as those frequented by celebrities or featured in media, while suggesting alternatives for a quieter or more unique experience.
3. Guide Restaurant Selection and Booking: Offer tips on evaluating restaurant reviews and selecting a place that fits their preferences and budget. Provide step-by-step instructions for making reservations, including sample scripts or translated phrases for phone or app bookings. Highlight if a restaurant offers online booking options or requires in-person reservations.
4. Enhance Onsite Experience: Assist users with finding the restaurant location and navigating transportation. Guide them in reading menus, asking for recommendations, and placing orders confidently. Share cultural tips on using side dishes, dining etiquette, and payment methods to ensure a smooth experience. For upscale or trendy restaurants, share specific tips, such as dress codes or pairing options.
중간생략
You provide clear and practical instructions at each step, ensuring users can navigate Korean dining culture effortlessly. Always be encouraging, patient, and eager to share your expertise.진행 방 법
크롤링코드입니다. (크롬의 개발자도구를 활용해서 작성했습니다)
import json
import csv
import requests
import time
from datetime import datetime
def fetch_all_reviews(v_rid):
"""특정 식당의 모든 리뷰를 수집하는 함수"""
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://www.diningcode.com',
'Referer': f'https://www.diningcode.com/profile.php?rid={v_rid}',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
reviews = []
page = 1
while True:
try:
# 리뷰 API 엔드포인트로 요청
params = {
'rid': v_rid,
'type': 'profile',
'page': str(page)
}
response = requests.get(
'https://www.diningcode.com/2018/ajax/review.php',
headers=headers,
params=params
)
# 응답이 비어있거나 "empty" 문자열이 포함되어 있으면 종료
if not response.text or 'empty' in response.text.lower():
break
try:
review_data = json.loads(response.text)
if not review_data:
break
reviews.extend(review_data)
except json.JSONDecodeError:
# HTML 응답인 경우 더 이상 리뷰가 없는 것으로 간주
break
print(f"리뷰 페이지 {page} 수집 완료")
page += 1
# API 호출 간 딜레이
time.sleep(0.5)
except Exception as e:
print(f"리뷰 수집 중 오류 발생: {str(e)}")
break
return reviews
def process_restaurant_data(query, search_type="location", pages=5):
"""식당 데이터를 수집하고 CSV로 저장하는 함수"""
headers = {
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://www.diningcode.com',
'Referer': 'https://www.diningcode.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
restaurants_filename = f'restaurants_{query}_{timestamp}.csv'
reviews_filename = f'reviews_{query}_{timestamp}.csv'
restaurant_headers = [
'v_rid', 'nm', 'branch', 'addr', 'road_addr', 'phone', 'distance',
'category', 'keywords', 'areas', 'lat', 'lng', 'open_status',
'score', 'image', 'user_score', 'favorites_cnt', 'review_cnt',
'recommend_cnt', 'review_total_cnt'
]
review_headers = [
'v_rid', 'restaurant_name', 'user_name', 'content', 'reg_date',
'rating', 'likes', 'replies'
]
try:
with open(restaurants_filename, 'w', newline='', encoding='utf-8-sig') as rest_file, \
open(reviews_filename, 'w', newline='', encoding='utf-8-sig') as rev_file:
rest_writer = csv.DictWriter(rest_file, fieldnames=restaurant_headers)
rev_writer = csv.DictWriter(rev_file, fieldnames=review_headers)
rest_writer.writeheader()
rev_writer.writeheader()
search_params = {
'distance': '10000',
'rn_search_flag': 'on',
'lat': '37.4898149',
'lng': '126.9927589',
'order': 'r_score',
'size': '20'
}
if search_type == "name":
search_params.update({
'query': '',
'keyword': query,
'search_type': 'keyword'
})
else:
search_params.update({
'query': query,
'keyword': '',
'search_type': 'poi_search'
})
for page in range(1, pages + 1):
print(f"페이지 {page} 수집 중...")
search_params['page'] = str(page)
response = requests.post(
'https://im.diningcode.com/API/isearch/',
headers=headers,
data=search_params
)
data = json.loads(response.text)
restaurants = data['result_data']['poi_section']['list']
if not restaurants:
print(f"페이지 {page}에서 더 이상 결과가 없습니다.")
break
for restaurant in restaurants:
v_rid = restaurant.get('v_rid', '')
restaurant_name = restaurant.get('nm', '')
# 식당 기본 정보 저장
restaurant_info = {
'v_rid': v_rid,
'nm': restaurant_name,
'branch': restaurant.get('branch', ''),
'addr': restaurant.get('addr', ''),
'road_addr': restaurant.get('road_addr', ''),
'phone': restaurant.get('phone', ''),
'distance': restaurant.get('distance', ''),
'category': restaurant.get('category', ''),
'keywords': ';'.join([k.get('term', '') for k in restaurant.get('keyword', [])]),
'areas': ';'.join(restaurant.get('area', [])),
'lat': restaurant.get('lat', ''),
'lng': restaurant.get('lng', ''),
'open_status': restaurant.get('open_status', ''),
'score': restaurant.get('score', ''),
'image': restaurant.get('image', ''),
'user_score': restaurant.get('user_score', ''),
'favorites_cnt': restaurant.get('favorites_cnt', 0),
'review_cnt': restaurant.get('review_cnt', 0),
'recommend_cnt': restaurant.get('recommend_cnt', 0),
'review_total_cnt': restaurant.get('review_total_cnt', 0)
}
rest_writer.writerow(restaurant_info)
# 해당 식당의 모든 리뷰 수집
print(f"식당 '{restaurant_name}' 의 리뷰 수집 중...")
reviews = fetch_all_reviews(v_rid)
for review in reviews:
review_info = {
'v_rid': v_rid,
'restaurant_name': restaurant_name,
'user_name': review.get('username', ''),
'content': review.get('contents', ''),
'reg_date': review.get('regdate', ''),
'rating': review.get('point', ''),
'likes': review.get('like', 0),
'replies': review.get('reply', 0)
}
rev_writer.writerow(review_info)
if page < pages:
time.sleep(1)
print(f"\n데이터 수집 완료!")
print(f"식당 정보 저장 위치: {restaurants_filename}")
print(f"리뷰 정보 저장 위치: {reviews_filename}")
except Exception as e:
print(f"오류 발생: {str(e)}")
def main():
print("식당 데이터 수집 프로그램")
print("=" * 50)
print("1. 지역명으로 검색")
print("2. 음식점 이름으로 검색")
print("3. 키워드로 검색")
print("=" * 50)
try:
choice = input("검색 방식을 선택하세요 (1-3): ")
if choice not in ['1', '2', '3']:
print("올바른 선택지를 입력해주세요.")
return
search_term = input("검색어를 입력하세요: ")
if not search_term.strip():
print("검색어를 입력해주세요.")
return
pages = input("수집할 페이지 수를 입력하세요 (최대 5, 기본값 5): ")
pages = int(pages) if pages.strip() else 5
pages = min(max(1, pages), 5)
search_type = "name" if choice == "2" else "location"
process_restaurant_data(search_term, search_type, pages)
except ValueError:
print("올바른 숫자를 입력해주세요.")
except Exception as e:
print(f"오류가 발생했습니다: {e}")
if __name__ == "__main__":
main()결과와 배운 점
RAG를 적용한 큐레이션봇을 만들기로하고, 크롤링을 통해 외국인이 좋아할 맛집을 뽑아내는게 저의 역할이라. 예전 문과생도 파이선에서 익혔던 기술로 "다이닝코드"에서 데이터를 추출했습니다. 주말에 팀원들끼리 토론을 하다보니 "벨루가(Veluga - 대시보드)"에서 만든 것과 우리가 만드는 것과의 차이점이 무엇일까?라는 점이 중요하며, 결국은 시스템프롬프트와 데이터의 품질과 정제과정이 노우하우라는 점을 이해할 수 있었습니다.
실제 프로젝트를 하다보니 취미수준에서 보던 RAG에 대한 관점이 바뀌게 된 좋은 기회였던 것 같습니다.
(이번주는 제가 진도나간것이 별로없어 사례글이 허접하네요)