[DRF Project] 당근마켓 클론#2

Gyubster·2022년 1월 24일
0

당근마켓클론

목록 보기
2/4

당근 마켓 클로닝을 진행하면서 가장 중요하게 생각하였고 해보고싶었던 부분은 현재 당근 마켓이 가지고 있지 않은 기능인 새제품의 현재 가격을 제공해주는 기능이였다.

따라서, 네이버 쇼핑몰에 게시글의 제품명을 검색하여 나오게 되는 정보를 크롤링하여 관련한 정보를 전달하는 방식을 구현하는 것이 최종 목표였다. 아래의 사진을 보면 이해가 빠를 것 같다.

이를 구현하기 위해서, 첫번째로 생각한 방법은 부끄럽지만.. 검색이 되는 순간 해당 되는 데이터를 크롤링하는 방법이였다. 이러한 방법을 생각한 주된 이유는 가격의 변동 여부 때문이였다. 가격은 계속 변동이 일어날 것이고 이에 따라 사용자가 요청을 했을때만 제공하는 것이 최적의 방법이라고 생각했다. 또한, db를 만들지 않아도 되기때문에 더 가벼운 방법이라고 생각했다.
이는 코드를 다 써놓고 생각해보니, 사용자가 많아지게 되면 요청을 받은 직후에 크롤링을 진행하는 방식은 많은 문제(ex. 요청에 대한 응답속도)가 일어 난다고 판단하여 폐.기하였다.

두번째로 생각한 방법은 크롤링한 데이터를 저장하는 db를 생성하고 주기적으로 업데이트하는 방법이다. 이를 위해서는 Python Schedule을 이용하면 쉽게 문제를 해결 할 수 있다는 것을 확인하여 최종적으로 이 방법으로 크롤링한 데이터를 관리하기로 채택하였다.

생각한 대로라면 매우 쉽게 모든 것이 해결 됬어야 했다... 물론 그렇게 흘러 갈줄 알았다.

마주한 문제 상황들은 아래와 같다.

  1. Django를 통해서 만들지 않은 python 파일에서 Django 관련 파일을 import 하는 과정에서 나타난 문제

이 문제는 필자가 가장 골머리를 앓았던.. 문제였다. 본 프로젝트에서는 crawling을 위해서 새로운 디렉토리를 생성시켜 그 안에 selenium을 이용하는 코드를 작성하였다. Post module에서 import를 하는 과정에서 관련한 환경 변수들을 가지고 오지 못했기 때문에 나타나는 문제가 계속 나타났다.
따라서, 이와 관련된 환경 변수들을 가져와주는 코드를 파일의 맨위에 작성하면 문제없이 Django 프로젝트에 등록되지 않은 파일들도 관련한 method들을 사용할 수 있게된다.
필자는 이 정보를 못찾아 정말 많이 헤맸다..

import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")

import django
django.setup()

from user.models import Users
  1. APScheduler UTC 시간 기준으로 인한 문제

APScheduler의 경우, UTC 기준으로 작동을 하게된다. 처음에 이 부분을 모르고, 컴퓨터가 받아오는 인터넷 시간을 기준으로 작동 할 것이라고 판단하여 시간을 허비했지만, UTC 기준인 것을 확인하여 문제없이 작동함을 확인 했다.

최종적으로 나온 코드는 아래와 같다.

from selenium                           import webdriver
from pathlib                            import Path
from apscheduler.schedulers.background  import BackgroundScheduler

import os
import sys
import pathlib
import bs4
import logging
import time
import django

#디렉토리 인식 및 Django 환경변수 끌어오기
FILE_DIR = os.path.abspath(os.path.join(os.path.realpath(__file__), os. pardir))
BASE_DIR = os.path.abspath(os.path.join(os.path.realpath(FILE_DIR), os.pardir))
sys.path.append(BASE_DIR)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "we_market.settings")
django.setup()

#Backgroundscheduler 세팅
sched = BackgroundScheduler()

from post.models import Post, CrawlProduct

#Crawling 기본 설정
options = webdriver.ChromeOptions()
options.add_argument('--headless')
options.add_argument('window_size=1920x1090')
options.add_argument('disable_gpu')

base_url='https://search.shopping.naver.com/search/all?query='

#Crawling 함수 선언 (쇼핑몰 5개에서의 제품 정보 가져옴. 5개보다 적은 쇼핑몰을 찾은 경우, 찾은 만큼의 쇼핑몰에서 제품 정보 가져옴.)
def Crawling(product):
    url     = base_url+product

    driver  = webdriver.Chrome('crawling/chromedriver', options=options)
    driver.get(url)
    product_source      = bs4.BeautifulSoup(driver.page_source, 'lxml')

    product_info        = product_source.find('div', {'class':"basicList_inner__eY_mq"})
    product_info_list   = product_info.find('ul', {'class':"basicList_mall_list__vIiQw"})
    
    product_info_image      = product_info.find('img')
    product_info_mall_list  = product_info_list.find_all('span', {'class':"basicList_mall_name__1XaKA"})
    product_info_price_list = product_info_list.find_all('span', {'class':"basicList_price__2r23_"})

    driver.close()
    
    CrawlProduct.objects.all().delete()

    if len(product_info_mall_list)<=5:
        MALL_NUM = len(product_info_mall_list)
    else:
        MALL_NUM = 5       

    for post in Post.objects.filter(product__isnull=False):
        for i in range(MALL_NUM):
            crawl, is_created = CrawlProduct.objects.get_or_create(
                    post_id = post.id,    
                    url     = product_info_image['src'],
                    mall    = product_info_mall_list[i].text,
                    price   = int(product_info_price_list[i].text.replace(',', ''))
                    )
            
            if is_created:
                crawl.url     = product_info_image['src'],
                crawl.mall    = product_info_mall_list[i].text,
                crawl.price   = int(product_info_price_list[i].text.replace(',', ''))
        
products = [post.product for post in Post.objects.all()]

#BackgroundScheduler를 위한 실제로 작동해야하는 함수인 CrawlingProduct 선언
def CrawlingProduct():
    print('start')
    for product in products:
        if product != None:
            Crawling(product)
            print(f'{products.index(product)} is done')
    print('done')

sched.add_job(CrawlingProduct, 'cron', hour='14', minute='03', id='CrawlingProduct')

print('sched before')
sched.start()
print('sched after')

while True:
    time.sleep(1)

이렇게 가장 만들고 넣어보고 싶어했었던 기능을 완성시켰다. 좀더 양이 많아졌을때 나타나는 문제 혹은 더 빠르게 작동이 필요한 경우와 같은 여러가지 생각을 많이 해보았지만 어떻게 해결할지에 대한 정답은 얻지못했다. 조금 더 관련한 공부를 마치고, 다시 한번 볼 필요가 있을 듯 하다. 다음 포스팅은 당근 마켓 클론 프로젝트의 기능 개선 및 리펙토링을 진행하면서 알게된 Django의 편리함에 대해서 정리함으로써 당근 마켓 클론 프로젝트에 대한 포스팅을 마칠 예정이다.

profile
공부하는 예비 개발자

1개의 댓글

comment-user-thumbnail
2024년 4월 23일

안녕하세요 이건 백엔드 영역인가요?

답글 달기