教程 04
验证码识别 API 多语言调用示例
下面的 cURL、Python、Java、Go、Node.js 和 Rust 示例调用同一个接口:POST /api/v1/recognize。图片放在表单字段 file,密钥放在请求头 X-API-Key。请把密钥保存在环境变量 CAPTCHA_API_KEY 中,不要写进仓库。
查询参数 charset=auto 会保留数字和字母。expected_length=4 按四位验证码校验,位数不同就改这个值,传 0 则关闭长度校验。HTTP 200 只表示请求已处理;先看 accepted,为 false 时不要直接使用 text。
已经持有 Base64 时,改用 Content-Type: application/json,正文为 {"image":"<base64>"},不要再同时上传文件。字段说明见 API 参考,Python 官方客户端见 Python SDK。
cURL
先在服务端将完整密钥保存到 CAPTCHA_API_KEY 环境变量,再上传一张图片。
curl -X POST "https://captchaocr.cn/api/v1/recognize?charset=auto&expected_length=4" \ -H "X-API-Key: $CAPTCHA_API_KEY" \ -F "file=@captcha.png"
Python
使用 requests。官方 SDK 见 Python SDK 文档。
import os
import requests
with open("captcha.png", "rb") as image:
response = requests.post(
"https://captchaocr.cn/api/v1/recognize?charset=auto&expected_length=4",
headers={"X-API-Key": os.environ["CAPTCHA_API_KEY"]},
files={"file": ("captcha.png", image, "image/png")},
timeout=30,
)
response.raise_for_status()
result = response.json()
if result["accepted"]:
print(result["text"], result["confidence"])
else:
print("rejected:", result["rejection_reason"])Java
使用 JDK 11 及以上的 java.net.http,不额外引入 HTTP 库。响应体是 JSON,生产代码建议用 Jackson 读取 accepted 与 text。
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class Recognize {
public static void main(String[] args) throws Exception {
String boundary = "----CaptchaOcrBoundary";
byte[] file = Files.readAllBytes(Path.of("captcha.png"));
byte[] body = multipart(boundary, file);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://captchaocr.cn/api/v1/recognize?charset=auto&expected_length=4"))
.header("X-API-Key", System.getenv("CAPTCHA_API_KEY"))
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(HttpRequest.BodyPublishers.ofByteArray(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
throw new IllegalStateException(response.body());
}
System.out.println(response.body());
}
static byte[] multipart(String boundary, byte[] file) {
String head = "--" + boundary + "\r\n"
+ "Content-Disposition: form-data; name=\"file\"; filename=\"captcha.png\"\r\n"
+ "Content-Type: image/png\r\n\r\n";
String tail = "\r\n--" + boundary + "--\r\n";
byte[] prefix = head.getBytes(StandardCharsets.UTF_8);
byte[] suffix = tail.getBytes(StandardCharsets.UTF_8);
byte[] body = new byte[prefix.length + file.length + suffix.length];
System.arraycopy(prefix, 0, body, 0, prefix.length);
System.arraycopy(file, 0, body, prefix.length, file.length);
System.arraycopy(suffix, 0, body, prefix.length + file.length, suffix.length);
return body;
}
}Go
仅使用标准库 net/http 与 mime/multipart。
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
type recognizeResult struct {
Text string `json:"text"`
Accepted bool `json:"accepted"`
Confidence float64 `json:"confidence"`
RejectionReason *string `json:"rejection_reason"`
}
func main() {
file, err := os.Open("captcha.png")
if err != nil {
panic(err)
}
defer file.Close()
var body bytes.Buffer
writer := multipart.NewWriter(&body)
part, err := writer.CreateFormFile("file", "captcha.png")
if err != nil {
panic(err)
}
if _, err = io.Copy(part, file); err != nil {
panic(err)
}
writer.Close()
req, err := http.NewRequest(http.MethodPost, "https://captchaocr.cn/api/v1/recognize?charset=auto&expected_length=4", &body)
if err != nil {
panic(err)
}
req.Header.Set("X-API-Key", os.Getenv("CAPTCHA_API_KEY"))
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
payload, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
panic(string(payload))
}
var result recognizeResult
if err = json.Unmarshal(payload, &result); err != nil {
panic(err)
}
if result.Accepted {
fmt.Println(result.Text, result.Confidence)
return
}
fmt.Println("rejected:", result.RejectionReason)
}Node.js
需要 Node.js 18 及以上,使用全局 fetch 与 FormData。
import { readFile } from "node:fs/promises";
const bytes = await readFile("captcha.png");
const form = new FormData();
form.append("file", new Blob([bytes], { type: "image/png" }), "captcha.png");
const response = await fetch("https://captchaocr.cn/api/v1/recognize?charset=auto&expected_length=4", {
method: "POST",
headers: { "X-API-Key": process.env.CAPTCHA_API_KEY },
body: form,
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.message ?? "recognize failed");
}
if (result.accepted) {
console.log(result.text, result.confidence);
} else {
console.log("rejected:", result.rejection_reason);
}Rust
使用 reqwest 的 multipart 功能。依赖写在示例顶部的 Cargo.toml 注释里。
// Cargo.toml
// reqwest = { version = "0.12", features = ["multipart", "json"] }
// tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
// serde = { version = "1", features = ["derive"] }
use serde::Deserialize;
#[derive(Deserialize)]
struct Recognize {
text: String,
accepted: bool,
confidence: f64,
rejection_reason: Option<String>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let bytes = std::fs::read("captcha.png")?;
let part = reqwest::multipart::Part::bytes(bytes)
.file_name("captcha.png")
.mime_str("image/png")?;
let form = reqwest::multipart::Form::new().part("file", part);
let response = reqwest::Client::new()
.post("https://captchaocr.cn/api/v1/recognize?charset=auto&expected_length=4")
.header("X-API-Key", std::env::var("CAPTCHA_API_KEY")?)
.multipart(form)
.send()
.await?;
let status = response.status();
if !status.is_success() {
return Err(response.text().await?.into());
}
let result: Recognize = response.json().await?;
if result.accepted {
println!("{} {}", result.text, result.confidence);
} else {
println!("rejected: {:?}", result.rejection_reason);
}
Ok(())
}