셀레니움의 locator 중 주요 선호되는 locator는 id, class name, xpath가 있습니다.
이 중 오늘은 xpath에 대해 방법에 대해 알아보려고 합니다 🍀
xpath의 경우 상대 경로, 절대 경로로 나뉠 수 있습니다.
절대 경로는 동적인 페이지의 경우 변경될 수 있고 수정이 편리하지 않으며 직관적으로 알아볼 수 없다는 단점들이 존재하여 상대 경로가 우선적으로 사용되어야 합니다.
velog 홈페이지 의 트렌딩 문구에 대한 xpath를 구해보겠습니다.
개발자 도구 > 네트워크 탭 > 해당 영역 클릭 후 xpath 복사 / 전체 xpath 복사 시 아래처럼 xpath를 구할 수 있습니다.
이때, 전체 xpath 복사한 값이 /로 시작하는 절대 xpath이고 xpath 복사한 값이 //로 시작하는 동적 xpath 값 입니다.
▸ 전체 xpath 복사 시, /html/body/div/div[1]/div[2]/div/div[1]/div[1]/div/a[1]/span
▸ xpath 복사 시, //*[@id="html"]/body/div/div[1]/div[2]/div/div[1]/div[1]/div/a[1]/span
단순히 개발자 도구에서 프로필 이미지의 xpath를 순차적으로 복사하면 아래처럼 복사됩니다. 차이점 보이시나요 ??
1️⃣ 첫 번째 프로필 xpath :
//*[@id="html"]/body/div/div[1]/div[2]/div/div[2]/main/ul/li[1]/div[2]/a/img
→ body 하단의 div 하단의 div 첫번째의 ... li 목록의 첫 번째의 div의 a의 이미지
2️⃣ 두 번째 프로필 xpath ://*[@id="html"]/body/div/div[1]/div[2]/div/div[2]/main/ul/li[2]/div[2]/a/img
3️⃣ 세 번째 프로필 xpath ://*[@id="html"]/body/div/div[1]/div[2]/div/div[2]/main/ul/li[2]/div[2]/a/img
....
정답은 li[N]에 N이 변경되고 있습니다~
첫 번째 프로필 xpath를 확인하자면 body 하단의 div 하단의 div 첫 번째의 ... li 목록의 첫 번째의 div의 a의 이미지! 라는 뜻이 됩니다.
위의 방식으로 프로필 이미지를 가져오려고 한다면 하나씩 n 값을 변경하면서 가져오는 등의 방법을 사용해야 합니다. 불가능한 것은 아니지만 가장 좋은 방법은 아닐 것 입니다!
ex :
profile_image_1 = driver.find_element(By.XPATH, "//*[@id="html"]/body/div/div[1]/div[2]/div/div[2]/main/ul/li[1]/div[2]/a/img" ) profile_image_2 = driver.find_element(By.XPATH, "//*[@id="html"]/body/div/div[1]/div[2]/div/div[2]/main/ul/li[2]/div[2]/a/img" ) profile_image_3 = driver.find_element(By.XPATH, "//*[@id="html"]/body/div/div[1]/div[2]/div/div[2]/main/ul/li[3]/div[2]/a/img" )
or
for i in range(1,10) : profile_image = driver.find_element(By.XPATH,"//*[@id="html"]/body/div/div[1]/div[2]/div/div[2]/main/ul/li[" + str(i) + "]/div[2]/a/img")
그렇다면 하나의 xpath로 모든 프로필 이미지를 가져올 수는 없을까요?
html 코드를 다시 확인해보면 모든 프로필 이미지는 PostCard_userInfo__Cu1X5라는 class 명의 a 노드의 바로 아래에 위치하고 있습니다.
▶︎ 때문에 아래처럼 PostCard_userInfo__Cu1X5라는 클래스 명의 a 노드 하단에 있는 모든 이미지! 를 표현하는 동적 xpath를 사용한다면 현재 페이지에 노출되고 있는 모든 프로필 이미지를 한번에 가져올 수 있습니다.
전체 프로필 xpath :
//a[@class="PostCard_userInfo__Cu1X5"]/img
ex ) profile_images = driver.find_elements(By.XPATH,"//a[@class="PostCard_userInfo__Cu1X5"]/img")
동적 xpath는 중간 노드부터 표현하는 값이기 때문에 어떤 노드부터 어떻게 표현하는지에 따라서 같은 노드에 대해 다양한 방법으로 표현할 수 있습니다.
표현식 | 설명 |
---|---|
노드명 | 해당 노드명인 노드 선택 |
/ | 루트 노드부터 탐색 |
// | 지정된 노드부터 탐색 |
. | 현재 노드 선택 |
.. | 현재 노드의 부모 노드 선택 |
@ | 현재 노드의 속성 선택 |
함수 | 설명 | 예제 |
---|---|---|
count() | 해당 경로를 가진 노드 개수 | # //table[count(tr)=1] |
position() | 노드의 위치 | # //ol/li[position()=2] |
name() | 현재 노드의 이름 | # //[starts-with(name(), 'h')] |
text() | 해당 텍스트를 가진 노드 | # //button[text()="Submit"] |
contains() | 해당 텍스트를 포함하는 노드 | # font[contains(@class,"head")] #id # //button[contains(text(),"Go")] |
starts-with() | 해당 텍스트로 시작하는 노드 | # font[starts-with(@class,"head")] |
ends-with() | 해당 텍스트로 끝나는 노드 | # font[ends-with(@class,"head")] |
선택자 | 예제 |
---|---|
h1 | //h1 |
div p | //div//p |
ul > li | //ul/li |
ul > li > a | //ul/li/a |
div > * | //div/* |
:root | / |
:root > body | /body |
ul > li:first-of-type | //ul/li[1] |
ul > li:nth-of-type(2) | //ul/li[2] |
ul > li:last-of-type | //ul/li[last()] |
li#id:first-of-type | //li[1][@id="id"] |
a:first-child | //*[1][name()="a"] |
a:last-child | //*[last()][name()="a"] |
선택자 | 예제 |
---|---|
#id | //*[@id="id"] |
.class | //*[@class="class"] |
input[type="submit"] | //input[@type="submit"] |
a#abc[for="xyz"] | //a[@id="abc"][@for="xyz"] |
a[rel] | //a[@rel] |
a[href^='/'] | //a[starts-with(@href, '/')] |
a[href$='pdf'] | //a[ends-with(@href, '.pdf')] |
a[href*='://'] | //a[contains(@href, '://')] |
a[rel~='help'] | //a[contains(@rel, 'help')] |
참고 :
https://tcpschool.com/xml/xml_xpath_filterExpression
https://devhints.io/xpath#class-check