All Articles

Selenium과 Beautiful Soup로 데이터 크롤링 하기

쇼핑몰 상품 상세 페이지 크롤링을 해보았다.

한 번에 모든 코드를 작성해서 돌리면 100% 에러가 나기 때문에 요소 하나마다 테스트를 해줬다. 각 요소가 잘 추출되는 것을 확인하고 한꺼번에 크롤러를 돌렸다.

import csv
import time

from selenium                       import webdriver
from bs4                            import BeautifulSoup
from selenium.webdriver.common.keys import Keys

csv_filename = 'products.csv'
csv_open     = open(csv_filename, "w+", encoding = 'utf-8')
csv_writer   = csv.writer(csv_open)
csv_writer.writerow(('product_number', 'product_name', 'like', 'product_image_url',
    'thumbnail_image_url', 'discount_price', 'original_price', 'material', 'country'))

PATH   = "/Users/NAON/myprojects/chromedriver"
driver = webdriver.Chrome(PATH)
driver.implicitly_wait(5)
driver.get("해당url")

htmlsrc  = driver.page_source
bs       = BeautifulSoup(htmlsrc, "html.parser")

rows = []
def productpage():
    elements = driver.find_elements_by_css_selector('.ProductList > li > a')
    links    = []

    for element in elements:
        link = element.get_attribute('href')
        links.append(link)

    for link in links:
        driver.get(link)
        driver.implicitly_wait(5)
        time.sleep(2)

        htmlsrc = driver.page_source
        bs      = BeautifulSoup(htmlsrc, "html.parser")
        
        # 상품명 긁어오기
        product_name = bs.find('h1', {'class' : 'ProductDetail__title'}).text

        # 좋아요 수 긁어오기
        like = bs.find('span', {'class' : 'WishButtonPc__text WishButtonPc__text--middle'}).text

        # 상품 상세 이미지 긁어오기
        # 한 제품 이미지가 여러 장이지만 각 주소를 ,로 구분해서 한 셀 안에 넣는다.
        # 그래야 한 제품 당 한 row만 차지하기 때문
        # join은 리스트 요소를 문자열로 꺼내올 수 있는 함수다.
        images            = bs.find_all('img', {'class' : 'ProductDetailContainer__form__thumbnail__image'})
        product_image_url = ",".join([image['data-src'] for image in images])

        # 옵션 선택 썸네일 이미지 긁어오기
        # 이미지가 style 속성의 값인 background-image로 설정되어있기 때문에
        # url만 남기고 긁어올 수 있도록 나머지 텍스트는 split으로 발라내준다.
        thumbs              = bs.find_all('a', {'class' : 'ProductColor__item'})
        thumbnail_image_url = ','.join([thumb['style'].split('background-image: url("')[1][:-3] for thumb in thumbs])

        # 가격 긁어오기
        # 할인 가격이 있는 상품이 있고 없는 상품이 있기 때문에
        # try, except로 케이스를 분기해서 처리했다.
        try:
            discount_price = bs.find('strong', {'class' : 'ProductDetail__price--sale-price'}).text.replace(",", "")
            original_price = bs.find('span', {'class' : 'ProductDetail__price--consumer-price'}).text.replace(",", "")
        except:
            discount_price = None
            original_price = bs.find('strong', {'class' : 'ProductDetail__price'}).text.replace(",", "")

        # 소재 긁어오기
        # 소재는 상품 페이지 아래 부분에 있으면서
        # 스크롤이 가까이 내려가야 비로소 로딩되기 때문에
        # 아래 키(↓)를 눌러 스크롤을 내려준다.
        body = driver.find_element_by_css_selector("body")
        for i in range(200):
            keys = body.send_keys(Keys.PAGE_DOWN)
        time.sleep(3)
        # 스크롤을 내려준 후에야 소스가 로딩되기 때문에 다시 긁어와서 html로 변환해준다.
        htmlsrc = driver.page_source
        bs      = BeautifulSoup(htmlsrc, "html.parser")
        material    = bs.select_one('.ProductDetailContent__desc > ul:nth-of-type(1) > li:nth-of-type(3) > p').text

        # 제조국 긁어오기
        country     = bs.select_one('.ProductDetailContent__desc > ul:nth-child(1) > li:nth-child(4) > p').text
        
        # 상품번호 긁어오기
        product_number_n = bs.select_one('.ProductDetailContent__desc--ul > li:nth-child(1)').text
        product_number   = " ".join(product_number_n.splitlines()).strip().split(':')[1].strip()

        # csv 파일에 넣을 것. 컬럼명 순서와 같게 해준다.
        csv_writer.writerow((product_number, product_name, like, product_image_url, thumbnail_image_url, discount_price, original_price, material, country))

productpage()

csv_open.close()
driver.quit()

어려웠던 점, 배운 점

이전에는 for문 안에서 긁어온 모든 소스를 row별로 리스트를 만들었다. 다시 그 리스트 요소를 꺼내서 csv 파일에 한 열씩 넣기 위해 for문 밖에 다른 for문을 썼다. 이번에는 굳이 그럴 필요가 없다는 걸 (다른 팀원 코드를 보고) 알게 되어서 for문 안에서 바로 한 열씩 들어가도록 바꿔줬다.

Selenium click 참고했던 페이지

click() 함수는 결국 쓰지 않았지만 아래 두 페이지를 참고해서 시도했었다. 페이지 1 페이지 2