Initial: Script de recherche médiathèques Plaine Commune

- Recherche par mot-clé, auteur, titre, sujet
- Utilise Selenium avec Firefox headless
- Affiche titre, auteur, type, disponibilité et résumé
- Fonctionne sur le mesh KAWA
This commit is contained in:
2026-05-15 19:14:32 +02:00
commit 93aa4fa2ad
4 changed files with 327 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
__pycache__/
*.py[cod]
*$py.class
.venv/
venv/
*.log
.geckodriver/

84
README.md Normal file
View File

@@ -0,0 +1,84 @@
# Médiathèques Plaine Commune - Recherche de livres
Script Python pour rechercher des livres dans le catalogue des médiathèques de Plaine Commune (Saint-Denis, Aubervilliers, etc.).
## Installation
```bash
# Créer un venv
python3 -m venv ~/mediatheque-venv
source ~/mediatheque-venv/bin/activate
# Installer les dépendances
pip install selenium webdriver-manager
```
## Prérequis
- **Firefox** doit être installé (le script utilise GeckoDriver)
- Python 3.8+
## Utilisation
### Recherche simple
```bash
python mediatheque_search.py "Carl Jung"
```
### Recherche par auteur
```bash
python mediatheque_search.py --author "Jung"
```
### Recherche par titre
```bash
python mediatheque_search.py --title "rêves"
```
### Recherche par sujet
```bash
python mediatheque_search.py --subject "psychologie"
```
### Options
- `--max N` : Nombre maximum de résultats (défaut: 20)
- `--no-headless` : Afficher le navigateur Firefox (pour debug)
## Exemple de sortie
```
============================================================
RECHERCHE: Carl Jung
============================================================
📚 1. Sur l'interprétation des rêves
Auteur: / Carl Gustav Jung
Type: Livre
Disponibilité: Au moins un exemplaire disponible sur le réseau
Résumé: Ouvrage issu d'un séminaire dans lequel le psychiatre recense les grands systèmes...
📚 2. Jung
Auteur: / Carole, Sédillot
Type: Livre
Disponibilité: Au moins un exemplaire disponible sur le réseau
📄 Page 1 de 2
```
## Intégration KAWA
Ce script est conçu pour fonctionner sur les nœuds du mesh KAWA. Le venv peut être partagé via Syncthing ou installé localement sur chaque nœud.
### Dépendances
- selenium >= 4.0
- webdriver-manager >= 4.0
## Licence
MIT

234
mediatheque_search.py Normal file
View File

