공부하며 jupyter notebook으로 정리한 내용을 복습하는 겸 공유하기 위해 작성한다.
1. 크롤링이란?
한마디로 "자동 정보 수집 기계" 라고 할 수 있다.
물론 쓰다보면 융통성 없는 크롤러에게 빡쳐서 "자동" 을 빼고싶어진다.
2. Selenium이란?
옛날 옛적 사람들이 C와 Java만을 사용하던 시절을 지나, 이젠 python으로 크롤링을 하기 시작했다.
파이썬은 게으른 사람들을 위한 언어인 만큼 남들 쓰기 편하게 라이브러리를 만들기 시작하는데, BeautifulSoup이 대표적이다.
그런데 이걸로 크롤링하니까 여러가지 한계점들이 있었다. 예를 들자면, 사람이 클릭할 때는 버튼이 다 뜬걸 보고 클릭하는데 기계는 버튼의 wrapper만 나타나도 클릭을 해버리는 등의 것이 있다. 그 외에도 robots.txt 나 서버에서 접근을 금지하거나 등등... 여러 문제점이 있는데.
일일히 이걸 우회하기 귀찮았던 사람들이 "그럼 그냥 정보수집하는 기계 말고 사람처럼 정보수집하는 기계를 만들자"같은 생각을 대뇌피질에서 뱉어낸 것이다.
그래서 직접 request등을 하는 기존의 크롤러와는 달리, Selenium은 마우스, 키보드의 동작과 디스플레이에 바로 나타나는 html과의 상호작용에서 더 큰 역할을 한다고 볼 수 있다.
한마디로 "요청 실행 대신, 요청 실행하는 버튼 누르기"를 하는 것이다.
이걸 쓰면 로그인, 버튼 노가다, 오토 싸강 플레이어 제작 등에 기존 크롤링 보다 유용하다.
Selenium은 이런 귀차니스트들의 니즈를 파악해 쓰기 편하게 함수(메서드)들을 싹다 모아놓은 놈이다.
3. 어떻게 동작하는데?
1) 페이지 정보를 직접 받아오는 대신, 진짜 브라우저(크롬, 파이어폭스, 익플)을 실행시켜 들어간다.
2) 그다음 그 브라우저의 드라이버를 통해 페이지 정보를 받아와서, 상호작용할 수 있는 정보들(텍스트 창, 클릭 버튼) 등의 포인터를 검색해 가져온다.
3) 거기에 내가 원하는 정보 넣고, 실행하고, 필요한 정보를 검색해 가져오기를 한다.
일반적인 크롤러보다 상호작용적인 성격이 훨씬 강조된다.
4. 그래서 어떻게 쓰는데?
아래에 주석과 함께 기초적인 코드를 넣어놓았다.
# 실행에 필요한 import 들
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
import sys
# 기본적인 import
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
# 동적 페이지 크롤링에 필요한 import
실행에 기본적으로 필요한 패키지
chromedriver = '크롬드라이버 경로'
sys.path.append(chromedriver)
# 옵션 추가
options = webdriver.ChromeOptions()
options.add_argument('window-size=1920x1080')
options.add_argument('disable-gpu')
options.add_argument('lang=ko_KR')
options.add_argument('headless')
# HeadlessChrome 사용시 브라우저를 켜지않고 크롤링할 수 있게 해줌
options.add_argument('User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36')
# 헤더에 headless chrome 임을 나타내는 내용을 진짜 컴퓨터처럼 바꿔줌.
경로를 설정한 뒤 환경변수에 저장하고(파이썬 내 저장은 임시이므로 걱정할 필요 없다)
필요한 옵션을 추가한다.
# 사이트 호출 및 확인
driver = webdriver.Chrome(chromedriver, options=options)
driver.get('크롤링 시작할 주소')
assert "제목 확인" in driver.title
assert "url 확인" in driver.current_url
옵션을 적용한 브라우저를 열고 주소를 넣어 사이트를 호출, 제목과 url이 기대한 바와 같은지 확인한다.
# Headless 금지 우회 : 플러그인탐지, 언어탐지, WebGL벤더
# driver.execute_script("Object.defineProperty(navigator, 'plugins', {get: function() {return[1, 2, 3, 4, 5];},});")
# driver.execute_script("Object.defineProperty(navigator, 'languages', {get: function() {return ['ko-KR', 'ko']}})")
# driver.execute_script("const getParameter = WebGLRenderingContext.getParameter;WebGLRenderingContext.prototype.getParameter = function(parameter) {if (parameter === 37445) {return 'NVIDIA Corporation'} if (parameter === 37446) {return 'NVIDIA GeForce GTX 980 Ti OpenGL Engine';}return getParameter(parameter);};")
driver.get() 하기 전에 위 코드를 실행하면 Headless Chrome 방지 처리를 한 사이트를 일부 우회할 수 있다.
loop, count = True, 0
while loop and count<10:
try:
button = WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.CSS_SELECTOR, "찾을 버튼의 CSS 선택자")))
button.click()
# webdriver.ActionChains(driver).click(button).perform()
count+=1
except TimeoutException:
print(count)
loop=False
위 코드는 설명이 조금 필요할 듯 싶은데,
1) wrapper : loop와 count 변수를 이용해 exception이 나올 때 까지 try를 실행한다.
2) WebDriverWait은 arg[0] 드라이버를 arg[1]초만큼 wait하는데, until의 조건이 실행되면 그 이전에 break하고, 없으면 exception을 일으킨다.
3) until(EC)는 Expected Condition이 달성되면 break하겠다는 뜻이다. 이때 presence_of_element_located는 arg로 받은 선택자가 존재하는 것을 조건으로 하는 method이다.
4) By.CSS_SELECTOR과 "찾을 버튼의 CSS 선택자" 를 비교해서, EC.presence_of_element_located의 결과값이 True가 된다.
5) 버튼이 존재하면 break하니 click한다. 버튼이 존재하지 않으면 TimeoutException의 구문을 실행하여 루프를 빠져나온다.
주석처리한 내용은 ActionChain이라는 것을 이용한 방법인데,
이는 button에 관련된 method가 많을 경우 각자의 delay로 이상행동이 나타날 수 있으니,
buffer에 명령을 쌓아두었다가 perform() method가 실행되면 한 번에 순차적으로 실행하는 방법이다.
comments_box = driver.find_element_by_css_selector("div.cmt_box > ul.list_comment")
comments = comments_box.find_elements_by_tag_name("li")
comments_text={}
for num, comment in enumerate(comments):
comments_text[num]=comment.find_element_by_css_selector("div > p").text
필요한 버튼을 모두 누른 후 크롤링을 하는 부분이다.
기존 코드가 다음 뉴스 댓글을 크롤링하는 것이었으니 참고하길 바람.
1) comments_box에 댓글 리스트 박스를 css_selector로 선택해서 가져오고
2) comments에 위 박스의 내용을 li 태그 기준으로 분류하여 리스트 형태로 가져온다.
3) comments 리스트 각각의 내용에 대해 p 태그 안에 쓰인 내용(댓글)을 가져온다. 이는 text 속성에 있다.
enumerate는 num을 알기 위해 가져왔으며, num은 댓글의 index를 알기 위해 넣은 변수다.
driver.quit()
브라우저를 닫아 끝낸다.
5. Selenium의 유용함?
크롤링 기초에 쓰이는 BeautifulSoup처럼 상세하게 설정하는 맛도 없고,
Scrapy처럼 무지막지하게 빠르지도 않으나,
Selenium은 분명 그 나름의 장점이 분명하다.
웹페이지는 인간의 시각을 목적으로 만들어졌기에,
시각화를 거친 페이지를 크롤링하는 selenium은
그 상호작용 방법에 있어서 다른 라이브러리와는 차원이 다른 강력함을 보여준다.
query를 필요로했던 검색,
인증을 필요로하던 로그인,
시차를 두고 생겨나는 광고와 더보기버튼.
이런 것들을 인간의 입장에서 대응하기에 최적이므로
간단한 응용법 몇 가지를 알고 있으면 빛 볼 날이 있을 것이다.