XML转API

修订: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 -e
40 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 restart

Web效果如下

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

演示地址 http://api.erw.cc/?ch=cctv1&date=2024-10-01

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注