chartbastan/_bmad-output/implementation-artifacts/2-1-implémenter-le-scraper-twitter-avec-rate-limiting.md
2026-02-01 09:31:38 +01:00

342 lines
12 KiB
Markdown

# Story 2.1: Implémenter le scraper Twitter avec rate limiting
Status: review
## Story
As a développeur,
I want implémenter un scraper Twitter avec gestion des rate limits,
So que je peux collecter des tweets sur les matchs de football sans dépasser les limites API.
## Acceptance Criteria
**Given** une clé API Twitter est configurée
**When** le scraper Twitter est exécuté
**Then** il collecte des tweets pour un match donné avec mots-clés pertinents
**And** il respecte la limite de 1000 requêtes/heure
**And** il gère les erreurs de rate limit avec retry avec backoff exponentiel
**And** les tweets collectés sont stockés avec timestamp, texte, engagement (retweets, likes)
**Given** le scraper détecte un rate limit
**When** la limite est atteinte (>90% utilisation)
**Then** une alerte est loggée
**And** le scraper passe en mode priorisation (matchs VIP uniquement)
**And** les données collectées sont sauvegardées avant arrêt
## Tasks / Subtasks
- [x] Installer les dépendances Twitter API (AC: #1)
- [x] Installer `tweepy` ou `tweepy-async` pour Twitter API
- [x] Configurer les credentials Twitter API
- [x] Créer le module scraper dans `backend/app/scrapers/`
- [x] Configurer la connexion Twitter API
- [x] Vérifier l'authentification Twitter
- [x] Implémenter le collecteur de tweets (AC: #1)
- [x] Créer la fonction de recherche de tweets par mots-clés
- [x] Extraire les données: texte, timestamp, retweets, likes
- [x] Implémenter le parsing des données Twitter
- [x] Stocker les tweets dans la base de données
- [x] Gérer les erreurs de connexion/timeout
- [x] Implémenter le rate limiting (AC: #1, #2)
- [x] Configurer le rate limiter pour 1000 req/heure
- [x] Implémenter le suivi de l'utilisation API
- [x] Implémenter l'alerte quand utilisation > 90%
- [x] Implémenter le retry avec backoff exponentiel
- [x] Configurer le mode priorisation (matchs VIP)
- [x] Implémenter le mode dégradé (AC: #2)
- [x] Définir les matchs VIP dans la configuration
- [x] Implémenter la priorisation dynamique des matchs
- [x] Sauvegarder les données collectées avant arrêt
- [x] Logger l'alerte de mode dégradé
- [x] Vérifier que le scraper continue avec autres sources
- [x] Créer les schémas de base de données pour tweets (AC: #1)
- [x] Créer la table `tweets` dans SQLite
- [x] Définir les colonnes: id, text, created_at, retweet_count, like_count
- [x] Ajouter les colonnes pour match_id et source
- [x] Créer les indexes appropriés
- [x] Générer et appliquer les migrations
- [x] Tester le scraper Twitter (AC: #1, #2)
- [x] Tester la collecte de tweets pour un match
- [x] Vérifier que le rate limiting fonctionne
- [x] Tester le mode priorisation VIP
- [x] Vérifier que les données sont stockées correctement
- [x] Tester le retry avec backoff exponentiel
## Dev Notes
### Architecture Patterns et Contraintes
**Stack Technique Imposé:**
- **Twitter API:** Tweepy ou tweepy-async
- **Rate Limiting:** 1000 requêtes/heure (gratuit)
- **Database:** SQLite avec Drizzle (Next.js) et SQLAlchemy (FastAPI)
- **Queue:** RabbitMQ pour traitement asynchrone (Phase 2+)
- **Logging:** Structuré avec alertes pour rate limits
**Configuration Requise:**
- Credentials Twitter API: Bearer token ou OAuth 1.0a
- Rate limiter: 1000 req/heure avec alerte à 90%
- Mode dégradé: Priorisation des matchs VIP
- Stockage: Table `tweets` dans SQLite
**Intégration avec Architecture Globale:**
- Part de la pondération: Twitter 60% (Reddit 25%, RSS 15%)
- Queue RabbitMQ pour découplage scraping et analyse
- Même structure de données que Reddit et RSS
- Format cohérent pour fusion multi-sources
**Conventions de Nommage:**
- Table: `tweets` (snake_case pluriel)
- Colonnes: `tweet_id`, `text`, `created_at`, `match_id` (snake_case)
- Functions: `scrape_twitter_match()`, `handle_rate_limit()` (snake_case)
- Variables: `max_tweets_per_hour`, `is_vip_match` (snake_case/UPPER_SNAKE_CASE)
### Source Tree Components à Toucher
**Fichiers à créer:**
1. `backend/app/scrapers/` (répertoire scrapers)
2. `backend/app/scrapers/__init__.py`
3. `backend/app/scrapers/twitter_scraper.py` (module Twitter)
4. `backend/app/models/tweet.py` (modèle SQLAlchemy pour tweets)
5. `backend/app/schemas/tweet.py` (schéma Pydantic pour tweets)
**Fichiers à modifier:**
1. `backend/requirements.txt` (ajouter tweepy)
2. `src/db/schema.ts` (ajouter schéma Drizzle pour tweets - Next.js)
### Project Structure Notes
**Alignment with unified project structure:**
- ✅ Twitter scraper dans `backend/app/scrapers/` comme spécifié
- ✅ Rate limiting à 1000 req/heure comme spécifié
- ✅ Pondération Twitter 60% comme spécifié dans epics
- ✅ Mode dégradé avec priorisation VIP comme spécifié
**Conventions de code à respecter:**
- Gestion asynchrone avec async/await (si tweepy-async)
- Logging structuré pour monitoring
- Retry avec backoff exponentiel
- Mode dégradé avec priorisation
**Intégration avec architecture existante:**
- SQLite partagé avec Next.js et FastAPI
- Même convention de nommage `snake_case`
- Préparation pour intégration RabbitMQ (Phase 2+)
### Technical Requirements
**Configuration Twitter API:**
```python
import tweepy
# Configuration Twitter API
auth = tweepy.BearerToken("YOUR_BEARER_TOKEN")
client = tweepy.Client(bearer_token=auth)
# Rate limiting configuration
MAX_TWEETS_PER_HOUR = 1000
RATE_LIMIT_ALERT_THRESHOLD = 0.9 # 90%
VIP_MATCH_IDS = [1, 2, 3] # IDs des matchs VIP
```
**Scraper Twitter:**
```python
async def scrape_twitter_match(match_id: str, keywords: List[str]):
# Collecte des tweets pour un match
tweets = client.search_recent_tweets(
query=f"{' OR '.join(keywords)}",
max_results=100,
tweet_fields=['created_at', 'public_metrics', 'text']
)
# Stockage dans la base de données
for tweet in tweets.data:
save_tweet_to_db(tweet, match_id)
```
**Rate Limiting:**
```python
from time import sleep
from math import exp
async def handle_rate_limit(api_calls_remaining: int):
if api_calls_remaining < (MAX_TWEETS_PER_HOUR * (1 - RATE_LIMIT_ALERT_THRESHOLD)):
log_alert("Rate limit approaching 90%")
if api_calls_remaining == 0:
# Mode dégradé: priorisation VIP
enable_vip_mode_only()
wait_time = 3600 # Attendre 1 heure
sleep(wait_time)
```
**Schéma Table Tweets:**
```python
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.orm import relationship
class Tweet(Base):
__tablename__ = "tweets"
id = Column(Integer, primary_key=True)
tweet_id = Column(String, unique=True, index=True)
text = Column(String, nullable=False)
created_at = Column(DateTime, nullable=False, index=True)
retweet_count = Column(Integer, default=0)
like_count = Column(Integer, default=0)
match_id = Column(Integer, ForeignKey("matches.id"))
source = Column(String, default="twitter") # twitter, reddit, rss
```
### Architecture Compliance
**Conformité avec Architecture Decision Document:**
**External API Management:**
- Rate limiting à 1000 req/heure
- Alertes prédictives (>90% utilisation)
- Mode dégradé avec priorisation VIP
**Data Architecture:**
- Table `tweets` dans SQLite
- Conventions `snake_case`
- Indexes optimisés
**Code Organization:**
- Module dans `backend/app/scrapers/`
- Séparation clear des responsabilités
### Library/Framework Requirements
**Packages:**
- `tweepy` ou `tweepy-async`
### File Structure Requirements
```
backend/
├── app/
│ ├── scrapers/
│ │ ├── __init__.py
│ │ └── twitter_scraper.py
│ ├── models/
│ │ └── tweet.py
│ └── schemas/
│ └── tweet.py
```
### Testing Requirements
- Tests de collecte de tweets
- Tests de rate limiting
- Tests de mode dégradé
- Tests de stockage en base de données
### References
- [Source: _bmad-output/planning-artifacts/epics.md#Story-2.1]
## Dev Agent Record
### Agent Model Used
GLM-4.7
### Completion Notes List
-**Dependencies Twitter API**: Tweepy 4.14.0 ajouté à requirements.txt
-**Twitter Scraper Module**: Module complet avec rate limiting et mode dégradé
- Classe `TwitterScraper` avec authentification Twitter API
- Tracking en temps réel des appels API (max 1000/heure)
- Alertes prédictives à 90% d'utilisation
- Mode dégradé automatique pour matchs VIP uniquement
- Retry avec backoff exponentiel
- Logging structuré pour monitoring
-**Database Schema**: Table `tweets` créée dans SQLite
- Colonnes: id, tweet_id, text, created_at, retweet_count, like_count, match_id, source
- Indexes: tweet_id (unique), created_at, match_id, composite (match_id, source)
- Migration Alembic: 20260117_0001_create_tweets_table.py
-**Pydantic Schemas**: Schémas de validation pour tweets
- TweetBase, TweetCreate, TweetResponse
- TweetListResponse avec pagination
- TweetStatsResponse pour statistiques
-**Drizzle Schema**: Schéma pour Next.js (src/db/schema.ts)
- Table `tweets` avec conventions TypeScript
- Mapping snake_case ↔ camelCase
-**Tests Unitaires**: Tests complets pour scraper et modèle
- test_twitter_scraper.py: 10+ tests pour rate limiting, mode VIP, scraping
- test_tweet_model.py: 8+ tests pour modèle SQLAlchemy
-**Documentation**: README complet dans backend/app/scrapers/
- Guide d'installation et configuration
- Exemples d'utilisation
- Documentation API et dépannage
### Technical Decisions
**Pourquoi Tweepy vs tweepy-async:**
- Choisie `tweepy` synchrone pour simplicité initiale
- Peut migrer vers `tweepy-async` dans Phase 2+ si performance nécessaire
- Rate limiting natif de Tweepy suffisant pour Phase 1
**Rate Limiting Strategy:**
- Tracking côté client (self.api_calls_made)
- Alertes prédictives à 90% pour éviter blocages
- Mode dégradé automatique: matchs VIP seulement
- Backoff exponentiel: minimum 1 minute, maximum 1 heure
**Database Schema Design:**
- Convention `snake_case` cohérente avec SQLAlchemy
- Indexes optimisés pour requêtes fréquentes:
- `tweet_id` unique pour éviter doublons
- `created_at` pour tri temporel
- `match_id` et `match_id+source` pour filtrage
- `source` colonne pour multi-source future (Reddit, RSS)
**Mode Dégradé Implementation:**
- VIP match IDs configurables dans scraper
- Activation automatique quand rate limit atteint
- Alertes loggées avec niveau WARNING
- Sauvegarde automatique avant arrêt
### Integration Points
**Backend Integration:**
- Module accessible via `app.scrapers.twitter_scraper`
- Factory function: `create_twitter_scraper()`
- Compatible avec SQLAlchemy sessions via `save_tweets_to_db()`
**Frontend Integration (Next.js):**
- Schéma Drizzle dans `src/db/schema.ts`
- Partage de la même base SQLite
- Conventions TypeScript (camelCase) automatiques
**Future Integration (Phase 2+):**
- RabbitMQ queue pour découplage scraping/analyse
- Workers asynchrones pour multi-source
- Dashboard monitoring temps réel
### File List
**Fichiers créés:**
- `backend/app/scrapers/__init__.py`
- `backend/app/scrapers/twitter_scraper.py`
- `backend/app/scrapers/README.md`
- `backend/app/models/tweet.py`
- `backend/app/schemas/tweet.py`
- `backend/alembic/versions/20260117_0001_create_tweets_table.py`
- `backend/tests/__init__.py`
- `backend/tests/test_twitter_scraper.py`
- `backend/tests/test_tweet_model.py`
- `backend/tests/run_tests.py`
**Fichiers modifiés:**
- `backend/requirements.txt` (ajouté tweepy==4.14.0)
- `backend/app/models/__init__.py` (ajouté Tweet import)
- `backend/app/schemas/__init__.py` (ajouté Tweet schemas)
- `chartbastan/src/db/schema.ts` (ajouté tweets table)