본문 바로가기
Scraping

[파이썬으로 웹스크래핑] 셀레니움 기능탐구(1) 요소찾기

by 돈민찌 2021. 8. 11.
반응형

자꾸 뭔가 좀 설명하려다가 코드 하나 가져와서 참 쉽죠?를 반복하니까 아는 사람만 재미있게 보고 깜깜이인 초보자 분들은 힘들 수도 있겠다는 생각이 들어 핵심적인 기능 소개를 몇가지 해보고자 합니다ㅎㅎ 한번 들어봐주세요.

셀레니움은 웹드라이버를 조작하는 라이브러리입니다. 사실 셀레니움은 웹스크래핑만을 위해 개발되어진게 아니고 그것이 주목적도 아닙니다. 오히려 웹개발 과정에서 가상의 유저의 행동을 만들어내 그 유저가 정해진 행동을 하도록 만들어내는 게 주목적에 더 가까운 것 같습니다. 제가 만든 로그인 페이지에 셀레니움이 회원가입을 하고, 아이디와 비밀번호를 기입해 로그인하고 사이트를 사용하는 과정을 지켜보면서 "아 여기서 이게 펼쳐지면 안되는군","아 이 부분이 대기시간이 긴 편이네" 할 수 있는 것이지요. 셀레니움으로 동작하는 프로그램은 크롤링을 하기에는 사실 조금 느린 편이기도 하구요. 그래서 이전 글에서 뷰티풀숲과 혼용했던 것입니다. 그래도 당장 쉽고 빠르고 유용하게 배울 수 있는 웹스크래핑 도구인 것에는 틀림없습니다. 

웹드라이버를 조작하는 셀레니움을 사용할 때 가장 기본적인 테크닉은 수많은 태그와 정보들로 이루어진 웹페이지에서 내가 필요로 하는 element를 찾아 동작하는 것입니다. 웹드라이버를 불러온 뒤, 필요한 요소를 찾아 driver.find를 입력하면 이렇게 많은 메소드가 나타납니다.

id로 찾기 driver.find_element_by_id("") driver.find_elements_by_id("")
xpath로 찾기 driver.find_element_by_xpath("") driver.find_elements_by_xpath("")
name으로 찾기 driver.find_element_by_name("") driver.find_elements_by_name("")
tag_name으로 찾기 driver.find_element_by_tag_name("") driver.find_elements_by_tag_name("")
class로 찾기 driver.find_element_by_class_name("") driver.find_elements_by_class_name("")
css 선택자로 찾기 driver.find_element_by_css_selector("") driver.find_elements_by_css_selector("")
링크로 찾기 driver.find_element_by_link("") driver.find_elements_by_link("")
링크 텍스트로 찾기 driver.find_element_by_link_text("") driver.find_elements_by_link_text("")
부분적인 링크 테스트로 찾기 driver.find_element_by_partial_link_text("") driver.find_elements_by_partial_link_text("")

표에 정리해보니 훨씬 알아보기 쉽네요. 왼쪽은 find_element 단수이니 None 혹은 엘리먼트 하나를 찾을 수 있겠네요. 오른쪽은 find_elements 복수이니 빈 배열 혹은 엘리먼트 배열을 리턴할 것입니다. 주의해야 할 것은 오른쪽의 코드로 찾아낸 것은 하나 뿐이라고 해도 배열의 형태로 리턴되기 때문에, 바로 get_text() 등의 메소드를 적용하면 오류가 발생합니다. 필요한 정보에 따라 반복문을 사용해서 데이터를 꺼내는 것이 좋아보입니다.

자 이번엔 각 행을 볼까요 id, class, css선택자는 html/css를 배운 사람이라면 금방 이해하실 것 같네요. 이 메소드를 사용할 때에는 앞에 #이나 .을 붙일 필요가 없습니다. 예를 들어 다음과 같은 태그가 있다면,

<a href="/menu" id="my-hyper-link" class="new-link"></a>

driver.find_element_by_id("my-hyper-link") 혹은
driver.find_element_by_class_name("new-link"), 

혹은 이를 tag_name과 합쳐서
driver.find_element_by_css_selector("a#my-hyper-link.new-link")
이렇게 사용할 수 있을 것 같습니다.

 

