반응형

파이썬, 셀레니움 XPATH로 요소 찾기 여러 예제 

 

글. 수알치 오상문

 

from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
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.support.relative_locator import locate_with
from selenium.webdriver.support.relative_locator import with_tag_name
import time

 

print('드라이버 로딩...')
options = Options()  # options = webdriver.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-logging"])  # 시스템 usb 오류 금지
options.add_argument("lang=ko_KR")
options.add_argument("user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36")
options.add_experimental_option(
    "prefs",
    { "download.default_directory": 'D:\\download',  # 다운로드 파일을 저장할 경로
      "download.prompt_for_download": False,
      "download.directory_upgrade": True,
      "safebrowsing.enabled": True
    })
# options.add_argument("disable-gpu")
# options.add_argument('--blink-settings=imagesEnabled=false')  # 이미지 로딩 금지
# options.add_argument('--mute-audio')  # 음소거
# options.add_argument('incognito')  # 시크릿 모드 실행
# options.add_argument('headless')
# driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
driver = webdriver.Chrome(options=options)
# driver.maximize_window()
driver.implicitly_wait(10)  # 크롬 브라우저 내부 대기 (암묵적 대기)

 

print('시작!')
try:
    driver.get('https://www.daum.net/')
    time.sleep(3)

 

    # XPATH로 가져오기
    el = WebDriverWait(driver, 3).until(EC.presence_of_element_located((By.XPATH, '//*[@id="daumHead"]/div[2]/div/div[1]/ul/li[4]/a/span')))
    print(1, el.get_attribute('innerText'))

 

    # XPATH에서 '지도' 텍스트 일치한 것을 가져오기
    el = WebDriverWait(driver, 3).until(EC.presence_of_element_located((By.XPATH,  '//*[@id="daumHead"]/div[2]/div/div/ul/li/a[span="지도"]')))
    print(2, el.get_attribute('innerText'))

 

    # '지도'와 같은 레벨의 첫 텍스트 가져오기 : 카페
    el = WebDriverWait(driver, 3).until(EC.presence_of_element_located((By.XPATH,  '//*[@id="daumHead"]/div[2]/div/div/ul/li/a[span="지도"]/../../li/a/span')))
    print(3, el.get_attribute('innerText'))

 

    # '지도' 옆에 있는 '증권' 찾아오기
    el = WebDriverWait(driver, 3).until(EC.presence_of_element_located((By.XPATH,  '//*[@id="daumHead"]/div[2]/div/div/ul/li/a[span="지도"]')))
    el = driver.find_element(with_tag_name("span").to_right_of(el))
    print(4, el.get_attribute('innerText'))

 

    # '지도' 아래에 있는 '영화' 요소 찾아오기
    el = WebDriverWait(driver, 3).until(EC.presence_of_element_located((By.XPATH,  '//*[@id="daumHead"]/div[2]/div/div/ul/li/a[span="지도"]')))
    el = driver.find_element(with_tag_name("span").below(el))
    print(5, el.get_attribute('innerText'))

 

    print('-'*10)
    els = driver.find_elements(By.XPATH, '//*[@id="daumHead"]/div[2]/div/div[1]/ul/li/a/span[text()]')
    for el in els:
        print(el.text)
    print('-'*10)

 

    driver.get('https://news.daum.net/')
    time.sleep(3)

 

    el = WebDriverWait(driver, 3).until(EC.presence_of_element_located((By.XPATH,
        '/html/body/div[2]/main/section/div/div[2]/div[1]/div/div/a/div[strong="코스피"]/span/span')))
    #   '/html/body/div[2]/main/section/div/div[2]/div[1]/div/div/a[1]/div[strong="코스피"]/span/span')))
    print(5, '코스피:', float(el.get_attribute('innerText').replace(',','')))
    el = WebDriverWait(driver, 3).until(EC.presence_of_element_located((By.XPATH,
        '/html/body/div[2]/main/section/div/div[2]/div[1]/div/div/a/div[strong="코스닥"]/span/span')))
    print(5, '코스닥:', float(el.get_attribute('innerText').replace(',','')))

 

