第 09 章:HTTP API
第 09 章:HTTP API
深入了解 Buku 的 RESTful API 接口,实现编程访问、脚本集成和自动化工作流。
9.1 API 概述
bukuserver 提供完整的 RESTful API,支持通过 HTTP 请求管理书签数据。
API 端点总览
| 方法 | 端点 | 功能 | 说明 |
|---|---|---|---|
| GET | /api/bookmarks | 获取所有书签 | 支持分页和搜索 |
| GET | /api/bookmarks/{id} | 获取单个书签 | 返回书签详情 |
| POST | /api/bookmarks | 添加书签 | JSON 请求体 |
| PUT | /api/bookmarks/{id} | 更新书签 | 部分或完整更新 |
| DELETE | /api/bookmarks/{id} | 删除书签 | 删除指定书签 |
| GET | /api/tags | 获取所有标签 | 含使用计数 |
| GET | /api/bookmarks/search | 搜索书签 | 支持关键词和标签 |
API 数据格式
// 书签对象结构
{
"id": 1,
"url": "https://github.com/jarun/buku",
"title": "Buku - Bookmark Manager",
"tags": ",bookmark,cli,python,",
"description": "A powerful bookmark manager"
}
// 标签对象结构
{
"tags": {
"python": 15,
"javascript": 12,
"tutorial": 8
}
}
9.2 启动 API 服务
# 启动 bukuserver
buku --sr 8080
# 带 Token 认证启动
buku --sr 8080 --sall token:your_api_token
# 后台运行
nohup buku --sr 8080 > bukuserver.log 2>&1 &
# 验证服务运行
curl http://localhost:8080/api/bookmarks | head
9.3 书签 CRUD 操作
获取所有书签
# 获取所有书签
curl http://localhost:8080/api/bookmarks
# 带 Token 认证
curl -H "Authorization: Bearer your_token" \
http://localhost:8080/api/bookmarks
# 获取特定书签
curl http://localhost:8080/api/bookmarks/1
添加书签
# 添加书签
curl -X POST http://localhost:8080/api/bookmarks \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"title": "示例网站",
"tags": ",demo,test,",
"description": "这是一个测试书签"
}'
# 仅添加 URL(自动获取标题)
curl -X POST http://localhost:8080/api/bookmarks \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'
更新书签
# 更新书签标题
curl -X PUT http://localhost:8080/api/bookmarks/1 \
-H "Content-Type: application/json" \
-d '{"title": "新标题"}'
# 更新多个字段
curl -X PUT http://localhost:8080/api/bookmarks/1 \
-H "Content-Type: application/json" \
-d '{
"title": "新标题",
"tags": ",newtag1,newtag2,",
"description": "新描述"
}'
删除书签
# 删除书签
curl -X DELETE http://localhost:8080/api/bookmarks/1
# 批量删除
for id in 1 2 3 4 5; do
curl -X DELETE http://localhost:8080/api/bookmarks/$id
done
9.4 搜索 API
# 关键词搜索
curl "http://localhost:8080/api/bookmarks/search?q=python"
# 标签搜索
curl "http://localhost:8080/api/bookmarks/search?tags=python"
# 组合搜索
curl "http://localhost:8080/api/bookmarks/search?q=tutorial&tags=python"
# 分页
curl "http://localhost:8080/api/bookmarks?page=1&per_page=20"
9.5 标签 API
# 获取所有标签
curl http://localhost:8080/api/tags
# 获取特定书签的标签
curl http://localhost:8080/api/bookmarks/1/tags
9.6 Python 客户端示例
#!/usr/bin/env python3
"""buku_api_client.py - Buku API Python 客户端"""
import requests
import json
from typing import Optional, List, Dict
class BukuClient:
"""Buku API 客户端"""
def __init__(self, base_url: str = "http://localhost:8080",
token: Optional[str] = None):
self.base_url = base_url.rstrip('/')
self.session = requests.Session()
if token:
self.session.headers['Authorization'] = f'Bearer {token}'
def get_bookmarks(self, page: int = 1, per_page: int = 100) -> List[Dict]:
"""获取所有书签"""
resp = self.session.get(
f"{self.base_url}/api/bookmarks",
params={'page': page, 'per_page': per_page}
)
resp.raise_for_status()
return resp.json()
def get_bookmark(self, bookmark_id: int) -> Dict:
"""获取单个书签"""
resp = self.session.get(f"{self.base_url}/api/bookmarks/{bookmark_id}")
resp.raise_for_status()
return resp.json()
def add_bookmark(self, url: str, title: str = "",
tags: str = "", description: str = "") -> Dict:
"""添加书签"""
data = {'url': url}
if title:
data['title'] = title
if tags:
data['tags'] = tags if tags.startswith(',') else f',{tags},'
if description:
data['description'] = description
resp = self.session.post(
f"{self.base_url}/api/bookmarks",
json=data
)
resp.raise_for_status()
return resp.json()
def update_bookmark(self, bookmark_id: int, **kwargs) -> Dict:
"""更新书签"""
resp = self.session.put(
f"{self.base_url}/api/bookmarks/{bookmark_id}",
json=kwargs
)
resp.raise_for_status()
return resp.json()
def delete_bookmark(self, bookmark_id: int) -> bool:
"""删除书签"""
resp = self.session.delete(f"{self.base_url}/api/bookmarks/{bookmark_id}")
return resp.status_code == 200
def search(self, query: str = "", tags: str = "") -> List[Dict]:
"""搜索书签"""
params = {}
if query:
params['q'] = query
if tags:
params['tags'] = tags
resp = self.session.get(
f"{self.base_url}/api/bookmarks/search",
params=params
)
resp.raise_for_status()
return resp.json()
def get_tags(self) -> Dict:
"""获取所有标签"""
resp = self.session.get(f"{self.base_url}/api/tags")
resp.raise_for_status()
return resp.json()
# 使用示例
if __name__ == '__main__':
client = BukuClient(
base_url="http://localhost:8080",
token="your_token_here"
)
# 添加书签
result = client.add_bookmark(
url="https://python.org",
title="Python 官方网站",
tags=",python,official,reference,"
)
print(f"添加书签: {result}")
# 搜索书签
results = client.search(query="python")
print(f"搜索结果: {len(results)} 条")
# 获取所有标签
tags = client.get_tags()
print(f"标签: {tags}")
9.7 Shell 脚本集成
批量导入脚本
#!/bin/bash
# batch_import.sh - 从 URL 列表批量导入书签
API_URL="http://localhost:8080"
TOKEN="your_token"
INPUT_FILE="$1"
if [ -z "$INPUT_FILE" ]; then
echo "用法: $0 <url_list_file>"
exit 1
fi
while IFS= read -r url; do
[ -z "$url" ] && continue
echo "添加: $url"
curl -s -X POST "$API_URL/api/bookmarks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"url\": \"$url\"}" | jq -r '.id // "失败"'
done < "$INPUT_FILE"
自动标记脚本
#!/bin/bash
# auto_tag.sh - 根据 URL 自动添加标签
API_URL="http://localhost:8080"
TOKEN="your_token"
# 定义规则
declare -A TAG_RULES=(
["github.com"]=",github,code,git,"
["stackoverflow.com"]=",stackoverflow,q&a,"
["docs.python.org"]=",python,doc,reference,"
["youtube.com"]=",video,tutorial,"
)
# 获取所有书签
bookmarks=$(curl -s -H "Authorization: Bearer $TOKEN" \
"$API_URL/api/bookmarks")
# 处理每个书签
echo "$bookmarks" | jq -c '.[]' | while read -r bookmark; do
id=$(echo "$bookmark" | jq -r '.id')
url=$(echo "$bookmark" | jq -r '.url')
existing_tags=$(echo "$bookmark" | jq -r '.tags')
# 检查规则
for domain in "${!TAG_RULES[@]}"; do
if echo "$url" | grep -q "$domain"; then
new_tags="${TAG_RULES[$domain]}"
# 避免重复添加
if ! echo "$existing_tags" | grep -q "${new_tags:1:5}"; then
combined_tags="${existing_tags%,},${new_tags:1}"
curl -s -X PUT "$API_URL/api/bookmarks/$id" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"tags\": \"$combined_tags\"}" > /dev/null
echo "更新 #$id: 添加标签 $new_tags"
fi
fi
done
done
链接检查脚本
#!/bin/bash
# check_links_api.sh - 使用 API 检查死链
API_URL="http://localhost:8080"
TOKEN="your_token"
# 获取所有书签
bookmarks=$(curl -s -H "Authorization: Bearer $TOKEN" \
"$API_URL/api/bookmarks")
# 检查每个链接
echo "$bookmarks" | jq -c '.[]' | while read -r bookmark; do
id=$(echo "$bookmark" | jq -r '.id')
url=$(echo "$bookmark" | jq -r '.url')
title=$(echo "$bookmark" | jq -r '.title')
status=$(curl -o /dev/null -s -w "%{http_code}" \
--connect-timeout 5 --max-time 10 "$url" 2>/dev/null)
if [ "$status" = "000" ]; then
echo "[无法连接] #$id: $title ($url)"
elif [ "$status" != "200" ]; then
echo "[${status}] #$id: $title ($url)"
fi
done
9.8 自动化工作流
定时备份工作流
#!/bin/bash
# workflow_backup.sh - 定时备份书签
API_URL="http://localhost:8080"
TOKEN="your_token"
BACKUP_DIR="$HOME/backups/buku"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
# 通过 API 导出
curl -s -H "Authorization: Bearer $TOKEN" \
"$API_URL/api/bookmarks" | \
python3 -m json.tool > "$BACKUP_DIR/bookmarks_$DATE.json"
# 清理 30 天前的备份
find "$BACKUP_DIR" -name "*.json" -mtime +30 -delete
echo "备份完成: $BACKUP_DIR/bookmarks_$DATE.json"
RSS 自动收藏
#!/bin/bash
# rss_to_buku.sh - 从 RSS 订阅自动添加书签
API_URL="http://localhost:8080"
TOKEN="your_token"
RSS_URL="https://example.com/feed.xml"
# 获取 RSS 并解析
curl -s "$RSS_URL" | python3 -c "
import sys
import xml.etree.ElementTree as ET
tree = ET.parse(sys.stdin)
root = tree.getroot()
for item in root.findall('.//item'):
link = item.find('link').text
title = item.find('title').text
tags = ',rss,auto-import,'
print(f'{link}\t{title}\t{tags}')
" | while IFS=$'\t' read -r url title tags; do
# 检查是否已存在
existing=$(curl -s -H "Authorization: Bearer $TOKEN" \
"$API_URL/api/bookmarks/search?q=$url")
if echo "$existing" | jq -e '.[0]' > /dev/null 2>&1; then
echo "已存在: $title"
else
curl -s -X POST "$API_URL/api/bookmarks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"url\": \"$url\", \"title\": \"$title\", \"tags\": \"$tags\"}" > /dev/null
echo "已添加: $title"
fi
done
9.9 API 最佳实践
┌────────────────────────────────────────────────────────────┐
│ API 使用最佳实践 │
├────────────────────────────────────────────────────────────┤
│ │
│ 🔐 安全 │
│ ├── 始终使用 Token 认证 │
│ ├── 通过 HTTPS 访问 API │
│ ├── 定期更换 Token │
│ └── 限制 API 访问 IP │
│ │
│ 📊 性能 │
│ ├── 使用分页获取大量数据 │
│ ├── 避免频繁的批量请求 │
│ ├── 缓存不常变化的数据 │
│ └── 使用异步请求提高并发性能 │
│ │
│ 🔧 可靠性 │
│ ├── 检查 HTTP 响应状态码 │
│ ├── 实现重试机制 │
│ ├── 处理超时和连接错误 │
│ └── 记录 API 调用日志 │
│ │
└────────────────────────────────────────────────────────────┘
9.10 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| API 401 未授权 | Token 错误或未提供 | 检查 Token 配置 |
| API 404 不存在 | 端点错误或书签不存在 | 检查 URL 和 ID |
| 连接被拒绝 | 服务未启动 | 启动 bukuserver |
| 请求超时 | 网络或服务器问题 | 增加超时时间 |
| JSON 格式错误 | 请求体格式错误 | 检查 JSON 格式 |
9.11 本章小结
| 要点 | 说明 |
|---|---|
| API 基础 URL | http://localhost:8080/api/ |
| 认证方式 | Bearer Token |
| 数据格式 | JSON |
| 分页支持 | ?page=1&per_page=20 |
| 搜索方式 | ?q=keyword&tags=tag |
扩展阅读
下一章:第 10 章:集成与插件 — 学习将 Buku 集成到浏览器、编辑器和其他工具中。