Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed README.md
Empty file.
21 changes: 21 additions & 0 deletions data/internal/imdb_movies.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
titre,acteur_1,acteur_2,acteur_3,realisateur,genres,duree,note,url
Jay Kelly,George Clooney,Adam Sandler,Stanley Townsend,Noah Baumbach,"Drames dans le show-business, Duos comiques, Passage à l'âge adulte",2h 12min,"6,6",https://www.imdb.com/fr/title/tt30446847/
Five Nights at Freddy's 2,Josh Hutcherson,Piper Rubio,Elizabeth Lail,Emma Tammi,"Horreur corporelle, Horreur pour adolescents, Horreur surnaturelle",1h 44min,"5,7",https://www.imdb.com/fr/title/tt30274401/
Dhurandhar,Naveen Kaushik,Manav Gohil,Danish Pandor,Aditya Dhar,"Hindi, Action épique, Drame épique",3h 32min,"8,1",https://www.imdb.com/fr/title/tt33014583/
Zootopie 2,Ginnifer Goodwin,Jason Bateman,Ke Huy Quan,Jared Bush,"Animation numérique, Aventure animalière, Aventure urbaine",1h 48min,"7,7",https://www.imdb.com/fr/title/tt26443597/
"Joyeux Noël, Maman!",Michelle Pfeiffer,Denis Leary,Felicity Jones,Michael Showalter,"Comédie de Noël, Comédie, Fêtes de fin d'année",1h 47min,"5,3",https://www.imdb.com/fr/title/tt31998881/
Une bataille après l'autre,Leonardo DiCaprio,Sean Penn,Benicio Del Toro,Paul Thomas Anderson,"Action épique, Comédie noire, Drame épique",2h 41min,"7,9",https://www.imdb.com/fr/title/tt30144839/
Frankenstein,Oscar Isaac,Jacob Elordi,Christoph Waltz,Guillermo del Toro,"Dark Fantasy, Drame psychologique, Horreur corporelle",2h 29min,"7,5",https://www.imdb.com/fr/title/tt1312221/
Train Dreams,Joel Edgerton,Clifton Collins Jr.,Felicity Jones,Clint Bentley,"Drame d’époque, Drame, Retour en haut de la page",1h 42min,"7,6",https://www.imdb.com/fr/title/tt29768334/
My Secret Santa,Alexandra Breckenridge,Ryan Eggold,Tia Mowry,Mike Rohl,Retour en haut de la page,1h 30min,"5,8",https://www.imdb.com/fr/title/tt35219851/
Bugonia,Emma Stone,Jesse Plemons,Aidan Delbis,Yorgos Lanthimos,"Comédie noire, Invasion extraterrestre, Satire",1h 58min,"7,5",https://www.imdb.com/fr/title/tt12300742/
Mission: Impossible - The Final Reckoning,Tom Cruise,Hayley Atwell,Ving Rhames,Christopher McQuarrie,"Action épique, Aventure épique, Espionnage",2h 49min,"7,2",https://www.imdb.com/fr/title/tt9603208/
Predator: Badlands,Elle Fanning,Dimitrius Schuster-Koloamatangi,Ravi Narayan,Dan Trachtenberg,"Invasion extraterrestre, Action, Aventure",1h 47min,"7,4",https://www.imdb.com/fr/title/tt31227572/
Wake Up Dead Man: A Knives Out Mystery,Daniel Craig,Josh O'Connor,Glenn Close,Rian Johnson,"Comédie noire, Énigme policière, Comédie",2h 24min,"7,9",https://www.imdb.com/fr/title/tt14364480/
Nuremberg,Michael Shannon,Colin Hanks,Russell Crowe,James Vanderbilt,"Docudrame, Drame d’époque, Drame épique",2h 28min,"7,6",https://www.imdb.com/fr/title/tt29567915/
"Maman, j'ai raté l'avion !",Macaulay Culkin,Joe Pesci,Daniel Stern,Chris Columbus,"Burlesque, Comédie à concept, Comédie de Noël",1h 43min,"7,7",https://www.imdb.com/fr/title/tt0099785/
Avatar: De feu et de cendres,Kate Winslet,Zoe Saldaña,Stephen Lang,James Cameron,"Action épique, Aventure épique, Drame épique",3h 15min,N/A,https://www.imdb.com/fr/title/tt1757678/
Vive le vol d'hiver,Olivia Holt,Connor Swindells,Lucy Punch,Michael Fimognari,"Casses et Braquage, Comédie noire, Comédie romantique",1h 36min,"5,7",https://www.imdb.com/fr/title/tt24852126/
Hamnet,Jessie Buckley,Paul Mescal,Zac Wishart,Chloé Zhao,"Drame d’époque, Drame épique, Tragédie",2h 5min,"8,2",https://www.imdb.com/fr/title/tt14905854/
Wicked: Partie II,Cynthia Erivo,Ariana Grande,Jeff Goldblum,Jon M. Chu,"Comédie musicale pop, Conte de fées, Comédie musicale",2h 17min,"7,0",https://www.imdb.com/fr/title/tt19847976/
Tere Ishk Mein,Dhanush,Kriti Sanon,Jaya Bhattacharya,Aanand L. Rai,"Hindi, Action, Comédie musicale",2h 47min,"7,9",https://www.imdb.com/fr/title/tt28142095/
51 changes: 51 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""
Script pour lancer le scraper IMDB.

