Pular para o conteúdo

XPath

Introdução

  • O XPath significa XML Path Language.
  • É uma linguagem de consulta que permite navegar por documentos que possuem marcações como o HTML (HyperText Markup Language).

Exemplos

  • Exemplo: Verifica quantos nós existem na página e informa a quantidade por tipo de nó. O programa usa um dicionário para armazenar as tags encontradas (chave) e a quantidade de cada tag (valor).
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
opcoes = webdriver.ChromeOptions()
opcoes.headless = True
driver = webdriver.Chrome(options=opcoes)
driver.get('https://www.python.org')
nos = driver.find_elements(By.XPATH, '//*')
print("Número de nós: " + str(len(nos)))

tags={}
for no in nos:
   valor = tags.setdefault(no.tag_name, 0) + 1
   chave = str(no.tag_name)
   if chave in tags:
      valor = tags[chave] + 1
      tags[chave] = valor
   else:
      tags[chave] = 1  

print("Número de diferentes tags =", len(tags))
for x, y in tags.items():
    print(x, ":", y)
driver.quit()

A resposta do programa é mostrada abaixo.

Número de nós: 691
Número de diferentes tags = 38
html : 1
head : 1
meta : 24
link : 16
script : 12
title : 1
body : 1
div : 49
p : 23
strong : 3
nav : 2
a : 217
span : 72
ul : 30
li : 171
header : 1
h1 : 6
img : 1
form : 1
fieldset : 1
label : 1
input : 1
button : 1
small : 2
pre : 5
code : 14
ol : 1
section : 1
h2 : 10
time : 10
blockquote : 1
table : 1
tbody : 1
tr : 1
td : 1
em : 1
b : 5
footer : 1
  • Exemplo: Localiza todas as âncoras (anchor) da página oficial do Python. Em seguida, mostra o valor de href (se existir) e o texto que aparece na âncora (se existir).
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
opcoes = webdriver.ChromeOptions()
opcoes.headless = True
driver = webdriver.Chrome(options=opcoes)
driver.get('https://www.python.org')
links = driver.find_elements(By.XPATH, '//a')
print("Número de links: " + str(len(links)) + "\n")
for link in links:
   print(link.get_attribute('href'))
   print(link.text)
driver.quit()

