avant codex

This commit is contained in:
2026-02-22 15:05:40 +01:00
parent fed449c784
commit 20af00d653
291 changed files with 51868 additions and 424 deletions
@@ -0,0 +1,266 @@
Voici des éléments structurés essentiels pour écrire un tutoriel de scraping Python à partir du site saint-dicton.com, en particulier pour la page dun jour précis (par exemple https://www.saint-dicton.com/0222.html) :
📌 Structure observée du site Saint-Dicton
Un exemple de page date contient :
Liste des saints fêtés ce jour
Exemples : “St-Sulpice Sévère”, “St-Valère”, etc.
Ce bloc est présenté sous forme de texte HTML listé en paragraphes ou sections.
Phase de la lune
Peut être présente mais nest pas structurée pour le scraping des saints.
Dicton du jour
Un bloc intitulé “## Dicton du jour” suivi dun ou plusieurs dictons.
Prénoms à fêter
Peut contenir une liste ou être vide.
🧠 Analyse de la pagination annuelle
Le site propose également une navigation jour par jour :
LURL pour une date donnée prend la forme https://www.saint-dicton.com/MMDD.html où MM est le mois sur deux chiffres et DD le jour sur deux chiffres (par exemple 0222 pour 22 février).
Il existe une page par mois avec tous les saints par ordre alphabétique (ex. SaintsA.html).
Ceci permet de parcourir toutes les dates de lannée systématiquement.
📘 Sources externes utiles
Pour enrichir ton scraping :
Nominis propose une base de plusieurs milliers de saints et fêtes liturgiques (utile pour comparer).
Des sites comme Éphéméride ou Wiki des proverbes donnent des dictons et proverbes météo/agricoles si besoin de compléter la base. (source non spécifique, généraliste).
🧪 Extrait de page (exemple réel)
Dans la page 0222.html, on observe :
## Dicton du jour
S'il gèle à la Saint-Sulpice,
Le printemps sera propice
Ici :
“Dicton du jour” est un titre h2
Le dicton est une ligne ou plusieurs lignes de texte juste après ce titre.
📌 Tutoriel ciblé pour écrire scrap_saint.md
Dans ce fichier .md, tu pourras expliquer :
Objectif du scraping
Extraire pour chaque date :
date (MM-DD)
nom du saint (ou saints)
dictons du jour
éventuellement prénoms associés
Analyse HTML du site
Les pages individuelles des dates
Les sections à scraper
Comment repérer les titres HTML des dictons
Étapes du script Python
itérer sur toutes les dates de lannée
fetch des pages MMDD.html
parser lHTML avec BeautifulSoup
extraire listes de saints
extraire bloc dictons
stocker dans une structure CSV ou JSON
Exemple de structure de sortie
{
"date": "0222",
"saints": ["St-Sulpice Sévère", "St-Valère"],
"dictons": ["S'il gèle à la Saint-Sulpice, …"]
}
Conseils pour éviter les pièges
certaines dates ont plusieurs dictons
structure HTML parfois variable
gérer les accents et encodage UTF-8
Si tu veux, je peux te générer directement le contenu complet de ce fichier scrap_saint.md avec un script Python détaillé (requests + BeautifulSoup) prêt à copier.
Dis-moi si tu veux aussi inclure le scraping du contenu de la page alphabétique SaintsA.html pour récupérer la liste complète des noms de saints.
genere scrap.md
Voici un tutoriel complet scrap_saint.md pour taider à scraper les pages de saints et leurs dictons à partir du site https://www.saint-dicton.com/0222.html
et des pages annuelles correspondantes. Ce fichier explique lanalyse de la structure, un exemple concret de script Python, et des conseils pour boucler sur toute lannée.
# scrap_saint.md
## Objectif
Ce tutoriel explique comment *scraper automatiquement* les informations suivantes pour chaque jour de lannée à partir du site **saint-dicton.com** :
- la **date** format MMDD (ex : *0222*)
- le(s) **nom(s) du saint** fêté(s)
- le ou les **dictons associés au jour**
Lobjectif est de générer une base de données annuelle (CSV/JSON) des saints et des dictons, utilisable ensuite dans un projet Python par exemple.
---
## Analyse de la structure du site
Le site propose une **page par date** selon le format dURL :
https://www.saint-dicton.com/MMDD.html
Exemples :
- https://www.saint-dicton.com/0222.html → *Saint-Sulpice Sévère* + dicton du jour :contentReference[oaicite:0]{index=0}
- https://www.saint-dicton.com/0208.html → *dicton de la Saint-Jean* :contentReference[oaicite:1]{index=1}
Chaque page contient typiquement :
1. **Fêtes du jour**
Une section listant un ou plusieurs saints (ex : *St-Sulpice Sévère*, *St-Valère*) :contentReference[oaicite:2]{index=2}
2. **Bloc Dicton du jour**
Titre suivi du texte du dicton (souvent une ou plusieurs lignes) :contentReference[oaicite:3]{index=3}
3. **(Optionnel) Prénoms à fêter**
Liste de prénoms associés à la date :contentReference[oaicite:4]{index=4}
> Le site propose aussi une page daccueil et des pages alphabétiques, mais pour extraire une *base annuelle*, le patron `MMDD.html` est utile pour itérer sur chaque jour de lannée. :contentReference[oaicite:5]{index=5}
---
## Pré-requis
Installer les dépendances Python nécessaires :
```bash
pip install requests beautifulsoup4
Optionnel mais recommandé :
pip install lxml
Exemple de script Python (scraper)
Ce script parcourt toutes les dates de lannée, récupère chaque page, analyse lHTML et enregistre les données dans un fichier CSV.
import requests
from bs4 import BeautifulSoup
import csv
import time
BASE_URL = "https://www.saint-dicton.com/{:02d}{:02d}.html"
def scrape_day(month: int, day: int):
url = BASE_URL.format(month, day)
resp = requests.get(url)
if resp.status_code != 200:
return None
soup = BeautifulSoup(resp.text, "lxml")
# Extraire les noms de saints
saints = []
h1 = soup.find("h1")
if h1:
# souvent la liste des saints est en texte dans la partie principale
for line in h1.text.split("\n"):
if line.strip():
saints.append(line.strip())
# trouver le div ou section contenant "Dicton du jour"
dicton_data = ""
target = soup.find(text="Dicton du jour")
if target:
parent = target.find_parent()
if parent:
# juste après ce bloc, prendre les lignes de dicton
for p in parent.find_next_siblings():
text = p.get_text(strip=True)
if text:
dicton_data += text + " "
return {
"month": month,
"day": day,
"saints": saints,
"dicton": dicton_data.strip()
}
# Boucle sur toute l'année
with open("saints_dictons.csv", "w", newline="", encoding="utf-8") as csvfile:
fieldnames = ["month", "day", "saints", "dicton"]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter=";")
writer.writeheader()
for m in range(1, 13):
for d in range(1, 32):
data = scrape_day(m, d)
if data:
writer.writerow({
"month": data["month"],
"day": data["day"],
"saints": "|".join(data["saints"]),
"dicton": data["dicton"]
})
print(f"Scraped {m:02d}-{d:02d}")
time.sleep(1) # pause pour éviter surcharge
Bonnes pratiques & conseils
💡 Respect et performance
Respecte les règles dutilisation du site. Le scraping est légitime sur du contenu public, mais il faut éviter une surcharge du serveur.
Met une pause (time.sleep(1)) entre les requêtes pour ne pas déclencher de blocage ou DDoS.
🧠 Inspection du HTML
Avant dextraire le texte, inspecte la page dans un navigateur (clic droit → Inspecter) pour confirmer la structure des balises.
Le site nutilise pas dAPI JSON, donc BeautifulSoup est adapté.
⚠️ Variations possibles
La structure peut varier légèrement dune date à lautre (par ex., plusieurs saints listés en texte, parfois des paragraphes additionnels).
Adaptation des sélecteurs (find, find_all, classes ou IDs) peut être nécessaire selon les pages.
Sorties possibles
À partir du script ci-dessus, tu peux générer :
un CSV (comme ci-dessous)
un JSON structuré
un SQLite pour intégration dans ton application jardinage
Exemple denregistrement CSV :
month;day;saints;dicton
2;22;St-Sulpice Sévère|St-Valère;"S'il gèle à la Saint-Sulpice, Le printemps sera propice"
Conclusion
En parcourant les URL de MMDD.html et en analysant lHTML, tu peux extraire automatiquement pour toute lannée la liste des saints et leurs dictons associés.
Ce fichier scrap_saint.md te fournit une base de départ claire avec un script Python prêt à lemploi.
@@ -0,0 +1,191 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import re
from datetime import date
from pathlib import Path
DATE_HEADING_RE = re.compile(
r"^\s{0,3}(?:#{1,6}\s*)?((?:0[1-9]|1[0-2])[-/](?:0[1-9]|[12][0-9]|3[01])|(?:0[1-9]|[12][0-9]|3[01])[-/](?:0[1-9]|1[0-2])|(?:0[1-9]|1[0-2])(?:0[1-9]|[12][0-9]|3[01]))\s*$"
)
INLINE_DATE_DIC_RE = re.compile(
r"^\s*((?:0[1-9]|1[0-2])[-/](?:0[1-9]|[12][0-9]|3[01])|(?:0[1-9]|[12][0-9]|3[01])[-/](?:0[1-9]|1[0-2])|(?:0[1-9]|1[0-2])(?:0[1-9]|[12][0-9]|3[01]))\s*[:\-]\s*(.+)$"
)
def _normalize_mmdd(token: str) -> str | None:
token = token.strip()
if re.fullmatch(r"(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])", token):
month = token[:2]
day = token[2:]
elif re.fullmatch(r"(0[1-9]|1[0-2])[-/](0[1-9]|[12][0-9]|3[01])", token):
month, day = re.split(r"[-/]", token)
elif re.fullmatch(r"(0[1-9]|[12][0-9]|3[01])[-/](0[1-9]|1[0-2])", token):
day, month = re.split(r"[-/]", token)
else:
return None
return f"{month}-{day}"
def _unique(values: list[str]) -> list[str]:
out: list[str] = []
seen: set[str] = set()
for value in values:
v = value.strip()
if not v:
continue
if v not in seen:
seen.add(v)
out.append(v)
return out
def _split_saints(text: str) -> list[str]:
# Normalize separators and preserve saint labels.
cleaned = text.strip().strip(".")
cleaned = re.sub(r"^(saints?\s*[:\-]\s*)", "", cleaned, flags=re.I).strip()
if not cleaned:
return []
parts = re.split(r"\s*(?:,|;|\||\set\s)\s*", cleaned, flags=re.I)
return _unique(parts)
def _split_dictons(text: str) -> list[str]:
cleaned = text.strip()
cleaned = re.sub(r"^(dictons?\s*[:\-]\s*)", "", cleaned, flags=re.I).strip()
if not cleaned:
return []
# Keep sentences readable; split on explicit separators first.
if "|" in cleaned or ";" in cleaned:
parts = re.split(r"\s*(?:\||;)\s*", cleaned)
else:
parts = [cleaned]
return _unique(parts)
def load_saints(path: Path) -> dict[str, list[str]]:
if not path.exists():
return {}
raw = json.loads(path.read_text(encoding="utf-8"))
out: dict[str, list[str]] = {}
for mmdd, saint_value in raw.items():
key = _normalize_mmdd(mmdd)
if key is None:
continue
if isinstance(saint_value, list):
saints = [str(x).strip() for x in saint_value]
else:
saints = _split_saints(str(saint_value))
out[key] = _unique(saints)
return out
def parse_dictons_text(path: Path) -> dict[str, list[str]]:
if not path.exists():
return {}
lines = path.read_text(encoding="utf-8").splitlines()
out: dict[str, list[str]] = {}
current_date: str | None = None
for raw in lines:
line = raw.strip()
if not line:
continue
# Date heading block
m_head = DATE_HEADING_RE.match(line)
if m_head:
current_date = _normalize_mmdd(m_head.group(1))
if current_date and current_date not in out:
out[current_date] = []
continue
# Inline date + dicton
m_inline = INLINE_DATE_DIC_RE.match(line)
if m_inline:
mmdd = _normalize_mmdd(m_inline.group(1))
if mmdd:
out.setdefault(mmdd, []).extend(_split_dictons(m_inline.group(2)))
current_date = mmdd
continue
# Bullets or plain lines inside current date block
if current_date:
line = re.sub(r"^\s*[-*]\s*", "", line).strip()
if not line:
continue
if re.match(r"^saints?\s*[:\-]", line, flags=re.I):
# Saints line is ignored here; saints come from saints_json.
continue
out.setdefault(current_date, []).extend(_split_dictons(line))
return {k: _unique(v) for k, v in out.items()}
def _as_iso(year: int, mmdd: str) -> str:
month, day = mmdd.split("-")
return date(year, int(month), int(day)).isoformat()
def build_output(
saints_by_date: dict[str, list[str]],
dictons_by_date: dict[str, list[str]],
year: int | None,
) -> list[dict]:
all_dates = sorted(set(saints_by_date) | set(dictons_by_date))
rows: list[dict] = []
for mmdd in all_dates:
row = {
"date": _as_iso(year, mmdd) if year else mmdd,
"saints": saints_by_date.get(mmdd, []),
"dictons": dictons_by_date.get(mmdd, []),
}
rows.append(row)
return rows
def main() -> int:
parser = argparse.ArgumentParser(
description="Génère un JSON saints+dictons: date, saints[], dictons[]"
)
parser.add_argument(
"--saints-json",
default="calendrier_lunaire/saints_dictons/saints_france.json",
help="Fichier JSON des saints (clé MM-DD)",
)
parser.add_argument(
"--dictons-file",
required=True,
help="Fichier texte/markdown des dictons (avec dates MM-DD, DD/MM ou MMDD)",
)
parser.add_argument(
"--output",
default="calendrier_lunaire/saints_dictons/saints_dictons.json",
help="Fichier JSON de sortie",
)
parser.add_argument(
"--year",
type=int,
help="Optionnel: convertit MM-DD en YYYY-MM-DD",
)
args = parser.parse_args()
saints_path = Path(args.saints_json)
dictons_path = Path(args.dictons_file)
output_path = Path(args.output)
saints_by_date = load_saints(saints_path)
dictons_by_date = parse_dictons_text(dictons_path)
rows = build_output(saints_by_date, dictons_by_date, args.year)
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"JSON généré: {output_path} ({len(rows)} dates)")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,171 @@
#!/usr/bin/env python3
import argparse
import json
import re
import time
from datetime import datetime
from datetime import date, timedelta
from html import unescape
from urllib.request import Request, urlopen
MONTHS_FR = {
1: "janvier", 2: "février", 3: "mars", 4: "avril", 5: "mai", 6: "juin",
7: "juillet", 8: "août", 9: "septembre", 10: "octobre", 11: "novembre", 12: "décembre",
}
def fetch_html(url: str) -> str:
req = Request(url, headers={"User-Agent": "Mozilla/5.0 (X11; Linux x86_64)"})
with urlopen(req, timeout=25) as resp:
raw = resp.read()
charset = (resp.headers.get_content_charset() or "utf-8").lower()
try:
return raw.decode(charset, errors="replace")
except Exception:
return raw.decode("utf-8", errors="replace")
def clean_html_text(s: str) -> str:
s = re.sub(r"<br\s*/?>", " ", s, flags=re.I)
s = re.sub(r"<[^>]+>", "", s)
s = unescape(s)
s = s.replace("\xa0", " ")
return re.sub(r"\s+", " ", s).strip()
def parse_saints(html: str) -> list[str]:
rows = re.findall(r'<p[^>]*class="sd-name"[^>]*>(.*?)</p>', html, flags=re.I | re.S)
out, seen = [], set()
for row in rows:
txt = clean_html_text(row)
if txt and txt not in seen:
out.append(txt)
seen.add(txt)
return out
def parse_dictons(html: str) -> list[str]:
rows = re.findall(r'<p[^>]*class="dict"[^>]*>(.*?)</p>', html, flags=re.I | re.S)
out = []
for row in rows:
txt = clean_html_text(row)
if txt:
out.append(txt)
return out
def parse_prenoms(html: str) -> list[str]:
block = re.search(
r'<h2[^>]*>[^<]*Pr[^<]*noms[^<]*f[^<]*ter[^<]*</h2>.*?<ul[^>]*>(.*?)</ul>',
html,
flags=re.I | re.S,
)
target = block.group(1) if block else ""
rows = re.findall(r'<li[^>]*>(.*?)</li>', target, flags=re.I | re.S)
out, seen = [], set()
for row in rows:
txt = clean_html_text(row)
if txt and txt not in seen:
out.append(txt)
seen.add(txt)
return out
def iter_mmdd_full_year(year: int):
d = date(year, 1, 1)
end = date(year, 12, 31)
while d <= end:
yield d.strftime("%m%d"), d
d += timedelta(days=1)
# assure 29 février même année non bissextile
if year % 4 != 0 or (year % 100 == 0 and year % 400 != 0):
yield "0229", None
def scrape_day(base_url: str, mmdd: str, d: date | None) -> dict:
url = f"{base_url.rstrip('/')}/{mmdd}.html"
html = fetch_html(url)
if d:
label = f"{d.day:02d} {MONTHS_FR[d.month]}"
iso = d.isoformat()
else:
label = "29 février"
iso = None
return {
"date": label,
"date_iso": iso,
"mmdd": mmdd,
"saints": parse_saints(html),
"dictons": parse_dictons(html),
"prenoms_a_feter": parse_prenoms(html),
"source_url": url,
}
def _ts() -> str:
return datetime.now().strftime("%H:%M:%S")
def _log(message: str, enabled: bool) -> None:
if enabled:
print(f"[{_ts()}] {message}", flush=True)
def main() -> int:
ap = argparse.ArgumentParser(description="Scrape saints/dictons pour toute une année (inclut 29 février)")
ap.add_argument("--year", type=int, default=date.today().year)
ap.add_argument("--base", default="https://www.saint-dicton.com")
ap.add_argument("--sleep-ms", type=int, default=150, help="Pause entre requêtes")
ap.add_argument("--limit", type=int, default=0, help="Limiter le nb de jours (test rapide)")
ap.add_argument("--out", default="", help="Fichier de sortie JSON (sinon stdout)")
ap.add_argument("--log-every", type=int, default=10, help="Affiche un log de progression tous les N jours")
ap.add_argument("--quiet", action="store_true", help="Réduit les logs")
args = ap.parse_args()
results = []
count = 0
verbose = not args.quiet
log_every = max(1, args.log_every)
_log(f"Démarrage scrape année={args.year}, base={args.base}", verbose)
for mmdd, d in iter_mmdd_full_year(args.year):
url = f"{args.base.rstrip('/')}/{mmdd}.html"
_log(f"[{count + 1}] fetch {mmdd} -> {url}", verbose)
try:
results.append(scrape_day(args.base, mmdd, d))
_log(f"[{count + 1}] ok {mmdd}", verbose and ((count + 1) % log_every == 0 or count == 0))
except Exception as e:
results.append({
"mmdd": mmdd,
"date_iso": d.isoformat() if d else None,
"error": str(e),
"source_url": url,
})
_log(f"[{count + 1}] erreur {mmdd}: {e}", True)
count += 1
if args.limit and count >= args.limit:
_log(f"Arrêt par --limit={args.limit}", verbose)
break
if args.sleep_ms > 0:
time.sleep(args.sleep_ms / 1000)
payload = {
"year": args.year,
"count": len(results),
"includes_feb29": any(r.get("mmdd") == "0229" for r in results),
"data": results,
}
txt = json.dumps(payload, ensure_ascii=False, indent=2)
if args.out:
with open(args.out, "w", encoding="utf-8") as f:
f.write(txt)
_log(f"Fichier écrit: {args.out}", True)
else:
print(txt)
_log(f"Terminé: {len(results)} jours", verbose)
return 0
if __name__ == "__main__":
raise SystemExit(main())
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,10 @@
{
"04-23": "Saint Georges",
"04-25": "Saint Marc",
"05-11": "Saint Mamert",
"05-12": "Saint Pancrace",
"05-13": "Saint Servais",
"05-14": "Saint Boniface",
"05-19": "Saint Yves",
"05-25": "Saint Urbain"
}