DeviantArt est l'une des plus grandes communautés en ligne pour les artistes numériques, avec des millions de membres publiant chaque jour peintures, illustrations, photographies, pixel art et concept art. Ses pages de recherche et de galerie publiques constituent un index utile des styles tendance, des créateurs actifs dans un genre donné et de la façon dont un corpus d'œuvres est organisé : exactement le type de catalogue qu'un projet de recherche, un tableau d'inspiration personnel ou une étude de tendances visuelles souhaite lire de manière programmatique plutôt que de faire défiler à la main.

Ce guide vous montre comment scraper des images depuis DeviantArt avec Python. Vous construirez un petit scraper fonctionnel qui récupère une page de recherche publique rendue via la Crawling API, parse chaque résultat avec BeautifulSoup, collecte les métadonnées des œuvres et télécharge les fichiers miniatures sur le disque. L'ensemble du tutoriel se limite aux pages de galerie publiques, et la section légale proche de la fin n'est pas une clause de style : les œuvres DeviantArt sont protégées par le droit d'auteur des personnes qui les ont créées, alors lisez cette partie avant de pointer ce scraper sur quoi que ce soit de concret.

Ce que vous allez construire

Un script Python qui prend une URL de recherche publique DeviantArt, récupère le HTML rendu via la Crawling API et extrait un enregistrement structuré pour chaque œuvre dans la grille de résultats. Nous utiliserons une recherche par mot-clé comme exemple fil rouge et extrairons ces champs de chaque carte de résultat :

  • Title le titre de l'œuvre, par exemple "Owl #6".
  • Artist le nom d'utilisateur DeviantArt de l'auteur, lu depuis le lien de déviation.
  • Image URL la source de l'image miniature affichée dans la grille de résultats.
  • Page URL le lien vers la page de déviation individuelle.
  • Favourites le nombre de favoris publics, lorsque la carte l'expose.
  • Views le nombre de vues publiques, lorsque la carte l'expose.

Pourquoi une requête ordinaire échoue sur DeviantArt

Si vous demandez une URL de recherche DeviantArt avec un client HTTP nu, vous obtenez une réponse avec le statut 200 et presque aucune donnée d'œuvre dans le corps. Deux obstacles se dressent contre vous. Premièrement, DeviantArt construit sa grille de résultats dans le navigateur avec JavaScript, de sorte que le HTML initial n'est qu'une enveloppe qui ne se remplit qu'après l'exécution des scripts de la page. Deuxièmement, la plateforme repère rapidement le trafic automatisé : les IPs de datacenter et les patterns de requêtes qui ne ressemblent pas à un vrai navigateur sont mis au défi ou bloqués avant même d'atteindre les miniatures rendues.

Un scraper DeviantArt fonctionnel a donc besoin de deux choses en une seule requête : un navigateur qui rend réellement la page, et une IP que la plateforme considère comme un visiteur réel. Vous pouvez assembler cela vous-même avec un navigateur headless et un pool de proxies résidentiels rotatifs, mais connecter ces éléments et les maintenir en bon état représente l'essentiel du travail. La Crawling API regroupe les deux en un seul appel : vous lui envoyez l'URL avec un token JavaScript, elle rend la page derrière une IP de confiance et renvoie un HTML finalisé à parser.

Pourquoi le token JS

Crawlbase propose deux types de tokens. Le token normal récupère le HTML statique ; le token JavaScript (JS) rend d'abord la page dans un vrai navigateur. La grille de recherche de DeviantArt est rendue côté client, vous avez donc besoin du token JS ici. Utiliser le token normal renvoie la même enveloppe vide qu'une requête ordinaire, et il n'y a rien à en parser. Vous pouvez commencer avec 1 000 requêtes gratuites, sans carte bancaire requise.

Prérequis

Quelques éléments doivent être en place avant d'écrire du code. Aucun ne prend longtemps.

