Files
nxxy335 f0ecfa6de0 chore: 初始化 123yunpan skill 仓库 (v1.3.0)
- 完整 skill 本体:SKILL.md / README.md / manifest.json / config.json
- scripts/123yunpan.py:零依赖 CLI(含空间双配额、QPS 限流、零依赖 XLSX)
- references/ 参考资料
- 排除 .workbuddy/ 开发目录与 __pycache__
2026-09-27 22:41:43 +08:00

284 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 123云盘批量操作指南
## 批量创建分享链接
为大量文件创建分享链接时,需要注意速率限制和断点续传。
### ⚠️ 关键:`fileIDList` 必须是字符串
API 要求 `fileIDList` 为**逗号分隔的字符串**,不是数组:
```python
# ✅ 正确
body = {"fileIDList": "12345,67890", "shareName": "xxx", "shareExpire": 0}
# ❌ 错误 — 会返回 "文件列表不能为空"
body = {"fileIDList": [12345, 67890], "shareName": "xxx", "shareExpire": 0}
```
### 关键参数
- API 端点: `POST /api/v1/share/create`
- 单次最多 100 个 fileID
- `shareExpire`: 0=永久, 1/7/30 天
- 建议请求间隔: 0.5 秒(每秒不超过 2 个请求)
### ⚠️ 分享链接构建
API 返回 `shareID` + `shareKey`,**不会返回完整 URL**,需要自行拼接。支持两种格式:
```python
# Short 格式 (默认)
share_url = f"https://www.123pan.com/s/{share_key}"
# UID 格式 — 需要 user_id(可从 JWT token 解析,无需额外 API)
share_url = f"https://{user_id}.share.123pan.cn/123pan/{share_key}"
```
响应示例:
```json
{"code": 0, "message": "ok", "data": {"shareID": 5193007, "shareKey": "JyAKVv-o6bI"}}
```
→ Short: `https://www.123pan.com/s/JyAKVv-o6bI`
→ UID: `https://666666666.share.123pan.cn/123pan/JyAKVv-o6bI`
### 完整示例:批量为文件创建分享并导出 Excel
```python
import json, time, urllib.request, urllib.parse
from collections import deque
BASE = "https://open-api.123pan.com"
def get_token():
with open(os.path.expanduser("~/.123yunpan_config.json")) as f:
return json.load(f)["access_token"]
def api_get(path, params=None):
url = BASE + path
if params:
url += "?" + urllib.parse.urlencode(params)
req = urllib.request.Request(url, headers={
"Authorization": f"Bearer {get_token()}", "Platform": "open_platform"})
return json.loads(urllib.request.urlopen(req, timeout=30).read())
def api_post(path, body):
url = BASE + path
data = json.dumps(body).encode()
req = urllib.request.Request(url, data=data, method="POST", headers={
"Authorization": f"Bearer {get_token()}", "Platform": "open_platform",
"Content-Type": "application/json"})
return json.loads(urllib.request.urlopen(req, timeout=30).read())
def list_folder(parent_id):
items, last_id = [], 0
while True:
params = {"parentFileId": parent_id, "limit": 100}
if last_id:
params["lastFileId"] = last_id
resp = api_get("/api/v2/file/list", params)
data = resp.get("data", {})
items.extend(data.get("fileList", []))
last_id = data.get("lastFileId", -1)
if last_id == -1:
break
time.sleep(0.3)
return items
def find_all_files(root_id):
queue, all_files = deque([(root_id, [])]), []
while queue:
pid, path = queue.popleft()
for item in list_folder(pid):
if item.get("trashed"):
continue
if item["type"] == 1:
queue.append((item["fileId"], path + [item["filename"]]))
else:
all_files.append({"fileId": item["fileId"],
"filename": item["filename"], "path": "/".join(path)})
return all_files
def create_share(file_id, name, user_id=None):
"""创建分享并返回 URL。user_id 非空时使用 UID 格式。"""
resp = api_post("/api/v1/share/create", {
"fileIDList": str(file_id), # ← 字符串,不是数组!
"shareName": name, "shareExpire": 0})
data = resp.get("data", {})
if resp.get("code") == 0:
key = data["shareKey"]
if user_id:
return f"https://{user_id}.share.123pan.cn/123pan/{key}"
return f"https://www.123pan.com/s/{key}"
return None
# --- Usage ---
all_files = find_all_files(root_folder_id)
results = []
for i, f in enumerate(all_files):
url = create_share(f["fileId"], f["filename"])
results.append({"name": f["filename"], "url": url})
time.sleep(0.6)
if (i + 1) % 10 == 0:
json.dump(results, open("progress.json", "w"), ensure_ascii=False)
```
### 断点续传模式
```python
# 保存进度
results_file = "/tmp/shares_progress.json"
if os.path.exists(results_file):
results = json.load(open(results_file))
done_ids = {r["fileId"] for r in results if r.get("share_url")}
# 跳过已完成的
for item in all_files:
if item["fileId"] in done_ids:
continue
# ... 创建分享 ...
# 每 N 条保存一次
if done_count % 10 == 0:
json.dump(results, open(results_file, "w"))
```
### 速率控制
```python
import time
time.sleep(0.5) # 每个请求后暂停
# 每 5 个请求可额外暂停
if (i + 1) % 5 == 0:
time.sleep(1)
```
## 短直链批量生成
为大量文件生成短直链(不暴露文件名),利用 JWT 解析 user_id,无需额外 API 调用:
```python
import base64, json, time
def get_user_id_from_token(token):
"""从 JWT token 解析 user_id"""
parts = token.split(".")
payload = parts[1] + "=" * (4 - len(parts[1]) % 4)
return json.loads(base64.b64decode(payload)).get("id", 0)
# 一次性获取 user_id
user_id = get_user_id_from_token(access_token)
# 批量生成短直链
for file_id in file_ids:
short_url = f"https://{user_id}.v.123pan.cn/{user_id}/{file_id}"
print(f"File {file_id}: {short_url}")
```
> ⚠️ 短直链要求文件在已开启直链功能的文件夹中。
## 深层目录遍历
123云盘文件夹可达 4-5 层深(学段/科目/版本/年级/文件)。
### BFS 遍历 + 缓存(推荐)
遍历大量文件夹时,API 调用次数 = 文件夹数量。100+ 文件夹需要 5-10 分钟。
**关键优化:将遍历结果缓存到 JSON,5 分钟内复用,跳过所有 API 调用。**
实测效果:357 个文件遍历从 **57 秒 → 0.15 秒**(380 倍加速)。
```python
import os, json, time
from collections import deque
CACHE_DIR = os.path.expanduser("~/.123yunpan_cache")
def bfs_with_cache(client, root_id, max_age=300):
"""BFS 遍历 + JSON 缓存"""
cache_path = os.path.join(CACHE_DIR, f"files_{root_id}.json")
os.makedirs(CACHE_DIR, exist_ok=True)
# 尝试加载缓存(默认 5 分钟有效)
if os.path.exists(cache_path):
age = time.time() - os.path.getmtime(cache_path)
if age < max_age:
with open(cache_path) as f:
data = json.load(f)
print(f"使用缓存 ({int(age)}秒前, {len(data['files'])} 个文件)")
return data["files"]
# BFS 遍历
queue = deque([(root_id, [])])
all_files = []
while queue:
pid, path = queue.popleft()
items = list_folder(pid) # 自动翻页
for item in items:
if item.get("trashed"):
continue
if item["type"] == 1:
queue.append((item["fileId"], path + [item["filename"]]))
else:
all_files.append({
"fileId": item["fileId"],
"filename": item["filename"],
"path": "/".join(path),
"size": item.get("size", 0),
})
# 保存缓存
with open(cache_path, "w") as f:
json.dump({"files": all_files, "ts": time.time()}, f, ensure_ascii=False)
return all_files
```
> 缓存路径: `~/.123yunpan_cache/files_{root_id}.json`
> 强制刷新: 删除缓存文件或传 `max_age=0`
### 分层遍历(已知结构时更快)
```python
# 已知 学段/科目/版本/年级/文件 结构
for stage in list_folder(root_id):
for subject in list_folder(stage["fileId"]):
for version in list_folder(subject["fileId"]):
for grade in list_folder(version["fileId"]):
files = list_folder(grade["fileId"])
```
### 翻页处理
```python
def list_folder(parent_id):
items = []
last_id = 0
while True:
params = {"parentFileId": parent_id, "limit": 100}
if last_id:
params["lastFileId"] = last_id
resp = api_get("/api/v2/file/list", params)
data = _extract(resp) # v1.2.0: always use _extract() for {code,data} wrappers
items.extend(data.get("fileList", []))
last_id = data.get("lastFileId", -1)
if last_id == -1:
break
return items
```
## 后台长时间运行
批量操作(如 2500+ 文件)需要 20+ 分钟,应在后台运行:
```bash
python3 -u script.py 2>&1 | tee /tmp/progress.log
```
监控进度:
```bash
tail -5 /tmp/progress.log
```
## 环境限制:pip 不可用时安装 Python 包
系统 Python 受限(externally managed),用临时 venv:
```bash
uv venv /tmp/xlsx_env
uv pip install openpyxl --python /tmp/xlsx_env/bin/python
/tmp/xlsx_env/bin/python your_script.py
```