L'ultime aide-mémoire XPath. Comment écrire facilement des sélecteurs puissants.

Mihai Maxim le 16 décembre 2022

Un aide-mémoire XPath ?

Avez-vous déjà eu besoin d'écrire un sélecteur CSS indépendant des classes ? Si la réponse est non, vous pouvez vous estimer heureux. Si la réponse est oui, notre aide-mémoire XPath est ce qu'il vous faut. Le web regorge de données. Des entreprises entières dépendent de l'assemblage de certaines d'entre elles pour offrir de nouveaux services au monde. Les API sont d'une grande utilité, mais tous les sites web ne disposent pas d'API ouvertes. Parfois, vous devrez obtenir ce dont vous avez besoin à l'ancienne. Vous devrez construire un scraper pour le site web. Les sites web modernes contournent le scraping en renommant leurs classes CSS. Par conséquent, il est préférable d'écrire des sélecteurs qui s'appuient sur quelque chose de plus stable. Dans cet article, vous apprendrez à écrire des sélecteurs basés sur la disposition des nœuds du DOM de la page.

Qu'est-ce que XPath et comment puis-je l'essayer ?

XPath est l'abréviation de XML Path Language. Il utilise une notation de chemin (comme dans les URL) pour fournir un moyen flexible de pointer vers n'importe quelle partie d'un document XML. 

XPath est principalement utilisé dans XSLT, mais peut également être utilisé comme un moyen beaucoup plus puissant de naviguer à travers le DOM de tout document de langage de type XML utilisant XPathExpression, tel que HTML et SVG, au lieu de s'appuyer sur les méthodes Document.getElementById() ou Document.querySelectorAll(), les propriétés Node.childNodes, et d'autres fonctionnalités du DOM Core. XPath | MDN (mozilla.org)

Une notation de chemin ?

<!DOCTYPE html>
<html lang="en">
<head>
<title>Nothing to see here</title>
</head>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
<div>
<h2>My Second Heading</h2>
<p>My second paragraph.</p>
<div>
<h3>My Third Heading</h3>
<p>My third paragraph.</p>
</div>
</div>
</body>
</html>
Il existe deux types de chemins : les chemins relatifs et les chemins absolus
Le chemin unique (ou chemin absolu) vers Mon troisième paragraphe est /html/body/div/div/p
Le chemin relatif vers mon troisième paragraphe est //body/div/div/p
Pour mon deuxième titre => //body/div/h2
Pour mon premier paragraphe => //body/p
Remarquez que j'utilise //body. Les chemins relatifs utilisent // pour passer directement à l'élément souhaité.
The usage of //<path> also implies that it should look for all occurrences of <path> in the document, regardless of what came before <path>.

For example, //div/p returns both My second paragraph. and My third paragraph.

Vous pouvez tester cet exemple dans votre navigateur pour avoir une meilleure vue d'ensemble !

Collez le code dans un fichier .html et ouvrez-le avec votre navigateur. Ouvrez les outils de développement et appuyez sur contrôle + F. Collez le localisateur XPath dans la petite barre de saisie et appuyez sur entrée.

Vous pouvez également obtenir le XPath de n'importe quelle balise en faisant un clic droit sur celle-ci dans l'onglet Éléments et en sélectionnant "Copier XPath"

blog-image
blog-image
blog-image

Remarquez que je passe de "Mon deuxième paragraphe" à "Mon troisième paragraphe".

Also, another important thing to know is that it is not necessary for a path to contain // in order to return multiple elements. Let's see what happens when I add another <p> in the last <div>.
blog-image
/html/body/div/div/p n'est plus un chemin absolu.

Si vous m'avez suivi jusqu'ici, félicitations, vous êtes sur la bonne voie pour maîtriser XPath. Vous êtes maintenant prêt à plonger dans les choses amusantes.

Les équerres

Vous pouvez utiliser les crochets pour sélectionner des éléments spécifiques.

blog-image
 In this case, //body/div/div[2]/p[3] only selects the last <p> tag.

Attributs

Vous pouvez également utiliser des attributs pour sélectionner vos éléments.

//body//p[@class="not-important"] => select all the <p> tags that are inside a <body> tag and have the "not-important" class.
//div[@id] => select all the <div> tags that have an id attribute.
//div[@class="p-children"][@id="important"]/p[3] => select the third <p> that is within a <div> tag that has both class="p-children" and id="important"
//div[@class="p-children" and @id="important"]/p[3] => same as above
//div[@class="p-children" or @id="important"]/p[3] => select the third <p> that is within a <div> that has class="p-children" or id="important"
Remarquez que @ marque le début d'un attribut

