brunch

You can make anything
by writing

C.S.Lewis

by 비즈스프링 Oct 18. 2022

selenium을 이용한 크롤러 구현과 3사데이터 획득

인터넷 상에서 획득할 수 있는 데이터들은 다양한 경로로 수집이 가능합니다. 방문자 행동 데이터를 획득하여 웹 분석, 광고 성과 분석에 이용하기도 하고, E-커머스 사이트의 경우 자사 제품 데이터를 이용하여 컨텐츠/아이템 추천에 활용하기도 합니다. 방문자 행동과 컨텐츠 데이터를 결합하여 개인화 추천/광고에 이용하기도 하고, 광고 제공자로 부터 API를 이용하여 획득할 수 있는 광고 집행 데이터와 방문자 행동 데이터를 결합하여 마케팅/캠페인에 활용하기도 합니다. (growthplatform.ai).


Growth Platform개요. Growth Platform Technology Stack.pdf 발췌


원천 데이터의 생산 주체가 누구인가에 따라 자사/1사(1st party) 데이터3사(3rd party) 데이터로 나누기도 하는데, 본 포스트 에서는 외부의 데이터 – 즉 3사 데이터를 획득하는 방법 중 하나인 크롤링에 대해 알아보고, 자동화 툴 인 selenium을 이용한 크롤러를 만드는 예제를 보여드리려고 합니다.


출처 : https://www.selenium.dev



크롤링, 크롤러는 무엇일까?


크롤링은 웹사이트의 특정 페이지 내용을 읽고, 내용을 추출하여 활용하는 행위를 말합니다. 주로 검색엔진에서 크롤링을 많이 사용하는데, 웹페이지의 URL이 정해지면 해당 웹페이지에 요청(http request)을 보내고, 응답으로 수신되는 HTML을 파싱하여 타이틀, 이미지, 다른 페이지로의 링크(URL), 그리고 가장 중요한 컨텐츠를 추출하여 저장합니다. 검색엔진은 이렇게 추출된 컨텐츠를 인덱싱하고, 키워드(검색어)가 주어질 경우 컨텐츠를 검색하여, 그 결과를 웹페이지 링크와 함께 보여주게 됩니다. 크롤러는 이러한 크롤링 행위를 하는 소프트웨어로, 주어진 URL에 대한 컨텐츠를 획득하고, 필요한 항목을 추출하여 저장하거나 API를 통해 서비스하게 됩니다.

최근의 웹사이트는 반응형(Respensive Web)으로 구성되는 경우가 많습니다. 다수의 상품이 진열되는 쇼핑몰 사이트의 경우, 특히 모바일의 경우 페이지 구조를 빠르게 로딩한 후 컨텐츠(상품 이미지 등)을 비동기식으로 채워넣는 경우를 많이 보셨을 겁니다. 이렇게 구성된 쇼핑몰에서 상품 리스트페이지를 curl 같은 전통적인 방식으로 크롤링 하는 경우 실제 내용을 가져 올 수가 없는 경우가 발생합니다. 최근의 크롤러들은 사람이 하는 행위를 흉내내며 작동하므로 단순한 HTML을 이용하는 것을 넘어서 브라우저 화면의 렌더링되는 객체까지 찾아서 데이터를 획득할 수 있습니다. 물론 이런 기술이 만능은 아니며, 자동화된 기술을 방지하는 기술도 있습니다.


자동화 툴을 방지하기 위한 CAPTCHA


Selenium 소개

Selenium은 웹 브라우저를 이용하는 자동화 프로그램입니다. 사람이 일반적으로 웹 브라우저를 이용하여 할 수 있는 행동들을 코드로 작성하면 selenium이 이 코드를 웹 브라우저에 전달하여 실행시킵니다. 여러 절차를 거쳐야 하는 일련의 행동을 미리 코드로 작성함으로써, 반복되는 작업을 자동화 하는데 많이 사용합니다.


출처 : https://www.selenium.dev/documentation/

