MIYAIP / DEVELOPERS

抓取 API 接入文档

使用 cURL 或 Python 查询接口、提交抓取任务并获取结果。了解登录鉴权、动态参数、credits 与错误处理。

01 / QUICK START

从接口到结果

基址:https://miyaip.com。使用本人控制台登录 Token,认证头为 Authorization: Bearer <LOGIN_TOKEN>。这是会话凭证,不是永久公开 API Key;MCP 密钥仅用于 MCP 服务。

先登录控制台。在浏览器开发者工具的网络面板中查看本人 /api/SysLogin/GetUserInfo 请求,仅复制 Authorization 头中 Bearer 后的 Token。请妥善保管,会话失效后重新获取;本页不会要求你粘贴凭证。 打开控制台 →

以下命令使用 Bash、支持 --fail-with-body 的 cURL 及 Python 3.8+。Windows 用户可在所用 Shell 中设置相同环境变量。请求使用你的账户,创建任务可能消耗额度。

1. 查询可用接口

# Bash: enter your own console login token without echoing it.
read -rsp 'Console token: ' MIYA_CONSOLE_TOKEN; echo
export MIYA_CONSOLE_TOKEN
export MIYA_INTERFACE_KEY='replace-with-a-key-from-the-list'
curl --fail-with-body -sS --max-time 30 \
  -H "Authorization: Bearer $MIYA_CONSOLE_TOKEN" \
  https://miyaip.com/api/CrawlerInterface/List

2. 读取所选接口参数

curl --fail-with-body -sS --max-time 30 --get \
  -H "Authorization: Bearer $MIYA_CONSOLE_TOKEN" \
  --data-urlencode "interfaceKey=$MIYA_INTERFACE_KEY" \
  https://miyaip.com/api/CrawlerInterface/Detail

3. 提交一次任务

# Save ONLY the selected interface's parameter object in params.json.
python -c 'import json,os; p=json.load(open("params.json",encoding="utf-8")); print(json.dumps({"InterfaceKey":os.environ["MIYA_INTERFACE_KEY"],"Params":p}))' > request.json
curl --fail-with-body -sS --max-time 30 \
  -H "Authorization: Bearer $MIYA_CONSOLE_TOKEN" \
  -H 'Content-Type: application/json' \
  --data-binary @request.json \
  https://miyaip.com/api/Crawler/Invoke

4. 查询返回的任务 ID

export MIYA_TASK_ID='replace-with-returned-taskId'
curl --fail-with-body -sS --max-time 30 --get \
  -H "Authorization: Bearer $MIYA_CONSOLE_TOKEN" \
  --data-urlencode "taskId=$MIYA_TASK_ID" \
  https://miyaip.com/api/Crawler/TaskResult

完成第 2 步后,按该接口实际字段和必填值创建 params.json;仅无必填参数时可使用 {}。同时检查 HTTP 状态和业务码。首次响应包含 taskId;running 时只重复查询 TaskResult,通常间隔五秒。

02 / PYTHON

完整且有等待时限的调用脚本

仅使用标准库。脚本先检查接口定义,再提交一次并轮询。stdout 输出最终任务对象,stderr 包含任务 ID;失败时退出码为 1。遇到未知状态会报错停止,不会无限等待。

下载 crawler_api.py ↓
python crawler_api.py "$MIYA_INTERFACE_KEY" params.json --wait-seconds 600
展开查看 / 复制完整 Python 脚本

crawler_api.py

"""MIYAIP crawler example. Python 3.8+, standard library only.

Set MIYA_CONSOLE_TOKEN, then run:
  python crawler_api.py INTERFACE_KEY params.json
Inspect available interfaces first with the cURL request in the public docs.
"""
import argparse
import json
import math
import os
import sys
import time
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode, urlsplit
from urllib.request import HTTPRedirectHandler, Request, build_opener


class NoRedirect(HTTPRedirectHandler):
    # Do not forward login credentials to a redirected host.
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None


def field(value, name, default=None):
    return value.get(name, value.get(name[0].upper() + name[1:], default))


