了解一些B站的API
使用的API均为WEB API
登录API (下载分辨率>480P的视频)
扫码登陆 (可能是最容易实现的api)
基本流程: 申请二维码URL及扫码密钥->生成二维码->轮询服务器->扫码登录后->获取cookie
申请二维码URL及扫码密钥
GET
http://passport.bilibili.com/qrcode/getLoginUrl
密钥超时为180秒
json回复:
根对象:
字段 | 类型 | 内容 | 备注 |
---|---|---|---|
code | num | 返回值 | 0:成功 |
status | bool | true | 作用尚不明确 |
ts | num | 请求时间 | 时间戳 |
data | obj | 信息本体 |
data
对象:
字段 | 类型 | 内容 | 备注 |
---|---|---|---|
url | str | 二维码内容url | 恒为87字符 |
oauthKey | str | 扫码登录秘钥 | 恒为32字符 |
二维码的url使用data.url里面的url,oauthKey请保存,并等待下一步操作
使用扫码登录
POST
http://passport.bilibili.com/qrcode/getLoginInfo
密钥超时为180秒
验证登录成功后会进行设置以下cookie项:
DedeUserID
DedeUserID__ckMd5
SESSDATA
bili_jct
正文参数( application/x-www-form-urlencoded ):
参数名 | 类型 | 内容 | 必要性 | 备注 |
---|---|---|---|---|
oauthKey | str | 扫码登录秘钥 | 必要 | |
gourl | str | 跳转url | 非必要 | 默认为http://www.bilibili.com |
json回复:
根对象:
字段 | 类型 | 内容 | 备注 |
---|---|---|---|
code | num | 返回值 | 0:成功 |
message | str | 错误信息 | 正确无 |
ts | num | 扫码时间 | 错误无 |
status | bool | 扫码是否成功 | true:成功 |
false:未成功 | |||
data | 正确时:obj | ||
错误时:num | 正确时:游戏分站url | ||
错误时:错误代码 | 未成功时: | ||
-1:密钥错误 | |||
-2:密钥超时 | |||
-4:未扫描 | |||
-5:未确认 |
data 对象:
字段 | 类型 | 内容 | 备注 |
---|---|---|---|
url | str | 游戏分站跨域登录url |
密码登录/短信登录接口注意事项
注意! 密码登陆以及短信登录需要人机验证通过!
GET
https://passport.bilibili.com/x/passport-login/captcha?source=main_web
- 请求验证码参数,得到登录密钥
key
与极验idgt
和极验KEYchallenge
- 进行滑动or点击验证
- 返回验证结果
validate
与seccode
,进行短信或密码登录
此api详情请查看: SocialSisterYi/bilibili-API-collect/bilibili-API-collect/login/login_action
验证器开源链接:
密码登录
基本流程: 密码输入->人机验证通过->获取盐值->RSA加密->返回数据->获取cookie
获取盐值
GET http://passport.bilibili.com/login?act=getkey
json回复:
字段 | 类型 | 内容 | 备注 |
---|---|---|---|
hash | str | 密码校验盐值 | 有效时间为20s |
key | str | RSA公钥 | 公钥为固定值 |
加密
盐值+登录密码->然后使用RSA公钥进行加密
登录
POST
http://passport.bilibili.com/web/login/v2
验证登录成功后会进行设置以下cookie项:
sid
DedeUserID
DedeUserID__ckMd5
SESSDATA
bili_jct
请务必保存 SESSDATA
和 bili_jct
正文参数( application/x-www-form-urlencoded ):
参数名 | 类型 | 内容 | 必要性 | 备注 |
---|---|---|---|---|
captchaType | num | 6 | 必要 | 必须为6 |
username | str | 用户登录账号 | 必要 | 手机号或邮箱地址 |
password | str | 加密后的带盐密码 | 必要 | base64格式 |
keep | bool | true | 必要 | 必须为true |
key | str | 登录秘钥 | 必要 | 从B站API获取 |
challenge | str | 极验challenge | 必要 | 从B站API获取 |
validate | str | 极验结果 | 必要 | 从极验获取 |
seccode | str | 极验结果 | 必要 | 从极验获取 |
json回复:
根对象:
字段 | 类型 | 内容 | 备注 |
---|---|---|---|
code | num | 返回值 | 0:成功 |
-400:请求错误 | |||
-629:账号或密码错误 | |||
-653:用户名或密码不能为空 | |||
-662:提交超时,请重新提交 | |||
-2001:缺少必要的的参数 | |||
-2100:需验证手机号或邮箱 | |||
2400:登录秘钥错误 | |||
2406:验证极验服务出错 | |||
86000:RSA解密失败 | |||
ts | num | 当前时间戳 | 成功时无此项 |
message | str | 错误信息 | 默认为0 |
data | obj | 数据本体 | 成功时有此项 |
短信验证码登录
基本流程: 获取国际地区代码->人机验证通过->发送验证码->返回数据->获取cookie
#### 获取国际地区代码
GET
http://passport.bilibili.com/web/generic/country/list
**json回复:**
根对象:
| 字段 | 类型 | 内容 | 备注 |
| ---- | ---- | -------- | ------- |
| code | num | 返回值 | 0:成功 |
| data | obj | 数据本体 | |
data
对象:
| 字段 | 类型 | 内容 | 备注 |
| ------ | ----- | -------------- | ---- |
| common | array | 常用国家或地区 | |
| others | array | 其他国家或地区 | |
data
中的common
和others
数组:
| 项 | 类型 | 内容 | 备注 |
| ---- | ---- | --------------- | ---- |
| 0 | obj | 国家或地区1 | |
| n | obj | 国家或地区(n+1) | |
| …… | obj | …… | …… |
common
和others
数组中的对象:
| 字段 | 类型 | 内容 | 备注 |
| ---------- | ---- | -------------- | ---- |
| id | num | 国际代码值 | |
| cname | str | 国家或地区名 | |
| country_id | str | 国家或地区区号 | |
#### 发送短信验证码
POST
http://passport.bilibili.com/x/passport-login/web/sms/send
**正文参数( application/x-www-form-urlencoded ):**
| 参数名 | 类型 | 内容 | 必要性 | 备注 |
| --------- | ---- | ------------------------- | ------ | ------------ |
| tel | num | 手机号码 | 必要 | |
| cid | num | 国际地区代码 | 必要 | |
| source | str | 固定为main_web
| 必要 | |
| token | str | 在获取gt,challenge处url有 | 必要 | |
| challenge | str | 极验challenge | 必要 | 从B站API获取 |
| validate | str | 极验结果 | 必要 | 从极验获取 |
| seccode | str | 极验结果+ | jordan | 必要 |
**json回复:**
根对象:
| 字段 | 类型 | 内容 | 备注 |
| ------------------------------------------------------------ | ---- | -------- | --------------- |
| code | num | 返回值 | 0:成功 |
| -400:请求错误 | | | |
| 1002:手机号格式错误 | | | |
| 86203:短信发送次数已达上限 | | | |
| 1003:验证码已经发送 | | | |
| 1025:该手机号在哔哩哔哩有过永久封禁记录,无法再次注册或绑定新账号 | | | |
| 2400:登录秘钥错误 | | | |
| 2406:验证极验服务出错 | | | |
| message | str | 错误信息 | 成功为0 |
| data | obj | 数据 | 内含captcha_key |
captcha_key
在下方传参时需要,请备用.
#### 使用短信验证码登录
验证登录成功后会进行设置以下cookie项:
DedeUserID
DedeUserID__ckMd5
SESSDATA
bili_jct
**正文参数( application/x-www-form-urlencoded ):**
| 参数名 | 类型 | 内容 | 必要性 | 备注 |
| ----------- | ---- | ------------------------------ | ------ | ----------------------------------------------------------- |
| cid | num | 国际地区代码 | 必要 | |
| tel | num | 手机号码 | 必要 | |
| code | num | 短信验证码 | 必要 | 超时时间为5min |
| source | str | 固定为main_web
| 必要 | |
| captcha_key | str | 上方发送短信验证码时的一个参数 | 必要 | |
| go_url | str | 跳转url | 非必要 | 默认为[https://www.bilibili.com](https://www.bilibili.com/) |
| keep | str | 未知 | 非必要 | 默认为true |
**json回复:**
根对象:
| 字段 | 类型 | 内容 | 备注 |
| ---------------------------- | ---- | -------- | ------- |
| code | num | 返回值 | 0:成功 |
| -400:请求错误 | | | |
| 1006:请输入正确的短信验证码 | | | |
| 1007:短信验证码已过期 | | | |
| message | str | 错误信息 | |
| data | obj | 信息本体 | |
data
对象:
| 字段 | 类型 | 内容 | 备注 |
| ------ | ---- | ------- | ----------------------------------------------------------- |
| is_new | bool | false | 未知,估计是未注册时自动注册新用户 |
| status | num | 0 | 未知,可能0就是成功吧 |
| url | str | 跳转url | 默认为[https://www.bilibili.com](https://www.bilibili.com/) |
[该部分API文档](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/login/login_action/SMS.md)
## 获取下载视频URL
[获取视频详情(CID分P)](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/video/videostream_url.md)->[获取视频流URL](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/video/videostream_url.md)
这里就不过多赘述了,有兴趣的可以去看看
# 喜闻乐见的代码环节
登录
```java
package me.heartalborada.bilidownloader.utils;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import javafx.scene.control.Alert;
import me.heartalborada.bilidownloader.main;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.cookie.Cookie;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import static me.heartalborada.bilidownloader.utils.internet.sendPost;
public class login {
public static boolean checkIsLogin(){
String data= null;
try {
data = new internet().getWithCookie(
http://api.bilibili.com/x/web-interface/nav,
SESSDATA=+ main.SESSDATA+; bili_jct=+main.bili_jct
);
} catch (IOException e) {
e.printStackTrace();
}
if (data != null) {
if(JsonParser.parseString(data).getAsJsonObject().get(code).getAsInt()==0){
return true;
}
}
return false;
}
public static class password{
private static String key=;
private static String encrypt(String str) {
//base64编码的公钥
byte[] decoded = org.apache.commons.codec.binary.Base64.decodeBase64(key);
RSAPublicKey pubKey = null;
String outStr = null;
try {
pubKey = (RSAPublicKey) KeyFactory.getInstance(RSA).generatePublic(new X509EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
} catch (InvalidKeySpecException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
//RSA加密
return outStr;
}
private static String getHashAndKey(){
String data = null;
try {
data=new internet().Eget(http://passport.bilibili.com/login?act=getkey);
} catch (Exception e) {
e.printStackTrace();
}
key=JsonParser.parseString(data).getAsJsonObject().get(key).getAsString().replace(-----BEGIN PUBLIC KEY-----,).replace(-----END PUBLIC KEY-----,).replace(\n,);
return JsonParser.parseString(data).getAsJsonObject().get(hash).getAsString();
}
public static String[] getCaptcha(){
String data = null;
try {
data=new internet().Eget(https://passport.bilibili.com/web/captcha/combine?plat=6);
} catch (Exception e) {
e.printStackTrace();
}
JsonObject json = JsonParser.parseString(data).getAsJsonObject();
if(json.get(code).getAsInt()==0) {
json=json.getAsJsonObject(data).getAsJsonObject(result);
return new String[]{
json.get(gt).getAsString(),
json.get(challenge).getAsString(),
json.get(key).getAsString()
};
}
return null;
}
public static String getPw(String pw){
String pw1= getHashAndKey()+pw;
String tmp=null;
try {
tmp=encrypt(pw1);
} catch (Exception e) {
e.printStackTrace();
}
return tmp;
}
public static void doLogin(String acc, String pw, String[] captcha) {
HashMap<String,Object> map=new HashMap<>();
map.put(captchaType,6);
map.put(username, acc);
map.put(password, pw);
map.put(keep,true);
map.put(key,captcha[0]);
map.put(challenge,captcha[1]);
map.put(validate,captcha[2]);
map.put(seccode,captcha[3]);
try {
Object[] data=internet.sendPost(http://passport.bilibili.com/web/login/v2,map);
if (data != null) {
if(JsonParser.parseString(String.valueOf(data[0])).getAsJsonObject().get(code).getAsInt()==0){
JsonObject json=new JsonObject();
@SuppressWarnings(unchecked)
List<Cookie> list= (List<Cookie>) data[1];
for(Cookie cookie:list){
if(cookie.getName().equals(SESSDATA)){
json.addProperty(SESSDATA,cookie.getValue());
}
if(cookie.getName().equals(bili_jct)){
json.addProperty(bili_jct,cookie.getValue());
}
}
//new file().write(main.cookie_file_dir,json.toString());
//登录成功信息
} else {
int code=JsonParser.parseString(String.valueOf(data[0])).getAsJsonObject().get(code).getAsInt();
switch (code){
//检测是否登录
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static class sms{
public static LinkedHashMap<String,Integer> getSmsLocationMap(){
String data= null;
try {
data = new internet().Eget(http://passport.bilibili.com/web/generic/country/list);
} catch (Exception e) {
e.printStackTrace();
}
JsonObject json= JsonParser.parseString(data).getAsJsonObject().get(data).getAsJsonObject();
LinkedHashMap<String,Integer> map=new LinkedHashMap<>();
JsonArray array=json.get(common).getAsJsonArray();
for(JsonElement o:array){
JsonObject json1= o.getAsJsonObject();
map.put(json1.get(cname).getAsString(),json1.get(country_id).getAsInt());
}
array=json.get(others).getAsJsonArray();
for(JsonElement o:array){
JsonObject json1= o.getAsJsonObject();
map.put(json1.get(cname).getAsString(),json1.get(country_id).getAsInt());
}
return map;
}
public static String[] getCaptcha(){
String data = null;
try {
data=new internet().Eget(https://passport.bilibili.com/x/passport-login/captcha?source=main_web);
} catch (Exception e) {
e.printStackTrace();
}
JsonObject json = JsonParser.parseString(data).getAsJsonObject();
if(json.get(code).getAsInt()==0) {
JsonObject res=json.getAsJsonObject(data);
return new String[]{
res.getAsJsonObject(geetest).get(gt).getAsString(),
res.getAsJsonObject(geetest).get(challenge).getAsString(),
res.get(token).getAsString()
};
}
return null;
}
public static String SendSmsCaptcha(long phone_num, int cid, String[] captcha){
HashMap<String,Object> map=new HashMap<>();
map.put(tel,phone_num);
map.put(cid,cid);
map.put(source,main_web);
map.put(token,captcha[0]);
map.put(challenge,captcha[1]);
map.put(validate,captcha[2]);
map.put(seccode,captcha[3]);
Object[] data=sendPost(http://passport.bilibili.com/x/passport-login/web/sms/send,map);
JsonObject json= JsonParser.parseString(String.valueOf(data[0])).getAsJsonObject();
if(json.get(code).getAsInt()==0){
//提示信息
return json.getAsJsonObject(data).get(captcha_key).getAsString();
} else {
int code=json.get(code).getAsInt();
switch (code){
//检测是否登录
}
}
return ;
}
public static void doLogin(int cid, long phone_num, long sms_code,String captcha_key){
HashMap<String,Object> map=new HashMap<>();
map.put(cid,cid);
map.put(tel,phone_num);
map.put(code,sms_code);
map.put(captcha_key,captcha_key);
map.put(source,main_web);
try {
Object[] data=internet.sendPost(https://passport.bilibili.com/x/passport-login/web/login/sms,map);
if (data != null) {
if(JsonParser.parseString(String.valueOf(data[0])).getAsJsonObject().get(code).getAsInt()==0){
JsonObject json=new JsonObject();
@SuppressWarnings(unchecked)
List<Cookie> list= (List<Cookie>) data[1];
for(Cookie cookie:list){
if(cookie.getName().equals(SESSDATA)){
json.addProperty(SESSDATA,cookie.getValue());
}
if(cookie.getName().equals(bili_jct)){
json.addProperty(bili_jct,cookie.getValue());
}
}
new file().write(main.cookie_file_dir,json.toString());
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.titleProperty().set(信息);
alert.setHeaderText(登录成功);
alert.showAndWait();
me.heartalborada.bilidownloader.gui.login.close();
me.heartalborada.bilidownloader.gui.captcha.close();
} else {
int code=JsonParser.parseString(String.valueOf(data[0])).getAsJsonObject().get(code).getAsInt();
switch (code){
//检测是否登录
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static class qr{
public static String getOauthKey(){
String data= null;
try {
data = new internet().Eget(http://passport.bilibili.com/qrcode/getLoginUrl);
} catch (Exception e) {
e.printStackTrace();
}
return JsonParser.parseString(data).getAsJsonObject().getAsJsonObject(data).get(oauthKey).getAsString();
}
public static String[] IsLogin(String OauthKey) throws IOException {
HashMap<String,Object> map=new HashMap<>();
map.put(oauthKey,OauthKey);
Object[] data=sendPost(http://passport.bilibili.com/qrcode/getLoginInfo,map);
JsonObject json=JsonParser.parseString(String.valueOf(data[0])).getAsJsonObject();
if(!json.get(status).getAsBoolean()){
if(json.get(data).getAsInt()==-4){
return new String[]{0,未扫描};
}
if(json.get(data).getAsInt()==-5){
return new String[]{0,已扫描, 但未确认};
}
}
if(json.get(status).getAsBoolean()){
JsonObject json1=new JsonObject();
@SuppressWarnings(unchecked)
List<Cookie> list= (List<Cookie>) data[1];
for(Cookie cookie:list){
if(cookie.getName().equals(SESSDATA)){
json1.addProperty(SESSDATA,cookie.getValue());
}
if(cookie.getName().equals(bili_jct)){
json1.addProperty(bili_jct,cookie.getValue());
}
}
new file().write(main.cookie_file_dir,json1.toString());
//检测是否登录
return new String[]{1,json.getAsJsonObject(data).get(url).getAsString()};
}
return new String[]{0,未知错误, 错误代码+json.get(data).getAsInt()};
}
}
}
```
获取视频URL:
```java
package me.heartalborada.bilidownloader.utils;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import me.heartalborada.bilidownloader.main;
import java.math.BigDecimal;
import java.util.LinkedHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class video {
public static LinkedHashMap<String, Long> getCidList(String aid){
LinkedHashMap<String,Long> map=new LinkedHashMap<String,Long>();
String data=null;
try {
data=new internet().Eget(https://api.bilibili.com/x/web-interface/view?aid=+aid);
} catch (Exception e) {
e.printStackTrace();
}
JsonObject json= JsonParser.parseString(data).getAsJsonObject();
if(json.get(code).getAsInt()==0) {
JsonArray pages=json.get(data).getAsJsonObject().get(pages).getAsJsonArray();
int i=0;
for (JsonElement o : pages) {
map.put(P+i+ +o.getAsJsonObject().get(part).getAsString(),o.getAsJsonObject().get(cid).getAsLong());
i++;
}
}
return map;
}
public static String BVidToAid(String BVid){
String data=null;
try {
data=new internet().Eget(https://api.bilibili.com/x/web-interface/view?bvid=+BVid);
} catch (Exception e) {
e.printStackTrace();
}
JsonObject json=JsonParser.parseString(data).getAsJsonObject();
if(json.get(code).getAsInt()==0) {
return json.get(data).getAsJsonObject().get(aid).getAsString();
}
return 404;
}
private static JsonObject json;
public String getVideoUrl(String aid, long cid, int qn1){
String data=null;
try {
data=new internet().getWithCookie(
https://api.bilibili.com/x/player/playurl?avid=+aid+&cid=+cid+&fourk=1&qn=+qn1,
SESSDATA=+ main.SESSDATA+; bili_jct=+main.bili_jct
);
} catch (Exception e) {
e.printStackTrace();
}
json=JsonParser.parseString(data).getAsJsonObject();
System.out.println(json);
if(json.get(code).getAsInt()==0){
return json.getAsJsonObject(data).get(durl).getAsJsonArray().get(0).getAsJsonObject().get(url).getAsString();
}
return 404;
}
public static LinkedHashMap<String,Integer> getQuality(String aid, long cid){
LinkedHashMap<String,Integer>map= new LinkedHashMap<>();
String data=null;
try {
data=new internet().Eget(https://api.bilibili.com/x/player/playurl?avid=+aid+&cid=+cid+&fourk=1);
} catch (Exception e) {
e.printStackTrace();
}
JsonObject json=JsonParser.parseString(data).getAsJsonObject();
if(json.get(code).getAsInt()==0){
JsonArray description= json.get(data).getAsJsonObject().get(accept_description).getAsJsonArray();
JsonArray quality= json.get(data).getAsJsonObject().get(accept_quality).getAsJsonArray();
for(int i=0;i<description.size();i++){
map.put(description.get(i).getAsString(),quality.get(i).getAsInt());
}
}
return map;
}
public static JsonObject getVideoJson(String aid){
String data=null;
try {
data = new internet().Eget(https://api.bilibili.com/x/web-interface/view?aid= + aid);
} catch (Exception e) {
e.printStackTrace();
}
return JsonParser.parseString(data).getAsJsonObject();
}
public static String getVideoPic(JsonObject json){
return json.getAsJsonObject(data).get(pic).getAsString();
}
}
```
然后再加上一点点UI就可以正常运行了
演示GIF:
二维码登录
密码登陆
短信登录视频下载演示
我的开源项目:
[github author="heartalborada-del" project="bili-downloader"][/github]
Comments 2 条评论
博主 龙头桑
?∠( ᐛ 」∠)_
博主 Liu
tql