아직도 코딩 안배웠어? 5만원 깎아줄께 형아만 믿어

 

또 이렇게 타겟 태그가 id나 클래스를 가지지 않은 상태라면 css_selector는 상위 태그들부터 아래로 내려가는 형식을 취해야 하는데요. 크롬의 개발자 도구를 열어 첫번째 탭인 Elements에서 타겟에 우클릭을 한 뒤 Copy > Copy selector를 클릭하시면 됩니다.

#__next > div > section > div.MainArticle__MainSection-vnk5cw-0.cUAoQD > article > div.Title__ConTitle-nk34jq-0.eNspaB > div > h3

이렇게 복사되었네요. 아마도 앞서 타겟 태그만 보고 작성한 선택자보다 더 구체적이고 명확하게 해당 태그를 지정할 수 있겠네요. 여기 Copy selector 옆에 JS path와 XPath를 주목해주세요. XPATH를 클릭하면 아까 위에서 보여드린 find_element_by_xpath() 안에 넣을 수 있는 Xpath가 복사됩니다. 한번 들여다 볼까요?

//*[@id="__next"]/div/section/div[1]/article/div[1]/div/h3

위의 css 선택자랑 비교해보면 __next라는 아이디를 가진 태그로 시작하는 등 비슷한 구조를 나타내고 있는 것으로 보이기는 하는데, 글자 수 자체가 적고 보기에 편하긴 합니다. xpath가 뭐길래 그럴까요?

Xpath는 C://User/Kkori/Desktop 이런 식으로 윈도우의 특정한 디렉토리를 설정하듯이, XML 문서의 특정한 요소나 속성에 접근하기 위한 경로를 지정하는 언어입니다. 일반적인 컴퓨터에서 같은 이름을 가진 폴더나 파일을 한 곳에 여러개 생성할 수 없기 때문에 컴퓨터의 경로는 오직 이름만을 타고 내려가지만, XML 구조에서는 같은 이름의 태그가 동일한 깊이에 얼마든지 많이 존재할 수 있기 때문에, 이것을 [1]이런 식으로 인덱싱을 하거나, [@속성="값"] 같은 방법으로 특정할 수 있습니다. 반대로 말하자면 이 인덱싱 부분을 삭제하면 해당 태그의 형제태그들이 모두 선택될 수 있습니다. 

저는 간결하다는 이유로 xpath를 스크래핑 코드에 자주 씁니다. 보기 좋은 코드가 이해하기에도 편하니까요. css selector는 " > " 이 부분이 있어야 바로 직계 자식태그가 특정되는것에 비해 "/" 요거 하나뿐인 Xpath가 작성하기에도 수월하긴 하죠. 단 하나 평소 코드를 짤 때와 다르게 접근해야 하는것은, xpath의 인덱싱은 0이 아닌 1부터 시작입니다!!! (너네 통일 좀 해줘...)

그리고 JS Path라는 것도 보였었죠? 저는 이것 역시 꽤 자주 사용하는데요. 클릭해보면

document.querySelector("#__next > div > section > div.MainArticle__MainSection-vnk5cw-0.cUAoQD > article > div.Title__ConTitle-nk34jq-0.eNspaB > div > h3")

이런 값이 복사됩니다. 위의 css selector의 값이 document.querySelector() 괄호 안에 들어있는 구조네요. 이건 자바스크립트의 언어 속에서 해당 태그를 접근할 때 이런 문법으로 접근하는 거라고 생각하시면 됩니다. 셀레니움에는 자바스크립트 코드를 수행하는 메소드도 있다고 했죠? 그때 이 JS Path를 사용합니다. 뭐 예를 들면,

driver.execute_script('document.querySelector("#__next > div > section > div.MainArticle__MainSection-vnk5cw-0.cUAoQD > article > div.Title__ConTitle-nk34jq-0.eNspaB > div > h3").click();')

이런 코드를 통해 해당 h3태그를 클릭하도록 봇을 조작할 수 있는 것이죠? 상세보기를 눌러야 정보를 보여주는 쇼핑몰이라던가, 아니면 ActionChains를 사용하지 않고 특정 버튼을 클릭하는 코드를 작성할 때 꽤나 요긴하게 쓰입니다.