except Exception as e:
    print('Error', e)
else:
    time.sleep(2)
    print('종료!')
finally:
    driver.close()
    driver.quit()

 

[실행 결과]
드라이버 로딩...
시작!
1 지도
2 지도
3 카페
4 증권
5 영화
----------
카페
메일
뉴스
지도
증권
쇼핑
카카오TV
웹툰
캘린더
브런치
사전
게임
같이가치
----------
5 코스피: 2409.41
5 코스닥: 785.88
종료!
 

 

[참고] XPATH 문법

/catalog/cd[price>10000]   절대 경로
//catalog/cd[price>10000]  모든 catalog 경로

 

와일드 카드(*)
아래 XPath 식은 catalog 엘러먼트의 cd 엘러먼트의 모든 자식 노드를 선택
/catalog/cd/*
다음 XPath 식은 catalog 엘러먼트의 손자 엘러먼트인 모든 price 엘러먼트를 선택
/catalog/*/price
아래 XPath 식은 2 레벨의 조상 엘러먼트를 가진 모든 price 엘러먼트를 선택
/*/*/price
아래 XPath 식은 경로에 관계없이 문서 속의 엘러먼트를 선택
//*

 

대괄호[]
아래 XPath 표현은 catalog 엘러먼트의 첫 번째 cd 자식을 선택
/catalog/cd[1]
아래 XPath 표현은 catalog 엘러먼트의 마지막 cd 자식을 선택 (first() 함수는 존재하지 않는다)
/catalog/cd[last()]
아래 XPath 표현은 price 엘러먼트를 가진 catalog 엘러먼트의 모든 cd 엘러먼트를 선택
/catalog/cd[price]
아래 XPath 표현은 price 엘러먼트의 값으로 9000을 가진 catalog 엘러먼트의 모든 cd 엘러먼트 선택
/catalog/cd[price=9000]
아래 XPath 표현은 price 엘러먼트의 값으로 9000을 가진 catalog 엘러먼트의 cd 엘러먼트의 모든 price 엘러먼트를 선택
/catalog/cd[price=90000]/price

 

여러 path 선택, | 연산자
다음 XPath 식은 catalog 엘러먼트의 cd 엘러먼트의 모든 title과 artist 엘러먼트를 선택한
/catalog/cd/title | /catalog/cd/artist
다음 XPath 식은 문서 속에 있는 모든 title과 artist 엘러먼트를 선택
//title | //artist
다음 XPath 식은 문서 속에 있는 모든 title, artist, price 엘러먼트를 선택
//title | //artist | //price
다음의 XPath 식은 문서 속에 있는 모든 artist 엘러먼트와 catalog 엘러먼트의 cd 엘러먼트의 모든 title 엘러먼트를 선택
/catalog/cd/title | //artist

 

속성 이용
XPath에서는 모든 속성이 @로 명시되며 아래 예는 country라는 이름을 가진 속성을 선택
//@country
아래 XPath 식은 country라는 속성을 가진 모든 cd 엘러먼트를 선택
//cd[@country]
아래 XPath 식은 어떠한 속성이라도 속성이 존재하는 cd 엘러먼트를 선택
//cd[@*]
아래 XPath 식은 속성 country의 값으로 UK를 가지는 모든 cd 엘러먼트를 선택.
//cd[@country='UK']

 

축 그리고 노드 이용
ancestor            현재 노드의 모든 조상 노드를 포함 (이 축은 현재 노드가 루트인 경우를 제외하고는 항상 루트를 포함)
ancestor-or-self    현재 노드와 모든 조상 노드를 포함
attribute           현재 노드의 모든 속성을 포함
child               현재 노드의 모든 자식 노드를 포함
descendant          현재 노드의 모든 후손 노드를 포함 (이 축은 이름 공간이나 속성은 포함하지 않는다.)
descendant-or-self  현재 노드 및 현재노드의 후손 노드를 포함
following           현재 노드의 태그를 종료한 뒤에 있는 문서 속에 있는 모든 것을 포함
following-sibling   현재 노드의 모든 형제 노드를 포함 (현재 노드가 속성 노드 또는 이름 공간 노드라면 이 축은 비어있음)
namespace           현재 노드의 모든 이름 공간 노드를 포함
parent              현재 노드의 부모 노드를 포함
preceding           문서 속에서 현재 노드의 시작 태그 앞에 존재하는 모든 것을 포함
preceding-sibling   현재 노드 앞의 모든 형제 노드를 포함 (현재 노드가 속성노드 또는 이름공간 노드면 이 축은 비어있음)
self                현재 노드를 포함

 

