Skip to content

Commit abe6be6

Browse files
Edouard SilvestreEdouard Silvestre
authored andcommitted
[ADD] scrapping github et hugging_face
1 parent 9fd973a commit abe6be6

2 files changed

Lines changed: 254 additions & 76 deletions

File tree

scrape_github.py

Lines changed: 184 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,214 @@
1+
#!/usr/bin/env python3
2+
"""
3+
github_ai_theme_watcher.py
4+
5+
Veille thématique GitHub orientée IA — recherche de projets par thème (ex: "LLM", "diffusion", "RAG", ...)
6+
Stocke des résultats synthétiques dans une base SQLite pour consommation par un dashboard / newsletter / alertes.
7+
8+
Usage:
9+
python github_ai_theme_watcher.py # tourne en continu (sleep INTERVAL)
10+
python github_ai_theme_watcher.py --once # exécute une seule itération (utile pour cron/tests)
11+
12+
Configure via variables en tête du fichier ou via variables d'environnement:
13+
- GITHUB_TOKEN: token (optionnel mais recommandé)
14+
"""
15+
116
import os
17+
import sys
218
import time
319
import sqlite3
420
import requests
21+
import argparse
522
from datetime import datetime
23+
from typing import List
24+
625

7-
REPOS = [
8-
"huggingface/transformers",
9-
"openai/openai-python",
10-
"pytorch/pytorch",
11-
"tensorflow/tensorflow",
12-
"langchain/langchain",
13-
"microsoft/ML-For-Beginners",
14-
"microsoft/AI-For-Beginners",
15-
"karpathy/nn-zero-to-hero",
16-
"Significant-Gravitas/AutoGPT",
17-
"stabilityai/stablediffusion",
26+
THEMES = [
27+
"large-language-model",
28+
"llm",
29+
"transformer",
30+
"text-generation",
31+
"retrieval-augmented-generation",
32+
"rag",
33+
"agents",
34+
"chatbot",
35+
"fine-tuning",
36+
"quantization",
37+
"lora",
38+
"peft",
39+
"diffusion",
40+
"stable-diffusion",
41+
"image-generation",
42+
"multimodal",
43+
"speech-to-text",
44+
"speech-synthesis",
45+
"audio",
46+
"reinforcement-learning",
47+
"computer-vision",
1848
]
19-
INTERVAL = 300
20-
DB_FILE = os.path.join(os.path.dirname(__file__), "github_releases.db")
49+
50+
RESULTS_PER_THEME = 20
51+
52+
INTERVAL = int(os.getenv("GITHUB_WATCHER_INTERVAL", 21600))
53+
54+
DB_FILE = os.path.join(os.path.dirname(__file__), "github_ai_trending.db")
55+
2156
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
2257

58+
HEADERS = {
59+
"Accept": "application/vnd.github+json",
60+
"User-Agent": "github-ai-theme-watcher/1.0"
61+
}
62+
if GITHUB_TOKEN:
63+
HEADERS["Authorization"] = f"Bearer {GITHUB_TOKEN}"
64+
2365
conn = sqlite3.connect(DB_FILE)
2466
cur = conn.cursor()
67+
2568
cur.execute("""
26-
CREATE TABLE IF NOT EXISTS releases (
27-
id TEXT PRIMARY KEY,
28-
repo TEXT,
29-
tag TEXT,
69+
CREATE TABLE IF NOT EXISTS trending_ai_projects (
70+
full_name TEXT PRIMARY KEY,
3071
name TEXT,
31-
published TIMESTAMP,
32-
body TEXT,
33-
html_url TEXT
72+
description TEXT,
73+
stars INTEGER,
74+
language TEXT,
75+
theme TEXT,
76+
updated_at TEXT,
77+
html_url TEXT,
78+
last_seen TIMESTAMP
3479
)
3580
""")
3681
conn.commit()
3782

38-
headers = {"Accept": "application/vnd.github+json"}
39-
if GITHUB_TOKEN:
40-
headers["Authorization"] = f"Bearer {GITHUB_TOKEN}"
83+
cur.execute("""
84+
CREATE TABLE IF NOT EXISTS project_history (
85+
id INTEGER PRIMARY KEY AUTOINCREMENT,
86+
full_name TEXT,
87+
stars INTEGER,
88+
updated_at TEXT,
89+
captured_at TIMESTAMP
90+
)
91+
""")
92+
conn.commit()
4193

42-
def fetch_releases(repo):
43-
url = f"https://api.github.com/repos/{repo}/releases"
44-
r = requests.get(url, headers=headers)
45-
r.raise_for_status()
46-
return r.json()
4794

48-
def save_release(repo, release):
95+
def search_github_repos(query: str, per_page: int = RESULTS_PER_THEME) -> List[dict]:
96+
"""
97+
Recherche des repositories GitHub via l'API Search.
98+
`query` doit être la Q de recherche (ex: "transformer language:python").
99+
"""
100+
url = "https://api.github.com/search/repositories"
101+
params = {
102+
"q": query,
103+
"sort": "stars",
104+
"order": "desc",
105+
"per_page": per_page
106+
}
107+
resp = requests.get(url, headers=HEADERS, params=params, timeout=20)
108+
if resp.status_code == 403:
109+
retry_after = resp.headers.get("Retry-After")
110+
raise RateLimitError(retry_after=int(retry_after) if retry_after and retry_after.isdigit() else None)
111+
resp.raise_for_status()
112+
data = resp.json()
113+
return data.get("items", [])
114+
115+
def sanitize_text(s):
116+
if s is None:
117+
return ""
118+
return str(s)
119+
120+
def save_project(repo: dict, theme: str):
121+
"""INSERT OR REPLACE de l'enregistrement principal + ajout historique."""
122+
full_name = repo.get("full_name")
123+
name = repo.get("name")
124+
desc = sanitize_text(repo.get("description"))
125+
stars = repo.get("stargazers_count", 0)
126+
language = repo.get("language") or ""
127+
updated_at = repo.get("updated_at") or repo.get("pushed_at") or datetime.utcnow().isoformat()
128+
html_url = repo.get("html_url") or f"https://github.com/{full_name}"
129+
now = datetime.utcnow().isoformat()
130+
131+
cur.execute("""
132+
INSERT OR REPLACE INTO trending_ai_projects
133+
(full_name, name, description, stars, language, theme, updated_at, html_url, last_seen)
134+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
135+
""", (full_name, name, desc, stars, language, theme, updated_at, html_url, now))
136+
conn.commit()
137+
49138
cur.execute("""
50-
INSERT OR IGNORE INTO releases (id, repo, tag, name, published, body, html_url)
51-
VALUES (?, ?, ?, ?, ?, ?, ?)
52-
""", (
53-
release["id"],
54-
repo,
55-
release.get("tag_name", ""),
56-
release.get("name", ""),
57-
release.get("published_at", datetime.utcnow().isoformat()),
58-
release.get("body", ""),
59-
release.get("html_url", "")
60-
))
139+
INSERT INTO project_history (full_name, stars, updated_at, captured_at)
140+
VALUES (?, ?, ?, ?)
141+
""", (full_name, stars, updated_at, now))
61142
conn.commit()
62143

63-
def load_seen_ids():
64-
cur.execute("SELECT id FROM releases")
65-
return set(row[0] for row in cur.fetchall())
66144

67-
def main():
68-
print("Initialisation GitHub Releases...")
69-
seen_ids = load_seen_ids()
70-
print(f"{len(seen_ids)} releases déjà enregistrées.")
145+
class RateLimitError(Exception):
146+
def __init__(self, retry_after=None):
147+
self.retry_after = retry_after
148+
super().__init__("Rate limit hit on GitHub API. Retry after: {}".format(retry_after))
149+
150+
151+
def build_query_for_theme(theme: str) -> str:
152+
theme_token = theme.replace(" ", "+")
153+
q = f"{theme_token} in:name,description,readme stars:>50"
154+
155+
return q
156+
157+
def run_once(themes=THEMES):
158+
print(f"[{datetime.utcnow().isoformat()}] Démarrage d'une itération de veille (thèmes: {len(themes)})")
159+
total_saved = 0
160+
for theme in themes:
161+
try:
162+
q = build_query_for_theme(theme)
163+
print(f"-> Recherche thème '{theme}' (q={q})")
164+
items = search_github_repos(q)
165+
print(f" ↳ {len(items)} résultats récupérés pour '{theme}'")
166+
for repo in items:
167+
save_project(repo, theme)
168+
total_saved += 1
169+
except RateLimitError as rle:
170+
wait = rle.retry_after or 60
171+
print(f"[RATE LIMIT] Limit atteint. Pause {wait} secondes.")
172+
time.sleep(wait)
173+
except Exception as e:
174+
print(f"[ERREUR] thème '{theme}': {e}")
175+
print(f"[{datetime.utcnow().isoformat()}] Itération terminée — {total_saved} enregistrements traités.")
176+
return total_saved
177+
178+
def main_loop(interval=INTERVAL, once=False):
179+
if once:
180+
run_once()
181+
return
182+
71183
try:
72184
while True:
73-
for repo in REPOS:
74-
try:
75-
releases = fetch_releases(repo)
76-
for rel in releases:
77-
rel_id = str(rel["id"])
78-
if rel_id not in seen_ids:
79-
print(f"[NOUVELLE RELEASE] {repo}{rel.get('name','(no name)')}")
80-
print(" ->", rel.get("html_url", ""))
81-
save_release(repo, rel)
82-
seen_ids.add(rel_id)
83-
except Exception as e:
84-
print(f"[ERREUR] {repo}: {e}")
85-
86-
print(f"Attente {INTERVAL}s avant prochaine vérification...")
87-
time.sleep(INTERVAL)
185+
run_once()
186+
print(f"Attente {interval} secondes avant la prochaine vérification...")
187+
time.sleep(interval)
88188
except KeyboardInterrupt:
89-
print("Arrêt manuel.")
189+
print("")
90190
finally:
91191
conn.close()
92192

193+
def parse_args():
194+
p = argparse.ArgumentParser(description="Veille thématique GitHub orientée IA")
195+
p.add_argument("--once", action="store_true", help="Exécuter une unique itération et quitter")
196+
p.add_argument("--interval", type=int, default=INTERVAL, help="Intervalle entre itérations (secondes)")
197+
p.add_argument("--themes", type=str, help="Liste de thèmes séparés par des virgules (remplace la config)")
198+
return p.parse_args()
199+
93200
if __name__ == "__main__":
94-
main()
201+
args = parse_args()
202+
if args.themes:
203+
THEMES = [t.strip() for t in args.themes.split(",") if t.strip()]
204+
print(f"Themes remplacés: {THEMES}")
205+
206+
INTERVAL = args.interval
207+
208+
print("Github AI Theme Watcher démarré.")
209+
if GITHUB_TOKEN:
210+
print("")
211+
else:
212+
print("")
213+
214+
main_loop(interval=INTERVAL, once=args.once)

0 commit comments

Comments
 (0)