Usage:
python run_scraper.py [nombre_de_films]

Exemple:
python run_scraper.py 200
"""
import asyncio
import sys
import os

# Ajouter le répertoire src au path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))

from scrapping.imdb_scraper import IMDBScraper


async def main():
"""Point d'entrée principal."""
# Déterminer le nombre de films à scraper
target_count = 200
if len(sys.argv) > 1:
try:
target_count = int(sys.argv[1])
print(f"Scraping de {target_count} films...")
except ValueError:
print("Erreur: Le paramètre doit être un nombre entier")
sys.exit(1)
else:
print(f"Scraping de {target_count} films par défaut...")

# Créer le scraper et lancer le scraping
scraper = IMDBScraper(target_count=target_count)
await scraper.scrape_movies()

# Sauvegarder les résultats
output_file = "data/imdb_movies.csv"
scraper.save_to_csv(output_file)

print(f"\n{'='*60}")
print(f"Scraping terminé!")
print(f"Nombre de films récupérés: {len(scraper.movies_data)}")
print(f"Fichier de sortie: {output_file}")
print(f"{'='*60}")


if __name__ == "__main__":
asyncio.run(main())
1 change: 1 addition & 0 deletions src/scrapping/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Module de scraping IMDB."""
293 changes: 293 additions & 0 deletions src/scrapping/scraper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
"""
Scraper pour IMDB - Récupère les informations des derniers films.
"""
import asyncio
import csv
import logging
from typing import List, Dict, Optional
from datetime import datetime
from playwright.async_api import async_playwright, Page, Browser
import time

logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)


class IMDBScraper:
"""Scraper pour récupérer les informations des films IMDB."""

def __init__(self, target_count: int = 200):
"""
Initialize the IMDB scraper.

Args:
target_count: Nombre de films à récupérer (défaut: 200)
"""
self.target_count = target_count
self.movies_data: List[Dict] = []

async def scrape_movie_details(self, page: Page, movie_url: str) -> Optional[Dict]:
"""
Récupère les détails d'un film spécifique.

Args:
page: Page Playwright
movie_url: URL de la page du film