selenium은 java, python등 여러가지 언어를 이용하여 작성할 수 있습니다. 본 포스트의 예제는 수정이나 실행이 편리하도록 python을 이용하여 작성 해 보겠습니다.



1. selenium 설치 (python)


python이 설치되어 있다면 아래와 같이 pip를 이용하여 간단하게 설치가 됩니다. 본 예제에서 사용하는 함수를 사용하기 위해 최신 버전이 아닌 3.x 가 필요하며, 아래 명령처럼 selenium의 버전을 명시하여 설치 할 수 있습니다.

$ pip install selenium==3.141



2. webdriver 설치


Selenium으로 웹브라우저를 컨트롤 하기 위해 webdriver를 설치해야 합니다. 코드로 작성된 행동을 webdriver를 통해 브라우저에 전달하여 조작하게 됩니다. 본 포스트의 예제에서는 google chrome 브라우저를 조작하기 위해 chromedriver를 사용합니다. 지원되는 브라우저의 종류나 webdriver를 다운로드 받으려면 아래의 링크를 참조하시기 바랍니다.


지원하는 웹브라우저 : https://www.selenium.dev/documentation/webdriver/browsers/


웹드라이버는 브라우저의 버전에 맞는 것을 찾아서 설치하여야 합니다. 서버 프로그램으로 작성하여 운영 중에 웹브라우저가 업데이트되면, 웹드라이버가 원활하게 작동하지 않을 수 있습니다. 이런 경우 웹브라우저 버전에 맞는 웹드라이버로 교체해야 합니다.


웹브라우저(chrome)의 버전 확인


위와 같이 (크롬)브라우저의 버전을 확인하고 같은 버전의 크롬용 웹드라이버를 찾아 다운로드, 압축을 해제 합니다.


Chrome 브라우저와 Chrome Driver 버전 확인



3. 준비 [1] 처음 selenium코드 작성


아래는 chrome용 webdriver를 이용하여 웹브라우저를 띄우고 특정 네이버블로그 페이지를 띄우는 코드입니다. 프로그래밍을 처음 시작할때 배우는 ‘Hello world’ 예제 격입니다.


from selenium import webdriver
# chromedriver
# 압축해제한 웹드라이버의 경로와 파일명 지정
driver = webdriver.Chrome('./chromedriver')
# Load Page
# chrome을 띄워 네이버 블로그 페이지를 연다.
driver.get(url='https://blog.naver.com/bizspringcokr/222638819175')
# 현재 URL을 출력
print(driver.current_url)
driver.close()


4. 준비 [2]. headless로 GUI가 아닌 환경에서도 작동할 수 있도록 하기


준비 1과 같이 크롤러를 생성할 경우, 프로그램이 실행 될 때마다 화면에 chrome 브라우저 창을 띄워 작업하게 됩니다. 화면 렌더링등 자원을 소모하고, GUI 환경에서만 작동이 가능하게 되므로 크롤러로 사용하기에 불편함이 많아집니다. 화면을 보면서 일련의 작업을 위한 코드작성이 완료 되었다면, 화면 출력이 없이 작동하도록 headless로 방식을 변경하여 줍니다.


from selenium import webdriver
from selenium.webdriver.ie.options import Options
# chromedriver
options = webdriver.ChromeOptions()
options.add_argument('headless')
options.add_argument('window-size=1920x1080')
options.add_argument("disable-gpu")
driver = webdriver.Chrome('./chromedriver', options=options)
# Load Page
driver.get(url='https://blog.naver.com/bizspringcokr/222638819175')
print(driver.current_url)
driver.close()




예제 1. – selenium을 이용하여 네이버 블로그 게시물의 공감, 댓글 수 가져오기


먼저 예제로 ‘네이버 블로그의 공감, 댓글수’를 선택한 이유는 curl 같은 기존 방식으로는 공감 수를 획득할 수 없고, selenium 방식에서 가능하기 때문입니다.


