哔哩哔哩-API收集整理:接口文档自动化测试方案
你是否还在为B站API接口的频繁变更而头疼?是否因手动测试耗时费力而影响开发效率?本文将基于bilibili-API-collect项目,提供一套完整的接口文档自动化测试方案,帮助你轻松应对API测试挑战。读完本文,你将掌握API签名验证、自动化测试脚本编写、错误处理与报告生成等核心技能,让API测试效率提升80%。
项目概述
bilibili-API-collect是一个致力于收集整理哔哩哔哩(B站)各类非官方API的开源项目。该项目采用黑箱法、控制变量法、代码逆向分析等多种研究方法,对B站Web、APP、TV等客户端的野生API进行系统性梳理,为开发者提供了宝贵的API调用参考。
项目主要包含REST API和gRPC两种接口类型,接口请求数据大多为url query表单或JSON,返回数据大多为JSON或Protobuf,且强制使用HTTPS协议。完整的文档结构可参考项目README。
API签名机制解析
B站API采用多种签名机制进行接口鉴权,主要包括APP签名和WBI签名两种方式。
APP签名
APP签名主要用于客户端API接口,其核心步骤如下:
- 在参数中添加
appkey字段 - 按照参数Key进行排序
- 对排序后的参数进行url query序列化,并拼接对应的
appsec进行MD5哈希运算 - 将计算得到的哈希值作为
sign字段添加到参数中
import hashlib
import urllib.parse
def appsign(params, appkey, appsec):
'为请求参数进行 APP 签名'
params.update({'appkey': appkey})
params = dict(sorted(params.items())) # 按照 key 重排参数
query = urllib.parse.urlencode(params) # 序列化参数
sign = hashlib.md5((query+appsec).encode()).hexdigest() # 计算 api 签名
params.update({'sign':sign})
return params
详细实现可参考APP API签名文档,已知的APPKey列表可查阅APPKey文档。
WBI签名
WBI签名是自2023年3月起在Web端部分接口采用的新签名方式,主要涉及w_rid和wts两个字段。其签名流程相对复杂,主要包括:
- 获取实时口令
img_key和sub_key - 打乱重排实时口令获得
mixin_key - 计算签名
w_rid - 向请求参数中添加
w_rid和wts字段
def getMixinKey(orig: str):
'对 imgKey 和 subKey 进行字符顺序打乱编码'
return reduce(lambda s, i: s + orig[i], mixinKeyEncTab, '')[:32]
def encWbi(params: dict, img_key: str, sub_key: str):
'为请求参数进行 wbi 签名'
mixin_key = getMixinKey(img_key + sub_key)
curr_time = round(time.time())
params['wts'] = curr_time # 添加 wts 字段
params = dict(sorted(params.items())) # 按照 key 重排参数
# 过滤 value 中的 "!'()*" 字符
params = {
k : ''.join(filter(lambda chr: chr not in "!'()*", str(v)))
for k, v
in params.items()
}
query = urllib.parse.urlencode(params) # 序列化参数
wbi_sign = md5((query + mixin_key).encode()).hexdigest() # 计算 w_rid
params['w_rid'] = wbi_sign
return params
完整的WBI签名实现可参考Wbi签名文档。
自动化测试框架设计
测试框架架构
自动化测试框架主要包含以下几个核心模块:
- 用例管理模块:负责测试用例的定义、组织和管理
- 请求处理模块:处理API请求的构建、发送和响应解析
- 签名生成模块:实现APP签名和WBI签名等签名机制
- 断言模块:提供丰富的断言方法验证接口响应
- 报告生成模块:生成直观的测试报告
- 配置模块:管理测试环境、接口地址等配置信息
核心实现代码
以下是一个基于Python的简单测试框架实现:
import requests
import json
import hashlib
import urllib.parse
import time
from functools import reduce
class BiliAPITester:
def __init__(self, appkey=None, appsec=None):
self.appkey = appkey
self.appsec = appsec
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
'Referer': 'https://www.bilibili.com/'
})
self.mixinKeyEncTab = [
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
36, 20, 34, 44, 52
]
self.wbi_img_key = None
self.wbi_sub_key = None
def get_wbi_keys(self):
"""获取WBI签名所需的img_key和sub_key"""
resp = self.session.get('https://api.bilibili.com/x/web-interface/nav')
resp.raise_for_status()
json_content = resp.json()
img_url = json_content['data']['wbi_img']['img_url']
sub_url = json_content['data']['wbi_img']['sub_url']
self.wbi_img_key = img_url.rsplit('/', 1)[1].split('.')[0]
self.wbi_sub_key = sub_url.rsplit('/', 1)[1].split('.')[0]
return self.wbi_img_key, self.wbi_sub_key
def app_sign(self, params):
"""对参数进行APP签名"""
if not self.appkey or not self.appsec:
raise ValueError("appkey和appsec必须提供")
return appsign(params, self.appkey, self.appsec)
def wbi_sign(self, params):
"""对参数进行WBI签名"""
if not self.wbi_img_key or not self.wbi_sub_key:
self.get_wbi_keys()
return encWbi(params, self.wbi_img_key, self.wbi_sub_key)
def test_api(self, url, params, sign_type='wbi', method='get', expected_code=0):
"""测试API接口"""
if sign_type == 'app':
signed_params = self.app_sign(params.copy())
elif sign_type == 'wbi':
signed_params = self.wbi_sign(params.copy())
else:
signed_params = params.copy()
try:
if method.lower() == 'get':
resp = self.session.get(url, params=signed_params)
else:
resp = self.session.post(url, data=signed_params)
resp.raise_for_status()
result = resp.json()
# 断言状态码
assert result.get('code') == expected_code, f"API调用失败: {result.get('message')}"
return {
'success': True,
'status_code': resp.status_code,
'response': result,
'params': signed_params
}
except Exception as e:
return {
'success': False,
'error': str(e),
'params': signed_params
}
自动化测试用例设计
针对B站API的特点,我们可以从以下几个维度设计自动化测试用例:
接口功能测试
针对每个API接口,设计测试用例验证其基本功能是否正常,包括正常场景和异常场景。
例如,测试用户基本信息接口user/info.md:
def test_user_info(tester):
"""测试用户信息接口"""
# 正常场景
params = {'mid': '1'}
result = tester.test_api('https://api.bilibili.com/x/space/wbi/acc/info', params)
assert result['success'], f"用户信息接口测试失败: {result.get('error')}"
assert 'data' in result['response'], "响应中缺少data字段"
assert result['response']['data']['mid'] == 1, "返回的用户ID不正确"
# 异常场景 - 用户不存在
params = {'mid': '999999999'}
result = tester.test_api('https://api.bilibili.com/x/space/wbi/acc/info', params, expected_code=-404)
assert result['success'], f"用户信息接口异常场景测试失败: {result.get('error')}"
return "用户信息接口测试通过"
接口性能测试
针对核心API接口,设计性能测试用例,验证接口的响应时间、吞吐量等性能指标。
import time
import statistics
def test_api_performance(tester, url, params, sign_type='wbi', iterations=10):
"""测试API性能"""
response_times = []
for i in range(iterations):
start_time = time.time()
result = tester.test_api(url, params, sign_type)
end_time = time.time()
assert result['success'], f"API调用失败: {result.get('error')}"
response_times.append(end_time - start_time)
# 计算性能指标
avg_time = statistics.mean(response_times)
p90_time = statistics.quantiles(response_times, n=10)[8]
max_time = max(response_times)
min_time = min(response_times)
return {
'avg_time': avg_time,
'p90_time': p90_time,
'max_time': max_time,
'min_time': min_time,
'iterations': iterations
}
接口兼容性测试
B站API存在多个版本和多种客户端类型,因此需要测试接口在不同版本、不同客户端类型下的兼容性。
def test_api_compatibility(tester, url, params, appkey_list):
"""测试API在不同appkey下的兼容性"""
results = []
for app_info in appkey_list:
tester.appkey = app_info['appkey']
tester.appsec = app_info['appsec']
try:
result = tester.test_api(url, params, sign_type='app')
results.append({
'appkey': app_info['appkey'],
'app_type': app_info['app_type'],
'success': True,
'message': '测试通过'
})
except Exception as e:
results.append({
'appkey': app_info['appkey'],
'app_type': app_info['app_type'],
'success': False,
'message': str(e)
})
return results
错误处理与常见问题
在API测试过程中,可能会遇到各种错误,熟悉常见的错误码可以帮助我们快速定位问题。
常见错误码
| 代码 | 含义 |
|---|---|
| -101 | 账号未登录 |
| -102 | 账号被封停 |
| -111 | csrf校验失败 |
| -403 | 访问权限不足 |
| -404 | 资源不存在 |
| -500 | 服务器错误 |
| -503 | 过载保护,服务暂不可用 |
| -799 | 请求过于频繁,请稍后再试 |
完整的错误码列表可参考公共错误码文档。
常见问题解决方案
- 签名错误:检查签名算法实现是否正确,特别是参数排序和编码方式
- 请求过于频繁:实现请求限流机制,避免短时间内发送过多请求
- CSRF校验失败:确保请求头中包含正确的Referer信息
- WBI签名失败:定期更新img_key和sub_key,建议每24小时更新一次
测试报告生成
测试完成后,需要生成直观的测试报告,展示测试结果和关键指标。
def generate_report(test_results, output_file='api_test_report.md'):
"""生成测试报告"""
with open(output_file, 'w', encoding='utf-8') as f:
f.write('# B站API自动化测试报告\n\n')
f.write(f'测试时间: {time.strftime("%Y-%m-%d %H:%M:%S")}\n\n')
# 汇总统计
total_cases = len(test_results)
success_cases = sum(1 for r in test_results if r['success'])
failure_cases = total_cases - success_cases
f.write('## 测试汇总\n')
f.write(f'| 总用例数 | 通过用例数 | 失败用例数 | 通过率 |\n')
f.write(f'|----------|------------|------------|--------|\n')
f.write(f'| {total_cases} | {success_cases} | {failure_cases} | {(success_cases/total_cases*100):.2f}% |\n\n')
# 详细结果
f.write('## 详细测试结果\n')
for i, result in enumerate(test_results, 1):
f.write(f'### 测试用例 {i}: {result["name"]}\n')
f.write(f'- 状态: {"通过" if result["success"] else "失败"}\n')
f.write(f'- API地址: {result["url"]}\n')
f.write(f'- 请求方法: {result["method"]}\n')
f.write(f'- 签名类型: {result["sign_type"]}\n')
if not result["success"]:
f.write(f'- 错误信息: {result["error"]}\n')
f.write(f'- 响应时间: {result["response_time"]:.3f}s\n\n')
# 性能指标
if any('performance' in r for r in test_results):
f.write('## 性能指标\n')
for result in test_results:
if 'performance' in result:
perf = result['performance']
f.write(f'### {result["name"]}\n')
f.write(f'- 平均响应时间: {perf["avg_time"]:.3f}s\n')
f.write(f'- P90响应时间: {perf["p90_time"]:.3f}s\n')
f.write(f'- 最大响应时间: {perf["max_time"]:.3f}s\n')
f.write(f'- 最小响应时间: {perf["min_time"]:.3f}s\n\n')
测试自动化与持续集成
为了实现测试的自动化和持续集成,我们可以使用GitHub Actions或其他CI/CD工具来定期运行测试用例,并生成测试报告。
GitHub Actions配置示例
name: API测试
on:
schedule:
- cron: '0 0 * * *' # 每天运行一次
push:
branches: [ main ]
paths:
- 'docs/**'
- '.github/workflows/api-test.yml'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run API tests
run: |
python run_tests.py
- name: Generate report
run: |
python generate_report.py
- name: Upload report
uses: actions/upload-artifact@v3
with:
name: api-test-report
path: api_test_report.md
总结与展望
本文详细介绍了基于bilibili-API-collect项目的接口文档自动化测试方案,包括API签名机制解析、自动化测试框架设计、测试用例设计、错误处理和持续集成等方面。通过实施这套方案,开发者可以有效提高API测试效率,确保接口调用的稳定性和可靠性。
未来,我们可以从以下几个方面进一步完善测试方案:
- 增加接口覆盖率:不断完善测试用例,提高API接口的覆盖率
- 实时监控:建立API实时监控系统,及时发现接口异常
- 性能优化:基于测试结果,优化接口性能
- 自动化文档更新:结合测试结果,自动更新API文档
通过持续优化和完善测试方案,我们可以为B站API的使用者提供更加稳定、可靠的接口服务,促进相关应用生态的健康发展。
参考资料
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考