Returns:
Dictionnaire contenant les informations du film ou None
"""
try:
await page.goto(movie_url, wait_until="domcontentloaded", timeout=45000)
await page.wait_for_timeout(2000)

movie_data = {}

# Titre du film
try:
title_element = await page.query_selector('h1[data-testid="hero__primary-text"]')
if title_element:
movie_data['titre'] = await title_element.inner_text()
else:
title_element = await page.query_selector('h1')
movie_data['titre'] = await title_element.inner_text() if title_element else "N/A"
except Exception as e:
logger.warning(f"Titre non trouvé: {e}")
movie_data['titre'] = "N/A"

# Note du film
try:
rating_element = await page.query_selector('span[class*="AggregateRatingButton__RatingScore"]')
if not rating_element:
rating_element = await page.query_selector('div[data-testid="hero-rating-bar__aggregate-rating__score"] span')
if rating_element:
rating_text = await rating_element.inner_text()
movie_data['note'] = rating_text.strip().split('/')[0]
else:
movie_data['note'] = "N/A"
except Exception as e:
logger.warning(f"Note non trouvée: {e}")
movie_data['note'] = "N/A"

# Durée du film
try:
duration_element = await page.query_selector('li[data-testid="title-techspec_runtime"]')
if not duration_element:
duration_element = await page.query_selector('ul[class*="TitleBlockMetaData"] li:has-text("min")')
if duration_element:
duration_text = await duration_element.inner_text()
import re
match = re.search(r'(\d+h\s*\d+min|\d+min|\d+h)', duration_text)
if match:
movie_data['duree'] = match.group(1).strip()
else:
movie_data['duree'] = duration_text.strip()
else:
movie_data['duree'] = "N/A"
except Exception as e:
logger.warning(f"Durée non trouvée: {e}")
movie_data['duree'] = "N/A"

# Genres
try:
genre_elements = await page.query_selector_all('a[class*="GenresAndPlot__GenreChip"]')
if not genre_elements:
genre_elements = await page.query_selector_all('span[class*="ipc-chip__text"]')
if not genre_elements:
genre_div = await page.query_selector('div[data-testid="genres"]')
if genre_div:
genre_elements = await genre_div.query_selector_all('a, span')

genres = []
if genre_elements:
for genre_elem in genre_elements[:3]:
genre_text = await genre_elem.inner_text()
if genre_text and len(genre_text) > 1:
genres.append(genre_text.strip())
movie_data['genres'] = ", ".join(genres) if genres else "N/A"
else:
movie_data['genres'] = "N/A"
except Exception as e:
logger.warning(f"Genres non trouvés: {e}")
movie_data['genres'] = "N/A"

# Réalisateur
try:
director_element = None
# Essayer plusieurs sélecteurs (français et anglais)
director_section = await page.query_selector('li[data-testid="title-pc-principal-credit"]:has-text("Réalisation")')
if not director_section:
director_section = await page.query_selector('li[data-testid="title-pc-principal-credit"]:has-text("Director")')
if not director_section:
director_section = await page.query_selector('li[data-testid="title-pc-principal-credit"]:has-text("Réalisateur")')

if director_section:
director_links = await director_section.query_selector_all('a')
if director_links:
director_element = director_links[0]

if director_element:
movie_data['realisateur'] = (await director_element.inner_text()).strip()
else:
movie_data['realisateur'] = "N/A"
except Exception as e:
logger.warning(f"Réalisateur non trouvé: {e}")
movie_data['realisateur'] = "N/A"

# Top 3 acteurs
try:
# Chercher dans la section cast
cast_section = await page.query_selector('section[data-testid="title-cast"]')
actors = []

if cast_section:
actor_links = await cast_section.query_selector_all('a[data-testid="title-cast-item__actor"]')
for actor_link in actor_links[:3]:
actor_name = await actor_link.inner_text()
actors.append(actor_name.strip())

# Si pas assez d'acteurs, chercher dans une autre section
if len(actors) < 3:
alternative_actors = await page.query_selector_all('a[href*="/name/"]')
for actor_elem in alternative_actors:
if len(actors) >= 3:
break
actor_text = await actor_elem.inner_text()
if actor_text and actor_text not in actors and len(actor_text) > 2:
actors.append(actor_text.strip())

movie_data['acteur_1'] = actors[0] if len(actors) > 0 else "N/A"
movie_data['acteur_2'] = actors[1] if len(actors) > 1 else "N/A"
movie_data['acteur_3'] = actors[2] if len(actors) > 2 else "N/A"

except Exception as e:
logger.warning(f"Acteurs non trouvés: {e}")
movie_data['acteur_1'] = "N/A"
movie_data['acteur_2'] = "N/A"
movie_data['acteur_3'] = "N/A"

movie_data['url'] = movie_url

logger.info(f"Film récupéré: {movie_data.get('titre', 'Unknown')}")
return movie_data

except Exception as e:
logger.error(f"Erreur lors du scraping de {movie_url}: {e}")
return None

async def get_movie_urls(self, page: Page) -> List[str]:
"""
Récupère les URLs des films récents depuis IMDB.

