
Wps Ocr
- 8 installs
- 33 repo stars
- Updated April 26, 2026
- bighardperson/computer-science-skills-collection
WPS OCR is a Claude skill that extracts text, tables, formulas, and seals from image and scanned files into Markdown using the WPS Kingsoft cloud OCR API.
About
WPS OCR extracts text, handwriting, formulas, tables, and seals from files into Markdown structure. It sends the file to the WPS/Kingsoft cloud OCR service at aiwrite.wps.cn and returns the recognized text plus detection details. It supports scanned documents, screenshots, and photos in formats like JPG, PNG, BMP, HEIF, and WEBP. A developer uses it as a first step to digitize file content for translation or editing. It requires a WPS_OCR_ACCESS_KEY.
- Extracts text, tables, formulas, and seals from files into Markdown structure
- Calls the WPS/Kingsoft cloud OCR API (aiwrite.wps.cn), needs WPS_OCR_ACCESS_KEY
- Supports JPG, PNG, BMP, HEIF, and WEBP scans, screenshots, and photos
Wps Ocr by the numbers
- 8 all-time installs (skills.sh)
- Ranked #500 of 687 Office & Documents skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
wps-ocr capabilities & compatibility
Requires a WPS_OCR_ACCESS_KEY; the current cloud version is a rate-limited free trial.
- Capabilities
- ocr · text recognition · pdf parsing
- Use cases
- pdf parsing · translation · documentation
- Runs
- Local or remote
- Pricing
- Bring your own API key
What wps-ocr says it does
extract text, handwritten text, formulas, tables, documents and seals from files into Markdown structure
compatible with multiple file formats including JPG, PNG, BMP, HEIF and WEBP
This skill will send the file you provide to the official Kingsoft Office server (aiwrite.wps.cn) for recognition.
Requires WPS OCR credentials via environment variables. No credential hardcoding. Enforces domain allowlist in code.
npx skills add https://github.com/bighardperson/computer-science-skills-collection --skill wps-ocrAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 33 |
| Last updated | April 26, 2026 |
| Repository | bighardperson/computer-science-skills-collection ↗ |
What it does
Extract text, tables, and formulas from images and scanned files into Markdown via the WPS cloud OCR API.
Who is it for?
Digitizing scanned documents, screenshots, and photos into text and Markdown
Skip if: Fully offline use; it must send files to the WPS cloud OCR service
When should I use this skill?
The user sends a file and asks to extract text, or needs OCR before translating or editing
What you get
Recognized text and structured Markdown from images and scanned files
- recognized text string
- Markdown-structured output
- detection details
By the numbers
- 5 supported image formats (JPG, PNG, BMP, HEIF, WEBP)
- 4-step execution flow
Files
🧭 Must-Read Before Use (30 Seconds)
[!WARNING]
⚠️ Important Privacy & Data Flow Notice
- Service Interaction Required: This skill will send the file you provide to the official Kingsoft Office server (aiwrite.wps.cn) for recognition.
- Data Visibility: Kingsoft Office services will access and process the content of your file.
- This skill supports local file uploads, and will only verify the file type without performing any verification on the path.
✅ Recommended Method: Environment Variables (Permission-Free, Instant Effect, Webchat-Friendly)
# Run in the terminal (effective immediately for the current session):
export WPS_OCR_ACCESS_KEY="your_client_access_key"# Append the credential to the ~/.openclaw/env file
echo 'export WPS_OCR_ACCESS_KEY="your_client_access_key"' >> ~/.openclaw/env[!TIP] 🔧 How to obtain the key?
- Get your API key: https://aiwrite.wps.cn/pdf/parse/accesskey/
✅ Environment Dependency Check Make sure the required libraries are installed:
pip install requests🎯 Skill Execution Guide
1. Applicable Scenarios
Invoke this skill when the user’s intent includes any of the following:
- Sends a file and asks “What text is this?”, “Extract text”, or “Convert to text”.
- Uploads document screenshots, invoices, business cards, photos, or scanned files with mixed Chinese and English text to be recognized.
- Needs to translate or edit the file content (text extraction is a required first step).
2. Execution Actions
Once it is confirmed that text extraction is required, perform the following operations immediately: Input Processing: Obtain the file resource provided by the user (using a download link: url or a local file: path). Command Execution: Call the Python script for recognition. If the current environment supports command-line execution, construct the command as follows:
# use file download URL:
python3 skills/wps-ocr/scripts/wps_ocr.py --url <URL>
# use local file:
python3 skills/wps-ocr/scripts/wps_ocr.py --path <LOCAL-PATH>Execution Flow
1. File Acquisition
The file will be sent to Kingsoft Office Cloud Service, which will download the file provided by the user.
2. File Validation
Verify that the file is in a supported format.
3. Recognize File Content
Identify elements such as text, images, tables, formulas, and other content in the file, and extract the text. ⚠️ Note: Image elements will be returned as placeholders; file elements will not be returned.
4. Return Results to the User
On success: Return all recognized text (concatenated into one string) and detailed detection information. On failure: Return error messages (e.g., "No text detected in the file", "API call failed", etc.).
OCR API Usage Notes
This skill relies on the WPS-OCR parsing and recognition capabilities hosted on Kingsoft Cloud Service. The current version is a free trial. To ensure stable operation, the cloud service enforces rate limiting. The service will reject requests under high concurrency; please use it appropriately. To experience the full features, visit the demo platform.
{
"ownerId": "kn7cwybqsdpkkjxmysjhdajk8h83snre",
"slug": "wps-ocr",
"version": "1.0.1",
"publishedAt": 1775037546751
}{
"version": 1,
"registry": "https://clawhub.ai",
"slug": "wps-ocr",
"installedVersion": "1.0.1",
"installedAt": 1776068761513
}
#!/usr/bin/env python3
import argparse
import base64
import json
import os
import re
import sys
import socket
import ipaddress
from typing import Tuple, Dict, Any, Optional
from dataclasses import dataclass
from urllib.parse import urlparse
from pathlib import Path
import requests
from requests.exceptions import Timeout, ConnectionError
# --- Configuration constants ---
API_URL = "https://aiwrite.wps.cn/pdf/parse/web/claw-skill"
ALLOWED_TARGET_HOST = "aiwrite.wps.cn"
# URL safety configuration
MAX_URL_LENGTH = 2048
ALLOWED_PROTOCOLS = {"http", "https"}
SUPPORTED_IMAGE_EXT = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp'} # Supported image extensions
SUPPORTED_PDF_EXT = {'.pdf'} # Supported PDF extension
MAX_FILE_SIZE = 10 * 1024 * 1024 # Maximum file size: 10MB
# Dangerous domain keywords
DANGEROUS_KEYWORDS = ["localhost", "internal", "intranet", "admin", "test"]
@dataclass
class OCRResult:
"""OCR result"""
code: int
message: Optional[str]
md_text: str
def to_json(self) -> str:
"""Convert to JSON string"""
return json.dumps({
"code": self.code,
"message": self.message,
"markdown_text": self.md_text
}, ensure_ascii=False, indent=2)
class URLValidator:
"""URL safety validator"""
@staticmethod
def validate(url: str) -> Tuple[bool, Optional[str]]:
"""
Validate the safety of the URL
Returns: (is_valid, error_message)
"""
if not url or not isinstance(url, str):
return False, "URL cannot be empty"
url = url.strip()
if len(url) > MAX_URL_LENGTH:
return False, f"URL exceeds maximum length of {MAX_URL_LENGTH} characters"
try:
parsed = urlparse(url)
except Exception as e:
return False, f"Invalid URL format: {str(e)}"
if parsed.scheme.lower() not in ALLOWED_PROTOCOLS:
return False, f"Protocol '{parsed.scheme}' not allowed."
hostname = parsed.hostname
if not hostname:
return False, "URL must contain a valid hostname"
# 1. Basic keyword filtering
hostname_lower = hostname.lower()
for keyword in DANGEROUS_KEYWORDS:
if keyword in hostname_lower:
return False, f"Hostname contains restricted keyword: {keyword}"
# 2. [Enhanced] Security check after DNS resolution (prevents DNS Rebinding / SSRF)
# Even if the hostname appears to be a public domain, verify its resolved IP
is_safe, err_msg = URLValidator._validate_dns_resolution(hostname)
if not is_safe:
return False, err_msg
return True, None
@staticmethod
def _validate_dns_resolution(hostname: str) -> Tuple[bool, str]:
"""
[Security Critical] Resolve domain and verify all associated IPs are public IPs.
Prevents DNS Rebinding attacks.
"""
try:
# Get all resolution records (IPv4 & IPv6)
addr_info = socket.getaddrinfo(hostname, None, socket.AF_UNSPEC, socket.SOCK_STREAM)
for family, sock_type, proto, canon_name, sockaddr in addr_info:
ip = sockaddr[0]
try:
ip_obj = ipaddress.ip_address(ip)
# Reject private, loopback, link-local, multicast addresses
if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local or ip_obj.is_multicast:
return False, f"Security Block: {hostname} resolves to private/internal IP {ip}"
except ValueError:
continue
return True, "OK"
except socket.gaierror:
# If resolution fails, allow it (will be handled by requests later) or block
# For strict security, consider blocking unresolvable domains
return True, "OK"
@staticmethod
def _is_ip_address(hostname: str) -> bool:
ipv4_pattern = r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"
return bool(re.match(ipv4_pattern, hostname))
@staticmethod
def _is_private_ip(ip: str) -> bool:
# This function is kept as a backup; main logic is in _validate_dns_resolution
private_patterns = [
r"^127\.", r"^10\.", r"^172\.(1[6-9]|2[0-9]|3[0-1])\.",
r"^192\.168\.", r"^169\.254\.", r"^0\.", r"^224\.", r"^240\."
]
return any(re.match(p, ip) for p in private_patterns)
class FileValidatorAndReader:
"""File validation and reading utility: validate extension, file size, read PDF/image file content"""
def __init__(self, max_file_size: int = MAX_FILE_SIZE):
self.max_file_size = max_file_size
self.supported_ext = SUPPORTED_IMAGE_EXT.union(SUPPORTED_PDF_EXT)
@staticmethod
def _validate_file_exists(file_path: str) -> None:
"""Validate if file exists"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"file not exist: {file_path}")
if not os.path.isfile(file_path):
raise IsADirectoryError(f"the path is not a file: {file_path}")
def _validate_file_ext(self, file_path: str) -> None:
"""Validate if file extension is supported"""
file_ext = Path(file_path).suffix.lower() # Convert to lower case to avoid case issues (e.g., .PNG/.png)
if file_ext not in self.supported_ext:
raise ValueError(
f"file type not supported: {file_ext}, support file type:{', '.join(self.supported_ext)}"
)
def _validate_file_size(self, file_path: str) -> None:
"""Validate if file size exceeds limit"""
file_size = os.path.getsize(file_path)
if file_size > self.max_file_size:
raise OverflowError(
f"file size limited:{file_size/1024/1024:.2f}MB(max support file size is {self.max_file_size/1024/1024}MB)"
)
@staticmethod
def get_file_type(file_path: str) -> Tuple[str, str]:
"""Get file type (image/pdf)"""
file_ext = Path(file_path).suffix.lower()
if file_ext in SUPPORTED_IMAGE_EXT:
return "image", file_ext
elif file_ext in SUPPORTED_PDF_EXT:
return "pdf", file_ext
else:
raise ValueError(f"unknown file type:{file_ext}")
def read_file(self, file_path: str) -> str:
"""
Read file content, perform full validation first, then read
:param file_path: path to the file
:return: file content as base64-encoded string (str)
"""
# 1. Perform all validations
self._validate_file_exists(file_path)
self._validate_file_ext(file_path)
self._validate_file_size(file_path)
# 2. Read file content
try:
file_type, file_ext = self.get_file_type(file_path)
with open(file_path, mode='rb', encoding=None) as f:
content = f.read()
base64_str = base64.b64encode(content).decode('utf-8')
base64_str = f"data:{file_type}/{file_ext.lstrip('.')};base64,{base64_str}"
return base64_str
except Exception as e:
raise IOError(f"read file failed:{str(e)}") from e
class CredentialManager:
@staticmethod
def load() -> str:
c_sec = os.getenv("WPS_OCR_ACCESS_KEY", "").strip()
if c_sec:
return c_sec
raise ValueError("Credentials missing: WPS_OCR_ACCESS_KEY required")
class WPSOCRClient:
def __init__(self, client_secret: str):
self.client_secret = client_secret
self.session = requests.Session()
# Explicitly verify that the hardcoded API URL matches the expected domain
parsed_api = urlparse(API_URL)
if parsed_api.hostname != ALLOWED_TARGET_HOST and not parsed_api.hostname.endswith("." + ALLOWED_TARGET_HOST):
raise RuntimeError(
f"Security Configuration Error: API_URL hostname '{parsed_api.hostname}' is not allowed.")
def recognize_url(self, image_url: str) -> OCRResult:
is_valid, error_msg = URLValidator.validate(image_url)
if not is_valid:
return OCRResult(code=400, message=f"URL validation failed: {error_msg}", md_text="")
param = self._build_request_param(url=image_url, file_data='')
response = self._send_request(param)
return self._parse_response(response)
def recognize_file(self, file_path: str) -> OCRResult:
file_tool = FileValidatorAndReader()
try:
file_content = file_tool.read_file(file_path)
param = self._build_request_param(url='', file_data=file_content)
response = self._send_request(param)
return self._parse_response(response)
except Exception as e:
return OCRResult(code=400, message=f"URL validation failed: {e}", md_text="")
@staticmethod
def _build_request_param(url: str, file_data: str) -> Dict[str, Any]:
if url and url != '':
return {"url": url}
return {"url": file_data}
def _send_request(self, param: Dict[str, Any]) -> requests.Response:
headers = {"Content-Type": "application/json", "Authorization": self.client_secret}
# 1. Use hardcoded API_URL (already confirmed as whitelisted domain)
# 2. allow_redirects=False prevents 302 redirects to internal networks
# 3. timeout prevents DoS
response = self.session.post(
API_URL,
json=param,
headers=headers,
timeout=60,
allow_redirects=False # Critical: disable redirects
)
return response
@staticmethod
def _parse_response(response: requests.Response) -> OCRResult:
if response.status_code >= 500:
error_msg = response.text[:200] if response.text else "No error message"
return OCRResult(response.status_code, message=f"HTTP {response.status_code}: {error_msg}", md_text="")
try:
body = response.json()
except json.JSONDecodeError as e:
return OCRResult(code=500, message=f"Failed to parse JSON: {str(e)}", md_text="")
if response.status_code != 200:
detail = body.get("detail")
return OCRResult(code=response.status_code, message=detail, md_text="")
md_text = body.get("markdown_text")
return OCRResult(code=0, message="ok", md_text=md_text)
def main():
parser = argparse.ArgumentParser(description="WPS OCR - Secure JSON Output")
parser.add_argument("--url", "-u", required=False, help="File URL")
parser.add_argument("--path", "-p", required=False, help="Local File Path")
args = parser.parse_args()
try:
client_secret = CredentialManager.load()
client = WPSOCRClient(client_secret)
if args.url :
result = client.recognize_url(args.url)
print(result.to_json())
elif args.path :
result = client.recognize_file(args.path)
print(result.to_json())
else :
print(OCRResult(code=400, message="url or path required", md_text="").to_json())
except ValueError as e:
print(OCRResult(code=400, message=str(e), md_text="").to_json())
sys.exit(1)
except Timeout:
print(OCRResult(code=408, message="Request timed out", md_text="").to_json())
sys.exit(1)
except ConnectionError as e:
print(OCRResult(code=504, message=f"Network failed: {str(e)}", md_text="").to_json())
sys.exit(1)
except Exception as e:
print(OCRResult(code=500, message=f"Unexpected error: {str(e)}", md_text="").to_json())
sys.exit(1)
if __name__ == "__main__":
main()
🧭 使用前必读(30秒)
[!WARNING]
⚠️ 隐私与数据流向重要提示
- 需要与服务交互:本技能会将您提供的文件 发送至金山办公官方服务器 (`aiwrite.wps.cn`) 进行识别。
- 数据可见性:金山办公服务将获取并处理该文件内容。
- 本skill支持本地文件上传,只会对文件类型做校验,不会对路径做任何校验
✅ 推荐方式:环境变量(免权限、即时生效、webchat 友好)
# 在终端中运行(本次会话立即可用):
export WPS_OCR_ACCESS_KEY="your_client_access_key_here"# 将密钥追加写入到 ~/.openclaw/env 文件
echo 'export WPS_OCR_ACCESS_KEY="your_client_access_key_here"' >> ~/.openclaw/env[!TIP] 🔧 如何获取密钥?
- 秘钥申请获取:https://aiwrite.wps.cn/pdf/parse/accesskey/
✅ 环境依赖检查 确保已安装必要库:
pip install requests🎯 技能执行指南
1. 适用场景
当用户意图包含以下任一情况时,调用此技能:
- 发送了一张文件并询问“这是什么字?”、“提取文字”、“转成文本”。
- 上传了文档截图、发票、名片、照片、扫描件,文件里中英文混排文本识别。
- 需要对文件内容进行翻译或编辑(先提取文本是必要步骤)。
2. 执行动作
一旦确认需要提取文本,请立即执行以下操作:
输入处理: 获取用户提供的文件资源(使用下载链接:url或者 本地文件:path)。
执行命令: 调用python脚本进行识别。若当前环境支持命令行执行,请构造如下命令:
# 使用下载链接:
python3 skills/wps-ocr/scripts/wps_ocr.py --url <用户提供的文件 URL>
# 本地文件:
python3 skills/wps-ocr/scripts/wps_ocr.py --path <用户提供的文件 PATH>执行流程
1. 获取文件
将文件数据发往金山办公云服务,服务会下载用户输入的文件。
2. 文件校验
校验文件是否为已经支持的文件格式。
3. 识别文件内容
识别文件中的文本、图片、表格、公式等等元素,提取其中的文本内容。
⚠️ 注意:图片元素会以占位符的形式返回,不会返回文件元素
4. 向用户返回结果
如果成功:返回所有识别出的文本(拼接成一句)和详细检测信息。
如果失败:返回错误信息(如“文件中未检测到文本”、“API调用失败”等)。
OCR接口调用说明:
本技能依托WPS-OCR解析识别能力,已托管在金山云服务,当前版本为免费试用版本,为了保证云服务运行稳定,云服务做了限流处理,并发过高的情况服务将拒绝响应,请合理使用。
体验完整功能可以访问 demo平台
Related skills
FAQ
Does WPS OCR need credentials?
Yes. It requires a WPS_OCR_ACCESS_KEY set as an environment variable, obtained from aiwrite.wps.cn, with no credential hardcoding and a domain allowlist enforced in code.
Where is the file processed?
The file is sent to the official Kingsoft Office server at aiwrite.wps.cn for recognition, so Kingsoft Office services access and process the file content.