네이버 블로그의 공감, 댓글의 위치를 확인


블로그 화면이 로딩된 후 크롬의 개발자도구를 이용하여 확인 해 보면 area_sympathy class를 가진 <div> 내 <em> 엘리먼트가 공감수 값을, area_comment class를 가진 <div> 내 <em> 엘리먼트가 댓글수 값을 가지고 있는 것이 확인됩니다. 하지만, 아래 이미지와 같이 웹페이지를 호출하여 응답으로 출력된 HTML을 보면 공감수가 비어 있습니다. 웹페이지가 웹브라우저에 로딩 된 후 ajax 스크립트 등 추가적인 작업에 의해 공감수 값이 채워지게 되는 것입니다. 따라서 웹페이지 소스 HTML을 가져와 사용하는 전통적인 크롤러에서는 공감값을 가져올 수 없는 것입니다.


블로그 페이지 요청 응답 결과 HTML


이제 네이버 블로그의 공감수, 댓글수를 가져오는 크롤러를 작성해 보겠습니다.







from selenium import webdriver
from selenium.webdriver.ie.options import Options

# chromedriver
options = webdriver.ChromeOptions()
options.add_argument('headless')
options.add_argument('window-size=1920x1080')
options.add_argument("disable-gpu")
driver = webdriver.Chrome('./chromedriver', options=options)

# Load Page
driver.get(url='https://blog.naver.com/bizspringcokr/222638819175')

# 실제 페이지는 "https://blog.naver.com/PostView.naver?blogId=bizspringcokr&logNo=222638819175&redirect=Dlog&widgetTypeCall=true&directAccess=false"
# iframe으로 호출된다.
driver.switch_to.frame("mainFrame");

# 공감
print("공감")
sympathy_element = driver.find_element_by_css_selector('div.area_sympathy')
em_elements = sympathy_element.find_elements_by_css_selector("em.u_cnt._count")
for i, em in enumerate(em_elements):
    print("  L", i, "번째 em:", em.text)

# 댓글
print("댓글")
comment_elements = driver.find_element_by_css_selector('div.area_comment')
em_elements = comment_elements.find_elements_by_css_selector("#floating_bottom_commentCount")
for i, em in enumerate(em_elements):
    print("  L",i, "번째 em:", em.text)
driver.close()


12라인까지 브라우저에서 블로그 페이지를 여는 코드는 동일합니다. 블로그 페이지 소스를 살펴보면 iframe으로 컨텐츠를 불러오도록 되어있는데 이는 두개의 창을 연 것과 같습니다. 이에 따라 selenium이 공감수 객체에 접근할 수 있도록 mainFrame id를 가진 iframe을 지정해 줍니다.


네이버 블로그의 실제 컨텐츠가 보여질 IFRAME


그리고 css셀렉터(find_element_by_css_selector)를 이용하여 공감, 댓글이 표시되는 <div> 객체를 찾고, child element 중 <em>을 찾아서 값을 출력 해 봅니다. 예제 코드를 실행 해 보면, 결과는 아래와 같은데, area_sympathy 하위의 두번째 <em>이 공감수를 가지고, area_comment 하위의 첫번째 <em>이 댓글수를 가지고 있는 것이 확인됩니다.


$ python run_nblog.py 
공감
  L 0 번째 em: 
  L 1 번째 em: 3
댓글
  L 0 번째 em: 2


run_nblog.py 실행 스크린 샷




예제 2. 크롤러 – 크롤링 서비스


네이버 블로그의 공감/댓글 수를 확인하는 코드가 작성되었으니, 이제는 주어지는 네이버 블로그 URL에 따라서 공감/댓글 수 를 출력하는 서비스를 만들어 볼 차례입니다. 웹 서비스를 이용하여 URL을 입력 받고 JSON 형식으로 결과를 출력하도록 합니다. python의 flask를 이용하여 웹 서버를 구축할 수 있습니다. pip를 이용하여 간단하게 설치 합니다.