Args:
page: Page Playwright

Returns:
Liste d'URLs de films
"""
movie_urls = []

try:
# Aller sur la page des films les plus populaires
logger.info("Récupération des URLs de films depuis IMDB...")
await page.goto("https://www.imdb.com/chart/moviemeter/", wait_until="domcontentloaded", timeout=45000)
await page.wait_for_timeout(3000)

# Récupérer les liens des films
movie_links = await page.query_selector_all('a[class*="ipc-title-link-wrapper"]')

for link in movie_links[:self.target_count]:
href = await link.get_attribute('href')
if href and '/title/' in href:
full_url = f"https://www.imdb.com{href}" if href.startswith('/') else href
# Nettoyer l'URL
full_url = full_url.split('?')[0]
if full_url not in movie_urls:
movie_urls.append(full_url)

logger.info(f"{len(movie_urls)} URLs de films trouvées")

except Exception as e:
logger.error(f"Erreur lors de la récupération des URLs: {e}")

return movie_urls[:self.target_count]

async def scrape_movies(self):
"""Lance le scraping des films IMDB."""
async with async_playwright() as p:
logger.info("Lancement du navigateur...")
browser = await p.chromium.launch(headless=True)
context = await browser.new_context(
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
locale='fr-FR'
)
page = await context.new_page()

try:
# Récupérer les URLs des films
movie_urls = await self.get_movie_urls(page)

logger.info(f"Début du scraping de {len(movie_urls)} films...")

# Scraper chaque film
for idx, url in enumerate(movie_urls, 1):
logger.info(f"[{idx}/{len(movie_urls)}] Scraping: {url}")

movie_data = await self.scrape_movie_details(page, url)
if movie_data:
self.movies_data.append(movie_data)

# Pause pour éviter de surcharger le serveur
await page.wait_for_timeout(2000)

if idx >= self.target_count:
break

logger.info(f"Scraping terminé: {len(self.movies_data)} films récupérés")

except Exception as e:
logger.error(f"Erreur générale: {e}")
finally:
await browser.close()

def save_to_csv(self, filename: str = "imdb_movies.csv"):
"""
Sauvegarde les données dans un fichier CSV.

Args:
filename: Nom du fichier CSV (défaut: imdb_movies.csv)
"""
if not self.movies_data:
logger.warning("Aucune donnée à sauvegarder")
return

fieldnames = [
'titre', 'acteur_1', 'acteur_2', 'acteur_3',
'realisateur', 'genres', 'duree', 'note', 'url'
]

try:
with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(self.movies_data)

logger.info(f"Données sauvegardées dans {filename}")
logger.info(f"Nombre total de films: {len(self.movies_data)}")

except Exception as e:
logger.error(f"Erreur lors de la sauvegarde CSV: {e}")


async def main():
"""Point d'entrée principal du script."""
scraper = IMDBScraper(target_count=200)
await scraper.scrape_movies()
scraper.save_to_csv("data/imdb_movies.csv")


if __name__ == "__main__":
asyncio.run(main())
Loading