또, 셀레니움의 하위 모듈들 중 by라고 하는 것이 있는데요. 이것은 특별한 기능을 할 때도 있지만 평소에 쓰는 코드를 좀 더 읽기 쉽게 바꾸는데에 도움을 줍니다. 큰 차이가 있는 것은 아닌데 검색하시다보면 자주 보시게 될 것 같아 여기에 알려드려요.

from selenium import webdriver
from selenium.webdriver.common.by import By

# Before
formulas = driver.find_elements_by_class_name('product_card__content')

# After
formulas = driver.find_elements(By.CLASS_NAME, 'product_card__content')

차이가 느껴지시나요? 보통 이렇게 사용하라고 있는 코드는 아닌뎋ㅎㅎ 저는 이게 더 이뻐서 코드를 이렇게 작성합니다. 다른 사람과 함께 작업할 때에는 상대방에게 익숙한 코드 구조를 따라주는 것이 좋지요. 이게 파이썬이 아닌 환경(자바, C#, 자바스크립트, 코틀린 등)에서 셀레니움을 사용할 때 쓰는 코드와 유사해서 이쪽이 더 익숙한 분들이 있나봐요. 사실 이런 상황보다는 expected_conditions와 WebDriverWait을 사용해 명시적인 대기(Explicit Wait)를 구현할 때 By 모듈을 잘 사용하는 것 같아요. 예를 들면

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Chrome('chromedriver')
driver.get(url='https://www.google.com/')
wait = WebDriverWait(driver, 5)
try:
    wait.until(EC.presence_of_element_located((By.CLASS_NAME , 'gLFyf')))
finally:
    driver.quit()

이렇게 말이죠. 당장 이 부분을 공부하셔야 하는 것은 아닌데, 구글링하시다보면 무조건 보시게 될 코드 구조라 여기서 언급만 해봅니다. 결국엔 아시게 될... 것들이라서요..ㅎㅎ

아 이것도 중요한 부분이니까 알려드리고 끝낼게요:D 아낌없이 다준다... Selenium은 4버전부터 태그를 특정하는 것 뿐만 아니라, 해당 태그와 관련있는 (앞에 있거나 오른쪽 왼쪽에 있다거나 하는) 요소까지 특정할 수 있는 withTagName() 메소드를 지원합니다. 이런 식으로 말이죠... 선택하고 싶은 모든 웹엘리먼트들이 id나 class를 가지고 있을 수는 없기 때문에 자주는 안쓰더라도 종종 써먹을 만한 코드예요.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import with_tag_name

# above()와 below() 메소드
passwordField = driver.find_element(By.ID, "password")
emailAddressField = driver.find_element(with_tag_name("input").above(passwordField))
passwordField = driver.find_element(with_tag_name("input").below(emailAddressField))

# to_left_of()와 to_right_of() 메소드
submitButton = driver.find_element(By.ID, "submit")
cancelButton = driver.find_element(with_tag_name("button").to_left_of(submitButton))
submitButton = driver.find_element(with_tag_name("button").to_right_of(cancelButton))

# near() 메소드
emailAddressLabel = driver.find_element(By.ID, "lbl-email")
emailAddressField = driver.find_element(with_tag_name("input").near(emailAddressLabel))

마지막으로 제가 셀레니움에 대해 공부할 때 참고했던 공식 문서와 한 개발자분의 블로그 링크를 던지고 사라지겠숩니다.

Selenium 공식 문서 한국어버전 [Element 찾기]

 

Element 찾기

하나의 element 찾기 웹드라이버를 사용할 때 알아야할 가장 기본적인 테크닉 중 하나는 페이지에서 element들을 찾는 것입니다. 웹드라이버는 다양한 종류의 내장된 셀렉터 타입들을 제공해주고,

www.selenium.dev

Python Selenium 사용법 [파이썬 셀레늄 사용법, 크롤링]

 

Python, Machine & Deep Learning

Python, Machine Learning & Deep Learning

greeksharifa.github.io

반응형

댓글