Python de base. Vous devez être à l'aise pour écrire et exécuter un script Python et installer des packages avec pip. Si BeautifulSoup vous est nouveau, notre guide sur l'utilisation de BeautifulSoup en Python couvre les bases du parsing que ce tutoriel suppose acquises.

Python 3.8 ou version ultérieure. Confirmez votre version avec python --version. Si vous ne l'avez pas, installez-la depuis python.org ou via une distribution comme Anaconda.

Un compte Crawlbase et un token JS. Inscrivez-vous, ouvrez votre tableau de bord et copiez votre token JavaScript (JS) depuis la page de documentation de votre compte. Traitez le token comme un mot de passe : il authentifie vos requêtes, alors gardez-le hors du contrôle de version.

Configurer le projet

Créez un environnement virtuel pour isoler les dépendances du projet, puis installez les bibliothèques dont le scraper a besoin.

bash
python --version

python -m venv deviantart_env
source deviantart_env/bin/activate

pip install crawlbase beautifulsoup4 requests

Sous Windows, activez l'environnement avec deviantart_env\Scripts\activate à la place de la ligne source. Trois dépendances font le travail : crawlbase est le client officiel pour la Crawling API, beautifulsoup4 analyse le HTML renvoyé pour vous permettre d'extraire des champs individuels par sélecteur CSS, et requests diffuse les fichiers image sur le disque à la dernière étape.

Étape 1 : Récupérer la page de recherche rendue

Commencez par obtenir la page finale. Importez la classe CrawlingAPI, initialisez-la avec votre token JS et demandez une URL de recherche construite à partir d'un mot-clé. Le chemin de recherche de DeviantArt est /search?q=KEYWORD, donc une requête pour "fantasy" vous donne une grille de résultats publics. Vérifier le statut avant de parser permet de rendre les échecs visibles plutôt que silencieux.

python
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"})

base_url = "https://www.deviantart.com"

def crawl(page_url):
    options = {"ajax_wait": "true", "page_wait": 5000}
    response = api.get(page_url, options)
    if response["status_code"] == 200:
        return response["body"].decode("latin1")
    print(f"Request failed: {response['status_code']}")
    return None

if __name__ == "__main__":
    keyword = "fantasy"
    search_url = f"{base_url}/search?q={keyword}"
    html = crawl(search_url)
    print(html[:500] if html else "No HTML returned")

Les deux options d'attente sont importantes pour une cible rendue côté client. ajax_wait attend la fin du chargement du contenu asynchrone, et page_wait maintient un délai fixe en millisecondes après le chargement pour que les miniatures à rendu tardif apparaissent avant la capture de la page. Cinq secondes constituent un bon point de départ ; augmentez si la grille revient vide. Le corps est décodé en latin1 pour que les octets bruts passent proprement. Exécutez le script avec python scraper.py et vous devriez voir de véritables balises d'œuvres, pas l'enveloppe vide qu'une requête ordinaire renvoie. Cela confirme que le rendu fonctionne avant d'écrire un seul sélecteur.

Crawlbase DeviantArt Scraper

La grille de recherche de DeviantArt nécessite une page rendue derrière une IP de confiance, en un seul appel. La Crawling API prend un token JS, exécute la page dans un vrai navigateur, effectue une rotation des IPs résidentielles côté serveur et vous remet un HTML finalisé, vous dispensant d'exploiter vous-même une flotte de navigateurs headless et un pool de proxies. Pointez-la sur une page de recherche publique dans le cadre du niveau gratuit d'abord.

Étape 2 : Parser les cartes d'œuvres avec BeautifulSoup

Une fois le HTML rendu en main, chargez-le dans BeautifulSoup et extrayez chaque résultat par son sélecteur. DeviantArt enveloppe chaque miniature dans un lien de déviation, vous sélectionnez donc tous ces liens une fois et lisez ensuite les mêmes champs de chacun d'eux. Faites un clic droit sur une miniature dans votre navigateur, choisissez "Inspecter", et vous trouverez une structure similaire à celle ci-dessous.