child::price[price=9000]              현재 노드의 모든 price 자식 엘러먼트중 값이 9000인 price 엘러먼트를 선택
child::cd[position()=1]               현재 노드의 첫 cd 엘러먼트를 선택
child::cd[position()=last()]          현재 노드의 마지막 cd 엘러먼트를 선택
child::cd[position()=last()-1]        현재 노드의 cd 자식 노드 중 마지막에서 두 번째 cd 자식 엘러먼트를 선택
child::cd[position()<6]               현재 노드의 첫 다섯 cd 자식 노드를 선택
/descendant::cd[position()=7]         문서 속의 7번째 cd 엘러먼트를 선택
child::cd[attribute::type="classic"]  현재 노드의 cd 엘러먼트 중 type 속성으로 classic이라는 값을 가지는 엘러먼트 선택

 

cd          현재 노드의 모든 자식 cd 엘러먼트를 선택
*           현재 노드의 모든 자식 엘러먼트를 선택
text()      현재 노드의 모든 텍스트 노드 자식을 선택
@src        현재 노드의 모든 src 속성을 선택
@*          현재 노드의 모든 속성을 선택
cd[1]       현재 노드의 첫 번째 자식을 선택
cd[last()]  현재 노드의 마지막 자식을 선택
*/cd        현재 노드의 모든 손자 노드를 선택
/book/chapter[3]/para[1]   book의 chapter[3]의 첫 para를 선택
//cd        문서 루트의 모든 cd 자손을 선택하고 나서 해당 문서를 현재 노드로 하여 모든 cd 엘러먼트 선택
.           현재 노드 선택
.//cd       현재 노드의 cd 엘러먼트 자손들을 선택
..          현재 노드의 부모를 선택
../@src     현재 노드의 부모의 src 속성을 선택
cd[@type="classic"]     현재 노드의 모든 cd 자식 노드중 type 속성으로 classic을 갖는 자식을 선택
cd[@type="classic"][5]  현재 노드의 cd 자식 노드중 type 속성으로 classic을 갖는 자식중 5번째 자식을 선택
cd[5][@type="classic"]  현재 노드의 5번째 cd 자식 노드가 ype 속성으로 classic을 갖는 다면 선택
cd[@type and @country]  현재 노드의 모든 cd 자식 중 속성으로 type과 country를 가지는 자식을 선택

 

count()          선택된 엘러먼트의 수를 반환
id()             Selects 엘러먼트들을 그들 고유의 ID로 선택
last()           식 평가 컨텍스트(expression evaluation context)로부터 컨텍스트의 크기에 해당하는 수를 반환
local-name()     문서 순서에서 첫 번째인 인수 노드셋(argument node-set)의 노드의 확장명의 local part를 반환
name()           엘러먼트의 이름을 반환
namespace-uri()  문서 순서에서 첫 번째인 인수 노드셋(argument node-set)의 노드의 확장명의 이름 공간 URI를 반환
position()       식 평가 컨텍스트(expression evaluation context)로부터 컨텍스트의 포지션에 해당하는 수를 반환

 

