- 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
234 lines
7.7 KiB
Python
234 lines
7.7 KiB
Python
#!/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()) |