html
<a data-hook="deviation_link"
   href="https://www.deviantart.com/siobhan-o-wisp/art/Owl-6-925596734"
   aria-label="Owl #6 by Siobhan-o-wisp, visual art">
  <div data-testid="thumb" typeof="ImageObject">
    <img alt="Owl #6" src="src_url_here" property="contentUrl" />
  </div>
</a>

Cela vous donne tout ce dont vous avez besoin depuis une carte. L'ancre porte l'URL de la page de déviation dans href et le titre et l'artiste dans aria-label, et l'img imbriqué porte la miniature dans src. Le sélecteur CSS pour une image miniature est a[data-hook="deviation_link"] img[property="contentUrl"], et vous pouvez atteindre le titre de l'œuvre avec l'attribut alt de l'image. Voici le parser.

python
from bs4 import BeautifulSoup

def artist_from_url(page_url):
    # DeviantArt page URLs look like /<username>/art/<slug>
    parts = page_url.split("/")
    return parts[3] if len(parts) > 3 else None

def count_for(link, label):
    el = link.select_one(f'span[aria-label*="{label}"]')
    return el.get_text(strip=True) if el else None

def parse_artworks(html):
    soup = BeautifulSoup(html, "html.parser")
    links = soup.select('a[data-hook="deviation_link"]')
    artworks = []

    for link in links:
        img = link.select_one('img[property="contentUrl"]')
        if not img:
            continue
        page_url = link.get("href")
        artworks.append({
            "title": img.get("alt"),
            "artist": artist_from_url(page_url),
            "image_url": img.get("src"),
            "page_url": page_url,
            "favourites": count_for(link, "Favourites"),
            "views": count_for(link, "Views"),
        })

    return artworks

Le titre provient de l'attribut alt de l'image, l'artiste du segment nom d'utilisateur de l'URL de déviation, et la miniature de src. Les compteurs de favoris et de vues se trouvent dans de petits spans étiquetés ; count_for interroge par texte aria-label et renvoie None lorsqu'une carte n'expose pas ce nombre, de sorte qu'un compteur manquant ne fait jamais planter la boucle. Tous les résultats n'affichent pas leurs statistiques dans la grille, c'est pourquoi les deux champs sont optionnels.

Les sélecteurs évoluent

Les noms de classes et attributs internes de DeviantArt changent sans préavis. Les points d'ancrage stables ici sont data-hook="deviation_link" et property="contentUrl", qui correspondent à des balises de schéma public, mais traitez chaque sélecteur comme un modèle de départ, pas comme un contrat. Quand un champ revient en None pour chaque carte, ré-inspectez une miniature en direct dans les outils de développement de votre navigateur et mettez à jour le sélecteur. La maintenance périodique est normale pour tout scraper en production.

Étape 3 : Assembler le scraper

Reliez maintenant la récupération et le parsing en un seul script fonctionnel, ajoutez la pagination pour pouvoir lire plus que la première grille, et écrivez le résultat en JSON. DeviantArt pagine la recherche avec un paramètre &page=, donc une petite boucle sur les numéros de pages collecte un ensemble de résultats plus large. Faites une pause entre les requêtes pour ne pas surcharger le site dans une boucle serrée.

python
import json
import time
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"})
base_url = "https://www.deviantart.com"

def crawl(page_url):
    options = {"ajax_wait": "true", "page_wait": 5000}
    response = api.get(page_url, options)
    if response["status_code"] == 200:
        return response["body"].decode("latin1")
    print(f"Request failed: {response['status_code']}")
    return None

def artist_from_url(page_url):
    parts = page_url.split("/")
    return parts[3] if len(parts) > 3 else None

def count_for(link, label):
    el = link.select_one(f'span[aria-label*="{label}"]')
    return el.get_text(strip=True) if el else None

