Un aide-mémoire XPath ?
Avez-vous déjà eu besoin d'écrire un sélecteur CSS indépendant de la classe ? Si votre réponse est non, eh bien, vous pouvez vous estimer chanceux. Si la réponse est oui, alors notre aide-mémoire XPath est exactement ce qu'il vous faut. Le Web regorge de données. Des entreprises entières dépendent de la mise en commun de certaines d'entre elles pour proposer de nouveaux services au monde entier. Les API sont très utiles, mais tous les sites web ne disposent pas d'API ouvertes. Parfois, vous devrez obtenir ce dont vous avez besoin à l'ancienne. Vous devrez créer 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 structure des nœuds DOM de la page.
Qu'est-ce que XPath et comment l'essayer ?
XPath signifie XML Path Language. Il utilise une notation de chemin (comme dans les URL) pour offrir un moyen flexible de pointer vers n'importe quelle partie d'un document XML.
XPath est principalement utilisé dans XSLT, mais peut également servir de moyen bien plus puissant pour naviguer dans le DOM de tout document en langage de type XML à l'aide de XPathExpression, comme le HTML et le 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>
There are two types of paths: relative and absolute
The unique path ( or absolute path ) to My third paragraph. is /html/body/div/div/p
A relative path to My third paragraph. is //body/div/div/p
For My Second Heading. => //body/div/h2
For My first paragraph. => //body/p
Notice that I'm using //body. Relative paths use // to skip right to the desired element.
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 mieux comprendre !
Collez le code dans un fichier .html et ouvrez-le dans votre navigateur. Ouvrez les outils de développement et appuyez sur Ctrl + 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 cliquant dessus avec le bouton droit de la souris dans l'onglet Éléments et en sélectionnant « Copier le XPath ».
Remarquez comment 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>.
/html/body/div/div/p is no longer an absolute path.
Si vous m'avez suivi jusqu'ici, félicitations, vous êtes sur la bonne voie pour maîtriser XPath. Vous êtes désormais prêt à vous plonger dans les aspects les plus amusants.
Les crochets
Vous pouvez utiliser les crochets pour sélectionner des éléments spécifiques.
In this case, //body/div/div[2]/p[3] only selects the last <p> tag.Les 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"
Notice @ marks the start of an attributeFonctions
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) => merges string1 with 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") => returns true if @attribute starts with "value"
ends-with(@attribute, "value") => returns true if @attribute ends with "value"
substring(@attribute,start_index,end_index)] => returns the substring of the attribute value based on two index values
Ex:
//p[substring(text(),3,12)="am the third"] => returns true if text() = "I am the third child"
normalize-space() => acts like text(), but it removes the trailing spaces
Ex: normalize-space(" example ") = "example"
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
Ces fonctions peuvent être un peu difficiles à mémoriser. Heureusement, le guide pratique « 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(return_value,1,15) will return the first 15 characters of the return_value string.
substring(text(),16,20) will return the last 5 characters of the same
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 de chemins
XPath prend en charge l'imbrication des chemins. C'est génial, mais qu'est-ce que j'entends exactement par « imbrication des chemins » ?
Let's try something new: /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"
In this particular example, /html/body/div[./div[./p]] and /html/body/div[.//p] yield the same result.
By now, I'm sure that you are wondering what is up with those dots in ./ and .//
The dot represents the self element. When used in a pair of brackets, it references the specific tag that opened them. Let's dive a little deeper.
In our example, /html/body/div returns two divs:
<div class="no-content"> and <div class="content">
/html/body/div[.//p] translates to:
/html/body/div[1][/html/body/div[1]//p]
and /html/body/div[2][/html/body/div[2]//p]
/html/body/div[2][/html/body/div[2]//p] is true, so it returns /html/body/div[2]
In our case, the dot ensures that /html/body/div and /html/body/div//p refer to the same <div>
Now let's look at what would have happened if it didn't.
/html/body/div[/html/body/div//p] would return both
<div class="no-content"> and <div class="content">
Why? Because /html/body/div//p is true for both /html/body/div[1] and /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.
C'est dommage que les autres aide-mémoire XPath ne mentionnent rien à propos de l'imbrication. Je trouve ça génial. Ça te permet de parcourir le document à la recherche de différents motifs et de revenir pour renvoyer autre chose. Le seul inconvénient, c'est que les requêtes écrites de cette manière peuvent devenir difficiles à 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 du contexte.
Explorons-en quelques-uns.
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]] is equivalent to /html/body/div/div/p/parent::div/parent::div
But /html/body/div[.//p] is NOT equivalent to /html/body/div//p/ancestor::div
The good news is that we can tweak it a little bit.
/html/body/div//p/ancestor::div[last()] is equivalent to /html/body/div[.//p]

Autres axes importants
//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.
//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 venez d'ajouter un tout nouvel outil à votre boîte à outils de sélection ! Si vous développez un scraper web ou automatisez des tests web, cette fiche de référence XPath vous sera très utile ! Si vous recherchez un moyen 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 encore plus de cas d'utilisation. Le concept de web scraping vous intéresse ? Vous pouvez nous contacter ici : WebScrapingAPI - Contact. Si vous souhaitez scraper le web, nous serons ravis de vous accompagner dans cette démarche. En attendant, pensez à essayer gratuitement WebScrapingAPI - Produit.




