LOADING
scrape home, search, detail, streaming url
"""
ใ Donghub API Scraper ใ
Creator : ShanMolvyr
Base : https://donghub.vip
Desc : untuk mengambil data dari situs DonghuaStream, termasuk homepage (latest episode & slider), detail anime (sinopsis, genre, episode list), streaming link, search, dan jadwal rilis.
Channel : https://whatsapp.com/channel/0029VbB4Kw8EFeXfeExaXc3Q
Note : Kembangin sendiri
"""
import requests
from bs4 import BeautifulSoup
from flask import Flask, jsonify, request
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
BASE_URL = "https://donghub.vip"
HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
class DonghubScraper:
def get_home(self):
"""Scrape homepage - latest episodes & popular series"""
response = requests.get(BASE_URL, headers=HEADERS)
soup = BeautifulSoup(response.text, 'html.parser')
latest_episodes = []
for item in soup.select('.listupd.normal .styleegg'):
title_el = item.select_one('.eggtitle')
episode_el = item.select_one('.eggepisode')
link_el = item.select_one('a')
img_el = item.select_one('img')
if link_el:
latest_episodes.append({
'title': title_el.text.strip() if title_el else 'Unknown',
'episode': episode_el.text.strip() if episode_el else 'Episode',
'url': link_el['href'],
'thumbnail': img_el['src'] if img_el else None
})
popular = []
for item in soup.select('.serieslist.pop ul li'):
title_el = item.select_one('.leftseries h4 a')
img_el = item.select_one('.imgseries img')
if title_el:
popular.append({
'title': title_el.text.strip(),
'url': title_el['href'],
'thumbnail': img_el['src'] if img_el else None
})
return {
'latest_episodes': latest_episodes[:20],
'popular_series': popular[:10]
}
def get_detail(self, slug):
"""Get series detail and episode list"""
url = f"{BASE_URL}/{slug}/"
response = requests.get(url, headers=HEADERS)
soup = BeautifulSoup(response.text, 'html.parser')
# Basic info
title = soup.select_one('.infox h1')
if not title:
return {'error': 'Series not found'}
# Extract metadata
status = None
total_episodes = None
for span in soup.select('.spe span'):
text = span.get_text(strip=True)
if 'Status:' in text:
status = text.replace('Status:', '').strip()
if 'Episodes:' in text:
total_episodes = text.replace('Episodes:', '').strip()
# Genre
genres = [a.text.strip() for a in soup.select('.genxed a')]
# Synopsis
synopsis_el = soup.select_one('.entry-content p')
synopsis = synopsis_el.text.strip() if synopsis_el else ''
# Poster
poster = soup.select_one('.thumb img')
poster_url = poster['src'] if poster else None
# Episode list
episodes = []
for ep in soup.select('.eplister ul li a'):
num_el = ep.select_one('.epl-num')
title_el = ep.select_one('.epl-title')
date_el = ep.select_one('.epl-date')
episodes.append({
'episode': num_el.text.strip() if num_el else '0',
'title': title_el.text.strip() if title_el else '',
'url': ep['href'],
'release_date': date_el.text.strip() if date_el else None
})
return {
'title': title.text.strip(),
'status': status,
'total_episodes': total_episodes,
'genres': genres,
'synopsis': synopsis,
'poster': poster_url,
'episodes': episodes
}
def get_watch(self, episode_slug):
"""Get video URL and navigation for specific episode"""
url = f"{BASE_URL}/{episode_slug}"
response = requests.get(url, headers=HEADERS)
soup = BeautifulSoup(response.text, 'html.parser')
# Episode title
title = soup.select_one('h1.entry-title')
# Video URL from iframe
iframe = soup.select_one('#embed_holder iframe')
video_url = iframe['src'] if iframe else None
# Video servers (mirrors)
servers = []
for option in soup.select('.mirror option'):
value = option.get('value')
if value and value.strip():
# Value is base64 encoded iframe
import base64
try:
decoded = base64.b64decode(value).decode('utf-8')
# Extract src from iframe
src_start = decoded.find('src="')
if src_start != -1:
src_end = decoded.find('"', src_start + 5)
server_url = decoded[src_start + 5:src_end]
servers.append({
'name': option.text.strip(),
'url': server_url
})
except:
servers.append({
'name': option.text.strip(),
'url': value
})
# Navigation
nav_links = soup.select('.naveps .nvs a')
prev_url = None
next_url = None
for link in nav_links:
href = link['href']
text = link.text.lower()
if 'next' in text or 'โบ' in text:
next_url = href
elif 'prev' in text or 'โน' in text:
prev_url = href
# Series info
series_title = soup.select_one('.single-info .infox h2')
series_link = soup.select_one('.single-info .infox h2 a')
return {
'title': title.text.strip() if title else episode_slug,
'video_url': video_url,
'servers': servers,
'prev_episode': prev_url,
'next_episode': next_url,
'series': {
'title': series_title.text.strip() if series_title else None,
'url': series_link['href'] if series_link else None
}
}
def search(self, query):
"""Search for series"""
url = f"{BASE_URL}/?s={query}"
response = requests.get(url, headers=HEADERS)
soup = BeautifulSoup(response.text, 'html.parser')
results = []
for item in soup.select('.listupd .bsx'):
link = item.select_one('a')
title = item.select_one('.tt h2')
img = item.select_one('img')
if link and title:
results.append({
'title': title.text.strip(),
'url': link['href'],
'thumbnail': img['src'] if img else None
})
return {'query': query, 'results': results}
# Flask API Endpoints
scraper = DonghubScraper()
@app.route('/api/home')
def api_home():
return jsonify(scraper.get_home())
@app.route('/api/detail/<slug>')
def api_detail(slug):
return jsonify(scraper.get_detail(slug))
@app.route('/api/watch/<path:episode_slug>')
def api_watch(episode_slug):
return jsonify(scraper.get_watch(episode_slug))
@app.route('/api/search')
def api_search():
query = request.args.get('q', '')
if not query:
return jsonify({'error': 'Query parameter q required'}), 400
return jsonify(scraper.search(query))
if __name__ == '__main__':
print("๐ Donghub API running on http://localhost:5000")
print("๐ Available endpoints:")
print(" GET /api/home")
print(" GET /api/detail/<slug> (ex: renegade-immortal)")
print(" GET /api/watch/<episode_slug> (ex: renegade-immortal-episode-75-subtitle-indonesia)")
print(" GET /api/search?q=<query>")
app.run(debug=True, host='0.0.0.0', port=5000)