Fonctions

XPath fournit un ensemble de fonctions utiles que vous pouvez utiliser à l'intérieur des crochets.

position() => returns the index of the element
Ex: //body/div[position()=1] selects the first <div> in the <body>
last() => returns the last element
Ex: //div/p[last()] selects all the last <p> children of all the <div> tags
count(element) => returns the number of elements
Ex: //body/count(div) returns the number of child <div> tags inside the <body>
node() or * => returns any element
Ex: //div/node() and //div/*=> selects all the children of all the <div> tags
text() => returns the text of the element
Ex: //p/text() returns the text of all the <p> elements
concat(string1, string2) => fusionne string1 avec string2
contains(@attribute, "value") => returns true if @attribute contains "value" 
Ex:
//p[contains(text(),"I am the third child")] selects all the <p> tags that have the "I am the third child" text value.
starts-with(@attribute, "value") => retourne vrai si @attribute commence par "value" 
ends-with(@attribute, "value") => retourne vrai si @attribute se termine par "value" 
substring(@attribute,start_index,end_index)] => renvoie la sous-chaîne de la valeur de l'attribut en fonction de deux valeurs d'index
Ex :
//p[substring(text(),3,12)="suis le troisième"] => renvoie true si text() = "je suis le troisième enfant"
normalize-space() => agit comme text(), mais supprime les espaces de fin
Ex : normalize-space(" exemple ") = "exemple"
string-length() => returns the length of the text
Ex: //p[string-length()=20] returns all the <p> tags that have the text length of 20

Les fonctions peuvent être un peu difficiles à mémoriser. Heureusement, The Ultimate Xpath Cheat Sheet fournit des exemples utiles :

//p[text()=concat(substring(//p[@class="not-important"]/text(),1,15), substring(text(),16,20))]
//p[text()=<expression_return_value>] will select all the <p> elements that have the text value equal to the return value of the condition.
//p[@class="not-important"]/text() returns the text values of all the <p> tags that have class="not-important".
If there is only one <p> tag that satisfies this condition, then we can pass the return_value to the substring function.
substring(valeur_de_retour,1,15) renvoie les 15 premiers caractères de la chaîne valeur_de_retour.
substring(text(),16,20) renvoie les 5 derniers caractères du même texte. 
text() value that we used in //p[text()=<expression_return_value>].
Finally, concat() will merge the two substrings and create the return value of <expression_return_value>.

Imbrication des chemins d'accès

XPath prend en charge l'imbrication des chemins. C'est bien, mais qu'est-ce que j'entends exactement par imbrication de chemins ?