def request(base, token, path, data=None, timeout=30):
    req = Request(base + path, headers={"Authorization": "Bearer " + token})
    if data is not None:
        req.data = json.dumps(data).encode("utf-8")
        req.add_header("Content-Type", "application/json")
    try:
        with build_opener(NoRedirect()).open(req, timeout=timeout) as response:
            payload = json.load(response)
    except HTTPError as error:
        raise RuntimeError("HTTP %s; check authentication (401), permissions or service availability. Do not blindly resubmit." % error.code) from None
    except (URLError, TimeoutError, OSError):
        raise RuntimeError("Network error; check call logs before resubmitting an uncertain creation request.") from None
    except (ValueError, UnicodeError):
        raise RuntimeError("Invalid JSON response") from None
    if isinstance(payload, dict):
        if "code" in payload and (type(payload["code"]) is not int or payload["code"] not in (0, 200)):
            raise RuntimeError("Business error %s: %s" % (payload.get("code"), payload.get("message", "Request failed")))
        return payload.get("body", payload.get("Body", payload))
    return payload


def run(base, token, interface_key, params, wait_seconds=600, interval=5):
    parsed = urlsplit(base)
    if parsed.username or parsed.password or parsed.query or parsed.fragment or parsed.path not in ("", "/"):
        raise ValueError("Base URL must be an origin without credentials, path or query")
    if parsed.scheme != "https" and not (parsed.scheme == "http" and parsed.hostname in ("localhost", "127.0.0.1")):
        raise ValueError("HTTPS is required (HTTP loopback is allowed for local tests)")
    if not token.strip() or not interface_key.strip() or not isinstance(params, dict):
        raise ValueError("Token, interface key and a JSON parameter object are required")
    if not math.isfinite(wait_seconds) or not math.isfinite(interval) or wait_seconds <= 0 or interval <= 0:
        raise ValueError("Wait and interval must be finite and positive")
    base = base.rstrip("/")
    mapping = request(base, token, "/api/CrawlerInterface/Detail?" + urlencode({"interfaceKey": interface_key}))
    if not isinstance(mapping, dict) or field(mapping, "interfaceKey") != interface_key:
        raise RuntimeError("Interface definition missing or mismatched")
    if mapping.get("isEnabled") is False or mapping.get("IsEnabled") is False:
        raise RuntimeError("Interface is disabled")
    # Params must follow the returned ParamSchema. The server validates the contract.
    created = request(base, token, "/api/Crawler/Invoke", {"InterfaceKey": interface_key, "Params": params})
    task_id = field(created, "taskId") if isinstance(created, dict) else None
    if not isinstance(task_id, str) or not task_id.strip():
        raise RuntimeError("Missing taskId; inspect call logs before resubmitting")
    print("Task ID: " + task_id, file=sys.stderr)
    deadline = time.monotonic() + wait_seconds
    while time.monotonic() < deadline:
        remaining = deadline - time.monotonic()
        if remaining <= 0:
            break
        result = request(base, token, "/api/Crawler/TaskResult?" + urlencode({"taskId": task_id}), timeout=min(30, remaining))
        if not isinstance(result, dict) or not isinstance(field(result, "status"), str):
            raise RuntimeError("Invalid task response; query the existing task ID later")
        status = field(result, "status").strip().lower()
        if status in ("success", "succeeded", "completed", "complete"):
            return result
        if status in ("failed", "fail", "error", "timeout", "timedout", "time_out"):
            raise RuntimeError("Task %s: %s" % (status, field(result, "error", "No error detail")))
        if status != "running":
            raise RuntimeError("Unknown task status: " + status)
        time.sleep(min(interval, max(0, deadline - time.monotonic())))
    raise RuntimeError("Waiting timed out. Task %s may still be running; check TaskResult or call logs. No new task was submitted." % task_id)


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("interface_key")
    parser.add_argument("params_file")
    parser.add_argument("--wait-seconds", type=float, default=600)
    args = parser.parse_args()
    try:
        with open(args.params_file, encoding="utf-8") as source:
            params = json.load(source)
        result = run("https://miyaip.com", os.environ.get("MIYA_CONSOLE_TOKEN", ""), args.interface_key, params, args.wait_seconds)
        print(json.dumps(result, ensure_ascii=False, indent=2))
    except (OSError, ValueError, RuntimeError) as error:
        print(str(error), file=sys.stderr)
        return 1
    return 0


if __name__ == "__main__":
    sys.exit(main())

03 / REFERENCE

五个接口,一条任务流程

JSON 响应通常为 { code, message, body },数字业务码 0 和 200 表示成功。响应兼容 body/Body 及模型字段 camelCase/PascalCase;请求参数请保留下文规定的大小写。

GET /api/CrawlerInterface/List

请求: 无参数。

响应: body 为接口数组。包含 InterfaceKey、DisplayName、Description、ParamSchema、CreditCostBase、CreditCostParam、CreditCostMultiplier、IsEnabled。

