아리아 날다

[Python Crawling] 셀레니움을 이용한 동적 크롤링, 잘 알고 쓰시나요? 본문

Python Crawling

[Python Crawling] 셀레니움을 이용한 동적 크롤링, 잘 알고 쓰시나요?

Aria Park 2023. 3. 11. 18:37

| 들어가며 |

안녕하세요. 인생의 고수가 되고 싶은 아리아입니다.

최근 프로젝트를 새로 시작하면서 사용하고 싶은 기술, 만들고 싶은 기능은 많은데 어느 것 하나 쉬운게 없다는 걸 몸소 깨달았습니다. 그래서 실패와 성공에 일희일비 하지 않고 고수처럼 초연하게 나만의 서비스를 만들고 싶다는 생각을 자주 했는데요. 이번 블로그를 시작으로 다양한 문제상황을 마주하며 그만큼 의미있는 경험들도 할 수 있었던 프로젝트인 JBLY를 진행하면서 겪은 문제 상황과 해결 방법에 대해서 세 차례에 걸쳐 다음 순서로 연재해보려고 합니다.


1️⃣ 셀레니움을 이용한 동적 크롤링, 잘 알고 쓰시나요?

2️⃣ 쇼핑몰 크롤링시 뭉쳐있는 이미지 OpenCV로 crop해서 저장하기

3️⃣ 20,000장의 사진을 몇 분 안에 크롤링하고 다운로드 받을 수 있을까?

 

| JBLY프로젝트 소개 |

JBLY는 크롤링을 이용한 쇼핑몰 모아보기 서비스 입니다. 크롤링하는 데이터는 상품 이름, 가격, 카테고리와 같은 상품 정보와 상품 상세 페이지에 있는 여러 장의 이미지입니다. 상품 정보는 DB(MySQL)에, 상세 이미지는 로컬 디스크에 저장하는 방식으로 진행합니다. 상세 페이지에 있는 여러장의 상세 이미지는 로컬에 저장한 이후 TensorFlow를 이용해 AI학습에 사용할 계획입니다. 이미지 처리에 대한 자세한 설명은 이후 업로드 될 게시물(쇼핑몰 크롤링시 뭉쳐있는 이미지 OpenCV로 crop해서 저장하기)을 참고해주세요😸

 

쇼핑몰은 한 페이지로 이루어져 있지 않습니다. 많게는 20페이지가 넘어가는 쇼핑몰도 있는데 어떻게 모든 페이지에 있는 데이터를 수집할 수 있을까요?  가장 쉽게 떠올릴 수 있는 방법은 셀레니움 라이브러리를 이용한 동적 크롤링 입니다. 셀레니움(Selenium)은 웹 브라우저를 제어하는 데 사용되는 자동화 도구로서 라이브러리를 이용하면 동적으로 생성되는 콘텐츠(JavaScript, Ajax)로 웹 페이지를 스크래핑 할 수 있습니다. 이번 게시글에는 셀레니움 라이브러리를 사용하여 데이터를 스크래핑했던 과정과 작업하며 발견한 문제상황, 고민한 내용에 대해서 작성해보겠습니다.

 

추가적으로 이 글은 "셀레니움 라이브러리 사용은 항상 비효율적이다"라는 의도로 작성한 글이 아님을 말씀 드립니다. 셀레니움을 이용해 프로젝트를 진행하면서 속도를 저하시키는 원인을 찾아냈고, JBLY프로젝트에 한정된 내용이니 참고해주시면 감사하겠습니다 😸

 

| 크롤링? 스크래핑? |

위 내용을 읽으며 눈치채신 분도 계실텐데요. 크롤링과 스크래핑이라는 단어가 불규칙적으로 등장했습니다. 크롤링과 스크래핑은 밀접한 관련이 있고 비슷한 개념이지만 이번에 프로젝트를 진행하면서 웹 크롤링과 스크래핑은 차이가 있다는 것을 알게 되었습니다. 먼저 두 개념에 대해서 짚어보겠습니다.

Crawling

 

Web Crawling

크롤러는 HTML 내부 링크를 따라 웹 페이지를 순회합니다. 크롤링을 요약하면 HTML 내부에 있는 a 태그의 href 속성을 추출하고 해당 URL에 접근하는 것의 반복이라고 할 수 있습니다. 인터넷에 있는 여러 컨텐츠를 미리 크롤링해 저장해 둠으로써 검색 서비스를 제공하는 구글(Google)과 같은 검색 서비스를 크롤러의 용도로 떠올릴 수 있습니다. 

 

Scraping

Wep Scraping

수집한 콘텐츠에서 필요한 정보를 추출하는 것을 스크래핑이라고 합니다. 크롤러가 HTML내부 링크를 타고 돌면서 콘텐츠를 수집했다면 스크래핑은 특정 콘텐츠 내에 있는 텍스트나 파일과 같이 필요한 정보를 추출하는 작업을 말합니다. 

 