def parse_artworks(html):
    soup = BeautifulSoup(html, "html.parser")
    links = soup.select('a[data-hook="deviation_link"]')
    artworks = []
    for link in links:
        img = link.select_one('img[property="contentUrl"]')
        if not img:
            continue
        page_url = link.get("href")
        artworks.append({
            "title": img.get("alt"),
            "artist": artist_from_url(page_url),
            "image_url": img.get("src"),
            "page_url": page_url,
            "favourites": count_for(link, "Favourites"),
            "views": count_for(link, "Views"),
        })
    return artworks

def main():
    keyword = "fantasy"
    total_pages = 2
    results = []
    for page in range(1, total_pages + 1):
        url = f"{base_url}/search?q={keyword}&page={page}"
        html = crawl(url)
        if html:
            results.extend(parse_artworks(html))
        time.sleep(3)

    with open("deviantart_data.json", "w") as f:
        json.dump(results, f, indent=2)
    print(f"Saved {len(results)} artworks")

if __name__ == "__main__":
    main()

Chaque page partage la même structure de carte, donc le parser que vous avez déjà écrit fonctionne sur toutes sans modification. Le time.sleep(3) entre les requêtes empêche une exécution multi-pages de surcharger le site. N'augmentez total_pages que jusqu'à ce dont vous avez réellement besoin, et surveillez les codes de statut au fur et à mesure.

À quoi ressemble le résultat

Exécutez le script complet avec python scraper.py et vous obtenez un enregistrement structuré propre pour chaque œuvre, prêt à écrire en JSON, CSV ou dans une base de données.

json
[
  {
    "title": "Owl #6",
    "artist": "siobhan-o-wisp",
    "image_url": "https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/.../owl_6.jpg",
    "page_url": "https://www.deviantart.com/siobhan-o-wisp/art/Owl-6-925596734",
    "favourites": "412",
    "views": "3.1K"
  },
  {
    "title": "Magic Forest",
    "artist": "postapodcast",
    "image_url": "https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/.../magic_forest.png",
    "page_url": "https://www.deviantart.com/postapodcast/art/Magic-Forest",
    "favourites": null,
    "views": null
  }
]

Les URLs d'images pointent vers le CDN de DeviantArt (l'hôte wixmp.com), et les favoris et vues reviennent en null sur les cartes qui n'exposent pas leurs statistiques, ce qui est attendu. Avec cette liste en main, vous pouvez indexer les métadonnées, les étudier ou passer au téléchargement des fichiers miniatures.

Étape 4 : Télécharger les fichiers image

Les enregistrements contiennent des URLs de miniatures, pas les octets d'image. Pour sauvegarder les fichiers localement, diffusez chaque URL sur le disque avec requests. Diffuser par morceaux maintient la mémoire stable même pour les images plus grandes, et raise_for_status transforme un téléchargement échoué en erreur capturée plutôt qu'en fichier silencieusement vide.

python
import os
import requests

def download_image(url, save_path):
    try:
        response = requests.get(url, stream=True, timeout=30)
        response.raise_for_status()
        with open(save_path, "wb") as file:
            for chunk in response.iter_content(chunk_size=8192):
                file.write(chunk)
        print(f"Saved {save_path}")
    except requests.exceptions.RequestException as e:
        print(f"Error downloading {url}: {e}")

def download_all(artworks, folder="downloaded_images"):
    os.makedirs(folder, exist_ok=True)
    for i, art in enumerate(artworks):
        url = art.get("image_url")
        if not url:
            continue
        artist = art.get("artist") or "unknown"
        path = os.path.join(folder, f"{artist}_{i}.jpg")
        download_image(url, path)

Appelez download_all(results) après le scraping et chaque miniature se retrouve dans un dossier downloaded_images, nommée avec le nom d'utilisateur de l'artiste et un index. Nommer par artiste maintient l'attribution attachée au fichier, ce qui importe ici car chacune de ces images appartient à la personne qui l'a créée. Lisez la section suivante avant de sauvegarder quoi que ce soit que vous comptez utiliser au-delà d'un simple coup d'œil.

Rester débloqué