Abaixo são mostradas quatro das 218 âncoras que aparecen na resposta do programa. A primeira âncora tem href (https://www.python.org/#content) e texto (“Skip to content”). A segunda âncora só tem href (https://www.python.org/#python-network). A terceira âncora também só tem href (“javascript:;”). A quarta âncora não tem href (“None”), mas tem texto (“1”).

https://www.python.org/#content
Skip to content
https://www.python.org/#python-network

javascript:;

None
1

Para localizar apenas as âncoras que têm atributo “href”, basta alterar a linha abaixo. Neste caso, são obtidos 213 links.

links = driver.find_elements(By.XPATH, '//a[@href]')

Para eliminar também os links sem texto, é preciso alterar o código como mostrado abaixo. Agora são encontrados 141 links.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
opcoes = webdriver.ChromeOptions()
opcoes.headless = True
driver = webdriver.Chrome(options=opcoes)
driver.get('https://www.python.org')
links = driver.find_elements(By.XPATH, '//a[@href]')
n = 0
for link in links:
   if link.text != "":
      print(link.get_attribute('href'))
      print(link.text)
      n += 1
print("Número de links: ", n, "\n")
driver.quit()
  • Exemplo: Verifica qual a quantidade de <script>, com atributo “src”, na página oficial do Python.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
opcoes = webdriver.ChromeOptions()
opcoes.headless = True
driver = webdriver.Chrome(options=opcoes)
driver.get('https://www.python.org')
nos = driver.find_elements(By.XPATH, '//script[@src]')
print("Número de nós: " + str(len(nos)) + "\n")
i = 0
for no in nos:
   i=i+1
   print("*** i =", i, "src =", no.get_attribute('src'))
driver.quit()

O programa apresenta a lista de oito nós. É importante observar que o primeiro nó listado abaixo é gerado pelo último script do <head> e inserido no início do próprio <head>. Os scripts restantes já estão definidos na página html.

Número de nós: 8

*** i = 1 src = https://ssl.google-analytics.com/ga.js
*** i = 2 src = https://media.ethicalads.io/media/client/v1.4.0/ethicalads.min.js
*** i = 3 src = https://www.python.org/static/js/libs/modernizr.js
*** i = 4 src = https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js
*** i = 5 src = https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
*** i = 6 src = https://www.python.org/static/js/libs/masonry.pkgd.min.js
*** i = 7 src = https://www.python.org/static/js/libs/html-includes.js
*** i = 8 src = https://www.python.org/static/js/main-min.dd72c1659644.js

O XPATH ‘/html/head//script[@src]’ informa que o <head> possui três scripts com atributo “src”, enquanto o XPATH ‘/html/body//script[@src]’ informa que existem cinco scripts com atributo “src” no <body>. Portanto, os três primeiros nós <script> da lista acima estão no <head> e os outros cinco nós estão no <body>.

O uso do XPATH ‘//script[@src][2]’ no programa fornece a seguinte resposta:

Número de nós: 2

*** i = 1 src = https://media.ethicalads.io/media/client/v1.4.0/ethicalads.min.js
*** i = 2 src = https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js

A primeira linha mostra o segundo nó <script> descendente do <head> e a segunda linha mostra o segundo nó <script> descendente do <body>. Naturalmente que, apenas a primeira linha é apresentada com o XPATH ‘/html/head//script[@src][2]’ e apenas a segunda linha é apresentada com o XPATH ‘/html/body//script[@src][2]’.

O XPATH ‘//script[@src][last()]’ mostra o último script de <head> e o último script de <body>, ambos com atributo “src”.

O XPATH ‘//script[@src][position()<5]’ seleciona os scripts com índice menor que 5. Assim, são apresentados os três primeiros scripts do <head> e quatro primeiros scripts do <body>.

O uso de parênteses, englobando a expressão antes do índice, faz com que a lista de nós seja tratada como uma única lista, mesmo tendo sido selecinados os nós de <head> e de <body>. Assim, o XPATH ‘(//script[@src])[4]’ seleciona o quarto nó <script> da lista completa.

Número de nós: 1

*** i = 1 src = https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js
  • Exemplo: Localiza as marcações meta, dentro do head, que possuem o atributo name. A resposta do programa mostra que existem treze nós <meta> com o atributo “name”.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
opcoes = webdriver.ChromeOptions()
opcoes.headless = True
driver = webdriver.Chrome(options=opcoes)
driver.get('https://www.python.org')
nos = driver.find_elements(By.XPATH, '/html/head/meta[@name]')
print("Número de nos: " + str(len(nos)) + "\n")
driver.quit()

Outros exemplos são mostrados abaixo. Basta substituir o valor de XPATH procurado.

  1. Para localizar todas as marcações HTML que possuem o atributo “name”.
nos = driver.find_elements(By.XPATH, '//*[@name]')

2. Para localizar os elementos “button” da página:

nos = driver.find_elements(By.XPATH, '//button')

3. Para localizar os links que possuem a classe “donate-button”:

nos = driver.find_elements(By.XPATH, '//a[@class="donate-button"]')

4. Para localizar um cabeçalho <h2> com texto que contém a “Upcoming Events”:

nos = driver.find_elements(By.XPATH, '//h2[text()="Upcoming Events"]')

ou

nos = driver.find_elements(By.XPATH, '//h2[contains(text(),"Upcoming Events")]')

5. Para localizar uma <div> que é filha de outra <div>, digite:

nos = driver.find_elements(By.XPATH, '//div/div')

Para encontrar uma <div> que é descendente de outra <div>, mas que não é necessariamente sua filha, entre com:

nos = driver.find_elements(By.XPATH, '//div//div')

A primeira expressão // indica que pode existir qualquer quantidade de nós ascendentes antes da primeira <div>. A segunda expressão // indica que podem existir um ou mais nós no caminho entre as duas <div>.

No primeiro XPATH, são encontradas 28 <div> filhas; enquanto no segundo XPATH, são encontradas 48.

  • Exemplo: Verifica quais nós possuem o texto “Docs”.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
opcoes = webdriver.ChromeOptions()
opcoes.headless = True
driver = webdriver.Chrome(options=opcoes)
driver.get('https://www.python.org')
nos = driver.find_elements(By.XPATH, '//*[text()="Docs"]')
print("Número de nós: " + str(len(nos)) + "\n")
i = 0
for no in nos:
   i=i+1
   print("*** i =", i)
   print("Text =", no.text)
   print("Tag_name =", no.tag_name, "\n")
driver.quit()

A resposta mostra que existem quatro textos com “Docs” na página do Python.

Número de nós: 4

*** i = 1
Text = Docs
Tag_name = a 

*** i = 2
Text = 
Tag_name = a 

*** i = 3
Text = Docs
Tag_name = h2 

*** i = 4
Text = Docs
Tag_name = a

Note que o texto do segundo nó não é mostrado pelo programa. O motivo é que este nó é descendente de um nó com atributo ‘aria-hidden=”true”‘. Este atributo remove o texto do nó e de todos os seus filhos da árvore de acessibilidade. Para ver o texto do nó, neste caso, use o método “execute_script()” do Selenium para executar um script em JavaScript como mostrado abaixo.

print("Text =", driver.execute_script("return arguments[0].textContent", no))

Outra forma de localizar os textos dos nós é usando “.”(ponto) no lugar de “text()”.

nos = driver.find_elements(By.XPATH, '//*[.="Docs"]')

O programa agora encontra seis nós.

Número de nós: 6

*** i = 1
Text = Docs
Tag_name = a 

*** i = 2
Text = 
Tag_name = li 

*** i = 3
Text = 
Tag_name = a 

*** i = 4
Text = Docs
Tag_name = h2 

*** i = 5
Text = Docs
Tag_name = li 

*** i = 6
Text = Docs
Tag_name = a 

Isto ocorre porque o “.” considera todo o contexto onde se encontra o texto procurado. Abaixo o código HTML da página Python usado neste exemplo. O primeiro caso (“text()”) seleciona apenas um nó (<a>). O segundo caso (“.”) seleciona 2 nós (<li> e <a>), pois o texto “Docs” existe dentro de uma âncora (<a>) que está dentro de uma lista (<li>). Devido ao atributo “aria-hidden”, o texto “Docs” não é mostrado nos dois nós.

O uso de “contains” permite definir uma parte do texto do nó. Assim,

nos = driver.find_elements(By.XPATH, '//*[contains(text(),"Docs")]')

seleciona todos os nós com texto que possui a string “Docs” como “Non-English Docs”. Neste caso são encontrados 6 nós. A substituição de “text()” por “.” faz com que o programa encontre 34 nós, pois a busca considera todo o contexto de cada nó com “Docs”.

  • Exemplo: Verifica na página Python quais listas não ordenadas (ul) possuem o atributo role igual a “menu” ou igual a “tree”.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
opcoes = webdriver.ChromeOptions()
opcoes.headless = True
driver = webdriver.Chrome(options=opcoes)
driver.get('https://www.python.org')
nos = driver.find_elements(By.XPATH, '//ul[@role = "menu" or @role = "tree"]')
print("Número de nós: " + str(len(nos)) + "\n")
for no in nos:
   print(no.get_attribute('role'))
driver.quit()

A resposta do programa informa que existem 10 nós: 7 nós com atributo role igual a “menu” e 3 nós com atributo role igual a “tree”.

Para verificar os nós com atributo role que não são iguais nem a “menu” e nem a “tree”, use ‘//ul[@role != “menu” and @role != “tree”]’. O programa encontra apenas 1 nó com valor “menubar”.

  • Exemplo: Localiza os nós que no texto do atributo “datetime” existe a string “2022-07-06”.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
opcoes = webdriver.ChromeOptions()
opcoes.headless = True
driver = webdriver.Chrome(options=opcoes)
driver.get('https://www.python.org')
nos = driver.find_elements(By.XPATH, "//time[contains(@datetime, '2022-07-06')]");
print("Número de nós: " + str(len(nos)) + "\n")
for no in nos:
   data = no.get_attribute('datetime');
   print(data);
driver.quit()

A resposta mostrada pelo programa quando executado em 08/07/22:

Número de nós: 1

2022-07-06T16:32:00.000002+00:00

O XPATH “//time[not(contains(@datetime, ‘2022-07-06’))]” mostra todos os nós <time> que não contém “2022-07-06”.

Número de nós: 9

2022-07-01T13:31:00.000004+00:00
2022-06-19T12:04:00.000006+00:00
2022-06-09T18:02:00.000003+00:00
2022-06-08T15:25:00.000002+00:00
2022-07-09T00:00:00+00:00
2022-07-11T00:00:00+00:00
2022-07-11T00:00:00+00:00
2022-07-12T18:00:00+00:00
2022-08-15T00:00:00+00:00

É possível colocar várias condições usando operadores como “not”, “or” e “and”. Por exemplo, o XPATH “//time[contains(@datetime, ‘2022’) and not(contains(@datetime,’00:00:00+00:00′))]” fornece como resposta:

Número de nós: 6

2022-07-06T16:32:00.000002+00:00
2022-07-01T13:31:00.000004+00:00
2022-06-19T12:04:00.000006+00:00
2022-06-09T18:02:00.000003+00:00
2022-06-08T15:25:00.000002+00:00
2022-07-12T18:00:00+00:00

O XPATH “//time[contains(text(), ’07-06′)]” seleciona apenas um nó com texto “07-06” na página. O código HTML deste nó é mostrado abaixo.

  • Exemplo: Considere o código HTML abaixo extraído da página oficial Python.
Parte do código da página inicial de https://www.python.org/

Para localizar o elemento indicado na imagem como “Elemento-filho”, entre com o código abaixo.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
opcoes = webdriver.ChromeOptions()
opcoes.headless = True
driver = webdriver.Chrome(options=opcoes)
driver.get('https://www.python.org')
nos = driver.find_elements(By.XPATH, '//div[@class="success-story-item"]')
print("Número de nós: " + str(len(nos)) + "\n")
for no in nos:
   print("Class =", no.get_attribute('class'))
   print("Id =", no.get_attribute('id'))
driver.quit()

O programa só localiza um nó com a definição fornecida.

Número de nós: 1
Class = success-story-item
Id = success-story-838

Para localizar o pai do nó acima, basta adicionar ‘/..’ ao final do XPATH, ou seja, ‘//div[@class=”success-story-item”]/..’. A resposta do programa é

Número de nós: 1
Class = shrubbery
Id =

Pode-se usar “/..” para cada nível hierárquico que se deseja ascender. Assim, para obter informações sobre o avô do nó use ‘//div[@class=”success-story-item”]/../..’.

O XPATH ‘//p[@class=”give-me-more”]//parent::div’ localiza nós <div> que são pais de parágrafos com classe “give-me-more”. Note que o nó de interesse na pesquisa é o nó-pai. Exitem 4 nós div que são pais de parágrafos com classe “give-me-more” na página do Python.

O XPATH ‘//p[@class=”give-me-more”]//parent::div[@class=”shrubbery”]’ localiza os nós <div> com classe “shrubbery” que são pais de parágrafos com classe “give-me-more”.

Use ‘//p[@class=”give-me-more”]//parent::*’ quando os nós procurados podem ser de qualquer tipo desde que tenha um filho que é um parágrafo com classe “give-me-more”.

Para localizar os parágrafos que são filhos de uma <div> com classe “shrubbery”, use ‘//div[@class=”shrubbery”]//child::p’. O programa encontra 5 nós. Ao substituir p por *, o programa selecionará todos os filhos das <div> com classe “”shrubbery”. A resposta do programa agora é 104.

Para localizar parágrafos com classe “give-me-more” que são filhos de uma div com classe “shrubbery”, basta usar ‘//div[@class=”shrubbery”]//child::p[@class=”give-me-more”]’. O programa encontra 4 nós.

É possível também localizar nós irmãos (mesmo nó-pai) que aparecem antes ou depois de um determinado nó. Por exemplo, ‘//p[@class=”give-me-more”]/preceding-sibling::*’ identifica o nó-irmão (de qualquer tipo) que vem antes do parágrafo com classe “give-me-more”. Já o XPATH ‘//h2[@class=”widget-title”]/following-sibling::div’ localiza os nós div que aparecem depois de um h2.

É importante observar que o uso de / ou de //, antes de quaiquer definição de parentesco (filho, pai, irmão, etc), não altera a resposta fornecida pelo programa.

Raspagem de Dados

Para identificar o XPATH de um nó basta seguir dois passos:

  1. Clicar com o botão direiro em cima do campo desejado e selecionar “inspecionar”;
  2. Clicar novamente com o botão direito em cima do texto marcado no item anterior e selecionar “Copiar” e em seguida “XPath”.
  • Exemplo: Acessa o Google, escreve “unirio” no campo de pesquisa e pressiona o botão “Estou com sorte”.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

opcoes = webdriver.ChromeOptions()
#opcoes.headless = True
driver = webdriver.Chrome(options=opcoes)
driver.get('https://www.google.com.br')
no_pesq = driver.find_element(By.XPATH, '/html/body/div[1]/div[3]/form/div[1]/div[1]/div[1]/div/div[2]/input')
no_pesq.clear()
no_pesq.send_keys("unirio")
no_sort = driver.find_element(By.XPATH, '/html/body/div[1]/div[3]/form/div[1]/div[1]/div[3]/center/input[2]')
no_sort.click();
  • Exemplo: Pesquisa, no Portal da Transparência, o total das despesas pagas pelas quatro universidades cariocas no ano anterior.O programa não trata a possibilidade do aparecimento de uma tela de captcha.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

opcoes = webdriver.ChromeOptions()
opcoes.headless = True
driver = webdriver.Chrome(options=opcoes)

universidades = {
      "UFF"   : 26236,
      "UFRJ"  : 26245,
      "UFRRJ" : 26249,
      "UNIRIO": 26269
}

for uni in universidades:
   nome='https://www.portaltransparencia.gov.br/orgaos/'+ str(universidades[uni])
   driver.get(nome)
   no_ano = driver.find_element(By.XPATH, '/html/body/main/div[2]/div/div[2]/div/ul/li[4]/a')
   ano = str(no_ano.text)
   no_ano.click()
   no_valor = driver.find_element(By.XPATH, '/html/body/main/div[3]/div/div[2]/div[2]/div[1]/div[2]/div/table/tbody/tr[3]/td[2]/b')
   print("Total de pagamentos realizados pela " + uni + " em " + ano  + " = " + str(no_valor.text))

O primeiro nó selecionado (e depois clicado) corresponde ao do ano anterior à data de acesso do site. O site marca inicialmente o ano atual.

O segundo nó selecionado é o que contém o valor procurado pelo programa.

A resposta do programa é

Total de pagamentos realizados pela UFF em 2021 = R$ 2.096.577.771,75
Total de pagamentos realizados pela UFRJ em 2021 = R$ 3.671.253.625,11
Total de pagamentos realizados pela UFRRJ em 2021 = R$ 609.048.608,81
Total de pagamentos realizados pela UNIRIO em 2021 = R$ 591.228.972,98