➡️ 결국 크롤링하며 수집하는 콘텐츠나 스크래핑 하며 수집하는 필요한 정보나 '수집한다'는 개념이 동일해 크롤링과 스크래핑은 큰 구분없이 사용되고 있습니다. 이번 프로젝트의 경우에도 상세 페이지 URL을 접근할 때는 크롤링, 특정 콘텐츠 내에 있는 데이터 중 필요한 것만 추출할 때는 스크래핑한다고 표현하는게 더 적절합니다.하지만 편의를 위해 이후 블로그부터는 크롤링으로 단어를 통일하도록 하겠습니다.

 

| 데이터 스크래핑 과정 |

✔️ 타겟이 될 쇼핑몰과 쇼핑몰에서 어떤 데이터를 스크래핑할지 정합니다.

사이트 구조와 특성이 각기 다 다른 세개의 쇼핑몰 사이트(Porterna, More-Cherry, The-Verlin)를 타겟으로 정하고, 카테고리 별로 상품 이름, 상품 이미지, 상품 가격, 상품 상세 페이지 URL, 상세 페이지 내용을 수집했습니다. 

 

✔️ 어떤 방식을 이용해서 데이터를 스크래핑 할지 정합니다.

Pagination 되어있는 상품 리스트를 넘기면서 필요한 모든 데이터를 수집해야 하기 때문에 동적 크롤링이 가능한 라이브러리를 이용하는 것이 적합하다고 생각셀레니움 라이브러리를 사용했습니다. (자세한 코드는 여기에서 확인하실 수 있습니다)

 

✔️ 데이터를 적재할 DB를 선택하고 스크래핑 후 데이터를 Insert 합니다.

MySQL 디비에서 product 테이블 컬럼은 id, detailInfo, price, image, productName, productType, shopId, shopName, detailHtml로 구성되어 있습니다. 상품 대표 이미지는 img 태그의 src속성을 스크래핑해 디비에 적재하는 방식으로 진행했습니다.

 

product table

 

| 문제 상황 |

⚠️ 데이터 4078개(2023/03/11 기준)를 MySQL에 적재하는데 걸린 시간 총 2시간 32분

셀레니움은 웹 브라우저를 제어하여 웹 페이지를 로드하고 JavaScript코드를 실행하는 방식으로 페이지가 렌더링됩니다. 그런데 이 방식은 스크래핑 하는 속도에 영향을 미칩니다. 

driver = webdriver.Chrome(executable_path='chromedriver')

'chromedriver'는 webdriver가 Chrome브라우저를 제어하는데 사용하는 별도의 실행파일로, 위 코드를 이용해서 webdriver 인스턴스를 생성할 수 있습니다. 스크래핑이 시작되는 코드 전에 생성한 인스턴스를 로드하면 Python 스크래핑 코드를 사용하여 웹 페이지를 탐색하고, 링크를 클릭한 후 웹 페이지에서 데이터를 추출할 수 있습니다. 요구사항 중 상품 상세 페이지에 있는 HTML 코드도 추출 해야하기 때문에 webdriver 인스턴스를 스크래핑 하는 코드 안에 한 개 더 추가했습니다. 

 # get detail information html
 detail_driver = WebExecutor.executor()
 detail_driver.get(detail_info)
 b_soup = BeautifulSoup(detail_driver.page_source, 'html.parser')
 detail_html = b_soup.find("div", "cont")

위 코드를 구현한 후 애플리케이션을 실행시키면 상세 페이지에 접근해 원하는 데이터를 스크래핑 할 수 있습니다. 아래 동영상은 스크래핑 해야하는 데이터가 있는 페이지를 순차적으로 렌더링하는 과정입니다. 

 

모어체리 사이트 상세 페이지 동적크롤링 과정

위 방법으로 상세페이지까지 접근해 필요한 데이터를 스크래핑 하게 된다면 상품 개수 X 화면이 렌더링 되는 시간이 소요됩니다. 즉 스크래핑 해야하는 상품이 총 10,000개라면 10,000번의 렌더링이 진행될 동안 기다려야 합니다. 필요한 데이터는 상품 정보 뿐만 아니라 상세 페이지에 있는 여러장의 상세 이미지도 있습니다. 이미지는 로컬에 저장하는 과정까지 진행해야 하는데요. 위와 같은 방법으로 이미지를 스크래핑 한 후 다운로드 받게 되면 얼마나 많은 시간이 걸릴지 혹시 감이 오시나요?

 

| 해결 방법 |

1️⃣ 셀레니움 웹 드라이버 옵션 추가하기

from selenium import webdriver