Même avec le rendu pris en charge, DeviantArt surveille le trafic qui ressemble à des scrapers. Quelques habitudes maintiennent une exécution saine, et elles s'appliquent à toute cible difficile.

  • Espacez vos requêtes. Enchaîner les pages de recherche dans une boucle serrée est le moyen le plus rapide d'être limité. Le time.sleep dans la boucle est là pour cette raison ; gardez-le.
  • Misez sur la rotation. Un pool d'IPs résidentielles répartit les requêtes sur de nombreuses adresses d'utilisateurs réels, de sorte qu'aucune ne déclenche une limite de débit. La Crawling API s'en charge pour vous ; si vous gérez votre propre stack, c'est la partie à soigner.
  • Lisez les codes de statut. Une exécution qui commence à retourner des défis ou des erreurs vous indique que le débit ou le niveau d'IP actuel n'est plus suffisant. Traitez-le comme un signal pour ralentir, pas comme du bruit à ignorer.

Pour le guide de référence, consultez comment scraper des sites web sans être bloqué. Si votre cible est rendue côté client comme DeviantArt, notre guide sur crawler des sites web JavaScript explique pourquoi le rendu importe. Et si vous préférez acheminer votre propre trafic via un pool rotatif plutôt que d'utiliser l'API gérée, le Smart AI Proxy (aussi appelé AI Proxy) vous offre la même rotation d'IPs résidentielles comme endpoint proxy direct.

Est-il légal de scraper DeviantArt ?

C'est la partie qui importe le plus sur une plateforme créative, alors ne la survolez pas. Le travail technique de ce guide est la moitié facile ; la question des droits est la moitié difficile. La légitimité du scraping de DeviantArt dépend des conditions d'utilisation de DeviantArt, de votre juridiction et de ce que vous faites des données. Les conditions de DeviantArt limitent l'accès automatisé, le scraping peut donc aller à l'encontre de ces conditions quelle que soit la prudence de vos outils. Lisez les conditions d'utilisation de DeviantArt et son fichier robots.txt, et traitez les deux comme la frontière de ce que vous collectez.

Le fait le plus important : chaque œuvre sur DeviantArt est protégée par le droit d'auteur de l'artiste qui l'a créée. Collecter une URL de miniature publique ou un titre, c'est une chose ; ce que vous faites ensuite de cette image en est une autre, et la loi s'intéresse à cette différence. Ne redistribuez pas, ne republiez pas, ne revendez pas et n'entraînez pas de modèles sur des œuvres téléchargées sans la permission explicite du créateur. Limitez votre travail à l'indexation des métadonnées et à la sauvegarde de miniatures à des fins personnelles, de recherche ou d'étude interne authentique, et gardez le nom de l'artiste attaché pour que l'attribution ne soit jamais perdue. Une image scrapée n'est pas une image sous licence, et "c'était public" n'est pas une licence.

Ce guide est délibérément limité aux pages de galerie et de recherche publiques car c'est la ligne qui rend le travail défendable. Il ne couvre rien derrière une connexion, rien soumis à une restriction de contenu pour adultes, les données de compte ou personnelles, ou toute tentative de contournement de l'authentification ou des filtres de contenu. Uniquement les métadonnées d'œuvres publiques et non restreintes. Si votre projet nécessite plus que cela, la voie sanctionnée est l'API OAuth officielle de DeviantArt, qui expose les déviations et leurs métadonnées selon des conditions que la plateforme accorde réellement. Pour tout usage au-delà de l'utilisation personnelle, en particulier pour la réutilisation commerciale, l'API officielle accompagnée de la permission de l'artiste est la voie correcte, pas un scraper plus astucieux.

Récapitulatif

