Closed Drelf2018 closed 1 year ago
很久很久之前,我刚来到这个仓库,当时有个 Issue #31 他说:
这个包可以在脚手架里用吗,我在vue-cli开发模式下启动直接提示跨域
解决办法是在后端写请求,例如使用 fastapi + uvicorn 开一个后端,自己写接口。
开始我不懂啥意思,直到后来我也写了点 vue ,用到了 bilibili 的接口发现跨域,我就打算按照那个方法写后端。
但是一个个重新写接口名再找对应函数确实很累,所以我写了这个解析器:
import re from enum import Enum from inspect import iscoroutinefunction as isAsync, isfunction as isFn, isclass from typing import List, Tuple import bilibili_api import uvicorn from fastapi import FastAPI, Response pattern = re.compile(r'(?:([:\$\w]+(?:=\w+)?),?)') app = FastAPI() class Parser: def __init__(self, var: str): self.valid = True self.varDict = dict(v.split("<-") for v in var.split(";")) if var else dict() async def __aenter__(self): for key, val in self.varDict.items(): obj, err = await self.parse(val) if not err: if isinstance(obj, bilibili_api.Credential): self.valid = await obj.check_valid() self.varDict[key] = obj return self async def __aexit__(self, type, value, trace): ... async def parse(self, path: str) -> Tuple[any, bool]: "分析指令" sentences = path.split(".") # 指令列表 position: any = bilibili_api # 起始点 async def inner() -> bool: "递归取值" nonlocal position if len(sentences) == 0: return position is None # 判断是否取得具体对象 sentence = sentences.pop(0) # 分解执行的函数名、参数、指名参数 flags: List[str] = pattern.findall(sentence) func = flags.pop(0) args, kwargs = list(), dict() for flag in flags: # 假设分为键值形式 利用列表特性从 -1 读取值 # 即使没有键也能读到值 arg = flag.split("=") # 类型装换 if arg[-1].endswith(":int"): arg[-1] = int(arg[-1][:-4]) # 将值与储存的变量替换 arg[-1] = self.varDict.get(arg[-1], arg[-1]) # 存入对应的参数、指名参数 if len(arg) == 1: args.append(arg[0]) else: kwargs[arg[0]] = arg[1] # 开始转移 if isinstance(position, dict): position = position.get(func, None) else: position = getattr(position, func, None) # 赋值参数 if isAsync(position): position = await position(*args, **kwargs) elif isFn(position): position = position(*args, **kwargs) elif isclass(position) and not issubclass(position, Enum): position = position(*args, **kwargs) # 递归 return await inner() err = await inner() return position, err @app.get("/{path}") async def bilibili_api_web(response: Response, path: str, var: str = "", max_age: int = -1): # 返回头设置 response.headers["Access-Control-Allow-Origin"] = "*" if max_age != -1: response.headers["Cache-Control"] = f"max-age={max_age}" # 先判断是否有效 再分析 async with Parser(var) as parser: if not parser.valid: return {"code": 1, "error": "Cookies Error"} try: obj, err = await parser.parse(path) # 什么 golang 写法 if not err: return {"code": 0, "data": obj} else: return {"code": 2, "error": "Path Error"} except Exception as e: return {"code": 3, "error": str(e)} if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=9000)
这段代码我已经部署在阿里云的函数计算里了,域名:https://aliyun.nana7mi.link
from bilibili_api import user, sync async def main(): return await user.User(uid=2).get_user_info() print(sync(main()))
上述代码现在只需要一个链接:[https://aliyun.nana7mi.link/user.User(2).get_user_info()](https://aliyun.nana7mi.link/user.User(2).get_user_info()) 就能实现。
属于是从接口来回接口去了。
类似的还有 [https://aliyun.nana7mi.link/live.LiveRoom(21452505).get_room_info()](https://aliyun.nana7mi.link/live.LiveRoom(21452505).get_room_info())
Q1. 这个有什么用呢?
前端访问不跨域了。
Q2. 为什么要解析器,直接用 eval() 不好吗?
有安全隐患,用解析器这样一步一步调用比较安全。
https://aliyun.nana7mi.link/comment.get_comments(708326075350908930,type,1:int)?var=type<-comment.CommentResourceType.DYNAMIC
在网址后使用 var 参数用于储存变量,变量名与值用 <- 连接,多个变量用 ; 分割。
<-
;
这个变量是另一个需要被解析的文本,为什么不直接放在网址里呢?因为放前面会被当做字符串传进去。
同时为了不让所有参数都以字符串传入,还加了类型标注,在变量后使用类似 :int 的方式来强制转换,目前只写了 int 的。
:int
int
使用 ?max_age=86400 参数设置为期 86400 秒的缓存。
?max_age=86400
在获取的字典结果后再使用 .key 的方式获得更精细数据,节省带宽,例如:
.key
[https://aliyun.nana7mi.link/user.User(2).get_user_info().face?max_age=86400](https://aliyun.nana7mi.link/user.User(2).get_user_info().face?max_age=86400)
我的想法是这样子的:把解析器代码加进模块的 tools 包里面或者是整理成另一个模块发布在 pypi (如果选第二种的话那么你就自己建一个仓库吧),然后在模块的文档(或者是整理之后的另一个模块的 README)中用一个简单的 vue + python 后端的示例展示一下这个 API 接口怎么用,同时提供一下部署后端的文档。
很久很久之前,我刚来到这个仓库,当时有个 Issue #31 他说:
解决办法是在后端写请求,例如使用 fastapi + uvicorn 开一个后端,自己写接口。
开始我不懂啥意思,直到后来我也写了点 vue ,用到了 bilibili 的接口发现跨域,我就打算按照那个方法写后端。
但是一个个重新写接口名再找对应函数确实很累,所以我写了这个解析器:
用法
这段代码我已经部署在阿里云的函数计算里了,域名:https://aliyun.nana7mi.link
上述代码现在只需要一个链接:[https://aliyun.nana7mi.link/user.User(2).get_user_info()](https://aliyun.nana7mi.link/user.User(2).get_user_info()) 就能实现。
属于是从接口来回接口去了。
类似的还有 [https://aliyun.nana7mi.link/live.LiveRoom(21452505).get_room_info()](https://aliyun.nana7mi.link/live.LiveRoom(21452505).get_room_info())
FAQ
前端访问不跨域了。
有安全隐患,用解析器这样一步一步调用比较安全。
进阶用法
https://aliyun.nana7mi.link/comment.get_comments(708326075350908930,type,1:int)?var=type<-comment.CommentResourceType.DYNAMIC
在网址后使用 var 参数用于储存变量,变量名与值用
<-
连接,多个变量用;
分割。这个变量是另一个需要被解析的文本,为什么不直接放在网址里呢?因为放前面会被当做字符串传进去。
同时为了不让所有参数都以字符串传入,还加了类型标注,在变量后使用类似
:int
的方式来强制转换,目前只写了int
的。再高级一点呢
使用
?max_age=86400
参数设置为期 86400 秒的缓存。在获取的字典结果后再使用
.key
的方式获得更精细数据,节省带宽,例如:[https://aliyun.nana7mi.link/user.User(2).get_user_info().face?max_age=86400](https://aliyun.nana7mi.link/user.User(2).get_user_info().face?max_age=86400)