#!/usr/bin/env python3 """PHPWind 验证码手动标注 Web 服务。 显示验证码大图,输入数字回车保存并跳下一张。标签存 JSON。 用法: label_server.py <图片目录> <标签文件> [端口] [host] """ import glob import io import json import os import sys from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from urllib.parse import urlparse from PIL import Image IMG_DIR = os.path.abspath(sys.argv[1]) LABEL_FILE = sys.argv[2] PORT = int(sys.argv[3]) if len(sys.argv) > 3 else 8000 HOST = sys.argv[4] if len(sys.argv) > 4 else "0.0.0.0" _upscale_cache = {} def load_labels(): if os.path.exists(LABEL_FILE): return json.load(open(LABEL_FILE, encoding="utf-8")) return {} def save_labels(labels): json.dump(labels, open(LABEL_FILE, "w", encoding="utf-8"), ensure_ascii=False, indent=1) def all_images(): return sorted(os.path.basename(f) for f in glob.glob(os.path.join(IMG_DIR, "*.png"))) def upscaled_bytes(name): if name in _upscale_cache: return _upscale_cache[name] p = os.path.join(IMG_DIR, name) if not os.path.exists(p): return None im = Image.open(p).convert("RGB") im = im.resize((im.width * 3, im.height * 3), Image.LANCZOS) buf = io.BytesIO() im.save(buf, "PNG") _upscale_cache[name] = buf.getvalue() return _upscale_cache[name] PAGE = """ PHPWind 验证码标注

PHPWind 验证码标注

加载中...
captcha
还没有标注记录
""" class Handler(BaseHTTPRequestHandler): labels = load_labels() def _send(self, code, body, ctype): self.send_response(code) self.send_header("Content-Type", ctype) self.send_header("Content-Length", str(len(body))) self.send_header("Cache-Control", "no-store") self.end_headers() self.wfile.write(body) def _json(self, obj): self._send(200, json.dumps(obj).encode(), "application/json") def do_GET(self): u = urlparse(self.path) if u.path == "/": self._send(200, PAGE.encode(), "text/html; charset=utf-8") elif u.path == "/api/images": total = all_images() labeled = set(Handler.labels.keys()) self._json({"images": [n for n in total if n not in labeled], "total": len(total), "labeled": len(labeled)}) elif u.path == "/api/stats": self._json({"labeled": len(Handler.labels), "total": len(all_images())}) elif u.path.startswith("/img/"): name = os.path.basename(u.path[len("/img/"):]) data = upscaled_bytes(name) if data is None: self._send(404, b"not found", "text/plain") else: self._send(200, data, "image/png") else: self._send(404, b"not found", "text/plain") def do_POST(self): u = urlparse(self.path) if u.path == "/api/label": length = int(self.headers.get("Content-Length", 0)) try: data = json.loads(self.rfile.read(length)) except Exception: data = {} name, label = data.get("name", ""), str(data.get("label", "")).strip() if name and label: Handler.labels[name] = {"label": label, "votes": [label], "count": 1, "agreed": True} save_labels(Handler.labels) self._json({"ok": True, "labeled": len(Handler.labels)}) else: self._send(404, b"not found", "text/plain") def log_message(self, fmt, *args): pass def main(): os.makedirs(IMG_DIR, exist_ok=True) srv = ThreadingHTTPServer((HOST, PORT), Handler) print(f"标注服务已启动: http://{HOST}:{PORT} 图片目录={IMG_DIR} 标签文件={LABEL_FILE}", flush=True) print(f"待标图片数: {len(all_images())} 已标: {len(Handler.labels)}", flush=True) srv.serve_forever() if __name__ == "__main__": main()