$ pip install flask


예제 1.에서 만든 코드는 get_sympathy라는 함수로 정의하고, 웹서버에서 /get_sympathy URL로 접근시 매핑되도록 설정합니다.


from selenium import webdriver
from selenium.webdriver.ie.options import Options
import json
from flask import Flask
from flask import request
from datetime import date, datetime

app = Flask(__name__)

# get 공감수, 댓글수
@app.route('/get_sympathy',methods = ['POST','GET'])
def get_sympathy():

    sympathy = 0
    comment = 0
    return_obj = dict([])

    now =  datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    return_obj["date"] = now

    try:
        # chromedriver
        options = webdriver.ChromeOptions()
        options.add_argument('headless')
        options.add_argument('window-size=1920x1080')
        options.add_argument("disable-gpu")
        driver = webdriver.Chrome('./chromedriver', options=options)

        try:
            blog_url = request.args.get('url')
            return_obj["blog_url"] = blog_url
        except:
            blog_url = 'unknown'

        # Load Page
        driver.get(url=blog_url)

        # 실제 페이지는 "https://blog.naver.com/PostView.naver?blogId=bizspringcokr&logNo=222638819175&redirect=Dlog&widgetTypeCall=true&directAccess=false"
        # iframe으로 호출된다.
        driver.switch_to.frame("mainFrame");

        # 공감
        #print("공감")
        sympathy_element = driver.find_element_by_css_selector('div.area_sympathy')
        em_elements = sympathy_element.find_elements_by_css_selector("em.u_cnt._count")
        #for i, em in enumerate(em_elements):
        #    print(i, "번째 em:", em.text)

        sympathy = int(em_elements[1].text)

        # 댓글
        #print("댓글")
        comment_elements = driver.find_element_by_css_selector('div.area_comment')
        em_elements = comment_elements.find_elements_by_css_selector("#floating_bottom_commentCount")
        #for i, em in enumerate(em_elements):
        #    print(i, "번째 em:", em.text)

        comment = int(em_elements[0].text)

    finally:
        driver.close()

    return_obj["sympathy"] = sympathy
    return_obj["comment"] = comment

    return return_obj

# 웹서비스로
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080, debug=True)


아래와 같은 명령으로 웹서비스를 올리게 되면 8080 포트를 이용하는 웹서비스가 실행됩니다.


run_nblog_api.py 실행 스크린샷


$ python run_nblog_api.py 
 * Serving Flask app 'run_nblog_api'
 * Debug mode: ondeployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:8080
 * Running on http://172.16.10.24:8080
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 124-883-050


웹브라우저나 REST client, curl 등 http로 접근하여 사용할 수 있는 크롤러가 완성되었습니다.



여기에 더 깊게 들어가서, 크롤링 대상 URL을 저장하는 데이터베이스, URL들을 loop 돌리면서 크롤러를 호출하는 배치잡, 크롤링 결과를 저장하는 부분까지 만들면 저희 제품인 LOGGER for Viral 제품에서 제공하는 블로그/게시물의 댓글/공감 리포팅과 유사한 기능을 구현 할 수 있게 됩니다. 물론, 저희 상용 제품에서는 공개된 네이버의 API를 활용하는 등 작동 방식이 상이합니다.


지금까지 예제를 통해 자동화 프로그램인 selenium을 이용하여 웹브라우저를 컨트롤할 수 있다는 것, selenium을 통한 크롤링, 간단하게 작동하는 크롤러 서비스를 작성 해 보았습니다. 읽어주셔서 감사합니다.





마케팅에서의 데이터 활용 기술과 인사이트
No.1 Data Partner for Data-Driven Growth
비즈스프링

공식 블로그 | 페이스북 | 네이버 블로그 | 유튜브 | 트위터 | 슬라이드쉐어





매거진의 이전글 UTM 유입 성과 분석 기법

작품 선택

키워드 선택 0 / 3 0

댓글여부

afliean
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari