Importer les URLs d’un sitemap avec Python

par | 19/01/2121

Python 3

Il est courant d’avoir besoin de scraper un sitemap XML pour en exporter les URLs. En Python, comme toujours, ça prend 2 minutes et c’est un jeu d’enfant. Et on peut même scraper les index de sitemaps ! Allez, c’est parti.

Pourquoi parser un sitemap XML ?

Si vous lisez cet article, vous avez probablement une bonne raison de vouloir importer la liste des URLs de votre sitemap. Que ce soit pour y rechercher des anomalies, comparer des données ou encore s’en servir comme input pour un crawler ou tout autre type d’outil, il y a 1001 raisons de vouloir importer la liste des URLs d’un site à partir de son sitemap.

Par exemple, un usage classique consiste à vérifier les pages présentes dans votre sitemap sont toutes indexables et en code 200 sans redirection préalable : faire figurer des URLs non-indexables, redirigées ou en erreur (4xx ou 5xx) doit être considéré comme une erreur d’un point de vue SEO 😥.

Récupérer la liste des URLs d’un sitemap avec Python

Prérequis

  • Python 3 🐍
  • BeautifulSoup (pip3 install bs4 ou python -m pip install bs4)
  • Requests (pip3 install requests ou python -m pip install requests)

Scraper un sitemap

Visuellement, un sitemap peut ressembler à un tableau HTML contenant des données faciles à lire, mais sous le capot, c’est du bon vieux XML.

Exemple de sitemap généré par All In One SEO

Pour récupérer le contenu d’un sitemap, il suffit donc de :

  1. Télécharger le contenu de la page web qui le sert (souvent domaine.com/sitemap.xml) grâce à Requests
  2. Parser le XML et en extraire les URLs grâce à BeautifulSoup

Ci-dessous, vous trouverez la fonction qui fait très exactement ça :

from bs4 import BeautifulSoup
import requests

def get_sitemaps_from_index(sitemap_index_url) :

    r = requests.get(sitemap_index_url)

    soup = BeautifulSoup(r.text,"xml")
    sitemapList = soup.find_all("sitemap")
    allSitemaps = []

    for sitemap in sitemapList:
        allSitemaps.append(sitemap.findNext("loc").text)

    return(allSitemaps)

En input, il suffit d’indiquer l’URL du sitemap à parser, par exemple :

urls = get_urls_from_sitemap("https://www.attractys.fr/sitemap.xml")

Et en output, vous vous en doutez, se trouve la liste des URLs du sitemap.

Scraper un index de sitemaps

Beaucoup de gros sites ont recours à des indexes de sitemaps qui comprennent une liste de plusieurs sitemaps organisés par catégorie ou par date (mois / années par exemple).

Un index de sitemap prend la forme d’un document XML qui fait la liste des URLs de tous les sitemaps du site. Il est courant qu’un index pointe vers un autre index, et ainsi de suite, de manière à organiser les contenus de manière un peu plus granulaire.

Si votre site comprend un index de sitemap, vous pouvez donc scraper cet index de manière à récupérer les URLs des sitemaps :

from bs4 import BeautifulSoup
import requests

def get_sitemaps_from_index(sitemap_index_url) :

    r = requests.get(sitemap_index_url)

    soup = BeautifulSoup(r.text,"xml")
    sitemapList = soup.find_all("sitemap")
    allSitemaps = []

    for sitemap in sitemapList:
        allSitemaps.append(sitemap.findNext("loc").text)

    return(allSitemaps)

Avec ce code, vous définissez la fonction get_sitemaps_from_index qui peut être appelée avec pour input l’URL de l’index et pour output un objet Python de type List qui contient les URLs des sitemaps de l’index.

Scraper un index de sitemap et les URLs de tous les sitemaps enfants

Puisque vous savez désormais comment récupérer la liste des sitemaps XML compris dans un index de sitemap, vous pouvez, par exemple, demander à Python d’explorer tous ces sitemaps et vous renvoyer la liste des URLs grâce à la fonction suivante :

from bs4 import BeautifulSoup
import requests

def get_urls_from_sitemap_index(sitemap_index_url) :
    allUrls = []
    for sitemap in get_sitemaps_from_index(sitemap_index_url) :
        sitemapUrls = get_urls_from_sitemap(sitemap)
        allUrls.extend(sitemapUrls)
    return(allUrls)

Il reste encore un petit problème, un index de sitemaps peut pointer vers un autre index de sitemap, et dans ce cas ce code ne fonctionne pas. Je vous propose donc d’introduire cette fonction, qui scrape les URLs pointant vers des pages ET les URLs pointant vers des sitemaps et vous renvoie le résultat dans un Dict.