concat()                 Returns 인수의 연접을 반환
concat('The',' ','XML') 결과: 'The XML'
contains()               첫 번째 문자열이 두 번째 문자열을 포함하고 있으면 참이며 그 이외에는 거짓
contains('XML','X') 결과: true
normalize-space()        앞뒤 공백 제거
normalize-space(' The XML')  결과: 'The XML'
starts-with()            첫 번째 문자열이 두 번째 문자열로 시작하면 참이며 그 이외에는 거짓
starts-with('XML','X') 결과: true
string()                 객체를 문자열로 변환
string(3.14) 결과: '3,14'
string-length()          문자열 속의 문자의 개수를 반환
string-length('Beatles') 결과: 7
substring()              부분 문자열을 반환
substring('Beatles',1,4) 결과: 'Beat'
substring-after()        부분 문자열 뒤의 부분 문자열을 반환
substring-after('12/10','/')  결과: '10'
substring-before()       부분 문자열 앞의 부분 문자열을 반환
substring-before('12/10','/') 결과: '12'
translate()              문자를 문자열로 변환
translate('12:30',':','!') 결과: '12!30'

 

ceiling()                인수보다 크지 않은 작은 정수 반환
ceiling(3.14) 결과: 4
floor()                  인수보다 크기 않은 가장 큰 정수 반환
floor(3.14) 결과: 3
number()                 인수를 수로 변환
number(price)
round()                  인수에 가장 근접한 정수를 반환
round(3.14) 결과: 3
sum()                    인수 노드 셋의 각 노드에 대한 노드의 문자열 값을 수로 변환한 결과의 합을 반환
sum(/cd/price)           불리언 함수(Boolean Functions)

 

boolean()                인수를 불리언으로 변환
false()                  거짓 반환
number(false())          결과: 0
lang()
not()                    인수가 참이면 거짓 반환, 나머지의 경우는 거짓
not(false())             true()
number(true()) 결과 : 1

 

# 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))

 

# 위/아래 요소 찾기
email = driver.find_element(locate_with(By.TAG_NAME, "input").above({By.ID: "password"}))
password = driver.find_element(locate_with(By.TAG_NAME, "input").below({By.ID: "email"}))

 

# 왼쪽, 오른쪽 요소 찾기
cancel = driver.find_element(locate_with(By.TAG_NAME, "button").to_left_of({By.ID: "submit"}))
submitr = driver.find_element(locate_with(By.TAG_NAME, "button").to_right_of({By.ID: "cancel"}))

 

# 50px 근처 요소 찾기
email = driver.find_element(locate_with(By.TAG_NAME, "input").near({By.ID: "lbl-email"})

 

# 체인 만들기
submit = driver.find_element(locate_with(By.TAG_NAME, "button").below({By.ID: "email"}).to_right_of({By.ID: "cancel"})

 

# 자바스크립트로 요소의 텍스트 가져오기
el = driver.find_element(By.CSS_SELECTOR, "h1")
driver.execute_script('return arguments[0].innerText', el)

 

# 자바스크립트로 요소 클릭하기
btn = driver.find_element(BY.ID, 'ok')
driver.execute_script('arguments[0].click()', btn)

 

# 텍스트가 'My Button'인 요소 찾기 (대소문자 구분함)
el = driver.find_element(By.XPATH, "//div[contains(., 'My Button')]")

 

# 텍스트가 'My Button'인 요소 찾기 (대소문자 구분 안함)
el = driver.find_element(By.XPATH, "//div[normalize-space()='My Button']]")

 

# 자바 코드를 수정함 (테스트 안함
wait.until(ExpectedConditions.visibility_of_element_located(By.XPATH("//*[contains(text(), 'YourTextHere')]")))
driver.find_element(By.XPATH("//*[contains(text(), 'YourTextHere')]")))
el =driver.find_element(By.XPATH("//*[contains(text(), 'YourTextHere')]")).get_attribute("innerText")
el.equals_ignore_case("YourTextHere"))

 

# <div>My Button</div>
el = driver.find_element(By.XPATH, "//div[text()='My Button']")

 

# 페이지에서 영문 텍스트 검색 (translate 방식은 추천 안함)
driver.find_elements(By.XPATH, "//*[contains(translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), 'my button')]")

 

# 찾은 요소의 부모 요소 찾기
el = driver.find_element(By.XPATH, "//div[text()='My Button']")
el2 = el.get_element(By.XPATH, '..')
 
반응형

+ Recent posts