Utiliser l'API REST Infoscience¶
L'API REST d'Infoscience donne un accès programmatique à l'ensemble des métadonnées et fichiers publics du dépôt. C'est le socle technique de la plateforme elle-même — elle peut servir à construire des intégrations, automatiser des workflows et moissonner des données.
URL de base : https://infoscience.epfl.ch/server/api
Convention des URL dans les exemples
Tous les exemples curl de cette page utilisent des chemins relatifs à l'URL de base ci-dessus. Pour les exécuter directement, préfixez l'URL de base :
curl "https://infoscience.epfl.ch/server/api/core/items/{{uuid}}" ...
Explorateur interactif
Le HAL Browser offre une interface en direct et auto-documentée de tous les points d'accès. Utilisez-le pour explorer la structure de l'API et tester des requêtes sans écrire de code.
Rétro-ingénierie de l'interface
Tout ce qui est possible dans l'interface web d'Infoscience est également réalisable via l'API. Ouvrez les outils de développement de votre navigateur (F12 → onglet Réseau), effectuez une action dans l'interface, et inspectez les requêtes émises — cela révèle les appels API et paramètres exacts utilisés par chaque fonctionnalité.
Spécifications officielles
- DSpace (noyau) : github.com/DSpace/RestContract
- Extensions DSpace-CRIS : github.com/4Science/Rest7Contract
Architecture¶
L'API suit trois standards complémentaires :
- HAL — chaque réponse intègre des
_linksqui décrivent les opérations disponibles et les ressources liées. - HATEOAS — l'API est auto-navigable : commencez depuis la racine et suivez les liens plutôt que de construire les URL manuellement.
- ALPS — des profils sémantiques lisibles par machine documentent ce que chaque endpoint accepte et retourne.
Toutes les réponses sont en JSON. L'API est en lecture seule pour les utilisateur·trices anonymes ; les requêtes authentifiées permettent aussi de modifier des données avec les droits appropriés.
Authentification¶
Accès anonyme¶
Toutes les métadonnées et tous les fichiers accessibles publiquement sont disponibles sans token :
curl "/core/items/{{uuid}}" \
-H 'accept: application/json'
Token Bearer¶
Un token personnel donne accès aux ressources restreintes et est nécessaire pour les opérations d'écriture.
Obtenir votre token :
- Connectez-vous à infoscience.epfl.ch.
- Cliquez sur votre icône de profil → Compte et profil.
- Faites défiler vers le bas et cliquez sur Générer un nouveau token.
- Copiez le token immédiatement — il ne sera plus visible ensuite.
Warning
Le token n'est pas récupérable une fois la fenêtre fermée. En cas de perte, générez-en un nouveau — le précédent est automatiquement révoqué.
export INFOSCIENCE_TOKEN="votre_token"
curl "/core/items/{{uuid}}" \
-H 'accept: application/json' \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Comptes de service¶
Pour les workflows automatisés non rattachés à un compte individuel, contactez infoscience@epfl.ch. Dans des contextes spécifiques et justifiés, des droits en écriture peuvent être accordés.
Bonnes pratiques de sécurité¶
- N'incluez jamais votre token dans du code source ou des dépôts publics.
- Stockez les tokens dans des variables d'environnement ou un gestionnaire de secrets (fichier
.env, secrets CI). - Renouvelez votre token si vous suspectez une compromission.
Concepts fondamentaux¶
Pagination¶
Tous les endpoints de collection sont paginés. La réponse inclut page.totalElements et page.totalPages pour la navigation :
| Paramètre | Défaut | Max | Description |
|---|---|---|---|
page |
0 |
— | Numéro de page (indexé à zéro) |
size |
20 |
100 |
Résultats par page |
# Page 3, 50 résultats par page
curl "/discover/search/objects\
?configuration=researchoutputs&query=dc.description.sponsorship:LASUR\
&page=2&size=50" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
La manière la plus simple d'itérer toutes les pages est de suivre _links.next dans chaque réponse — il est présent tant qu'il reste des pages, et absent sur la dernière :
import requests, os
BASE = "https://infoscience.epfl.ch/server/api"
HEADERS = {
"accept": "application/json",
"Authorization": f"Bearer {os.environ['INFOSCIENCE_TOKEN']}",
}
def search_all_raw(query, configuration="researchoutputs", size=100):
"""Itère toutes les pages via _links.next et retourne les dicts indexableObject bruts."""
url = f"{BASE}/discover/search/objects"
params = {
"configuration": configuration,
"query": query,
"sort": "dc.date.issued,DESC",
"size": size,
}
results = []
while url:
r = requests.get(url, headers=HEADERS, params=params)
r.raise_for_status()
data = r.json()
params = None # les params sont encodés dans _links.next à partir de la 2e page
search_result = data.get("_embedded", {}).get("searchResult", {})
for obj in search_result.get("_embedded", {}).get("objects", []):
item = obj.get("_embedded", {}).get("indexableObject")
if item:
results.append(item)
url = (search_result.get("_links", {})
.get("next", {})
.get("href"))
return results
Ou bien, itérer par numéro de page en utilisant page.totalPages :
# BASE et HEADERS sont définis dans la variante _links.next ci-dessus
def search_all_raw(query, configuration="researchoutputs", size=100):
"""Itère toutes les pages par numéro et retourne les dicts indexableObject bruts."""
page, results = 0, []
while True:
r = requests.get(
f"{BASE}/discover/search/objects",
headers=HEADERS,
params={
"configuration": configuration,
"query": query,
"sort": "dc.date.issued,DESC",
"page": page,
"size": size,
}
)
r.raise_for_status()
data = r.json()
search_result = data.get("_embedded", {}).get("searchResult", {})
for obj in search_result.get("_embedded", {}).get("objects", []):
item = obj.get("_embedded", {}).get("indexableObject")
if item:
results.append(item)
page_info = search_result.get("page", {})
if page >= page_info.get("totalPages", 1) - 1:
break
page += 1
return results
Intégration de ressources liées (embed)¶
Le paramètre embed évite les allers-retours superflus en intégrant des ressources liées en une seule requête. Combinez plusieurs valeurs sous forme de liste séparée par des virgules :
| Valeur | Intègre |
|---|---|
bundles/bitstreams |
Tous les bundles et leurs fichiers (ORIGINAL, TEXT, THUMBNAIL…) |
thumbnail |
La vignette principale de la notice |
metrics |
Métriques de citation et d'usage (Scopus, vues, téléchargements) |
# Fichiers uniquement
curl "/core/items/{{uuid}}\
?embed=bundles%2Fbitstreams" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
# Vignette uniquement
curl "/core/items/{{uuid}}\
?embed=thumbnail" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
# Fichiers + vignette + métriques en une seule requête
curl "/core/items/{{uuid}}\
?embed=bundles%2Fbitstreams,thumbnail,metrics" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
embed fonctionne également sur l'endpoint de recherche — utile pour récupérer fichiers ou métriques pour tous les résultats en un seul appel :
curl "/discover/search/objects\
?configuration=researchoutputs&query=dc.description.sponsorship:LASUR\
&size=20&embed=bundles%2Fbitstreams,thumbnail,metrics" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Structure des réponses¶
Réponse de recherche¶
Une réponse de recherche enveloppe les résultats dans un objet _embedded imbriqué :
{
"_embedded": {
"searchResult": {
"_embedded": {
"objects": [
{
"_embedded": {
"indexableObject": {
"uuid": "a1b2c3d4-e5f6-7890-abcd-000000000001",
"metadata": {
"dc.title": [{ "value": "Titre de ma publication", "language": null }],
"dc.date.issued": [{ "value": "2024", "language": null }],
"dc.contributor.author": [
{ "value": "Martin, Sophie", "language": null },
{ "value": "Dupont, Jean", "language": null }
]
},
"_links": {
"self": { "href": "/core/items/a1b2c3d4-..." },
"bundles": { "href": "/core/items/a1b2c3d4-.../bundles" },
"owningCollection":{ "href": "/core/items/a1b2c3d4-.../owningCollection" }
}
}
}
}
]
},
"page": {
"size": 20,
"totalElements": 1234,
"totalPages": 62,
"number": 0
}
}
}
}
Champs clés :
| Champ | Description |
|---|---|
_embedded.searchResult._embedded.objects |
Tableau de résultats ; l'item réel est dans _embedded.indexableObject |
page.totalElements |
Nombre total de notices correspondantes |
page.totalPages |
Nombre total de pages pour la taille demandée |
page.number |
Page courante (indexée à zéro) |
_links.next |
URL de la page suivante (absent sur la dernière page) |
Notice (GET direct)¶
Un GET direct retourne l'objet sans l'enveloppe de recherche. L'exemple ci-dessous est tiré d'une vraie notice Infoscience :
{
"id": "a1b2c3d4-e5f6-7890-abcd-000000000001",
"uuid": "a1b2c3d4-e5f6-7890-abcd-000000000001",
"name": "A novel numerical method for plasma simulation",
"handle": "20.500.14299/000001",
"metadata": {
"dc.title": [
{ "value": "A novel numerical method for plasma simulation",
"language": null, "authority": null, "confidence": -1, "place": 0 }
],
"dc.date.issued": [
{ "value": "2024-10-01",
"language": null, "authority": null, "confidence": -1, "place": 0 }
],
"dc.contributor.author": [
{ "value": "Durand, Alice",
"language": null, "authority": null, "confidence": -1, "place": 0 },
{ "value": "Martin, Thomas",
"language": null, "authority": "a1b2c3d4-e5f6-7890-abcd-000000000002",
"confidence": 600, "place": 1 },
{ "value": "Bernard, Claire",
"language": null, "authority": null, "confidence": -1, "place": 2 }
],
"dc.description.sponsorship": [
{ "value": "LABX",
"language": null, "authority": "a1b2c3d4-e5f6-7890-abcd-000000000003",
"confidence": 600, "place": 0 }
],
"dc.relation.journal": [
{ "value": "Journal of Computational Physics",
"language": null, "authority": "a1b2c3d4-e5f6-7890-abcd-000000000004",
"confidence": 600, "place": 0 }
],
"dc.identifier.doi": [
{ "value": "10.1000/example.2024.001",
"language": null, "authority": null, "confidence": -1, "place": 0 }
],
"cris.virtual.orcid": [
{ "value": "#PLACEHOLDER_PARENT_METADATA_VALUE#",
"language": null, "authority": null, "confidence": -1, "place": 0 },
{ "value": "0000-0001-2345-6789",
"language": null, "authority": null, "confidence": -1, "place": 1 },
{ "value": "#PLACEHOLDER_PARENT_METADATA_VALUE#",
"language": null, "authority": null, "confidence": -1, "place": 2 }
]
},
"_links": {
"self": { "href": "/core/items/a1b2c3d4-..." },
"bundles": { "href": "/core/items/a1b2c3d4-.../bundles" },
"owningCollection": { "href": "/core/items/a1b2c3d4-.../owningCollection" }
}
}
Les valeurs de métadonnées sont toujours des tableaux. Chaque entrée contient cinq attributs :
| Attribut | Description |
|---|---|
value |
La chaîne d'affichage |
language |
Code de langue ISO, ou null |
authority |
UUID de l'entité CRIS liée (Person, OrgUnit, Journal…), ou null si non résolu |
confidence |
Score de confiance : 600 = lien confirmé, -1 = sans autorité |
place |
Position dans la liste de valeurs du champ (indexée à zéro) — préserve l'ordre d'insertion et l'alignement des groupes |
Quand authority est renseigné, l'UUID peut être utilisé directement comme scope dans une requête RELATION.* — sans recherche préalable.
Champs virtuels et valeurs de remplacement¶
Les champs préfixés cris.virtual.* sont calculés à partir des entités CRIS liées et sont alignés positionnellement avec leur champ parent via l'index place. Par exemple, cris.virtual.orcid[place=N] correspond à dc.contributor.author[place=N].
Quand une entrée d'auteur·e n'a pas d'item Person lié (authority: null), toutes les entrées cris.virtual.* à ce même place portent la valeur sentinelle #PLACEHOLDER_PARENT_METADATA_VALUE#. Cela préserve la structure de groupe afin que les index place restent cohérents entre tous les champs virtuels.
dc.contributor.author[place=0]: "Durand, Alice" authority: null → pas de Person lié
dc.contributor.author[place=1]: "Martin, Thomas" authority: "a1b2c3d4-..."
dc.contributor.author[place=2]: "Bernard, Claire" authority: null
cris.virtual.orcid[place=0]: "#PLACEHOLDER_PARENT_METADATA_VALUE#" ← pas d'ORCID
cris.virtual.orcid[place=1]: "0000-0001-2345-6789" ← ORCID de Martin
cris.virtual.orcid[place=2]: "#PLACEHOLDER_PARENT_METADATA_VALUE#" ← pas d'ORCID
Les champs compagnons cris.virtualsource.* stockent l'UUID de l'item source de chaque valeur virtuelle — ce sont des champs d'infrastructure, non destinés à l'usage externe.
Structure des métriques¶
Quand embed=metrics est utilisé, la réponse inclut un tableau _embedded.metrics. Chaque entrée représente un type de métrique :
{
"metricType": "scopusCitation",
"metricCount": 2.0,
"acquisitionDate": "2024-09-01T00:00:00.000+00:00",
"last": true,
"deltaPeriod2": null
}
metricType |
Description |
|---|---|
scopusCitation |
Nombre de citations Scopus |
wosCitation |
Nombre de citations Web of Science |
view |
Nombre de consultations de la page |
download |
Nombre de téléchargements de fichiers |
altmetric |
Score Altmetric (configuration uniquement, pas de metricCount) |
last: true marque l'acquisition la plus récente pour ce type de métrique. Utilisez-le pour exclure les snapshots historiques quand plusieurs entrées du même type sont présentes.
Utilitaire Python — extraire des valeurs de métadonnées¶
Quand on travaille avec des réponses JSON brutes (par exemple issues de search_all_raw), les métadonnées sont un simple dictionnaire. Ces utilitaires simplifient l'accès aux champs :
def get_meta(item, field, default=None):
"""Retourne la première valeur d'un champ de métadonnée depuis un dict JSON brut."""
entries = item.get('metadata', {}).get(field, [])
return entries[0].get('value', default) if entries else default
def get_meta_all(item, field):
"""Retourne toutes les valeurs d'un champ, en ignorant les placeholders."""
return [
e.get('value') for e in item.get('metadata', {}).get(field, [])
if e.get('value') != '#PLACEHOLDER_PARENT_METADATA_VALUE#'
]
# Exemple : extraire des champs depuis les résultats de search_all_raw
items = search_all_raw("dc.description.sponsorship:LABX")
for item in items:
print(get_meta(item, 'dc.title'))
print(get_meta(item, 'dc.date.issued'))
print(get_meta_all(item, 'dc.contributor.author'))
Rechercher des notices¶
Endpoint de base : GET /discover/search/objects
Paramètre configuration¶
| Valeur | Portée |
|---|---|
researchoutputs |
Publications, jeux de données, brevets (défaut) |
persons |
Profils de chercheur·euses |
orgunits |
Laboratoires et unités |
journals |
Revues |
events |
Conférences et événements |
Syntaxe de requête¶
Le paramètre query accepte la syntaxe Lucene/Solr :
| Opérateur | Exemple |
|---|---|
| Texte libre | query=intelligence artificielle |
| Champ spécifique | query=author_editor:(Martin, Sophie) |
| Expression exacte | query=title:("deep learning") |
| ET booléen | query=author_editor:(bernard) AND dateIssued.year:[2020 TO 2024] |
| OU booléen | query=(author_editor:(dupont) OR author_editor:(martin)) |
| Exclusion | query=dc.description.sponsorship:LASUR -types:(conference poster) |
| Joker | query=author_editor:(Martin, S*) |
| Plage | query=dateIssued.year:[2020 TO 2024] |
Rechercher par UUID¶
Deux approches équivalentes :
# GET direct (le plus rapide)
curl "/core/items/a1b2c3d4-e5f6-7890-abcd-000000000001" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
# Via la recherche discovery
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=search.resourceid:a1b2c3d4-e5f6-7890-abcd-000000000001\
&size=1" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Rechercher par DOI¶
Préférez itemidentifier_keyword — cet index normalise la chaîne (insensible à la casse, gère les variantes de format) :
# Recommandé
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=itemidentifier_keyword:(10.5281/zenodo.0000000)\
&sort=dc.date.issued,DESC&page=0&size=100" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
# Alternative (correspondance exacte sur la valeur stockée)
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=dc.identifier.doi:(10.5281/zenodo.0000000)\
&sort=dc.date.issued,DESC&page=0&size=100" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Rechercher par titre¶
Pour une correspondance exacte, encadrez le titre de guillemets doubles. Les deux-points (:) brisent la requête — nettoyez le titre en supprimant la ponctuation et les diacritiques :
# Titre exact (entre guillemets)
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=title:(%22SEFI SIG Workshop: Eager to further develop the field of engineering ethics education?%22)\
&sort=dc.date.issued,DESC&page=0&size=100" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
# Titre nettoyé (plus sûr — supprime la ponctuation qui bloque le parseur)
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=title:(SEFI SIG Workshop Eager to further develop the field of engineering ethics education)\
&sort=dc.date.issued,DESC&page=0&size=100" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Nettoyage du titre
Les deux-points, points d'interrogation et caractères spéciaux dans les titres cassent le parseur de requêtes. Supprimez la ponctuation et les diacritiques avant d'envoyer une requête sur le titre, ou encadrez la chaîne exacte de guillemets doubles.
Rechercher par ISSN¶
Couvrez les deux champs dc.relation.issn et dc.relation.seriesissn — les notices anciennes peuvent utiliser l'un ou l'autre :
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=dc.relation.issn:%221069-4730%22 OR dc.relation.issn:%222168-9830%22 \
OR dc.relation.seriesissn:%221069-4730%22 OR dc.relation.seriesissn:%222168-9830%22\
&sort=dc.date.issued,DESC&page=0&size=50" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Couverture des ISSN
Infoscience n'a pas stocké les ISSN de façon systématique dans les notices anciennes. Si une requête par ISSN retourne peu de résultats, complétez par une recherche sur le titre de la revue (voir ci-dessous).
Rechercher par titre de revue¶
Quand les ISSN sont incomplets, recherchez par titre de revue en couvrant les deux champs dc.relation.journal et dc.relation.ispartofseries :
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=dc.relation.journal:%22Journal of Engineering Education%22 \
OR dc.relation.journal:%22European Journal of Engineering Education%22 \
OR dc.relation.ispartofseries:%22Journal of Engineering Education%22 \
OR dc.relation.ispartofseries:%22European Journal of Engineering Education%22\
&sort=dc.date.issued,DESC&page=0&size=100" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Rechercher par conférence¶
Combinez oairecerif.acronym, dc.relation.conference et dc.relation.ispartof :
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=oairecerif.acronym:(*SEFI*) \
OR dc.relation.conference:(*SEFI*) \
OR dc.relation.ispartof:(*SEFI*)\
&sort=dc.date.issued,DESC&page=0&size=100" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Rechercher les thèses EPFL¶
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=types:(doctoral thesis) publisher:EPFL\
&sort=dc.date.issued,DESC&page=0&size=100" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Filtrer par unité¶
# Par code court d'unité (index Solr)
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=dc.description.sponsorship:LASUR\
&sort=dc.date.issued,DESC&size=20" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Notices avec résumé et texte intégral¶
# Notices avec résumé + au moins un fichier déposé
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=dc.description.abstract:* has_content_in_original_bundle:(true)\
&sort=dc.date.issued,DESC&page=0&size=100" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Moissonnage incrémental¶
Trois champs de date servent des usages différents :
| Champ | Ce qu'il trace | Cas d'usage |
|---|---|---|
lastModified |
Toute modification de la notice (métadonnées ou fichiers) | Détecter les mises à jour de notices existantes |
dc.date.accessioned |
Date d'entrée dans le dépôt | Filtrer par date de dépôt |
dc.date.created |
Date de création de la notice dans le système | Alternative à accessioned pour certains types |
# Notices modifiées un jour donné (mises à jour + nouveaux dépôts)
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=lastModified:[2026-01-09T00:00:00Z TO 2026-01-09T23:59:59Z]\
&sort=lastModified,DESC&page=0&size=100" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
# Notices déposées dans une plage de dates (nouveaux items uniquement)
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=dc.date.accessioned:[2025-01-01T00:00:00Z TO 2025-12-31T23:59:59Z]\
&sort=dc.date.accessioned,DESC&page=0&size=100" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
# Notices créées dans une plage de dates (alternative)
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=dc.date.created:[2025-01-01T00:00:00Z TO 2025-12-31T23:59:59Z]\
&sort=dc.date.accessioned,DESC&page=0&size=100" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Note
Pour un moissonnage incrémental complet (nouvelles notices + mises à jour), utilisez lastModified. Pour un chargement initial filtré par période de dépôt, utilisez dc.date.accessioned.
Accès aux fichiers (bundles et bitstreams)¶
Les réponses de l'API incluent des _links pointant vers les opérations disponibles, notamment bundles pour l'accès aux fichiers.
Types de bundles¶
Chaque notice peut contenir plusieurs bundles :
| Nom du bundle | Contenu |
|---|---|
ORIGINAL |
Fichiers déposés par l'auteur·e (visibles dans l'interface publique) |
THUMBNAIL |
Images de vignette dérivées des fichiers originaux |
TEXT |
Texte extrait par OCR depuis les PDF — utile pour l'analyse plein texte |
Récupérer les fichiers d'une notice¶
# Via embed (une seule requête)
curl "/core/items/{{uuid}}\
?embed=bundles%2Fbitstreams" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
# Via le lien bundles (deux requêtes)
curl "/core/items/{{uuid}}/bundles" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Télécharger un fichier spécifique¶
Suivez le lien content dans _links du bitstream :
curl -L "/core/bitstreams/{{uuid}}/content" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN" \
-o fichier.pdf
Note
Pour les fichiers en accès restreint, le token est obligatoire. Les requêtes anonymes sur des bitstreams restreints retournent HTTP 401.
Licences et data mining
Les métadonnées Infoscience sont publiées sous licence CC0 — elles peuvent être librement réutilisées sans restriction.
Les bitstreams (fichiers) obéissent à des règles différentes : vérifiez la licence de chaque fichier avant tout téléchargement à grande échelle. Les fichiers sous licence Creative Commons peuvent être utilisés pour le text and data mining ; les fichiers sous copyright peuvent être soumis à des restrictions. La licence est généralement stockée dans oaire.licenseCondition dans les métadonnées de la notice.
Client Python (dspace-rest-python)¶
La Bibliothèque EPFL maintient un client Python officiel pour l'API Infoscience :
github.com/epfllibrary/dspace-rest-python (branche : dev)
Installation¶
git clone https://github.com/epfllibrary/dspace-rest-python.git
cd dspace-rest-python
git checkout dev
pip install -r requirements.txt
Créez un fichier .env à partir du modèle fourni :
cp .sample.env .env
Ajoutez les variables suivantes dans .env :
DS_API_ENDPOINT=https://infoscience.epfl.ch/server/api
DS_API_TOKEN=votre_token
ENV=prod
Utilitaires d'accès aux métadonnées¶
Le DSpaceClient retourne des objets DSO dont les métadonnées sont accessibles via dso.metadata. Ces utilitaires sont équivalents aux versions dict brut de la section Structure des réponses :
def get_meta(dso, field, default=None):
"""Retourne la première valeur d'un champ de métadonnée depuis un objet DSO."""
entries = dso.metadata.get(field, [])
return entries[0].get('value', default) if entries else default
def get_meta_all(dso, field):
"""Retourne toutes les valeurs d'un champ, en ignorant les placeholders."""
return [
e.get('value') for e in dso.metadata.get(field, [])
if e.get('value') != '#PLACEHOLDER_PARENT_METADATA_VALUE#'
]
Utilisation de base¶
import pandas as pd
from dspace_rest_client.client import DSpaceClient
d = DSpaceClient()
authenticated = d.authenticate()
# Rechercher toutes les notices avec un DOI
query = "dc.identifier.doi:*"
dsos = d.search_objects(
query=query,
page=0,
size=100,
dso_type="item",
configuration="researchoutputs"
)
output = []
for dso in dsos:
output.append({
'dc.title': dso.metadata.get('dc.title', [{}])[0].get('value'),
'dc.type': dso.metadata.get('dc.type', [{}])[0].get('value'),
'dspace.entity.type': dso.metadata.get('dspace.entity.type', [{}])[0].get('value'),
'dc.relation.journal':dso.metadata.get('dc.relation.journal', [{}])[0].get('value'),
'dc.date.issued': dso.metadata.get('dc.date.issued', [{}])[0].get('value'),
'epfl.writtenAt': dso.metadata.get('epfl.writtenAt', [{}])[0].get('value'),
})
df = pd.DataFrame(output)
df.to_csv('output.csv', index=False)
Paginer tous les résultats¶
def search_all(client, query, configuration="researchoutputs", size=100):
"""Récupère tous les résultats d'une requête en gérant la pagination."""
page, results = 0, []
while True:
batch = client.search_objects(
query=query,
page=page,
size=size,
dso_type="item",
configuration=configuration
)
if not batch:
break
results.extend(batch)
if len(batch) < size:
break
page += 1
return results
all_items = search_all(d, "dc.description.sponsorship:LASUR")
print(f"{len(all_items)} notices trouvées")
Extraire des métadonnées par unité¶
Utilisez dc.description.sponsorship (index Solr : unitOrLab) pour regrouper les résultats par unité affiliée — et non cris.virtual.department, qui est un champ d'affichage virtuel à ne pas utiliser pour filtrer ou grouper :
output = []
for dso in dsos:
units = [x.get('value') for x in dso.metadata.get('dc.description.sponsorship', [])]
for unit in units:
output.append({
'dc.title': dso.metadata.get('dc.title', [{}])[0].get('value'),
'dc.date.issued': dso.metadata.get('dc.date.issued', [{}])[0].get('value'),
'dc.description.sponsorship': unit,
})
Moissonnage incrémental¶
from datetime import date, timedelta
yesterday = (date.today() - timedelta(days=1)).strftime('%Y-%m-%dT00:00:00Z')
today = date.today().strftime('%Y-%m-%dT23:59:59Z')
# Toutes les notices modifiées hier (mises à jour + nouveaux dépôts)
modified = search_all(
d,
query=f"lastModified:[{yesterday} TO {today}]",
configuration="researchoutputs"
)
print(f"{len(modified)} notices modifiées hier")
# Nouveaux dépôts uniquement (par date d'entrée)
new_items = search_all(
d,
query=f"dc.date.accessioned:[{yesterday} TO {today}]",
configuration="researchoutputs"
)
print(f"{len(new_items)} notices déposées hier")
Navigation par relations CRIS¶
DSpace-CRIS permet de traverser les relations entre entités — par exemple, récupérer toutes les publications liées à une unité, une personne ou une revue directement via leur UUID.
Étape 1 — Obtenir l'UUID d'une unité¶
Recherchez par code d'unité EPFL (epfl.unit.code) ou par numéro CF (epfl.orgUnit.cf) :
# Par acronyme
curl "/discover/search/objects\
?configuration=orgunit\
&query=(oairecerif.acronym:("12345"))\
&sort=score,DESC&page=0&size=1&projection=preventMetadataSecurity" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
# Par code d'unité
curl "/discover/search/objects\
?configuration=orgunit\
&query=(epfl.unit.code:("13020"))\
&sort=score,DESC&page=0&size=1&projection=preventMetadataSecurity" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
# Par numéro CF
curl "/discover/search/objects\
?configuration=orgunit\
&query=(epfl.orgUnit.cf:("12345"))\
&sort=score,DESC&page=0&size=1&projection=preventMetadataSecurity" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Le champ uuid se trouve dans l'indexableObject du premier résultat :
{
"_embedded": {
"searchResult": {
"_embedded": {
"objects": [{
"_embedded": {
"indexableObject": {
"id": "a1b2c3d4-e5f6-7890-abcd-000000000003",
"uuid": "a1b2c3d4-e5f6-7890-abcd-000000000003",
"name": "Laboratoire de modélisation numérique",
"handle": "20.500.14299/000003"
}
}
}]
}
}
}
}
Étape 2 — Obtenir les publications liées à l'unité¶
Utilisez configuration=RELATION.OrgUnit.publications avec scope égal à l'UUID de l'unité :
curl "/discover/search/objects\
?configuration=RELATION.OrgUnit.publications\
&scope=a1b2c3d4-e5f6-7890-abcd-000000000003\
&sort=dc.date.issued,DESC&page=0&size=10&projection=preventMetadataSecurity" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Cette approche est plus précise qu'une requête dc.description.sponsorship : elle suit le lien de relation CRIS explicite plutôt que de faire correspondre un champ de métadonnée textuel.
Étape 3 — Requêtes au niveau faculté¶
Pour récupérer toutes les publications affiliées à une faculté (qui regroupe plusieurs unités), utilisez l'index organizationHierarchy_authority avec l'UUID de la faculté :
# Toutes les publications affiliées à une faculté (ex. : SV — Sciences de la vie)
curl "/discover/search/objects\
?configuration=researchoutputs\
&query=organizationHierarchy_authority:a1b2c3d4-e5f6-7890-abcd-000000000005\
&sort=dc.date.issued,DESC&page=0&size=100" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
L'UUID de la faculté (a1b2c3d4-... dans cet exemple pour SV) s'obtient en cherchant l'entité faculté via configuration=orgunit, exactement comme pour une unité (étape 1). L'index organizationHierarchy_authority indexe l'ensemble de la chaîne organisationnelle — il correspond à toute unité dont la hiérarchie passe par l'UUID donné, donc un UUID de faculté couvre tous ses laboratoires constituants.
Note
organizationHierarchy_authority peut aussi être combiné avec embed=bundles/bitstreams pour récupérer les fichiers de toutes les notices correspondantes en une seule requête — utile pour les workflows de data mining.
Configurations RELATION disponibles¶
| Configuration | Entité (scope) | Retourne |
|---|---|---|
RELATION.OrgUnit.publications |
UUID d'une unité | Publications liées à l'unité |
RELATION.OrgUnit.persons |
UUID d'une unité | Personnes affiliées à l'unité |
RELATION.Person.researchoutputs |
UUID d'une personne | Productions scientifiques d'un·e chercheur·euse |
RELATION.Journal.publications |
UUID d'une revue | Publications parues dans la revue |
RELATION.Event.publications |
UUID d'un événement | Publications liées à une conférence |
Exemple Python — toutes les publications d'une unité par code¶
from dspace_rest_client.client import DSpaceClient
d = DSpaceClient()
d.authenticate()
# Étape 1 : résoudre le code d'unité en UUID
results = d.search_objects(
query='epfl.unit.code:("13020")',
page=0, size=1,
dso_type="item",
configuration="orgunit"
)
if not results:
raise ValueError("Unité introuvable")
unit_uuid = results[0].uuid
print(f"UUID de l'unité : {unit_uuid}")
# Étape 2 : récupérer les publications liées
publications, page = [], 0
while True:
batch = d.search_objects(
query=None, page=page, size=100,
dso_type="item",
configuration="RELATION.OrgUnit.publications",
scope=unit_uuid
)
if not batch:
break
publications.extend(batch)
if len(batch) < 100:
break
page += 1
print(f"{len(publications)} publications trouvées")
for pub in publications[:5]:
title = pub.metadata.get('dc.title', [{}])[0].get('value', '(sans titre)')
print(f" - {title}")
Exemple Python — toutes les productions d'un·e chercheur·euse¶
Le même schéma en deux étapes s'applique pour une personne : configuration=persons pour résoudre l'UUID, puis RELATION.Person.researchoutputs pour récupérer ses productions.
Étape 1 — Obtenir l'UUID de la personne
Recherchez par SCIPER, ORCID ou par nom :
# Par SCIPER
curl "/discover/search/objects\
?configuration=persons\
&query=cris.virtual.sciperId.:123456\
&sort=score,DESC&page=0&size=1&projection=preventMetadataSecurity" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
# Par ORCID
curl "/discover/search/objects\
?configuration=persons\
&query=orcid:0000-0001-2345-6789\
&sort=score,DESC&page=0&size=1&projection=preventMetadataSecurity" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
# Par nom
curl "/discover/search/objects\
?configuration=persons\
&query=dc.contributor.author:(Martin, Sophie)\
&sort=score,DESC&page=0&size=5&projection=preventMetadataSecurity" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
L'UUID se trouve dans _embedded.searchResult._embedded.objects[0]._embedded.indexableObject.uuid.
Étape 2 — Récupérer les productions liées
curl "/discover/search/objects\
?configuration=RELATION.Person.researchoutputs\
&scope={{person_uuid}}\
&sort=dc.date.issued,DESC&page=0&size=100&projection=preventMetadataSecurity" \
-H "Authorization: Bearer $INFOSCIENCE_TOKEN"
Python — workflow complet
from dspace_rest_client.client import DSpaceClient
d = DSpaceClient()
d.authenticate()
# Étape 1 : résoudre l'ORCID en UUID de personne
results = d.search_objects(
query='orcid:0000-0001-2345-6789',
page=0, size=1,
dso_type="item",
configuration="persons"
)
if not results:
raise ValueError("Personne introuvable")
person_uuid = results[0].uuid
print(f"UUID de la personne : {person_uuid}")
# Étape 2 : récupérer les productions liées
outputs, page = [], 0
while True:
batch = d.search_objects(
query=None, page=page, size=100,
dso_type="item",
configuration="RELATION.Person.researchoutputs",
scope=person_uuid
)
if not batch:
break
outputs.extend(batch)
if len(batch) < 100:
break
page += 1
print(f"{len(outputs)} productions trouvées")
Note
RELATION.Person.researchoutputs suit le lien d'autorship CRIS explicite. Cette approche est plus précise qu'une requête orcid: ou author_editor: sur researchoutputs, qui porte sur des métadonnées textuelles et peut inclure des variantes de nom ou des homonymes.
Index de recherche clés¶
| Index | Description | Exemple |
|---|---|---|
search.resourceid |
UUID de la notice | search.resourceid:a1b2c3d4-... |
cris.legacyId |
Ancien identifiant Infoscience | cris.legacyId:175201 |
dc.identifier.doi |
DOI (correspondance exacte) | dc.identifier.doi:(10.1000/example.2020.002) |
itemidentifier_keyword |
Tout identifiant, normalisé | itemidentifier_keyword:(10.1000/example.2020.002) |
author_editor |
Nom de l'auteur·e | author_editor:(Martin, Sophie) |
author_editor_authority |
UUID de l'auteur·e | author_editor_authority:a1b2c3d4-... |
dc.description.sponsorship |
Acronyme de l'unité EPFL affiliée | dc.description.sponsorship:LASUR |
unitOrLab |
Acronyme de l'unité EPFL affiliée | unitOrLab:CRPP |
unitOrLab_authority |
UUID de l'unité EPFL affiliée | unitOrLab_authority:a1b2c3d4-... |
types |
Type de document | types:(research article) |
title |
Titre | title:("deep learning") |
dateIssued.year |
Année de publication | dateIssued.year:[2020 TO 2024] |
dc.relation.issn |
ISSN de la revue | dc.relation.issn:"1069-4730" |
dc.relation.seriesissn |
ISSN de la série | dc.relation.seriesissn:"1069-4730" |
dc.relation.journal |
Titre de la revue | dc.relation.journal:"Nature" |
dc.relation.ispartofseries |
Titre de la série | dc.relation.ispartofseries:"Lecture Notes" |
dc.relation.conference |
Nom de la conférence | dc.relation.conference:(*SEFI*) |
oairecerif.acronym |
Acronyme de la conférence | oairecerif.acronym:(*SEFI*) |
orcid |
Identifiant ORCID | orcid:0000-0001-2345-6789 |
epfl.peerreviewed |
Évalué par les pairs | epfl.peerreviewed:REVIEWED |
fundername |
Nom du financeur | fundername:(FNS) |
lastModified |
Date de dernière modification | lastModified:[2026-01-09T00:00:00Z TO ...] |
has_content_in_original_bundle |
Fichiers déposés | has_content_in_original_bundle:(true) |
dc.description.abstract |
Présence d'un résumé | dc.description.abstract:* |
organizationHierarchy_authority |
UUID de hiérarchie faculté/unité | organizationHierarchy_authority:a1b2c3d4-... |
Référence complète : Profil d'application des métadonnées · Modèle de données
Bonnes pratiques¶
- Préférez
itemidentifier_keywordàdc.identifier.doi— cet index gère les variantes de casse et de format. - Nettoyez les titres avant de les requêter — supprimez les deux-points, points d'interrogation et diacritiques, ou encadrez la chaîne de guillemets doubles.
- Couvrez les deux champs ISSN —
dc.relation.issnetdc.relation.seriesissnpour une couverture complète. - Utilisez
embedavec des valeurs séparées par des virgules —?embed=bundles%2Fbitstreams,thumbnail,metricspour récupérer fichiers et métriques sans requêtes supplémentaires. - Limitez la taille de page à 100 — le maximum autorisé par l'API ; utilisez la pagination pour les grands ensembles.
- Utilisez
lastModifiedpour le moissonnage incrémental — capture à la fois les nouveaux dépôts et les mises à jour de notices existantes. - Utilisez
dc.description.sponsorship(indexunitOrLab) pour grouper par unité — ne pas utilisercris.virtual.department, qui est un champ d'affichage uniquement. - Exploitez
authoritydirectement comme UUID de scope — quand une entrée de métadonnée a uneauthoritynon nulle, elle peut être utilisée directement dans une requêteRELATION.*. - Ne divulguez jamais votre token — utilisez des variables d'environnement ou des fichiers
.env(ne jamais committer dans git). - Utilisez
organizationHierarchy_authoritypour les requêtes à l'échelle d'une faculté — couvre toutes les unités de la hiérarchie. - Les métadonnées sont CC0 — réutilisation libre. Pour les bitstreams, vérifiez
oaire.licenseConditionavant tout téléchargement en masse — les fichiers sous copyright peuvent restreindre le text and data mining. - Respectez la charge de la plateforme — planifiez les moissonnages en dehors des heures de pointe (soirées, week-ends).
