동적 크롤링을 하면서 자주 나는 오류 정리

site loading 기다리기

셀레니움은 기본적으로 사이트가 로딩되는 것을 기다려준다. HTTP response와 rendering을 기다린다. 정확히는 HTML과 CSS와 일부의 JS를 페이지에 그려줄때까지만 기다려준다. JS는 렌더링시에 실행되는 스크립트와 onload 이벤트가 호출되면 추가적인 데이터를 가져와 실행되는 스크립트 두가지로 나뉘며, 앞에서 말한 일부의 JS는 전자를 의미한다. 후자는 셀레니움이 기다려주지 않기 때문에 Expected Condition이라는 추가적인 셀레니움 패키지를 사용하여 기대하는 element가 나올때까지 기다려줘야한다. 아래는 자주 사용되는 expected condition들이다.

  • title_is(‘title’)
    • head에 선언된 title (탭에 표시되는 제목)을 확인
  • title_contains(‘title’)
    • title에 특정 문자가 포함되어 있는지를 확인
  • presence_of_element_located(locator)
    • 특정 요소가 존재하는지 확인
  • visibility_of_element_located(locator)
    • 해당 요소가 페이지에 존재하며 화면에 표시되는지 확인
  • text_to_be_present_in_element(locator, text)
    • 데이터가 없는데도 element는 존재할 수 있기 때문에 해당 요소에 텍스트가 존재하는지 확인
  • text_to_be_present_in_element_value(locator, text)
    • 해당 요소의 value값으로 text를 가지는지 확인
  • frame_to_be_available_and_switch_to_it(locator)
    • 프레임이 있다가 없다가 할수도 있기 때문에 iframe이 존재하는 페이지에서 해당 프레임으로 넘어갈수 있는지 여부를 확인 (iframe이 존재하면서 사용가능할때까지 기다림)
  • element_to_be_clickable(locator)
    • 해당 요소가 클릭할 수 있는 상태인지 확인
    • ex) 약관 동의를 하지 않으면 다음으로 넘어가는 버튼이 활성화 되지 않는 것을 확인
  • alert_is_present
    • alert 창이 생기는지 확인
    • 새로고침시에 작성하던 데이터가 날라갈수도 있다고 경고하는 팝업 등을 alert라고 함

screenshot 찍기

chrome.get('https://www.naver.com')
find_visible('input#query').send_keys('아이폰14\n') # 검색

image

chrome.save_screenshot('./full_screenshot.png') 

image

e = find_visible("li[data-area-name*=',1']") # 첫번째 검색결과 가져오기
print(e.text)

# 해당 element 스크린샷
e.screenshot('./test.png')

image

페이지 잘리지 않게 스크린샷 찍기

# 전체 페이지 스크린샷
chrome.set_window_size(1000,10000)
body = find_visible('body')
body.screenshot('./full_screenshot.png')
'''
# 모니터에 담기는 부분만 표시됨
chrome.save_screenshot('./full_screenshot.png') 
모니터를 안띄우고 진행하면 전체 페이지 스크린샷 가능
-> options.set_headless(True): 가상의 창을 메모리에 띄움
'''

원하는 부분 테두리 그리기

# border 그리기
chrome.execute_script("""
    document.querySelector("li[data-area-name*=',1']").setAttribute('style', 'border:10px solid red')
""")

image

전체 코드

from selenium import webdriver
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.webdriver.common.keys import Keys

options = webdriver.ChromeOptions()
options.headless = True
options.add_experimental_option('excludeSwitches', ['enable-logging'])

chrome = webdriver.Chrome('./chromedriver.exe', options=options)
wait = WebDriverWait(chrome, 10)

def find_visible(css):
    return wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, css)))

def find_visibles(css):
    find_visible(css)
    return chrome.find_elements(By.CSS_SELECTOR, css)

chrome.get('https://www.naver.com')
find_visible('input#query').send_keys('아이폰14\n') # 검색

e = find_visible("li[data-area-name*=',1']") # 첫번째 검색결과 가져오기
print(e.text)

# 해당 element 스크린샷
e.screenshot('./test.png')

# border 그리기
chrome.execute_script("""
    document.querySelector("li[data-area-name*=',1']").setAttribute('style', 'border:10px solid red')
""")

# 전체 페이지 스크린샷
chrome.set_window_size(1000,10000)
body = find_visible('body')
body.screenshot('./full_screenshot.png')
'''
# 모니터에 담기는 부분만 표시됨
chrome.save_screenshot('./full_screenshot.png') 
모니터를 안띄우고 진행하면 전체 페이지 스크린샷 가능
-> options.set_headless(True): 가상의 창을 메모리에 띄움
'''

chrome.quit()

stealth 셀레니움 아닌척 하기

웹서버는 자동화된 봇을 막기 때문에 셀레니움도 막힐 수도 있다. 따라서 사용자가 직접 사용하는 웹브라우저와 셀레니움이 사용하는 웹 브라우저의 차이를 없게 만들어 주는 stealth 라이브러리로 셀레니움이라는 것을 티 안나게 만들어줄 수 있다.

pip install selenium-stealth

스텔스 적용 전에는 webdriver로 판별됨

from selenium import webdriver
from selenium_stealth import stealth
import time

chrome = webdriver.Chrome('./chromedriver.exe')

url = "https://intoli.com/blog/not-possible-to-block-chrome-headless/chrome-headless-test.html"
chrome.get(url)
time.sleep(5)

chrome.quit()

image

스텔스 적용후에는 웹 드라이버로 판별되지 않음

from selenium import webdriver
from selenium_stealth import stealth
import time

chrome = webdriver.Chrome('./chromedriver.exe')

stealth(chrome,
        languages=["en-US", "en"],
        vendor="Google Inc.",
        platform="Win32",
        webgl_vendor="Intel Inc.",
        renderer="Intel Iris OpenGL Engine",
        fix_hairline=True,
        )

url = "https://intoli.com/blog/not-possible-to-block-chrome-headless/chrome-headless-test.html"
chrome.get(url)
time.sleep(5)

chrome.quit()

image


동적 크롤링 한계 - recaptcha

특정 웹사이트는 실제 유저이더라도 제재나 한계를 정하는 경우가 있다. 1분에 1개 이상 트윗 금지라던지, 카카오톡 1초내로 6개 이상 메시지 금지가 그 예이다. 이 경우는 stealth 를 써도 동적 크롤링이 동작하지 못한다. 또한, recaptcha라고 사람만 풀 수 있는 문제를 풀게하여 컴퓨터 자동화 봇을 방지하는 경우도 있다. 이는 비정상적 요청 제한을 위한 것으로 동적 크롤링이 불가능하다.

image