Essayons quelque chose de nouveau : /html/body/div[./div[./p]]
You can read it as "Select all the <div> sons of the <body> that have a <div> child. Also, the children must also be parents to a <p> element."
If you don't care about the father of the <p> element, you can write: /html/body/div[.//p]
This now translates to "Select all the div children of the body that have a <p> descendant"
blog-image
Dans cet exemple particulier, /html/body/div[./div[./p]] et /html/body/div[.//p] donnent le même résultat.
Je suis sûr que vous vous demandez maintenant ce que signifient ces points dans ./ et .//. 
Le point représente l'élément self. Lorsqu'il est utilisé dans une paire de crochets, il fait référence à la balise spécifique qui les ouvre. Penchons-nous un peu plus sur la question.
In our example, /html/body/div returns two divs:
<div class="no-content"> and <div class="content">
/html/body/div[.//p] se traduit par :

/html/body/div[1][/html/body/div[1]//p]
et /html/body/div[2][/html/body/div[2]//p]
/html/body/div[2][/html/body/div[2]//p] est vrai, il renvoie donc /html/body/div[2] 
In our case, the dot ensures that /html/body/div and /html/body/div//p refer to the same <div>
Voyons maintenant ce qui se serait passé si elle ne l'avait pas fait.
/html/body/div[/html/body/div//p] would return both 
<div class="no-content">  and <div class="content">
Pourquoi ? Parce que /html/body/div//p est vrai à la fois pour /html/body/div[1] et /html/body/div[2].
/html/body/div[/html/body/div//p] actually translates to "Select all the div children of the <body> if /html/body/div//p is true. 
/html/body/div//p is true if the body has a <div> child, and that child has a <p> descendent". In our case, this statement is always true.

Il est dommage que les autres antisèches Xpath ne mentionnent pas l'imbrication. Je considère qu'il s'agit d'un outil extraordinaire. Il vous permet de parcourir le document à la recherche de différents motifs et de revenir pour renvoyer autre chose. Le seul inconvénient est que l'écriture de requêtes de cette manière peut devenir difficile à suivre. La bonne nouvelle, c'est qu'il existe d'autres façons de procéder.

Les axes

Vous pouvez utiliser des axes pour localiser des nœuds par rapport à d'autres nœuds contextuels.

Examinons-en quelques-unes.

Les quatre axes principaux

//p/ancestor::div => selects all the divs that are ancestors of <p>
How I read it: Get all the <p> tags, for each <p> look through its ancestors. If you find <div> tags, select them.
//p/parent::div => selects all the <div> tags that are parents of <p> 
How I read it: Get all the <p> tags and of all their parents, if the parent is a <div>, select it.
//div/child::p=> selects all the <p> tags that are children of <div> tags.
How I read it: Get all the <div> tags and their children, if the child is a <p>, select it.
//div/descendant::p => selects all the <p> tags that are descendants of <div> tags.
How I read it: Get all the <div> tags and their descendants, if the descendant is a <p>, select it.

Il est maintenant temps de réécrire l'expression précédente :

/html/body/div[./div[./p]] est équivalent à /html/body/div/div/p/parent::div/parent::div
Mais /html/body/div[.//p] n'est PAS équivalent à /html/body/div//p/ancestor::div
La bonne nouvelle, c'est qu'il est possible d'y apporter quelques modifications.
/html/body/div//p/ancestor::div[last()] est équivalent à /html/body/div[.//p]
blog-image

Autres axes importants

blog-image
//p/following-sibling::span => for each <p> tag, select its following <span> siblings.
//p/preceding-sibling::span => for each <p> tag, select its preceding <span> siblings.
blog-image
//title/following::span => selects all the <span> tags that appear in the DOM after the <title>.
In our example, //title/following::span selects all the <span> tags in the document.
//p/preceding::div => selects all the <div> tags that appear in the DOM before any <p> tag. But it ignores ancestors, attribute nodes and namespace nodes.
In our case, //p/preceding::div only selects <div class="p-children"> and <div class="no_content">.
Most of the <p> tags are in <div class="content">, but this <div> is not selected because it is a common ancestor for them. As I mentioned, the 
preceding axe ignores ancestors.
<div class="p-children"> is selected because it is not an ancestor for the <p> tags inside <div class="p-children" id="important">

Résumé

Félicitations, vous avez réussi. Vous avez ajouté un tout nouvel outil à votre boîte à outils de sélecteur ! Si vous construisez un scraper web ou si vous automatisez des tests web, cet aide-mémoire Xpath vous sera très utile ! Si vous cherchez une manière plus fluide de parcourir le DOM, vous êtes au bon endroit. Quoi qu'il en soit, cela vaut la peine d'essayer XPath. Qui sait, peut-être découvrirez-vous d'autres cas d'utilisation.
Le concept de web scraping vous semble-t-il intéressant ? Vous pouvez nous contacter ici WebScrapingAPI - Contact. Si vous souhaitez scraper le web, nous serons heureux de vous aider dans votre démarche. En attendant, vous pouvez essayer gratuitement WebScrapingAPI - Product.

Nouvelles et mises à jour

Restez au courant des derniers guides et nouvelles sur le web scraping en vous inscrivant à notre lettre d'information.

We care about the protection of your data. Read our <l>Privacy Policy</l>.Privacy Policy.

Articles connexes

vignette
La science du Web ScrapingScrapy vs. Selenium : Un guide complet pour choisir le meilleur outil de Web Scraping

Explorez la comparaison approfondie entre Scrapy et Selenium pour le web scraping. De l'acquisition de données à grande échelle à la gestion de contenus dynamiques, découvrez les avantages, les inconvénients et les caractéristiques uniques de chacun. Apprenez à choisir le meilleur framework en fonction des besoins et de l'échelle de votre projet.

WebscrapingAPI
avatar de l'auteur
WebscrapingAPI
14 minutes de lecture
vignette
GuidesGuide de démarrage rapide de l'API Web Scraping

Commencez avec WebScrapingAPI, la solution ultime de web scraping ! Collectez des données en temps réel, contournez les systèmes anti-bots et bénéficiez d'une assistance professionnelle.

Mihnea-Octavian Manolache
avatar de l'auteur
Mihnea-Octavian Manolache
9 minutes de lecture
vignette
La science du Web ScrapingLe Web Scraping en toute simplicité : l'importance de l'analyse des données

Découvrez comment extraire et organiser efficacement des données pour le web scraping et l'analyse de données grâce à l'analyse de données, aux bibliothèques d'analyse HTML et aux métadonnées schema.org.

Suciu Dan
avatar de l'auteur
Suciu Dan
12 minutes de lecture