Spaces:
Running
Running
Upload vtv_scraper.py
Browse files- vtv_scraper.py +24 -27
vtv_scraper.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
"""
|
| 2 |
VTV Channels Scraper
|
| 3 |
-
Fetches stream URLs from hd.xemtv.net for VTV1-
|
|
|
|
| 4 |
"""
|
| 5 |
import requests, re, time, threading
|
| 6 |
from bs4 import BeautifulSoup
|
|
@@ -11,34 +12,31 @@ UA = {
|
|
| 11 |
"Referer": "https://hd.xemtv.net/",
|
| 12 |
}
|
| 13 |
|
| 14 |
-
# Channel page URLs on xemtv.net
|
| 15 |
XEMTV_CHANNELS = {
|
| 16 |
"VTV1": "https://hd.xemtv.net/kenh-vtv1-truc-tuyen-hot.html",
|
| 17 |
"VTV2": "https://hd.xemtv.net/kenh-vtv2-truc-tuyen-hot.html",
|
| 18 |
"VTV3": "https://hd.xemtv.net/kenh-vtv3-truc-tuyen-hot.html",
|
| 19 |
"VTV4": "https://hd.xemtv.net/kenh-vtv4-truc-tuyen-hot.html",
|
| 20 |
"VTV5": "https://hd.xemtv.net/kenh-vtv5-truc-tuyen-hot.html",
|
| 21 |
-
"VTV CẦN THƠ": "https://hd.xemtv.net/kenh-
|
| 22 |
-
"VTV6": "https://hd.xemtv.net/kenh-vtv6-truc-tuyen-hot.html",
|
| 23 |
"VTV7": "https://hd.xemtv.net/kenh-vtv7-truc-tuyen-hot.html",
|
| 24 |
"VTV8": "https://hd.xemtv.net/kenh-vtv8-truc-tuyen-hot.html",
|
| 25 |
"VTV9": "https://hd.xemtv.net/kenh-vtv9-truc-tuyen-hot.html",
|
| 26 |
-
"VTV10": "https://hd.xemtv.net/kenh-vtv10-truc-tuyen-hot.html",
|
| 27 |
}
|
| 28 |
|
| 29 |
-
# CDN
|
|
|
|
| 30 |
CDN_STREAMS = {
|
| 31 |
-
"VTV1": "https://
|
| 32 |
-
"VTV2": "https://
|
| 33 |
-
"VTV3": "https://
|
| 34 |
-
"VTV4": "https://
|
| 35 |
-
"VTV5": "https://
|
| 36 |
-
"VTV CẦN THƠ": "https://
|
| 37 |
-
"
|
| 38 |
-
"
|
| 39 |
-
"
|
| 40 |
-
"VTV9": "https://vips-livecdn.hoichanhtv.com/vtv9/index.m3u8",
|
| 41 |
-
"VTV10": "https://vips-livecdn.hoichanhtv.com/vtv10/index.m3u8",
|
| 42 |
}
|
| 43 |
|
| 44 |
_vtv_cache = {}
|
|
@@ -78,7 +76,6 @@ def extract_m3u8_from_html(html):
|
|
| 78 |
m = re.search(pat, html, re.IGNORECASE)
|
| 79 |
if m:
|
| 80 |
url = m.group(1).strip()
|
| 81 |
-
# Validate it looks like a real m3u8
|
| 82 |
if '.m3u8' in url and len(url) > 20:
|
| 83 |
return url
|
| 84 |
return None
|
|
@@ -87,28 +84,28 @@ def extract_m3u8_from_html(html):
|
|
| 87 |
def fetch_vtv_stream(channel_name):
|
| 88 |
"""Fetch m3u8 stream URL for a VTV channel from xemtv.net."""
|
| 89 |
channel_name = channel_name.upper().strip()
|
| 90 |
-
|
| 91 |
# Normalize name
|
| 92 |
name_map = {
|
| 93 |
'VTVCT': 'VTV CẦN THƠ', 'VTV-CAN-THO': 'VTV CẦN THƠ',
|
| 94 |
-
'VTV CAN THO': 'VTV CẦN THƠ', '
|
|
|
|
| 95 |
}
|
| 96 |
channel_name = name_map.get(channel_name, channel_name)
|
| 97 |
-
|
| 98 |
# Check cache
|
| 99 |
cached = _cached(channel_name)
|
| 100 |
if cached:
|
| 101 |
return cached
|
| 102 |
-
|
| 103 |
page_url = XEMTV_CHANNELS.get(channel_name)
|
| 104 |
if not page_url:
|
| 105 |
-
# Try CDN fallback
|
| 106 |
cdn = CDN_STREAMS.get(channel_name)
|
| 107 |
if cdn:
|
| 108 |
_set_cache(channel_name, cdn)
|
| 109 |
return cdn
|
| 110 |
return None
|
| 111 |
-
|
| 112 |
try:
|
| 113 |
r = requests.get(page_url, headers=UA, timeout=15, allow_redirects=True)
|
| 114 |
if r.status_code == 200:
|
|
@@ -116,15 +113,15 @@ def fetch_vtv_stream(channel_name):
|
|
| 116 |
if m3u8:
|
| 117 |
_set_cache(channel_name, m3u8)
|
| 118 |
return m3u8
|
| 119 |
-
except
|
| 120 |
pass
|
| 121 |
-
|
| 122 |
# Fallback to CDN
|
| 123 |
cdn = CDN_STREAMS.get(channel_name)
|
| 124 |
if cdn:
|
| 125 |
_set_cache(channel_name, cdn)
|
| 126 |
return cdn
|
| 127 |
-
|
| 128 |
return None
|
| 129 |
|
| 130 |
|
|
|
|
| 1 |
"""
|
| 2 |
VTV Channels Scraper
|
| 3 |
+
Fetches stream URLs from hd.xemtv.net for VTV1-VTV9 + VTV Cần Thơ
|
| 4 |
+
Uses fptplay CDN as primary source (accessed via backend proxy with Referer header)
|
| 5 |
"""
|
| 6 |
import requests, re, time, threading
|
| 7 |
from bs4 import BeautifulSoup
|
|
|
|
| 12 |
"Referer": "https://hd.xemtv.net/",
|
| 13 |
}
|
| 14 |
|
| 15 |
+
# Channel page URLs on xemtv.net (for scraping)
|
| 16 |
XEMTV_CHANNELS = {
|
| 17 |
"VTV1": "https://hd.xemtv.net/kenh-vtv1-truc-tuyen-hot.html",
|
| 18 |
"VTV2": "https://hd.xemtv.net/kenh-vtv2-truc-tuyen-hot.html",
|
| 19 |
"VTV3": "https://hd.xemtv.net/kenh-vtv3-truc-tuyen-hot.html",
|
| 20 |
"VTV4": "https://hd.xemtv.net/kenh-vtv4-truc-tuyen-hot.html",
|
| 21 |
"VTV5": "https://hd.xemtv.net/kenh-vtv5-truc-tuyen-hot.html",
|
| 22 |
+
"VTV CẦN THƠ": "https://hd.xemtv.net/kenh-vtv10-truc-tuyen-hot.html",
|
|
|
|
| 23 |
"VTV7": "https://hd.xemtv.net/kenh-vtv7-truc-tuyen-hot.html",
|
| 24 |
"VTV8": "https://hd.xemtv.net/kenh-vtv8-truc-tuyen-hot.html",
|
| 25 |
"VTV9": "https://hd.xemtv.net/kenh-vtv9-truc-tuyen-hot.html",
|
|
|
|
| 26 |
}
|
| 27 |
|
| 28 |
+
# Primary CDN streams (fptplay) — these require Referer: https://fptplay.vn/
|
| 29 |
+
# The backend proxy (/api/proxy/hls) adds this header
|
| 30 |
CDN_STREAMS = {
|
| 31 |
+
"VTV1": "https://live.fptplay53.net/fnxch2/vtv1hd_abr.smil/chunklist.m3u8",
|
| 32 |
+
"VTV2": "https://live.fptplay53.net/fnxch2/vtv2hd_abr.smil/chunklist.m3u8",
|
| 33 |
+
"VTV3": "https://live.fptplay53.net/fnxch2/vtv3hd_abr.smil/chunklist.m3u8",
|
| 34 |
+
"VTV4": "https://live.fptplay53.net/fnxch2/vtv4hd_abr.smil/chunklist.m3u8",
|
| 35 |
+
"VTV5": "https://live-a.fptplay53.net/live/media/VTV5HD/live_hls_avc/index.m3u8",
|
| 36 |
+
"VTV CẦN THƠ": "https://live.fptplay53.net/fnxch2/vtvcantho_abr.smil/chunklist.m3u8",
|
| 37 |
+
"VTV7": "https://live.fptplay53.net/fnxhd1/vtv7hd_vhls.smil/chunklist_b5000000.m3u8",
|
| 38 |
+
"VTV8": "https://live.fptplay53.net/epzhd1/vtv8hd_vhls.smil/chunklist.m3u8",
|
| 39 |
+
"VTV9": "https://live.fptplay53.net/fnxhd1/vtv9hd_vhls.smil/chunklist.m3u8",
|
|
|
|
|
|
|
| 40 |
}
|
| 41 |
|
| 42 |
_vtv_cache = {}
|
|
|
|
| 76 |
m = re.search(pat, html, re.IGNORECASE)
|
| 77 |
if m:
|
| 78 |
url = m.group(1).strip()
|
|
|
|
| 79 |
if '.m3u8' in url and len(url) > 20:
|
| 80 |
return url
|
| 81 |
return None
|
|
|
|
| 84 |
def fetch_vtv_stream(channel_name):
|
| 85 |
"""Fetch m3u8 stream URL for a VTV channel from xemtv.net."""
|
| 86 |
channel_name = channel_name.upper().strip()
|
| 87 |
+
|
| 88 |
# Normalize name
|
| 89 |
name_map = {
|
| 90 |
'VTVCT': 'VTV CẦN THƠ', 'VTV-CAN-THO': 'VTV CẦN THƠ',
|
| 91 |
+
'VTV CAN THO': 'VTV CẦN THƠ', 'VTV6': 'VTV CẦN THƠ',
|
| 92 |
+
'VTCT': 'VTV CẦN THƠ',
|
| 93 |
}
|
| 94 |
channel_name = name_map.get(channel_name, channel_name)
|
| 95 |
+
|
| 96 |
# Check cache
|
| 97 |
cached = _cached(channel_name)
|
| 98 |
if cached:
|
| 99 |
return cached
|
| 100 |
+
|
| 101 |
page_url = XEMTV_CHANNELS.get(channel_name)
|
| 102 |
if not page_url:
|
|
|
|
| 103 |
cdn = CDN_STREAMS.get(channel_name)
|
| 104 |
if cdn:
|
| 105 |
_set_cache(channel_name, cdn)
|
| 106 |
return cdn
|
| 107 |
return None
|
| 108 |
+
|
| 109 |
try:
|
| 110 |
r = requests.get(page_url, headers=UA, timeout=15, allow_redirects=True)
|
| 111 |
if r.status_code == 200:
|
|
|
|
| 113 |
if m3u8:
|
| 114 |
_set_cache(channel_name, m3u8)
|
| 115 |
return m3u8
|
| 116 |
+
except:
|
| 117 |
pass
|
| 118 |
+
|
| 119 |
# Fallback to CDN
|
| 120 |
cdn = CDN_STREAMS.get(channel_name)
|
| 121 |
if cdn:
|
| 122 |
_set_cache(channel_name, cdn)
|
| 123 |
return cdn
|
| 124 |
+
|
| 125 |
return None
|
| 126 |
|
| 127 |
|