Aktools-解析动态接口调用-002


让我详细解析动态接口调用的实现。这个功能主要在 core/api.py 中实现,我们来看关键部分:

  1. 首先,看看公开接口的实现(private 接口实现类似,只是多了认证):
@app_core.get(path="/public/{item_id}", description="公开接口", summary="该接口主要提供公开访问来获取数据")
def root(request: Request, item_id: str):
    """
    接收请求参数及接口名称并返回 JSON 数据
    :param request: 请求信息
    :param item_id: 必选参数; 测试接口名 stock_dxsyl_em 来获取 打新收益率 数据
    :return: 指定 接口名称 和 参数 的数据
    """

动态调用的核心实现包含以下几个关键步骤:

  1. 接口存在性验证
interface_list = dir(ak)  # 获取 akshare 模块中所有可用的接口
if item_id not in interface_list:
    logger.info("未找到该接口,请升级 AKShare 到最新版本...")
    return JSONResponse(
        status_code=status.HTTP_404_NOT_FOUND,
        content={
            "error": "未找到该接口,请升级 AKShare..."
        },
    )
  1. 参数处理
# URL 解码,处理 URL 中的特殊字符
decode_params = urllib.parse.unquote(str(request.query_params))

# 特殊处理 cookie 参数的情况
if "cookie" in decode_params:
    eval_str = (
            decode_params.split(sep="=", maxsplit=1)[0]
            + "='"
            + decode_params.split(sep="=", maxsplit=1)[1]
            + "'"
    )
    eval_str = eval_str.replace("+", " ")
else:
    # 将 URL 参数转换为 Python 函数参数格式
    eval_str = decode_params.replace("&", '", ').replace("=", '="') + '"'
    eval_str = eval_str.replace("+", " ")  # 处理空格
  1. 动态调用实现
# 无参数调用
if not bool(request.query_params):
    try:
        # 使用 eval 动态执行 akshare 接口调用
        received_df = eval("ak." + item_id + "()")
        if received_df is None:
            return JSONResponse(
                status_code=status.HTTP_404_NOT_FOUND,
                content={"error": "该接口返回数据为空,请确认参数是否正确"}
            )
        # 将返回的 DataFrame 转换为 JSON
        temp_df = received_df.to_json(orient="records", date_format="iso")
    except KeyError as e:
        return JSONResponse(
            status_code=status.HTTP_404_NOT_FOUND,
            content={
                "error": f"请输入正确的参数错误 {e}..."
            },
        )
    return JSONResponse(status_code=status.HTTP_200_OK, content=json.loads(temp_df))
else:
    # 有参数调用
    try:
        # 构造带参数的函数调用
        received_df = eval("ak." + item_id + f"({eval_str})")
        if received_df is None:
            return JSONResponse(
                status_code=status.HTTP_404_NOT_FOUND,
                content={"error": "该接口返回数据为空..."}
            )
        temp_df = received_df.to_json(orient="records", date_format="iso")
    except KeyError as e:
        return JSONResponse(
            status_code=status.HTTP_404_NOT_FOUND,
            content={
                "error": f"请输入正确的参数错误 {e}..."
            },
        )
    return JSONResponse(status_code=status.HTTP_200_OK, content=json.loads(temp_df))

让我用一个具体的例子来说明这个动态调用过程:

假设用户想要调用 AKShare 的 stock_zh_a_spot_em 接口获取 A 股实时行情数据,URL 可能是:

GET /api/public/stock_zh_a_spot_em

处理流程如下:

  1. 接口验证
  2. 系统首先检查 stock_zh_a_spot_em 是否在 dir(ak) 中存在
  3. 如果不存在,返回 404 错误

  4. 参数处理

  5. 如果 URL 带有参数,比如:/api/public/stock_zh_a_spot_em?symbol=000001
  6. decode_params 会得到 symbol=000001
  7. 转换后的 eval_str 会变成 symbol="000001"

  8. 动态执行

  9. 系统会构造并执行:eval("ak.stock_zh_a_spot_em(symbol=\"000001\")")
  10. 这相当于在 Python 中直接调用:ak.stock_zh_a_spot_em(symbol="000001")

  11. 结果处理

  12. 获取到的 DataFrame 数据会被转换为 JSON 格式
  13. 通过 HTTP 响应返回给客户端

这种实现的优点: 1. 灵活性:可以动态调用 AKShare 中的任何接口,无需为每个接口单独编写代码 2. 可扩展性:当 AKShare 添加新接口时,无需修改 AKTools 代码 3. 参数兼容:支持各种参数类型的传递,包括特殊情况(如 cookie)

需要注意的安全考虑: 1. 使用 eval 进行动态调用可能存在安全风险,但这里通过以下方式降低风险: - 严格验证接口名称必须存在于 dir(ak) 中 - 参数值都被作为字符串处理 - 只允许调用 ak 模块中的方法

这就是 AKTools 动态接口调用的核心实现原理。它通过 Python 的动态特性,巧妙地将 HTTP 请求转换为 AKShare 接口调用,实现了一个灵活且可扩展的 API 系统。