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:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
.venv/
|
||||
venv/
|
||||
*.log
|
||||
.geckodriver/
|
||||
84
README.md
Normal file
84
README.md
Normal 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
234
mediatheque_search.py
Normal 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
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
selenium>=4.0.0
|
||||
webdriver-manager>=4.0.0
|
||||
Reference in New Issue
Block a user