@@ -0,0 +1,234 @@
#!/usr/bin/env python3
"""
Médiathèques Plaine Commune - Recherche de livres
Script de recherche dans le catalogue des médiathèques de Plaine Commune
Utilise Selenium pour interagir avec l'interface web (JavaScript requis).
Usage:
python mediatheque_search.py "Carl Jung"
python mediatheque_search.py --author "Jung"
python mediatheque_search.py --title "rêves"
python mediatheque_search.py --subject "psychologie"
"""
import argparse
import sys
import time
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from webdriver_manager.firefox import GeckoDriverManager
class MediathequeSearch:
"""Recherche dans le catalogue des médiathèques de Plaine Commune"""
BASE_URL = "https://mediatheques-plainecommune.fr/iguana/www.main.cls"
SEARCH_URL = "https://mediatheques-plainecommune.fr/iguana/www.main.cls?surl=search&searchProfile=Recherche"
def __init__(self, headless=True):
"""Initialise le driver Selenium"""
options = Options()
if headless:
options.add_argument("--headless")
options.add_argument("--width=1920")
options.add_argument("--height=1080")
service = Service(GeckoDriverManager().install())
self.driver = webdriver.Firefox(service=service, options=options)
self.driver.implicitly_wait(5)
def _reject_cookies(self):
"""Refuse la bannière cookies RGPD"""
try:
refuse_btn = WebDriverWait(self.driver, 3).until(
EC.element_to_be_clickable((By.ID, "gdpr-cookie-refuse"))
)
refuse_btn.click()
time.sleep(0.5)
return True
except:
return False
def _load_search_page(self):
"""Charge la page de recherche"""
self.driver.get(self.BASE_URL + "?surl=axiell")
time.sleep(2)
self._reject_cookies()
self.driver.get(self.SEARCH_URL)
time.sleep(3)
self._reject_cookies()
def search(self, query, max_results=20):
"""
Effectue une recherche dans le catalogue
Args:
query: Terme de recherche
max_results: Nombre maximum de résultats à retourner
Returns:
Liste de dictionnaires contenant les résultats
"""
try:
self._load_search_page()
# Entrer la requête
search_input = self.driver.find_element(By.CSS_SELECTOR, "input[id*='inputWords']")
search_input.clear()
search_input.send_keys(query)
search_input.send_keys(Keys.RETURN)
# Attendre les résultats
time.sleep(5)
self._reject_cookies()
# Extraire les résultats
results = []
items = self.driver.find_elements(By.CSS_SELECTOR, "li.listItem")
for item in items[:max_results]:
try:
result = self._extract_result(item)
if result:
results.append(result)
except Exception:
continue
# Infos de pagination
try:
page_info = self.driver.find_element(By.CSS_SELECTOR, ".pageInfo").text
except:
page_info = None
return {
"query": query,
"results": results,
"page_info": page_info,
"total_found": len(results)
}
finally:
self.driver.quit()
def _extract_result(self, item):
"""Extrait les informations d'un résultat"""
result = {}
# Titre
try:
result["title"] = item.find_element(By.CSS_SELECTOR, ".briefMainTitle").text.strip()
except:
result["title"] = "N/A"
# Auteur
try:
result["author"] = item.find_element(By.CSS_SELECTOR, ".BriefAuthorpco").text.strip()
except:
result["author"] = "N/A"
# Type de document
try:
result["type"] = item.find_element(By.CSS_SELECTOR, ".briefTypeDoc").text.strip()
except:
result["type"] = "N/A"
# Disponibilité
try:
result["availability"] = item.find_element(By.CSS_SELECTOR, ".briefAvailability span").text.strip()
except:
result["availability"] = "N/A"
# Résumé
try:
summary = item.find_element(By.CSS_SELECTOR, ".briefResume").text.strip()
result["summary"] = summary[:200] + "..." if len(summary) > 200 else summary
except:
result["summary"] = ""
return result
def search_author(self, author, max_results=20):
"""Recherche par auteur"""
return self.search(f"auteur: {author}", max_results)
def search_title(self, title, max_results=20):
"""Recherche par titre"""
return self.search(f"titre: {title}", max_results)
def search_subject(self, subject, max_results=20):
"""Recherche par sujet"""
return self.search(f"sujet: {subject}", max_results)
def print_results(data):
"""Affiche les résultats formatés"""
print(f"\n{'='*60}")
print(f"RECHERCHE: {data['query']}")
print(f"{'='*60}\n")
for i, result in enumerate(data["results"], 1):
print(f"📚 {i}. {result['title']}")
print(f" Auteur: {result['author']}")
print(f" Type: {result['type']}")
print(f" Disponibilité: {result['availability']}")
if result['summary']:
print(f" Résumé: {result['summary']}")
print()
if data['page_info']:
print(f"📄 {data['page_info']}")
def main():
parser = argparse.ArgumentParser(
description="Recherche dans le catalogue des médiathèques de Plaine Commune",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Exemples:
%(prog)s "Carl Jung"
%(prog)s --author "Jung"
%(prog)s --title "rêves"
%(prog)s --subject "psychologie" --max 50
"""
)
parser.add_argument("query", nargs="?", help="Terme de recherche")
parser.add_argument("--author", "-a", help="Rechercher par auteur")
parser.add_argument("--title", "-t", help="Rechercher par titre")
parser.add_argument("--subject", "-s", help="Rechercher par sujet")
parser.add_argument("--max", "-m", type=int, default=20, help="Nombre max de résultats (défaut: 20)")
parser.add_argument("--no-headless", action="store_true", help="Afficher le navigateur")
args = parser.parse_args()
# Déterminer le type de recherche
if args.author:
searcher = MediathequeSearch(headless=not args.no_headless)
results = searcher.search_author(args.author, args.max)
elif args.title:
searcher = MediathequeSearch(headless=not args.no_headless)
results = searcher.search_title(args.title, args.max)
elif args.subject:
searcher = MediathequeSearch(headless=not args.no_headless)
results = searcher.search_subject(args.subject, args.max)
elif args.query:
searcher = MediathequeSearch(headless=not args.no_headless)
results = searcher.search(args.query, args.max)
else:
parser.print_help()
sys.exit(1)
print_results(results)
return 0
if __name__ == "__main__":
sys.exit(main())

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
selenium>=4.0.0
webdriver-manager>=4.0.0