GET /api/CrawlerInterface/Detail

请求: query: interfaceKey (string)

响应: body 为单个接口定义,包括当前参数和计费元数据。

POST /api/Crawler/Invoke

请求: JSON: { "InterfaceKey": string, "Params": object }

响应: body 为 { taskId, status: "running", costCredits }。创建任务,不直接返回最终数据。

GET /api/Crawler/TaskResult

请求: query: taskId (string)

响应: body 包含 taskId、interfaceKey、interfaceName、status、result、error、costCredits、totalCostMs、startedAt、finishedAt。

GET /api/Crawler/CallLogPage

请求: query: PageNo=1, PageSize=10, KeyWord, InterfaceKey, Status, SearchBeginTime, SearchEndTime

响应: body 包含 records 和 totalRows(也兼容 Records、TotalRows、total、Total)。记录包含 taskId、requestJson、resultJson、errorMessage、status、costCredits、totalCostMs、refunded。

日志筛选条件可选,分页示例为 PageNo=1、PageSize=10。Status 可用 running/success/failed/timeout。日期边界格式为 YYYY-MM-DD 00:00:00 与 YYYY-MM-DD 23:59:59,统计前需确认服务端时区。UserId 属于管理员筛选参数,并不授予其他用户数据的访问权限。

04 / INPUT

以接口的 ParamSchema 为准

ParamSchema 是内容为字段定义数组的 JSON 字符串,不是标准 JSON Schema 文档。请先解析字符串。下例仅说明结构,不代表某个在线接口的参数契约。

[
  {
    "name": "url",
    "label": "URL",
    "type": "string",
    "required": true
  },
  {
    "name": "limit",
    "label": "Limit",
    "type": "int",
    "min": 1,
    "max": 100,
    "default": 10
  }
]
  • name 为提交字段名,label 为展示文本;required/default 指定必填与默认值。
  • 类型包括 string、int、number、float、bool、enum、string[]。检查有限数值、整数要求、min/max 和枚举 options。
  • showWhen 指定条件字段:不同键之间为 AND,候选值之间为 OR。不满足条件的字段应省略;hidden/advanced/group 是展示提示,不代表该值无需提交。
  • 请求使用 JSON 布尔值和数组。渲染、会话及输出选项是否可用,只能以返回的接口定义为准。

05 / OUTPUT

创建成功不等于抓取完成

success、failed 或 timeout 为终态。success 后解析 result,并验证是否包含应用所需的数据。result 可能是对象、数组、字符串或 null;totalCostMs 单位为毫秒。下例是最终响应示意,不是固定结果 Schema。

{
  "code": 200,
  "message": "",
  "body": {
    "taskId": "example-task-id",
    "interfaceKey": "example-interface",
    "status": "success",
    "result": {
      "items": []
    },
    "error": null,
    "costCredits": 2,
    "totalCostMs": 3200,
    "startedAt": "2026-09-05T10:00:00Z",
    "finishedAt": "2026-09-05T10:00:03.200Z"
  }
}
情况处理
HTTP 401 / 业务码 401重新登录并更换控制台 Token,不要使用 MCP 密钥。
其他 HTTP 失败检查状态、权限和服务响应。Invoke 网络超时不代表创建失败;重试前先检查日志。
业务码不是 0/200即使 HTTP 为 200 也按失败处理。读取 message,不作为成功结果解析。
failed / timeout任务终态。读取 error 并检查日志,不保证退款。
本地等待超时保留 taskId,稍后查询。结束脚本不会取消服务端任务。
响应格式异常如已取得任务 ID,请保留。反馈问题时不要分享 Token,也不要自动重复提交。

SignalR 为可选机制:控制台使用登录 Token 订阅 PublicCrawlerTaskStatus,Hub 地址取决于部署配置。没有确认的 Hub 地址时采用 HTTP 轮询即可。推送与轮询对应同一个任务,不要为重新连接创建新任务。

06 / COSTS

从任务读取实际消耗

CreditCostBase + Params[CreditCostParam] × CreditCostMultiplier

存在计费参数和乘数时使用该预估公式,否则按基础消耗。返回的消耗以 costCredits 和调用日志为准。refunded 是记录状态,不表示所有失败均免费。查询现有任务与创建新任务不同;当前契约未提供独立轮询费率。

并发限制同时运行的任务,不代表每个结果包含的数据量。本文不规定任务保留期、退款时点或限流错误码。下载脚本默认单请求超时为 30 秒、轮询期限为 600 秒;这是客户端设置,不是服务 SLA。