145 lines
4.6 KiB
Python
145 lines
4.6 KiB
Python
# coding=utf-8
|
|
import os
|
|
import logging
|
|
import sys
|
|
import asyncio
|
|
import aiohttp
|
|
|
|
BASE_URL = "http://api.hclyz.com:81/mf"
|
|
|
|
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
TARGET_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "..", "lib"))
|
|
M3U_FILE = os.path.join(TARGET_DIR, "sbjh.m3u")
|
|
|
|
HEADERS = {"User-Agent": "Mozilla/5.0"}
|
|
|
|
MAX_WORKERS = 5
|
|
|
|
def setup_logging():
|
|
logger = logging.getLogger("ScraperLogger")
|
|
logger.setLevel(logging.INFO)
|
|
|
|
formatter = logging.Formatter('[%(asctime)s] %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
|
|
|
consoleHandler = logging.StreamHandler(sys.stdout)
|
|
consoleHandler.setFormatter(formatter)
|
|
logger.addHandler(consoleHandler)
|
|
|
|
return logger
|
|
|
|
log = setup_logging()
|
|
|
|
async def safeGetJson(url, session, maxRetries=3, retryDelay=2):
|
|
for attemptCount in range(maxRetries):
|
|
try:
|
|
# Set 10 seconds timeout to prevent freezing
|
|
async with session.get(url, headers=HEADERS, timeout=10) as responseObj:
|
|
if responseObj.status == 200:
|
|
return await responseObj.json(content_type=None)
|
|
|
|
# Log non-200 status codes like 412
|
|
log.warning(f"HTTP {responseObj.status} for {url}. Attempt {attemptCount + 1} of {maxRetries}")
|
|
except Exception as e:
|
|
log.error(f"Request Exception: {url} -> {e}. Attempt {attemptCount + 1} of {maxRetries}")
|
|
|
|
# Wait before the next retry
|
|
if attemptCount < maxRetries - 1:
|
|
await asyncio.sleep(retryDelay)
|
|
|
|
return None
|
|
|
|
async def processPlatform(item, session, sem):
|
|
async with sem:
|
|
roomTitle = item.get("title", "").strip()
|
|
number = item.get("Number", "")
|
|
address = item.get("address", "")
|
|
|
|
xinImg = item.get("xinimg", "")
|
|
platformLogo = xinImg.replace("clun.top", "cdn.gcufbd.top")
|
|
|
|
log.info(f"📺 Fetching Platform: {roomTitle} (Resource count: {number})")
|
|
|
|
detail = await safeGetJson(f"{BASE_URL}/{address}", session)
|
|
if not detail:
|
|
return roomTitle, [], 1
|
|
|
|
zhubo = detail.get("zhubo", [])
|
|
if not zhubo:
|
|
return roomTitle, [], 1
|
|
|
|
groupName = f"{roomTitle}"
|
|
results = []
|
|
errors = 0
|
|
|
|
for vod in zhubo:
|
|
name = vod.get("title", "").strip()
|
|
url = vod.get("address", "").strip()
|
|
|
|
if not url:
|
|
errors += 1
|
|
continue
|
|
|
|
results.append((groupName, name, url, platformLogo))
|
|
|
|
return roomTitle, results, errors
|
|
|
|
async def mainAsync():
|
|
totalError = 0
|
|
totalSuccess = 0
|
|
|
|
log.info("🚀 Enhanced task initiated.")
|
|
|
|
# Increase limit_per_host to prevent DNS resolution issues
|
|
connector = aiohttp.TCPConnector(limit=MAX_WORKERS, limit_per_host=10)
|
|
async with aiohttp.ClientSession(connector=connector) as session:
|
|
|
|
home = await safeGetJson(f"{BASE_URL}/json.txt", session)
|
|
if not home:
|
|
log.error("❌ Retrieval failed, collection terminated.")
|
|
sys.exit(1)
|
|
|
|
# Retrieve platform list and remove the first element
|
|
data = home.get("pingtai", [])[1:]
|
|
|
|
m3uLines = ["#EXTM3U x-tvg-url=\"\""]
|
|
seenUrls = set()
|
|
|
|
log.info(f"⚡ Found {len(data)} platforms in total.")
|
|
|
|
sem = asyncio.Semaphore(MAX_WORKERS)
|
|
|
|
tasks = [processPlatform(item, session, sem) for item in data]
|
|
results = await asyncio.gather(*tasks)
|
|
|
|
for roomTitle, res, errors in results:
|
|
totalError += errors
|
|
|
|
for groupName, name, url, logo in res:
|
|
if url in seenUrls:
|
|
continue
|
|
|
|
seenUrls.add(url)
|
|
# Generate M3U tag with Logo
|
|
m3uLines.append(f'#EXTINF:-1 tvg-logo="{logo}" group-title="{groupName}",{name}')
|
|
m3uLines.append(url)
|
|
totalSuccess += 1
|
|
|
|
try:
|
|
os.makedirs(os.path.dirname(M3U_FILE), exist_ok=True)
|
|
|
|
with open(M3U_FILE, "w", encoding="utf-8") as f:
|
|
f.write("\n".join(m3uLines))
|
|
log.info(f"📄 Generation successful. Total Streams: {totalSuccess}")
|
|
except Exception as e:
|
|
log.error(f"❌ Failed to write to file: {e}")
|
|
sys.exit(1)
|
|
|
|
summaryMsg = f"Collection completed, valid: {totalSuccess}, Abnormal: {totalError}"
|
|
print(f"::notice title=📁 Save path: {M3U_FILE}::{summaryMsg}")
|
|
|
|
def main():
|
|
asyncio.run(mainAsync())
|
|
|
|
if __name__ == "__main__":
|
|
main()
|