修订:2024-10-01
系统:CentOS 9 Stream
硬件:最低1C1G
环境:Python3.9 Redis7.4 OpenResty1.25.3.2 可选:jemalloc 推荐安装上并集成到OpenResty Redis默认集成
优点:高并发 低延迟
提醒:Redis OpenResty 请自行搭建,操作系统、Redis、OpenResty需自行调优。
注意:该教程需要具备一些基础哟,用宝塔那简单很多了,佛系修改,不提供技术支持。
推荐:修改代码先下载all.xml.gz 执行一次导入7天的数据,之后在修改回e.xml.gz 使用系统自带的定时执行定时下载导入
crontab -e40 00 * * * python3 /home/xmlapi.py
05 10 * * * python3 /home/xmlapi.py
50 14 * * * python3 /home/xmlapi.py必须先安装上Redis 并设置密码 如果使用tcp协议 请修改代码 (为什么要设置密码呢? 暴露在互联网中推荐设置以免中redis病毒变成矿机,部分云商是会封矿机)
新建xmlapi.py文件把以下代码保存到py中
执行命令 python3 xmlapi.py
"""
+--------------------------------------------------------------------------
| 修订: 2024-10-01
+--------------------------------------------------------------------------
| 安装: pip3 install aiohttp aioredis requests redis
+--------------------------------------------------------------------------
| 环境: Python39 Redis7.4
+--------------------------------------------------------------------------
| 警告: 请勿把 e.erw.cc 作为API请求,一触即封。
+--------------------------------------------------------------------------
"""
import aiohttp, asyncio, gzip, shutil, aioredis, json, os, io
import xml.etree.ElementTree as ET
from datetime import datetime
u = 'http://e.erw.cc/e.xml.gz'
l = '/home/e.xml.gz'
e = '/home/e.xml'
async def d(f):
if os.path.exists(f):
os.remove(f)
print(f"文件已删除: {f}")
else:
print(f"文件不存在: {f}")
async def gz(u, g, x):
h = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64 erw) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'
}
async with aiohttp.ClientSession() as session:
async with session.get(u, headers=h) as resp:
if resp.status == 404:
print(f"错误: 文件不存在或地址错误: {u}")
exit(1)
rd = await resp.read()
with gzip.GzipFile(fileobj=io.BytesIO(rd)) as fin:
with open(x, 'wb') as fout:
shutil.copyfileobj(fin, fout)
async def log(dl, s, o, f):
log_dir = '/home/wwwlogs'
log_file = os.path.join(log_dir, 'xmlapi.log')
if not os.path.exists(log_dir):
os.makedirs(log_dir)
with open(log_file, 'a', encoding='utf-8') as m:
m.write(f"{datetime.now()} - 开始时间: {s} 结束时间: {o} 日期: {f}. 插入频道名称: '精彩节目'.\n")
async def j(redis, d, f, p):
k = f"{d}:{f}"
j = {
"date": f,
"channel_name": d,
"url": "e.erw.cc",
"epg_data": p
}
await redis.set(k, json.dumps(j))
await redis.expire(k, 7 * 24 * 3600)
async def main():
await gz(u, l, e)
if not os.path.exists(e):
print(f"错误: 解压后的文件不存在: {e}")
exit(1)
try:
t = ET.parse(e)
x = t.getroot()
except ET.ParseError as p:
print(f"XML 解析错误: {p}")
exit(1)
redis = aioredis.from_url(
"unix:///tmp/redis.sock?password=123456",
encoding='utf-8',
decode_responses=True
)
# st = datetime.now()
for n in x.findall('channel'):
i = n.get('id')
dl = n.find('display-name').text.lower()
if dl:
p = {}
for r in x.findall('programme'):
if r.get('channel') == i:
s = r.get('start').split(" ")[0]
o = r.get('stop').split(" ")[0]
date_str = s[:8] # YYYYMMDD
f = datetime.strptime(date_str, '%Y%m%d').strftime('%Y-%m-%d')
t = r.find('title')
if t is not None and t.text:
t = t.text.lower()
else:
t = "精彩节目"
await log(dl, s, o, f)
h = datetime.strptime(s, '%Y%m%d%H%M%S').strftime('%H:%M')
m = datetime.strptime(o, '%Y%m%d%H%M%S').strftime('%H:%M')
q = {
"start": h,
"end": m,
"title": t,
# "desc": ""
}
if f not in p:
p[f] = []
p[f].append(q)
for f, r in p.items():
await j(redis, dl, f, r)
# et = datetime.now()
# duration = (et - st).total_seconds()
await d(e)
await redis.close()
# print(f"导入 Redis 完成,用时: {duration:.2f} 秒")
if __name__ == '__main__':
asyncio.run(main())效果如下

新建 xmlapi.lua
local redis_module = require("resty.redis")
-- 创建 Redis 连接
local function redis()
local red = redis_module:new()
red:set_timeout(1000) -- 1 秒超时
-- 连接到 Redis Unix 套接字
local ok, err = red:connect("unix:/tmp/redis.sock")
if not ok then
ngx.log(ngx.ERR, "无法连接到 Redis: ", err)
return nil, err
end
-- 验证 Redis 密码
local res, err = red:auth("123456")
if not res then
ngx.log(ngx.ERR, "密码错误: ", err)
return nil, err
end
return red
end
-- 处理请求并返回响应
local function handle()
local redis_instance, err = redis()
if not redis_instance then
ngx.status = ngx.HTTP_INTERNAL_SERVER_ERROR
ngx.say('{"error": "无法连接到 Redis"}')
return ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local args = ngx.req.get_uri_args()
local ch = args.ch and string.lower(args.ch) or nil
local date = args.date
if not ch then
ngx.status = ngx.HTTP_NOT_FOUND
return ngx.exit(404)
end
local key
if date then
date = date:gsub("(%d%d%d%d)(%d%d)(%d%d)", "%1-%2-%3")
if not date:match("%d%d%d%d%-%d%d%-%d%d") then
ngx.status = ngx.HTTP_NOT_FOUND
return ngx.exit(404)
end
key = ch .. ":" .. date
else
key = "noepg:" .. os.date("%Y-%m-%d")
end
local res, err = redis_instance:get(key)
if res and res ~= ngx.null then
ngx.header["Content-Type"] = "application/json"
ngx.say(res)
else
if key:match("^noepg:") then
local default_res, default_err = redis_instance:get("noepg:" .. os.date("%Y-%m-%d"))
if default_res and default_res ~= ngx.null then
ngx.header["Content-Type"] = "application/json"
ngx.say(default_res)
else
ngx.status = ngx.HTTP_NOT_FOUND
return ngx.exit(404)
end
else
ngx.status = ngx.HTTP_NOT_FOUND
return ngx.exit(404)
end
end
redis_instance:set_keepalive(10000, 100)
end
handle()
使用如下命令后 把 xmlapi.lua 上传 /usr/local/openresty/nginx/lua/
mkdir -p /usr/local/openresty/nginx/lua/
cp /usr/local/openresty/lualib/resty/redis.lua /usr/local/openresty/nginx/lua/Ngin配置如下 访问 http://baidu.com/api/?ch=cctv1&date=2024-09-18 如果不想要api这个 把下面的 /api 修改成 / 地址就变成 http://baidu.com/?ch=cctv1&date=2024-09-18
location /api {
content_by_lua_file lua/xmlapi.lua;
}重启Nginx
service nginx restartWeb效果如下

考虑到会拿来牟利,不更新 xmlapi.lua 不支持模糊匹配