AI 生成: 一个自动采集IPTV频道(央视、卫视、地方台)并生成M3U播放列表的Python工具,支持每日定时更新。

This commit is contained in:
admin
2026-09-28 22:18:45 +08:00
commit ecfc269b49
4 changed files with 357 additions and 0 deletions
+242
View File
@@ -0,0 +1,242 @@
# -*- coding: utf-8 -*-
"""
IPTV采集器主脚本
采集公开IPTV源,解析频道信息,生成M3U播放列表
"""
import os
import re
import sys
import time
import argparse
import logging
from urllib.parse import urlparse
import requests
import config
# 配置日志
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)
def fetch_source(url):
"""下载源内容"""
headers = {"User-Agent": config.USER_AGENT}
try:
resp = requests.get(url, headers=headers, timeout=config.REQUEST_TIMEOUT)
resp.raise_for_status()
return resp.text
except Exception as e:
logger.warning("下载失败 %s: %s", url, e)
return None
def parse_m3u(content):
"""解析M3U格式内容,返回频道列表"""
channels = []
if not content:
return channels
lines = content.splitlines()
i = 0
while i < len(lines):
line = lines[i].strip()
if line.startswith("#EXTINF"):
# 解析属性
name = ""
group = ""
logo = ""
tvg_id = ""
# 提取逗号后的频道名
if "," in line:
name = line.split(",", 1)[1].strip()
# 提取属性
attrs = re.findall(r'(\w+-?\w*)="([^"]*)"', line)
for k, v in attrs:
if k == "group-title":
group = v
elif k == "tvg-logo":
logo = v
elif k == "tvg-id":
tvg_id = v
# 下一行是URL
if i + 1 < len(lines):
url_line = lines[i + 1].strip()
if url_line and not url_line.startswith("#"):
channels.append({
"name": name,
"group": group,
"logo": logo,
"tvg_id": tvg_id,
"url": url_line,
})
i += 1
i += 1
return channels
def parse_txt(content):
"""解析TXT格式内容(每行:频道名,URL)"""
channels = []
if not content:
return channels
for line in content.splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
if "," in line:
parts = line.split(",", 1)
name = parts[0].strip()
url = parts[1].strip()
if url.startswith("http"):
channels.append({
"name": name,
"group": "",
"logo": "",
"tvg_id": "",
"url": url,
})
return channels
def classify_channel(channel):
"""根据频道名和分组信息分类"""
name = channel.get("name", "")
group = channel.get("group", "")
text = f"{name} {group}"
for grp, keywords in config.GROUP_KEYWORDS.items():
for kw in keywords:
if kw.lower() in text.lower():
return grp
return config.DEFAULT_GROUP
def is_valid_url(url):
"""简单校验URL"""
try:
p = urlparse(url)
return p.scheme in ("http", "https") and bool(p.netloc)
except Exception:
return False
def collect_all():
"""采集所有源并合并去重"""
all_channels = []
seen_urls = set()
for src in config.SOURCES:
logger.info("采集源: %s", src)
content = fetch_source(src)
if not content:
continue
# 根据内容判断格式
if "#EXTM3U" in content or "#EXTINF" in content:
channels = parse_m3u(content)
else:
channels = parse_txt(content)
logger.info(" 解析到 %d 个频道", len(channels))
for ch in channels:
url = ch.get("url", "")
if not url or not is_valid_url(url):
continue
if url in seen_urls:
continue
seen_urls.add(url)
ch["group"] = classify_channel(ch)
all_channels.append(ch)
logger.info("合计有效频道: %d", len(all_channels))
return all_channels
def generate_m3u(channels, output_file):
"""生成M3U文件"""
os.makedirs(os.path.dirname(output_file), exist_ok=True)
# 按分组排序
group_order = ["央视", "卫视", "地方台", config.DEFAULT_GROUP]
channels.sort(key=lambda c: (
group_order.index(c["group"]) if c["group"] in group_order else len(group_order),
c["name"],
))
with open(output_file, "w", encoding="utf-8") as f:
f.write("#EXTM3U\n")
for ch in channels:
name = ch.get("name", "未知频道")
group = ch.get("group", config.DEFAULT_GROUP)
logo = ch.get("logo", "")
tvg_id = ch.get("tvg_id", "")
url = ch.get("url", "")
attrs = f'tvg-name="{name}" group-title="{group}"'
if logo:
attrs += f' tvg-logo="{logo}"'
if tvg_id:
attrs += f' tvg-id="{tvg_id}"'
f.write(f"#EXTINF:-1 {attrs},{name}\n")
f.write(f"{url}\n")
logger.info("已生成: %s", output_file)
def run_once():
"""执行一次采集"""
channels = collect_all()
if not channels:
logger.error("未采集到任何频道")
return False
generate_m3u(channels, config.OUTPUT_FILE)
return True
def run_daemon():
"""以守护模式运行,每天更新一次"""
try:
import schedule
except ImportError:
logger.error("守护模式需要安装 schedule: pip install schedule")
sys.exit(1)
logger.info("启动守护模式,每天 02:00 更新")
schedule.every().day.at("02:00").do(run_once)
# 启动时先执行一次
run_once()
while True:
schedule.run_pending()
time.sleep(60)
def main():
parser = argparse.ArgumentParser(description="IPTV采集器")
parser.add_argument("--daemon", action="store_true", help="以守护模式运行,每天自动更新")
args = parser.parse_args()
if args.daemon:
run_daemon()
else:
ok = run_once()
sys.exit(0 if ok else 1)
if __name__ == "__main__":
main()