C’est simple, vous lui donnez à manger un document XML, elle vous recrache tout ce qu’elle trouve 🙂

def get_all_entries_from_xml(url) :
    r = requests.get(url)
    soup = BeautifulSoup(r.text, "xml")

    # URLS
    all_url_tags = soup.find_all("url")
    allUrls = []
    for urls in all_url_tags :
        allUrls.append(urls.findNext("loc").text)

    # SITEMAPS
    sitemapList = soup.find_all("sitemap")
    allSitemaps = []
    for sitemap in sitemapList:
        allSitemaps.append(sitemap.findNext("loc").text)
        
    return ({"sitemaps" : allSitemaps, "urls" : allUrls})

Grâce à cette fonction, on va pouvoir créer un comportement récursif pour scraper toute l’architecture des sitemaps et indexes de sitemaps récursivement sans se poser de question 😎

En pratique, c’est de très loin la solution la plus simple et la plus adaptative : qu’il n’y ait qu’un sitemap.xml contenant des URLs ou toute une pyramide d’indexes pointant les uns vers les autres, vous récupérez la liste de toutes les URLs que le programme aura croisé sur son chemin.

👉 Le code clé en main qui fait le boulot à votre place 👈

import requests
from bs4 import BeautifulSoup

def get_all_entries_from_xml(url) :
    r = requests.get(url)
    soup = BeautifulSoup(r.text, "xml")

    # URLS
    all_url_tags = soup.find_all("url")
    allUrls = []
    for urls in all_url_tags :
        allUrls.append(urls.findNext("loc").text)

    # SITEMAPS
    sitemapList = soup.find_all("sitemap")
    allSitemaps = []
    for sitemap in sitemapList:
        allSitemaps.append(sitemap.findNext("loc").text)

    return ({"sitemaps" : allSitemaps, "urls" : allUrls})

def get_urls_recursively(url) :
    xml = get_all_entries_from_xml(url)
    allUrls = xml['urls']
    sitemaps = xml['sitemaps']
    visitedSitemaps = []

    while (sitemaps) :
        for sitemap in sitemaps :
            if sitemap not in visitedSitemaps :
                visitedSitemaps.append(sitemap)
                xml = get_all_entries_from_xml(sitemap)
                sitemaps.extend(xml['sitemaps'])

                for elt in xml['urls'] :
                    allUrls.append(elt)
            else :
                sitemaps.remove(sitemap)

    return(allUrls)

# Scraper récursivement toutes les URLs à partir d'un sitemap ou index de sitemap
all_urls_recursively = get_urls_recursively("https://www.attractys.fr/sitemap_index.xml")

Et voila, on branche ça les yeux fermés à son projet et on regarde les URLs pleuvoir en sirotant son café 👌

Exporter les URLs scrapées

Une fois que vous avez récupéré les donées qu’il vous faut, URLs contenues d’un ou plusieurs sitemaps ou indexes de sitemaps, il suffit d’exporter les URLs au format qui vous arrange. Pour un usage rapide, vous pouvez :

  • Imprimer les URLs à l’écran
for url in urls : 
    print(url)
  • Exporter les URLs dans un fichier texte
with open("urls.txt","w+") as f :
    for url in urls :
        f.write(url+"\n")

Pour aller plus loin

La plupart du temps, on a besoin d’une liste brute des URLs d’un sitemap. Mais il peut arriver que vous souhaitiez récupérer la liste complète des entrées relatives à chaque URL (par exemple : url, dernière modification, image, priorité etc.).

Auquel cas vous pourrez facilement obtenir le résultat voulu en faisant un usage intelligent de BeautifulSoup : il suffit de parser le nom de la balise et la valeur des filles de chaque élément du document XML qui correspond à ce que vous souhaitez scraper (url ou sitemap) dans un Dict, et d’insérer le Dict de chaque élément dans un CSV.

Par ailleurs, ce code fonctionne en single thread, c’est à dire qu’il va explorer les sitemaps et indexes l’un après l’autre. En fonction de votre connection, de la réactivité du serveur web et de la quantité de sitemaps à explorer, cela peut prendre un certain temps… les plus aventureux seront donc tentés de multihreader le programme.

Allez, vous pouvez y arriver. Sinon, laissez-moi un commentaire et je vous donnerai un coup de main ! 🐍

0 commentaires

Soumettre un commentaire

Votre adresse e-mail ne sera pas publiée.

D’autres articles qui pourront vous intéresser

Ensemble, donnons vie à vos projets.

Vous avez une idée, une question, un problème, une vision ou simplement besoin d’être conseillé ?