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
+83
View File
@@ -0,0 +1,83 @@
# IPTV Collector
自动采集央视、卫视、地方台等IPTV频道,生成标准的M3U播放列表,支持每日定时更新。
## 功能特性
- 从多个公开源采集频道信息
- 自动分类:央视、卫视、地方台
- 生成标准M3U格式播放列表
- 支持每日自动更新(通过cron或内置调度)
- 简洁实用,易于扩展
## 目录结构
```
iptv-collector/
├── collector.py # 主采集脚本
├── config.py # 配置文件
├── requirements.txt # 依赖
├── output/ # 输出目录(自动生成)
│ └── iptv.m3u # 生成的播放列表
└── README.md
```
## 安装
```bash
pip install -r requirements.txt
```
## 使用
### 单次采集
```bash
python collector.py
```
生成的文件位于 `output/iptv.m3u`。
### 每日自动更新
#### 方式一:使用cron(Linux/macOS)
```bash
# 每天凌晨2点执行
0 2 * * * cd /path/to/iptv-collector && python collector.py >> collector.log 2>&1
```
#### 方式二:使用内置调度(需安装schedule)
```bash
python collector.py --daemon
```
### 自定义配置
编辑 `config.py` 可修改:
- 采集源URL
- 输出路径
- 请求超时时间
- 用户代理
## 输出格式
生成的M3U文件示例:
```
#EXTM3U
#EXTINF:-1 tvg-name="CCTV-1" group-title="央视",CCTV-1 综合
http://example.com/cctv1.m3u8
...
```
## 注意事项
- 采集源为公开网络资源,请遵守相关法律法规
- 部分源可能失效,脚本会自动过滤无效链接
- 建议定期检查更新日志
## 许可证
MIT
+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()
+30
View File
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
"""配置文件"""
# 采集源列表(公开的IPTV源)
# 每个源应返回M3U或TXT格式的频道列表
SOURCES = [
# 示例源,实际使用时请替换为可用的公开源
"https://raw.githubusercontent.com/iptv-org/iptv/master/streams/cn.m3u",
"https://raw.githubusercontent.com/iptv-org/iptv/master/streams/cn_sichuan.m3u",
"https://raw.githubusercontent.com/iptv-org/iptv/master/streams/cn_guangdong.m3u",
]
# 输出文件路径
OUTPUT_FILE = "output/iptv.m3u"
# 请求超时时间(秒)
REQUEST_TIMEOUT = 15
# 用户代理
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
# 频道分组关键词映射
GROUP_KEYWORDS = {
"央视": ["CCTV", "央视", "CCTV-"],
"卫视": ["卫视", "TVS", "星空", "凤凰"],
"地方台": ["地方", "市", "县", "区", "广东", "四川", "湖南", "浙江", "江苏", "山东", "河南"],
}
# 默认分组
DEFAULT_GROUP = "其他"
+2
View File
@@ -0,0 +1,2 @@
requests>=2.28.0
schedule>=1.2.0