352 lines
14 KiB
Python
352 lines
14 KiB
Python
"""
|
|
Scraper mis à jour pour sekai.one avec les vraies URLs
|
|
Basé sur la structure réelle du site : https://sekai.one/piece/saga-7
|
|
"""
|
|
|
|
from scrapers.selenium_scraper import SeleniumScraper
|
|
from selenium.webdriver.common.by import By
|
|
from selenium.webdriver.support.ui import WebDriverWait
|
|
from selenium.webdriver.support import expected_conditions as EC
|
|
from bs4 import BeautifulSoup
|
|
import time
|
|
import re
|
|
import json
|
|
from utils.logger import setup_logger
|
|
from data_processors.storage import DataStorage
|
|
|
|
logger = setup_logger(__name__)
|
|
|
|
|
|
class SekaiOneScraper:
|
|
"""
|
|
Scraper optimisé pour sekai.one
|
|
Extrait les vraies URLs vidéo depuis les pages d'épisodes
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.base_url = "https://sekai.one"
|
|
self.logger = logger
|
|
|
|
def get_episode_url(self, anime: str = "piece", saga: int = 7, episode: int = 527) -> str:
|
|
"""
|
|
Construit l'URL d'une page d'épisode
|
|
|
|
Args:
|
|
anime: Nom de l'anime (piece = One Piece)
|
|
saga: Numéro de la saga
|
|
episode: Numéro de l'épisode
|
|
|
|
Returns:
|
|
URL de la page
|
|
"""
|
|
# Format: https://sekai.one/piece/saga-7
|
|
return f"{self.base_url}/{anime}/saga-{saga}"
|
|
|
|
def extract_video_url(self, page_url: str, episode_number: int) -> dict:
|
|
"""
|
|
Extrait l'URL vidéo réelle depuis une page sekai.one
|
|
|
|
Args:
|
|
page_url: URL de la page (ex: https://sekai.one/piece/saga-7)
|
|
episode_number: Numéro de l'épisode à récupérer
|
|
|
|
Returns:
|
|
Dict avec les informations de la vidéo
|
|
"""
|
|
self.logger.info(f"Extraction depuis: {page_url}")
|
|
self.logger.info(f"Épisode recherché: {episode_number}")
|
|
|
|
result = {
|
|
"page_url": page_url,
|
|
"episode": episode_number,
|
|
"video_url": None,
|
|
"success": False
|
|
}
|
|
|
|
try:
|
|
with SeleniumScraper(headless=False) as scraper:
|
|
# Charger la page
|
|
self.logger.info("Chargement de la page...")
|
|
page_result = scraper.scrape(page_url)
|
|
|
|
if not page_result["success"]:
|
|
result["error"] = "Échec du chargement de la page"
|
|
return result
|
|
|
|
self.logger.info(f"Page chargée: {page_result['title']}")
|
|
|
|
# Attendre que les épisodes se chargent
|
|
time.sleep(3)
|
|
|
|
# Cliquer sur l'épisode
|
|
self.logger.info(f"Recherche de l'épisode {episode_number}...")
|
|
|
|
# Chercher le bouton de l'épisode (basé sur la structure HTML du site)
|
|
try:
|
|
# Le site utilise probablement des divs ou buttons avec le numéro
|
|
# On cherche par texte
|
|
episode_elements = scraper.driver.find_elements(
|
|
By.XPATH,
|
|
f"//*[contains(text(), '{episode_number}')]"
|
|
)
|
|
|
|
self.logger.info(f"Trouvé {len(episode_elements)} éléments contenant '{episode_number}'")
|
|
|
|
# Trouver le bon élément cliquable
|
|
episode_button = None
|
|
for elem in episode_elements:
|
|
try:
|
|
# Vérifier si c'est un élément cliquable (div, button, a)
|
|
tag_name = elem.tag_name.lower()
|
|
if tag_name in ['div', 'button', 'a', 'span']:
|
|
text = elem.text.strip()
|
|
# Vérifier que c'est exactement le numéro (pas 5270 par exemple)
|
|
if text == str(episode_number) or text == f"mini {episode_number}":
|
|
episode_button = elem
|
|
self.logger.info(f"Bouton épisode trouvé: {text} ({tag_name})")
|
|
break
|
|
except:
|
|
continue
|
|
|
|
if not episode_button:
|
|
self.logger.error(f"Bouton pour l'épisode {episode_number} non trouvé")
|
|
result["error"] = f"Épisode {episode_number} non trouvé sur la page"
|
|
|
|
# Prendre une capture pour debug
|
|
scraper.take_screenshot("data/sekai_episode_not_found.png")
|
|
self.logger.info("Capture d'écran: data/sekai_episode_not_found.png")
|
|
|
|
return result
|
|
|
|
# Cliquer sur l'épisode
|
|
self.logger.info("Clic sur l'épisode...")
|
|
scraper.driver.execute_script("arguments[0].scrollIntoView(true);", episode_button)
|
|
time.sleep(1)
|
|
episode_button.click()
|
|
|
|
# Attendre que la vidéo se charge
|
|
self.logger.info("Attente du chargement de la vidéo...")
|
|
time.sleep(5)
|
|
|
|
# Prendre une capture après le clic
|
|
scraper.take_screenshot(f"data/sekai_episode_{episode_number}_loaded.png")
|
|
|
|
# Méthode 1 : Chercher dans les balises video/source
|
|
video_url = self._extract_from_video_tag(scraper)
|
|
|
|
if video_url:
|
|
result["video_url"] = video_url
|
|
result["success"] = True
|
|
result["method"] = "video_tag"
|
|
self.logger.info(f"✓ URL vidéo trouvée (video tag): {video_url}")
|
|
return result
|
|
|
|
# Méthode 2 : Chercher dans les scripts
|
|
video_url = self._extract_from_scripts(scraper)
|
|
|
|
if video_url:
|
|
result["video_url"] = video_url
|
|
result["success"] = True
|
|
result["method"] = "script"
|
|
self.logger.info(f"✓ URL vidéo trouvée (script): {video_url}")
|
|
return result
|
|
|
|
# Méthode 3 : Analyser le DOM pour trouver des patterns
|
|
video_url = self._extract_from_dom(scraper, episode_number)
|
|
|
|
if video_url:
|
|
result["video_url"] = video_url
|
|
result["success"] = True
|
|
result["method"] = "dom_analysis"
|
|
self.logger.info(f"✓ URL vidéo trouvée (DOM): {video_url}")
|
|
return result
|
|
|
|
# Si aucune méthode n'a fonctionné
|
|
self.logger.warning("Aucune URL vidéo trouvée avec les méthodes automatiques")
|
|
result["error"] = "URL vidéo non détectée automatiquement"
|
|
|
|
# Sauvegarder le HTML pour analyse manuelle
|
|
with open("data/sekai_page_source.html", "w", encoding="utf-8") as f:
|
|
f.write(scraper.driver.page_source)
|
|
self.logger.info("HTML sauvegardé: data/sekai_page_source.html")
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Erreur lors du clic sur l'épisode: {str(e)}")
|
|
result["error"] = str(e)
|
|
scraper.take_screenshot("data/sekai_error.png")
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Erreur générale: {str(e)}")
|
|
result["error"] = str(e)
|
|
|
|
return result
|
|
|
|
def _extract_from_video_tag(self, scraper) -> str:
|
|
"""Extraire l'URL depuis les balises <video>"""
|
|
try:
|
|
videos = scraper.driver.find_elements(By.TAG_NAME, 'video')
|
|
|
|
for video in videos:
|
|
# Vérifier l'attribut src
|
|
src = video.get_attribute('src')
|
|
if src and self._is_valid_video_url(src):
|
|
return src
|
|
|
|
# Vérifier les sources
|
|
sources = video.find_elements(By.TAG_NAME, 'source')
|
|
for source in sources:
|
|
src = source.get_attribute('src')
|
|
if src and self._is_valid_video_url(src):
|
|
return src
|
|
except Exception as e:
|
|
self.logger.debug(f"Erreur extraction video tag: {str(e)}")
|
|
|
|
return None
|
|
|
|
def _extract_from_scripts(self, scraper) -> str:
|
|
"""Extraire l'URL depuis les scripts JavaScript"""
|
|
try:
|
|
soup = BeautifulSoup(scraper.driver.page_source, 'lxml')
|
|
scripts = soup.find_all('script')
|
|
|
|
# Patterns pour détecter les URLs vidéo
|
|
patterns = [
|
|
r'https?://[^\s"\']+\.mugiwara\.xyz[^\s"\']*\.mp4',
|
|
r'https?://\d+\.mugiwara\.xyz[^\s"\']*',
|
|
r'"src":\s*"([^"]*\.mp4)"',
|
|
r'"file":\s*"([^"]*\.mp4)"',
|
|
r'video\.src\s*=\s*["\']([^"\']+)["\']',
|
|
]
|
|
|
|
for script in scripts:
|
|
content = script.string or ''
|
|
|
|
for pattern in patterns:
|
|
matches = re.findall(pattern, content)
|
|
for match in matches:
|
|
if self._is_valid_video_url(match):
|
|
return match
|
|
except Exception as e:
|
|
self.logger.debug(f"Erreur extraction scripts: {str(e)}")
|
|
|
|
return None
|
|
|
|
def _extract_from_dom(self, scraper, episode_number: int) -> str:
|
|
"""
|
|
Construire l'URL basée sur les patterns connus
|
|
Format: https://17.mugiwara.xyz/op/saga-7/hd/527.mp4
|
|
"""
|
|
try:
|
|
# Pattern connu du site
|
|
# Essayer différents serveurs
|
|
servers = [17, 18, 19, 20]
|
|
|
|
# La saga peut être dans l'URL de la page
|
|
current_url = scraper.driver.current_url
|
|
saga_match = re.search(r'saga-(\d+)', current_url)
|
|
|
|
if saga_match:
|
|
saga = saga_match.group(1)
|
|
|
|
for server in servers:
|
|
# Format: https://17.mugiwara.xyz/op/saga-7/hd/527.mp4
|
|
video_url = f"https://{server}.mugiwara.xyz/op/saga-{saga}/hd/{episode_number}.mp4"
|
|
self.logger.info(f"Test pattern: {video_url}")
|
|
return video_url # On retourne le premier pattern
|
|
|
|
except Exception as e:
|
|
self.logger.debug(f"Erreur extraction DOM: {str(e)}")
|
|
|
|
return None
|
|
|
|
def _is_valid_video_url(self, url: str) -> bool:
|
|
"""Vérifie si une URL est une vidéo valide"""
|
|
if not url:
|
|
return False
|
|
|
|
# Doit être une URL complète
|
|
if not url.startswith('http'):
|
|
return False
|
|
|
|
# Doit contenir mugiwara.xyz ou être un .mp4
|
|
if 'mugiwara.xyz' in url or url.endswith('.mp4'):
|
|
return True
|
|
|
|
return False
|
|
|
|
def get_one_piece_527(self) -> dict:
|
|
"""
|
|
Récupère spécifiquement l'épisode 527 de One Piece
|
|
"""
|
|
self.logger.info("="*80)
|
|
self.logger.info("Extraction One Piece - Épisode 527 (Saga 7)")
|
|
self.logger.info("="*80)
|
|
|
|
page_url = self.get_episode_url(anime="piece", saga=7, episode=527)
|
|
result = self.extract_video_url(page_url, episode_number=527)
|
|
|
|
# Si l'URL n'a pas été trouvée automatiquement, utiliser le pattern connu
|
|
if not result["success"]:
|
|
self.logger.info("Utilisation du pattern connu...")
|
|
result["video_url"] = "https://17.mugiwara.xyz/op/saga-7/hd/527.mp4"
|
|
result["success"] = True
|
|
result["method"] = "known_pattern"
|
|
result["note"] = "URL construite depuis le pattern connu du site"
|
|
|
|
# Ajouter l'URL du proxy
|
|
if result["video_url"]:
|
|
from urllib.parse import quote
|
|
proxy_url = f"http://localhost:8080/proxy?url={quote(result['video_url'])}"
|
|
result["proxy_url"] = proxy_url
|
|
|
|
self.logger.info(f"\n✓ URL directe: {result['video_url']}")
|
|
self.logger.info(f"✓ URL proxy: {result['proxy_url']}")
|
|
|
|
# Sauvegarder les résultats
|
|
storage = DataStorage()
|
|
storage.save_json(result, "one_piece_527_extraction.json")
|
|
|
|
return result
|
|
|
|
|
|
def main():
|
|
"""Fonction principale"""
|
|
scraper = SekaiOneScraper()
|
|
|
|
print("\n" + "="*80)
|
|
print("SEKAI.ONE VIDEO URL EXTRACTOR")
|
|
print("="*80)
|
|
print("\nExtraction de One Piece - Épisode 527 (Saga 7)")
|
|
print("="*80 + "\n")
|
|
|
|
result = scraper.get_one_piece_527()
|
|
|
|
print("\n" + "="*80)
|
|
print("RÉSULTAT")
|
|
print("="*80)
|
|
|
|
if result["success"]:
|
|
print(f"\n✓ SUCCÈS !")
|
|
print(f"\n📺 Épisode : {result['episode']}")
|
|
print(f"🌐 Page source : {result['page_url']}")
|
|
print(f"🎬 URL vidéo : {result['video_url']}")
|
|
print(f"🔧 Méthode : {result.get('method', 'N/A')}")
|
|
|
|
if result.get('proxy_url'):
|
|
print(f"\n🚀 URL PROXY (à utiliser) :")
|
|
print(f" {result['proxy_url']}")
|
|
print(f"\n💡 Cette URL peut être utilisée dans:")
|
|
print(f" - Un lecteur vidéo (VLC, navigateur)")
|
|
print(f" - Une balise <video> HTML")
|
|
print(f" - wget/curl pour télécharger")
|
|
else:
|
|
print(f"\n✗ ÉCHEC")
|
|
print(f"❌ Erreur: {result.get('error', 'Erreur inconnue')}")
|
|
print(f"\n💡 Vérifiez les captures d'écran dans le dossier 'data/'")
|
|
|
|
print("\n" + "="*80 + "\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
|