def execute():

  chrome_options = webdriver.ChromeOptions()
  chrome_options.add_argument('--headless')
  chrome_options.add_argument('--disable-dev-shm-usage')
  chrome_driver = "./chromedriver"
  return webdriver.Chrome(executable_path=chrome_driver, options=chrome_options)

✔️ --headless

앞서 말씀드린 페이지 렌더링 문제를 해결하기 위해서 추가한 옵션입니다. 이 옵션을 추가하면 브라우저 창을 표시하지 않고 브라우저를 백그라운드에서 실행하도록 지정할 수 있고, 브라우저가 백그라운드에서 실행되므로 시스템 자원을 절약 할 수 있습니다.

 

✔️ --no-sandbox

이 옵션을 추가하면 보안 기능을 비활성화하도록 지정할 수 있습니다. 크롬 브라우저가 실행 중에 발생할 수 있는 보안 문제를 회피할 수 있어서 스크래핑 기능에만 초점을 맞출 수 있다고 생각해 추가했습니다.

하지만 이 옵션은 크롬 브라우저 속도에 직접적인 영향을 주지 않습니다. 오히려 문제를 회피하는 과정에서 보안을 저해시킬 수 있는 위험성이 있어 삭제했습니다. 

 

✔️ --disable-dev-shm-usage

이 옵션은 크롬 브라우저가 Linux 운영체제에서 '/dev/shm' 디렉토리를 사용하지 않도록 지정하니다. 이 옵션을 사용하면 크롬 브라우저가 '/dev/shm' 대신 디스크에 메모리를 저장하게 되므로, 시스템 자원의 사용이 줄어들어 메모리 사용량이 감소하고 실행 속도가 향상될 수 있습니다.

 

⚠️ 하지만! 위 옵션을 추가해도 기대한만큼 스크래핑 속도가 빨라지진 않았습니다. 단순히 페이지가 클라이언트 사이드에서 렌더링 되는 시간을 줄여야 하는 것이 문제가 아니라, 서버 사이드에서 렌더링 되는 시간도 기다려야 한다는 것이 근본적인 문제였습니다. 결국 셀레니움 라이브러리를 이용해 동적으로 스크래핑을 했던 것 자체가 속도 저하의 주원인이라는 걸 알게 되었습니다.

2️⃣ BeautifulSoup로 셀레니움 기능을 대체했습니다.

결국 기존 있었던 웹 드라이버를 사용한 코드를 전부 삭제하고 지정된 URL로 HTTP요청을 보내 응답으로 원하는 페이지를 가져올 수 있게 구현했습니다. 

main_url = "https://porterna.com"
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"

header = {
        'Referrer': main_url,
        'user-agent': user_agent
    }
    response = requests.get(main_url, headers=header)

이렇게 사용자가 사용 중인 웹브라우저와 운영체제를 식별하는 user_agent를 추가한 header값과 함께 http요청을 보내고 원하는 페이지에 대한 정보를 응답 값으로 받아온 후 

   b_soup = BeautifulSoup(response.text, 'html.parser')

beautifulSoup을 이용해 파싱하는 작업을 할 수 있도록 리팩토링 했습니다.

HTTP GET요청을 통해 얻은 전체 HTML

위와 같은 작업을 통해서 상세 페이지에 접근하는 기능도 셀레니움 웹드라이버의 도움 없이 구현 할 수 있었습니다. 이 결과 2시간 30분에서 30분대로 전체 크롤링 시간을 단축시킬 수 있었습니다. 

 

| 마무리하며 |

프로젝트 전반적인 내용을 최대한 간략하고 독자분들께서도 읽기 편하게 설명하려다 보니 다 설명드리지 못한 부분도 있는데요. 자세한 내용은 프로젝트 링크를 통해 확인하실 수 있습니다. 어떤 라이브러리를 사용하는지가 성능에 큰 영향을 끼친다는 걸 몸소 알게 된 계기였고, 이후 진행할 상세 이미지 스크래핑 후 로컬 디스크에 저장하는 기능도 이번에 개선한 스크래핑 기능을 도입해 구현할 예정입니다. 추후 업로드 되는 게시물도 많은 관심 부탁드립니다. 긴 글 읽어주셔서 감사합니다 😸

 

 

 

https://github.com/f-lab-edu/JBLY

 

GitHub - f-lab-edu/JBLY: [성능 40배 튜닝] 크롤링을 이용한 쇼핑몰 모아보기 서비스

[성능 40배 튜닝] 크롤링을 이용한 쇼핑몰 모아보기 서비스. Contribute to f-lab-edu/JBLY development by creating an account on GitHub.

github.com

| 참고한 사이트 및 도서 |

https://brightdata.com/blog/leadership/web-crawling-vs-web-scraping?

 

Web Crawling vs. Web Scraping - Detailed Comparison

Is web crawling the same as web scraping? Here we'll explain the main difference between web crawling and web scraping and their uses.

brightdata.com

 

 

Comments