Points clés

  • DeviantArt est rendu côté client. Une requête ordinaire retourne une enveloppe vide, vous devez donc rendre la grille de recherche avant de la parser.
  • Vous avez besoin du rendu et d'une IP de confiance ensemble. La Crawling API avec un token JS fait les deux en un seul appel ; ajax_wait et page_wait contrôlent la durée d'attente du contenu.
  • BeautifulSoup se charge de l'extraction. Sélectionnez chaque a[data-hook="deviation_link"], puis lisez le titre, l'artiste, l'URL d'image, l'URL de page et tout favoris ou vue publics de chacun.
  • Téléchargez par diffusion. Utilisez requests avec stream=True pour écrire les miniatures sur le disque par morceaux, nommées par artiste pour que l'attribution reste attachée.
  • Les œuvres sont protégées par le droit d'auteur. Restez sur les pages publiques et non restreintes, ne redistribuez jamais, ne revendez ni n'entraînez sur des images téléchargées sans permission, et utilisez l'API OAuth officielle de DeviantArt pour tout usage au-delà de l'utilisation personnelle ou de recherche.

Foire aux questions

Pourquoi une requête ordinaire ne retourne-t-elle aucune œuvre de DeviantArt ?

Parce que DeviantArt construit sa grille de recherche côté client avec JavaScript. Le HTML initial n'est qu'une enveloppe qui ne se remplit qu'après l'exécution des scripts de la page dans un navigateur, donc une requête HTTP brute retourne le statut 200 avec la grille vide. Pour obtenir des données réelles, vous devez d'abord rendre la page, ce que gère le token JS de la Crawling API.

Ai-je besoin du token normal ou du token JS pour DeviantArt ?

Le token JS. Le token normal récupère le HTML statique, qui sur la page de recherche de DeviantArt est la même enveloppe vide qu'une requête ordinaire. Le token JS rend la page dans un vrai navigateur avant de renvoyer le HTML, de sorte que les miniatures et leurs métadonnées sont présentes lorsque BeautifulSoup les parse.

Comment scraper plus que la première page de résultats ?

DeviantArt pagine la recherche avec un paramètre &page=. Bouclez sur les numéros de pages dont vous avez besoin, construisez une URL comme /search?q=fantasy&page=2 pour chacune, récupérez-la via la Crawling API et exécutez le même parser. Gardez une courte pause entre les requêtes pour qu'une exécution multi-pages ne surcharge pas le site.

Mes sélecteurs retournent None pour chaque carte. Qu'est-ce qui a changé ?

Presque certainement le balisage de DeviantArt. Ses noms de classes et attributs internes changent sans préavis, de sorte que des sélecteurs qui fonctionnaient le mois dernier peuvent ne plus fonctionner. Les points d'ancrage de schéma data-hook="deviation_link" et property="contentUrl" sont les plus durables, mais ré-inspectez une miniature en direct dans les outils de développement de votre navigateur et mettez à jour les sélecteurs lorsqu'un champ revient vide.

Puis-je réutiliser ou vendre les images téléchargées depuis DeviantArt ?

Non, pas sans la permission de l'artiste. Chaque déviation est protégée par le droit d'auteur de son créateur, donc télécharger une miniature publique ne vous accorde pas de licence pour la redistribuer, republier, revendre ou l'utiliser pour entraîner un modèle. Limitez les téléchargements à l'usage personnel, de recherche ou d'étude interne, attribuez l'artiste, et pour toute réutilisation obtenez une permission explicite ou utilisez un arrangement de licence via le canal officiel.

Existe-t-il une API officielle de DeviantArt que je devrais utiliser à la place ?

Oui. DeviantArt propose une API OAuth qui expose les déviations et leurs métadonnées selon des conditions que la plateforme accorde directement. Pour tout usage au-delà de l'utilisation personnelle occasionnelle, et surtout pour les projets commerciaux, l'API OAuth officielle de DeviantArt est la voie sanctionnée et celle qui vous maintient du bon côté des conditions d'utilisation et du droit d'auteur.

Commencer à construire

Crawlez n'importe quel site à grande échelle, sans combattre l'infrastructure.

Crawlbase gère les proxies, les empreintes et les CAPTCHA afin que votre équipe livre des pipelines de données au lieu de maintenir la plomberie de crawl. 1 000 requêtes gratuites, sans carte requise.

En libre-service · Sans appel commercial requis · Volumes de crawl entreprise disponibles