├── README.md └── 通过 Serverless 实现 B 站定时签到 ├── BiliExp.py ├── README.md ├── config.json ├── models ├── Article.py ├── Biliapi.py ├── Manga.py ├── PushMessage.py ├── Video.py └── aria2py.py └── serverless.yml /README.md: -------------------------------------------------------------------------------- 1 | # 腾讯云 Serverless 最佳实践 2 | 3 | ## 产品简介 4 | 腾讯云 Severless 是腾讯云提供的安全稳定、管理简化、高效易用且低成本的无服务器产品平台。它通过多种 Serverless 产品组合,快速落地 Serverless 架构及应用,加速互联网和传统企业的业务迭代与升级,让您全面享受 Serverless 架构带来的弹性伸缩、秒级部署、按需付费、免运维等好处。 5 | 6 | ## 最佳实践 7 | | 项目名称 | 简介 | 8 | | :------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 9 | | [Bilibili 自动签到模版](https://github.com/TencentCloud/serverless/blob/master/%E9%80%9A%E8%BF%87%20Serverless%20%E5%AE%9E%E7%8E%B0%20B%20%E7%AB%99%E5%AE%9A%E6%97%B6%E7%AD%BE%E5%88%B0/README.md) | 基于腾讯云 Serverless,实现 B 站定时打卡签到 | 10 | |[基于 PG SQL 的全栈应用模版](https://github.com/serverless-components/tencent-examples/tree/master/fullstack) | 基于 PostgreSQL 和云函数,快速部署一个全栈应用 | 11 | 12 | 13 | ## 官方组件 14 | 15 | | 组件名称 | serverless.yml 全量配置指引 | 16 | | :------------------------------------------------------------------------------------------- |:------------------------------------------------------------------------------------------- | 17 | | [scf 组件](https://github.com/serverless-components/tencent-scf) | [全量配置](https://github.com/serverless-components/tencent-scf/blob/master/docs/configure.md)| 18 | |[express 组件](https://github.com/serverless-components/tencent-express) | [全量配置](https://github.com/serverless-components/tencent-express/blob/master/docs/configure.md)| 19 | |[website 组件](https://github.com/serverless-components/tencent-website) | [全量配置](https://github.com/serverless-components/tencent-website/blob/master/docs/configure.md)| 20 | |[apigateway 组件](https://github.com/serverless-components/tencent-apigateway)|[全量配置](https://github.com/serverless-components/tencent-apigateway/blob/master/docs/configure.md)| 21 | |[egg 组件](https://github.com/serverless-components/tencent-egg)|[全量配置](https://github.com/serverless-components/tencent-egg/blob/master/docs/configure.md)| 22 | |[nextjs 组件](https://github.com/serverless-components/tencent-nextjs)|[全量配置](https://github.com/serverless-components/tencent-nextjs/blob/master/docs/configure.md)| 23 | |[nuxtjs 组件](https://github.com/serverless-components/tencent-nuxtjs)|[全量配置](https://github.com/serverless-components/tencent-nuxtjs/blob/master/docs/configure.md)| 24 | |[cdn 组件](https://github.com/serverless-components/tencent-cdn)|[全量配置](https://github.com/serverless-components/tencent-cdn/blob/master/docs/configure.md)| 25 | |[cos 组件](https://github.com/serverless-components/tencent-cos)|[全量配置](https://github.com/serverless-components/tencent-cos/blob/master/docs/configure.md)| 26 | |[django 组件](https://github.com/serverless-components/tencent-django/)|[全量配置](https://github.com/serverless-components/tencent-django/blob/master/docs/configure.md)| 27 | |[flask 组件](https://github.com/serverless-components/tencent-flask)|[全量配置](https://github.com/serverless-components/tencent-flask/blob/master/docs/configure.md)| 28 | |[koa 组件](https://github.com/serverless-components/tencent-koa/)|[全量配置](https://github.com/serverless-components/tencent-koa/blob/master/docs/configure.md)| 29 | |[laravel 组件](https://github.com/serverless-components/tencent-laravel)|[全量配置](https://github.com/serverless-components/tencent-laravel/blob/master/docs/configure.md)| 30 | |[layer 组件](https://github.com/serverless-components/tencent-layer)|[全量配置](https://github.com/serverless-components/tencent-layer/blob/master/docs/configure.md)| 31 | |[postgresql 组件](https://github.com/serverless-components/tencent-postgresql)|[全量配置](https://github.com/serverless-components/tencent-postgresql/blob/master/docs/configure.md)| 32 | |[vpc 组件](https://github.com/serverless-components/tencent-vpc/)|[全量配置](https://github.com/serverless-components/tencent-vpc/blob/master/docs/configure.md)| 33 | -------------------------------------------------------------------------------- /通过 Serverless 实现 B 站定时签到/BiliExp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from models.Biliapi import BiliWebApi 3 | from models.PushMessage import PushMessage 4 | import json, time 5 | import logging 6 | 7 | def bili_exp(cookieData, pm): 8 | "B站直播签到,投币分享获取经验,模拟观看一个视频" 9 | try: 10 | biliapi = BiliWebApi(cookieData) 11 | except Exception as e: 12 | logging.info(f'登录验证id为{cookieData["DedeUserID"]}的账户失败,原因为({str(e)}),跳过此账户后续所有操作') 13 | pm.addMsg(f'id为:{cookieData["DedeUserID"]} 的账户登录失败') 14 | return 15 | 16 | pm.addMsg(f'目前账户为:({biliapi.getUserName()})') 17 | logging.info(f'登录账户 ({biliapi.getUserName()}) 成功') 18 | 19 | rdata = { 20 | "直播签到": False, 21 | "投币数量": 0, 22 | "视频观看": False, 23 | "视频分享": False, 24 | "脚本执行前经验": 0, 25 | "脚本执行前硬币": 0, 26 | } 27 | 28 | try: 29 | if biliapi.vipPrivilegeReceive(1)["code"] == 0: 30 | rdata["领取大会员B币"] = True 31 | if biliapi.vipPrivilegeReceive(2)["code"] == 0: 32 | rdata["领取会员购优惠券"] = True 33 | except: 34 | pass 35 | 36 | try: 37 | xliveInfo = biliapi.xliveSign() 38 | logging.info(f'bilibili直播签到信息:{str(xliveInfo)}') 39 | rdata["直播签到"] = (xliveInfo["code"] == 0) 40 | except Exception as e: 41 | logging.warning(f'直播签到异常,原因为{str(e)}') 42 | 43 | try: 44 | room_id = biliapi.xliveGetRecommendList()["data"]["list"][6]["roomid"] 45 | uid = biliapi.xliveGetRoomInfo(room_id)["data"]["room_info"]["uid"] 46 | now_time = int(time.time()) 47 | bagList = biliapi.xliveGiftBagList()["data"]["list"] 48 | for x in bagList: 49 | if x["expire_at"] - now_time < 172800: #礼物到期时间小于2天 50 | ret = biliapi.xliveBagSend(room_id, uid, x["bag_id"], x["gift_id"], x["gift_num"]) 51 | if ret["code"] == 0: 52 | logging.info(f'{ret["data"]["send_tips"]} {ret["data"]["gift_name"]} 数量{ret["data"]["gift_num"]}') 53 | except Exception as e: 54 | logging.warning(f'直播送出即将过期礼物异常,原因为{str(e)}') 55 | 56 | try: 57 | reward = biliapi.getReward() 58 | logging.info(f'经验脚本开始前经验信息 :{str(reward)}') 59 | except Exception as e: 60 | logging.warning(f'获取账户经验信息异常,原因为{str(e)},跳过此账户后续所有操作') 61 | pm.addMsg(str(rdata)) 62 | return 63 | 64 | rdata["脚本执行前经验"] = reward["level_info"]["current_exp"] 65 | 66 | try: 67 | coin_num = biliapi.getCoin() 68 | except Exception as e: 69 | logging.warning(f'获取账户剩余硬币数异常,原因为{str(e)}') 70 | coin_num = 0 71 | 72 | rdata["脚本执行前硬币"] = coin_num 73 | 74 | coin_exp_num = (50 - reward["coins_av"]) // 10 75 | toubi_num = coin_exp_num if coin_num > coin_exp_num else coin_num 76 | 77 | try: 78 | datas = biliapi.getRegions() 79 | except Exception as e: 80 | logging.warning(f'获取B站分区视频信息异常,原因为{str(e)},跳过此账户后续所有操作') 81 | pm.addMsg(str(rdata)) 82 | return 83 | 84 | if(toubi_num > 0): 85 | for i in range(toubi_num): 86 | try: 87 | info = biliapi.coin(datas[i]["aid"], 1, 1) 88 | logging.info(f'投币信息 :{str(info)}') 89 | if(info["code"] == 0): 90 | rdata["投币数量"] += 1 91 | except Exception as e: 92 | logging.warning(f'投币异常,原因为{str(e)}') 93 | 94 | try: 95 | info = biliapi.report(datas[5]["aid"], datas[5]["cid"], 300) 96 | logging.info(f'模拟视频观看进度上报:{str(info)}') 97 | rdata["视频观看"] = (info["code"] == 0) 98 | except Exception as e: 99 | logging.warning(f'模拟视频观看异常,原因为{str(e)}') 100 | 101 | try: 102 | info = biliapi.share(datas[5]["aid"]) 103 | logging.info(f'分享视频结果:{str(info)}') 104 | rdata["视频分享"] = (info["code"] == 0) 105 | except Exception as e: 106 | logging.warning(f'分享视频异常,原因为{str(e)}') 107 | 108 | pm.addMsg(str(rdata)) 109 | logging.info('本账户操作全部完成') 110 | 111 | def main(*args): 112 | try: 113 | logging.basicConfig(filename="exp.log", filemode='a', level=logging.INFO, format="%(asctime)s: %(levelname)s, %(message)s", datefmt="%Y/%d/%m %H:%M:%S") 114 | except: 115 | pass 116 | 117 | with open('config.json','r',encoding='utf-8') as fp: 118 | configData = json.load(fp) 119 | 120 | pm = PushMessage(title="B站经验脚本消息推送", email=configData["email"]) 121 | 122 | for x in configData["cookieDatas"]: 123 | bili_exp(x, pm) 124 | 125 | try: 126 | pm.pushMessage() 127 | except Exception as e: 128 | logging.warning(f'消息推送异常,原因为{str(e)}') 129 | 130 | if __name__=="__main__": 131 | main() 132 | -------------------------------------------------------------------------------- /通过 Serverless 实现 B 站定时签到/README.md: -------------------------------------------------------------------------------- 1 | ## Bilibili 自动签到模版 2 | 3 | ### 项目说明 4 | 本项目为您提供了基于腾讯云云函数部署的B站定时签到模版 5 | 6 | ### 部署前提 7 | 1. 主账号已开通 [SCF 云函数](https://console.cloud.tencent.com/scf)与 [Serverless Framework](https://console.cloud.tencent.com/sls) 服务 8 | 2. 如果为子账号,已拥有[调用服务角色 SLS_QcsRole](https://cloud.tencent.com/document/product/1154/43006#4) 的权限 9 | 10 | ### 部署说明 11 | 1. 通过一下指令安装命令行工具 **Serverless Framework**, 如果您已经安装过命令行工具,也可通过该指令更新至最新版本 12 | 13 | ``` 14 | npm install -g serverless 15 | ``` 16 | 17 | 2. 下载项目模版代码并进入模版目录 18 | ``` 19 | sls init biliexp-demo 20 | cd biliexp-demo 21 | ``` 22 | 23 | 3. 打开 config.json 文档,根据说明填入对应内容 24 | ``` 25 | { 26 | "cookieDatas":[ 27 | { 28 | "SESSDATA": "", 29 | "bili_jct": "", 30 | "DedeUserID": "" 31 | } 32 | ], 33 | "email": "" , 34 | "说明":"cookieDatas由浏览器获取, email 处填入您用于接受通知的邮件名" 35 | } 36 | ``` 37 | cookieDatas 获取方式: 浏览器打开B站主页并登陆--》按F12打开开发者工具--》application--》cookies 38 | 39 | ![](https://img.serverlesscloud.cn/2020928/1601296845381-%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202020-09-28%2020.36.26.png) 40 | 41 | 42 | 4. 通过 `sls deploy` 完成部署,部署成功后,每日可自动触发 43 | 44 | 部署时,您可以选择扫码授权,或配置 `.env ` 文件使用永久密钥进行授权 45 | 46 | ### 更多 47 | 1. 项目模版默认配置每天 15:30 的定时触发,部署地域在广州,您可以通过修改 serverless.yml 配置文件,修改您的更多项目配置信息 48 | 49 | 2. `.env` 文件配置指引 50 | ``` 51 | # .env 52 | TENCENT_SECRET_ID=xxx # 您账号的SecretId 53 | TENCENT_SECRET_KEY=xxx # 您账号的SecretKey 54 | ``` -------------------------------------------------------------------------------- /通过 Serverless 实现 B 站定时签到/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "cookieDatas":[ 3 | { 4 | "SESSDATA": "", 5 | "bili_jct": "", 6 | "DedeUserID": "" 7 | } 8 | ], 9 | "email": "" , 10 | "说明":"cookieDatas由浏览器获取, email 处填入您用于接受通知的邮件名" 11 | } -------------------------------------------------------------------------------- /通过 Serverless 实现 B 站定时签到/models/Article.py: -------------------------------------------------------------------------------- 1 | from models.Biliapi import BiliWebApi 2 | 3 | class Article(object): 4 | "B站专栏类,用于发表B站专栏" 5 | createArticle = BiliWebApi.createArticle 6 | deleteArticle = BiliWebApi.deleteArticle 7 | getArticle = BiliWebApi.getArticle 8 | articleUpcover = BiliWebApi.articleUpcover 9 | articleCreateVote = BiliWebApi.articleCreateVote 10 | articleCardsBvid = BiliWebApi.articleCardsBvid 11 | articleCardsCvid = BiliWebApi.articleCardsCvid 12 | articleMangas = BiliWebApi.articleMangas 13 | #本类只继承BiliWebApi中与Article有关的方法 14 | class Content(object): 15 | "文本类用来处理B站奇葩的专栏提交格式" 16 | def __init__(self): 17 | self.__content = "" 18 | def add(self, text): 19 | "添加内容" 20 | self.__content = f'{self.__content}{text}' 21 | return self 22 | def startH(self): 23 | "开始一个标题" 24 | self.__content = f'{self.__content}

' 25 | return self 26 | def endH(self): 27 | "结束一个标题" 28 | self.__content = f'{self.__content}

' 29 | return self 30 | def startP(self, align=""): 31 | "开始一段正文" 32 | if align == "": 33 | self.__content = f'{self.__content}

' 34 | elif align == "left": 35 | self.__content = f'{self.__content}

' 36 | elif align == "center": 37 | self.__content = f'{self.__content}

' 38 | elif align == "right": 39 | self.__content = f'{self.__content}

' 40 | else: 41 | self.__content = f'{self.__content}

' 42 | return self 43 | def endP(self): 44 | "结束一段正文" 45 | self.__content = f'{self.__content}

' 46 | return self 47 | def startD(self): 48 | "开始一段带下划线的文字" 49 | self.__content = f'{self.__content}' 50 | return self 51 | def endD(self): 52 | "结束一段带下划线的文字" 53 | self.__content = f'{self.__content}' 54 | return self 55 | def startD(self, size=16): 56 | "开始一段大小为size的文字" 57 | self.__content = f'{self.__content}' 58 | return self 59 | def endD(self): 60 | "结束一段特定大小的文字" 61 | self.__content = f'{self.__content}' 62 | return self 63 | def startB(self): 64 | "开始一段加粗的文字" 65 | self.__content = f'{self.__content}' 66 | return self 67 | def endB(self): 68 | "结束一段加粗的文字" 69 | self.__content = f'{self.__content}' 70 | return self 71 | def startY(self): 72 | "开始一段引用" 73 | self.__content = f'{self.__content}
' 74 | return self 75 | def endY(self): 76 | "结束一段引用" 77 | self.__content = f'{self.__content}
' 78 | return self 79 | def br(self): 80 | "插入换行,不用结束,一般新段默认换行" 81 | self.__content = f'{self.__content}


' 82 | return self 83 | def line(self,type=0): 84 | "插入一段分割线,不用结束" 85 | ll = ('
', 86 | '
', 87 | '
', 88 | '
', 89 | '
', 90 | '
') 91 | self.__content = f'{self.__content}{ll[type]}' 92 | return self 93 | def startU(self): 94 | "开始一段无序列表" 95 | self.__content = f'{self.__content}' 100 | return self 101 | def startO(self): 102 | "开始一段有序列表" 103 | self.__content = f'{self.__content}
    ' 104 | return self 105 | def endO(self): 106 | "结束一段有序列表" 107 | self.__content = f'{self.__content}
' 108 | return self 109 | def startL(self): 110 | "开始列表中的一列" 111 | self.__content = f'{self.__content}
  • ' 112 | return self 113 | def endL(self): 114 | "结束列表中的一列" 115 | self.__content = f'{self.__content}
  • ' 116 | return self 117 | def startA(self, url=""): 118 | "插入站内链接,链接说明文字请用add方法添加" 119 | self.__content = f'{self.__content}' 120 | return self 121 | def endA(self): 122 | "结束插入站内链接" 123 | self.__content = f'{self.__content}' 124 | return self 125 | def picUrl(self, url="", text="", width="", height=""): 126 | "插入站内图片链接,添加图片说明,指定图片长宽,比如15px,25%" 127 | self.__content = f'{self.__content}
    {text}
    ' 133 | return self 134 | def picFile(self, article: "Article类的实例", file: "本地图片文件或图片Bytes", text="", width="", height=""): 135 | "插入本地图片文件或Bytes,添加图片说明,指定图片长宽,比如15px,25%" 136 | ret = article.articleUpcover(file) 137 | picurl = ret["data"]["url"] 138 | picurl = picurl.replace("http", "https") 139 | return self.picUrl(picurl, text, width, height) 140 | def vote(self, article: "Article类的实例", vote: "vote投票结构体字典"): 141 | "插入站内投票" 142 | id = article.articleCreateVote(vote)["data"]["vote_id"] 143 | self.__content = f'{self.__content}
    {vote["title"]}
    ' 144 | return self 145 | def card(self, Article: "Article类的实例", id: "根据type类型填写id", type: "video:视频标签 str,article:专栏标签,fanju:番剧标签,music:音乐标签,shop:会员购标签,caricature:漫画标签 int,live:直播标签 str"): 146 | "插入引用标签" 147 | def video(): 148 | ret = Article.articleCardsBvid(id) 149 | picurl = ret["data"][id]["pic"] 150 | picurl = picurl.replace("http", "https") 151 | aid = ret["data"][id]["aid"] 152 | return f'
    ' 153 | def article(): 154 | ret = Article.articleCardsCvid(id) 155 | picurl = ret["data"]["banner_url"] 156 | picurl = picurl.replace("http", "https") 157 | aid = ret["data"]["id"] 158 | return f'
    ' 159 | def fanju(): 160 | ret = Article.articleCardsCvid(id) 161 | picurl = ret["data"]["cover"] 162 | picurl = picurl.replace("http", "https") 163 | return f'
    ' 164 | def music(): 165 | ret = Article.articleCardsCvid(id) 166 | picurl = ret["data"]["cover_url"] 167 | picurl = picurl.replace("http", "https") 168 | return f'
    ' 169 | def shop(): 170 | ret = Article.articleCardsCvid(id) 171 | picurl = ret["data"]["performance_image"] 172 | picurl = picurl.replace("http", "https") 173 | return f'
    ' 174 | def caricature(): 175 | ret = Article.articleMangas(id) 176 | picurl = ret["data"][id]["vertical_cover"] 177 | picurl = picurl.replace("http", "https") 178 | return f'
    ' 179 | def live(): 180 | ret = Article.articleCardsCvid(id) 181 | picurl = ret["data"]["cover"] 182 | picurl = picurl.replace("http", "https") 183 | aid = ret["data"]["room_id"] 184 | return f'
    ' 185 | 186 | index = { 187 | "video": video, 188 | "article": article, 189 | "fanju": fanju, 190 | "music": music, 191 | "shop": shop, 192 | "caricature": caricature, 193 | "live": live, 194 | } 195 | if type in index: 196 | self.__content = f'{self.__content}{index[type]()}' 197 | return self 198 | def output(self): 199 | "输出,用于Article类提交content" 200 | return self.__content 201 | 202 | def __init__(self, cookieData, tilte="", content="", aid=0, category=0, list_id=0, tid=4, original=1, image_urls="", origin_image_urls=""): 203 | "创建一个B站专栏草稿" 204 | self.DoNotDel = False #在本类销毁时删除未提交文章,如果想保留请设置为True 205 | BiliWebApi.__init__(self, cookieData) 206 | self.__tilte = tilte 207 | self.__content = content 208 | self.__category = category 209 | self.__list_id = list_id 210 | self.__tid = tid 211 | self.__original = original 212 | self.__image_urls = image_urls 213 | self.__origin_image_urls = origin_image_urls 214 | self.__issubmit = False 215 | if(aid == 0): 216 | ret = self.createArticle(tilte, content, aid, category, list_id, tid, original, image_urls, origin_image_urls) 217 | self.__aid = ret["data"]["aid"] 218 | else: 219 | self.__aid = aid 220 | 221 | def setTilte(self, tilte=0): 222 | "设置专栏标题" 223 | self.__tilte = tilte 224 | def setCategory(self, category=0): 225 | "设置专栏分类" 226 | self.__category = category 227 | def setListId(self, list_id=0): 228 | "设置文集编号" 229 | self.__list_id = list_id 230 | def setTid(self, tid=4): 231 | "设置专栏封面类型" 232 | self.__tid = tid 233 | def setOriginal(self, original=1): 234 | "设置专栏是否为原创,原创为1,非原创为0" 235 | self.__original = original 236 | def setImage(self, origin_image_urls: str, image_urls=""): 237 | "设置专栏缩略图,image_urls为缩略图网址,origin_image_urls为缩略图原图在文章中的网址" 238 | self.__origin_image_urls = origin_image_urls 239 | if image_urls: 240 | self.__image_urls = image_urls 241 | else: 242 | self.__image_urls = origin_image_urls 243 | def setContent(self, content): 244 | "设置文章内容" 245 | self.__content = content 246 | 247 | def __del__(self): 248 | "删除已创建的文章" 249 | if(self.__issubmit == False and self.__aid != 0 and self.DoNotDel == False): 250 | self.deleteArticle(self.__aid) 251 | 252 | def getAid(self, url=False): 253 | "返回创建文章的aid或url,可通过url在网页上修改此文章" 254 | if url: 255 | return f'https://member.bilibili.com/v2#/upload/text/edit?aid={self.__aid}' 256 | else: 257 | return self.__aid 258 | 259 | def refresh(self): 260 | "如果在本程序外(例如网页上)修改了本文章,执行此函数同步" 261 | ret = self.getArticle(self.__aid) 262 | self.__tilte = ret["data"]["tilte"] 263 | self.__content = ret["data"]["content"] 264 | self.__category = ret["data"]["category"]["id"] 265 | if(ret["data"]["list"] != None): 266 | self.__list_id = ret["data"]["list"]["id"] 267 | self.__tid = ret["data"]["template_id"] 268 | self.__original = ret["data"]["original"] 269 | self.__image_urls = ret["data"]["image_urls"][0] 270 | self.__origin_image_urls = ret["data"]["origin_image_urls"][0] #这里可能有丢失封面的问题 271 | 272 | def save(self): 273 | "保存至B站上草稿箱,不发布,网页上可编辑" 274 | return self.createArticle(self.__tilte, self.__content, self.__aid, self.__category, self.__list_id, self.__tid, self.__original, self.__image_urls, self.__origin_image_urls) 275 | 276 | def submit(self): 277 | "发布至B站上" 278 | self.__issubmit = True 279 | return self.createArticle(self.__tilte, self.__content, self.__aid, self.__category, self.__list_id, self.__tid, self.__original, self.__image_urls, self.__origin_image_urls, True) 280 | -------------------------------------------------------------------------------- /通过 Serverless 实现 B 站定时签到/models/Biliapi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import requests 3 | import json 4 | class BiliWebApi(object): 5 | "B站web的api接口" 6 | def __init__(self, cookieData=None): 7 | #创建session 8 | self.__session = requests.session() 9 | #设置header 10 | self.__session.headers.update({"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/63.0.3239.108","Referer": "https://www.bilibili.com/",'Connection': 'keep-alive'}) 11 | if cookieData == None: 12 | return 13 | 14 | #添加cookie 15 | requests.utils.add_dict_to_cookiejar(self.__session.cookies, cookieData) 16 | if 'bili_jct' in cookieData: 17 | self.__bili_jct = cookieData["bili_jct"] 18 | else: 19 | self.__bili_jct = '' 20 | #self.__uid = cookieData["DedeUserID"] 21 | 22 | data = self.__session.get("https://api.bilibili.com/x/web-interface/nav").json() 23 | if data["code"] != 0: 24 | raise Exception("参数验证失败,登录状态失效") 25 | self.__name = data["data"]["uname"] 26 | self.__uid = data["data"]["mid"] 27 | #print(self.__session.cookies) 28 | 29 | def getUserName(self): 30 | "获取登录的账户用户名" 31 | return self.__name 32 | 33 | def getReward(self): 34 | "取B站经验信息" 35 | url = "https://account.bilibili.com/home/reward" 36 | return self.__session.get(url).json()["data"] 37 | 38 | def getWebNav(self): 39 | "取导航信息" 40 | url = "https://api.bilibili.com/x/web-interface/nav" 41 | return self.__session.get(url).json()["data"] 42 | 43 | @staticmethod 44 | def getId(url): 45 | "取B站指定视频链接的aid和cid号" 46 | import re 47 | content = requests.get(url, headers=Biliapi.__headers) 48 | match = re.search( 'https:\/\/www.bilibili.com\/video\/av(.*?)\/\">', content.text, 0) 49 | aid = match.group(1) 50 | match = re.search( '\"cid\":(.*?),', content.text, 0) 51 | cid = match.group(1) 52 | return {"aid": aid, "cid": cid} 53 | 54 | def getCoin(self): 55 | "获取剩余硬币数" 56 | url = "https://api.bilibili.com/x/web-interface/nav?build=0&mobi_app=web" 57 | return int(self.__session.get(url).json()["data"]["money"]) 58 | 59 | def coin(self, aid, num, select_like): 60 | "给指定av号视频投币" 61 | url = "https://api.bilibili.com/x/web-interface/coin/add" 62 | post_data = { 63 | "aid": aid, 64 | "multiply": num, 65 | "select_like": select_like, 66 | "cross_domain": "true", 67 | "csrf": self.__bili_jct 68 | } 69 | return self.__session.post(url, post_data).json() 70 | 71 | def share(self, aid): 72 | "分享指定av号视频" 73 | url = "https://api.bilibili.com/x/web-interface/share/add" 74 | post_data = { 75 | "aid": aid, 76 | "csrf": self.__bili_jct 77 | } 78 | return self.__session.post(url, post_data).json() 79 | 80 | def report(self, aid, cid, progres): 81 | "B站上报观看进度" 82 | url = "http://api.bilibili.com/x/v2/history/report" 83 | post_data = { 84 | "aid": aid, 85 | "cid": cid, 86 | "progres": progres, 87 | "csrf": self.__bili_jct 88 | } 89 | return self.__session.post(url, post_data).json() 90 | 91 | def getHomePageUrls(self): 92 | "取B站首页推荐视频地址列表" 93 | import re 94 | url = "https://www.bilibili.com" 95 | content = self.__session.get(url) 96 | match = re.findall( '
    ', content.text, 0) 97 | match = ["https:" + x for x in match] 98 | return match 99 | 100 | @staticmethod 101 | def getRegions(rid=1, num=6): 102 | "获取B站分区视频信息" 103 | url = "https://api.bilibili.com/x/web-interface/dynamic/region?ps=" + str(num) + "&rid=" + str(rid) 104 | datas = requests.get(url).json()["data"]["archives"] 105 | ids = [] 106 | for x in datas: 107 | ids.append({"title": x["title"], "aid": x["aid"], "bvid": x["bvid"], "cid": x["cid"]}) 108 | return ids 109 | 110 | @staticmethod 111 | def getRankings(rid=1, day=3): 112 | "获取B站分区排行榜视频信息" 113 | url = "https://api.bilibili.com/x/web-interface/ranking?rid=" + str(rid) + "&day=" + str(day) 114 | datas = requests.get(url).json()["data"]["list"] 115 | ids = [] 116 | for x in datas: 117 | ids.append({"title": x["title"], "aid": x["aid"], "bvid": x["bvid"], "cid": x["cid"], "coins": x["coins"], "play": x["play"]}) 118 | return ids 119 | 120 | def repost(self, dynamic_id, content="", extension='{"emoji_type":1}'): 121 | "转发B站动态" 122 | url = "https://api.vc.bilibili.com/dynamic_repost/v1/dynamic_repost/repost" 123 | post_data = { 124 | "uid": self.__uid, 125 | "dynamic_id": dynamic_id, 126 | "content": content, 127 | "extension": extension, 128 | #"at_uids": "", 129 | #"ctrl": "[]", 130 | "csrf_token": self.__bili_jct 131 | } 132 | return self.__session.post(url, post_data).json() 133 | 134 | def dynamicRepostReply(self, rid, content="", type=1, repost_code=3000, From="create.comment", extension='{"emoji_type":1}'): 135 | "评论动态并转发" 136 | url = "https://api.vc.bilibili.com/dynamic_repost/v1/dynamic_repost/reply" 137 | post_data = { 138 | "uid": self.__uid, 139 | "rid": rid, 140 | "type": type, 141 | "content": content, 142 | "extension": extension, 143 | "repost_code": repost_code, 144 | "from": From, 145 | "csrf_token": self.__bili_jct 146 | } 147 | return self.__session.post(url, post_data).json() 148 | 149 | def followed(self, followid: 'up的uid', isfollow=True): 150 | "关注或取关up主" 151 | url = "https://api.vc.bilibili.com/feed/v1/feed/SetUserFollow" 152 | post_data = { 153 | "type": 1 if isfollow else 0, 154 | "follow": followid, 155 | "csrf_token": self.__bili_jct 156 | } 157 | return self.__session.post(url, post_data).json() 158 | 159 | def followedModify(self, followid: 'up的uid', act=1, re_src=11): 160 | "改变关注状态(增加、删除关注的up)" 161 | url = "https://api.bilibili.com/x/relation/modify" 162 | post_data = { 163 | "fid": followid, 164 | "act": act, 165 | "re_src": re_src, 166 | "csrf": self.__bili_jct 167 | } 168 | return self.__session.post(url, post_data).json() 169 | 170 | def groupAddFollowed(self, followid: 'up的uid', tagids=0): 171 | "移动关注的up主的分组" 172 | url = "https://api.bilibili.com/x/relation/tags/addUsers?cross_domain=true" 173 | post_data = { 174 | "fids": followid, 175 | "tagids": tagids, #默认是0,特别关注是-10 176 | "csrf": self.__bili_jct 177 | } 178 | return self.__session.post(url, post_data).json() 179 | 180 | def getFollowing(self, uid=0, pn=1, ps=50, order='desc'): 181 | "获取指定账户的关注者(默认取本账户)" 182 | if uid == 0: 183 | uid = self.__uid 184 | url = f"https://api.bilibili.com/x/relation/followings?vmid={uid}&pn={pn}&ps={ps}&order={order}" 185 | return self.__session.get(url).json() 186 | 187 | def getTopicInfo(self, tag_name): 188 | "取B站话题信息" 189 | url = f'https://api.bilibili.com/x/tag/info?tag_name={tag_name}' 190 | return self.__session.get(url).json() 191 | 192 | def getTopicList(self, tag_name): 193 | "取B站话题列表,返回一个可迭代对象" 194 | topic_id = self.getTopicInfo(tag_name)["data"]["tag_id"] 195 | url = f'https://api.vc.bilibili.com/topic_svr/v1/topic_svr/topic_new?topic_id={topic_id}' 196 | jsobj = self.__session.get(url).json() 197 | cards = jsobj["data"]["cards"] 198 | for x in cards: 199 | yield x 200 | has_more = (jsobj["data"]["has_more"] == 1) 201 | while has_more: 202 | offset = jsobj["data"]["offset"] 203 | jsobj = self.__session.get(f'https://api.vc.bilibili.com/topic_svr/v1/topic_svr/topic_history?topic_name={tag_name}&offset_dynamic_id={offset}').json() 204 | if not 'cards' in jsobj["data"]: 205 | break 206 | cards = jsobj["data"]["cards"] 207 | for x in cards: 208 | yield x 209 | has_more = (jsobj["data"]["has_more"] == 1) 210 | 211 | def getDynamicNew(self, type_list='268435455'): 212 | "取B站用户最新动态数据" 213 | url = f'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid={self.__uid}&type_list={type_list}' 214 | content = self.__session.get(url) 215 | content.encoding = 'utf-8' #需要指定编码 216 | return json.loads(content.text) 217 | 218 | def getDynamic(self, uid=0, type_list='268435455'): 219 | "取B站用户动态数据,生成器" 220 | if not uid: 221 | uid = self.__uid 222 | url = f'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_new?uid={uid}&type_list={type_list}' 223 | content = self.__session.get(url) 224 | content.encoding = 'utf-8' #需要指定编码 225 | jsobj = json.loads(content.text) 226 | cards = jsobj["data"]["cards"] 227 | for x in cards: 228 | yield x 229 | hasnext = True 230 | offset = cards[len(cards) - 1]["desc"]["dynamic_id"] 231 | while hasnext: 232 | content = self.__session.get(f'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/dynamic_history?uid={uid}&offset_dynamic_id={offset}&type={type_list}') 233 | content.encoding = 'utf-8' 234 | jsobj = json.loads(content.text) 235 | hasnext = (jsobj["data"]["has_more"] == 1) 236 | #offset = jsobj["data"]["next_offset"] 237 | cards = jsobj["data"]["cards"] 238 | for x in cards: 239 | yield x 240 | offset = cards[len(cards) - 1]["desc"]["dynamic_id"] 241 | 242 | def getMyDynamic(self, uid=0): 243 | "取B站用户自己的动态列表,生成器" 244 | import time 245 | def retry_get(url): 246 | times = 3 247 | while times: 248 | try: 249 | jsobj = self.__session.get(url).json() 250 | assert jsobj["code"] == 0 251 | return jsobj 252 | except: 253 | times -= 1 254 | time.sleep(3) 255 | raise Exception(str(jsobj)) 256 | 257 | if uid == 0: 258 | uid = self.__uid 259 | url = f'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history?host_uid={uid}&need_top=1&offset_dynamic_id=' 260 | hasnext = True 261 | offset = '' 262 | while hasnext: 263 | jsobj = retry_get(f'{url}{offset}') 264 | hasnext = (jsobj["data"]["has_more"] == 1) 265 | if not 'cards' in jsobj["data"]: 266 | continue 267 | cards = jsobj["data"]["cards"] 268 | for x in cards: 269 | yield x 270 | offset = x["desc"]["dynamic_id_str"] 271 | 272 | def removeDynamic(self, dynamic_id: int): 273 | "删除自己的动态" 274 | url = f'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/rm_dynamic' 275 | post_data = { 276 | "dynamic_id": dynamic_id, 277 | "csrf_token": self.__bili_jct 278 | } 279 | return self.__session.post(url, post_data).json() 280 | 281 | def getLotteryNotice(self, dynamic_id: int): 282 | "取指定抽奖信息" 283 | url = f'https://api.vc.bilibili.com/lottery_svr/v1/lottery_svr/lottery_notice?dynamic_id={dynamic_id}' 284 | content = self.__session.get(url) 285 | content.encoding = 'utf-8'#不指定会出错 286 | return json.loads(content.text) 287 | 288 | def xliveSign(self): 289 | "B站直播签到" 290 | url = "https://api.live.bilibili.com/xlive/web-ucenter/v1/sign/DoSign" 291 | return self.__session.get(url).json() 292 | 293 | def xliveGetStatus(self): 294 | "B站直播获取金银瓜子状态" 295 | url = "https://api.live.bilibili.com/pay/v1/Exchange/getStatus" 296 | return self.__session.get(url).json() 297 | 298 | def silver2coin(self): 299 | "银瓜子兑换硬币" 300 | url = "https://api.live.bilibili.com/pay/v1/Exchange/silver2coin" 301 | post_data = { 302 | "csrf_token": self.__bili_jct 303 | } 304 | return self.__session.post(url, post_data).json() 305 | 306 | def createArticle(self, tilte="", content="", aid=0, category=0, list_id=0, tid=4, original=1, image_urls="", origin_image_urls="", submit=False): 307 | "发表专栏" 308 | post_data = { 309 | "title": tilte, 310 | "content": content, 311 | "category": category,#专栏分类,0为默认 312 | "list_id": list_id,#文集编号,默认0不添加到文集 313 | "tid": 4, #4为专栏封面单图,3为专栏封面三图 314 | "reprint": 0, 315 | "media_id": 0, 316 | "spoiler": 0, 317 | "original": original, 318 | "csrf": self.__bili_jct 319 | } 320 | url = 'https://api.bilibili.com/x/article/creative/draft/addupdate'#编辑地址,发表前可以通过这个来编辑草稿,没打草稿不允许发表 321 | if aid: 322 | post_data["aid"] = aid 323 | if submit: 324 | url = 'https://api.bilibili.com/x/article/creative/article/submit'#正式发表地址 325 | if origin_image_urls and image_urls: 326 | post_data["origin_image_urls"] = origin_image_urls 327 | post_data["image_urls"] = image_urls 328 | return self.__session.post(url, post_data).json() 329 | 330 | def deleteArticle(self, aid: int): 331 | "删除专栏" 332 | url = 'https://member.bilibili.com/x/web/draft/delete' 333 | post_data = { 334 | "aid": aid, 335 | "csrf": self.__bili_jct 336 | } 337 | return self.__session.post(url, post_data).json() 338 | 339 | def getArticle(self, aid: int): 340 | "获取专栏内容" 341 | url = f'https://api.bilibili.com/x/article/creative/draft/view?aid={aid}' 342 | return self.__session.get(url).json() 343 | 344 | def articleUpcover(self, file): 345 | "上传本地图片,返回链接" 346 | url = 'https://api.bilibili.com/x/article/creative/article/upcover' 347 | files = { 348 | 'binary':(file) 349 | } 350 | post_data = { 351 | "csrf": self.__bili_jct 352 | } 353 | return self.__session.post(url, post_data, files=files, timeout=(5, 60)).json() 354 | 355 | def articleCardsBvid(self, bvid: 'str 加上BV前缀'): 356 | "根据bv号获取视频信息,在专栏引用视频时使用" 357 | url = f'https://api.bilibili.com/x/article/cards?ids={bvid}&cross_domain=true' 358 | return self.__session.get(url).json() 359 | 360 | def articleCardsCvid(self, cvid: 'str 加上cv前缀'): 361 | "根据cv号获取专栏,在专栏引用其他专栏时使用" 362 | url = f'https://api.bilibili.com/x/article/cards?id={cvid}&cross_domain=true' 363 | return self.__session.get(url).json() 364 | 365 | def articleCardsId(self, epid: 'str 加上ep前缀'): 366 | "根据ep号获取番剧信息,在专栏引用站内番剧时使用" 367 | return self.articleCardsCvid(epid) 368 | 369 | def articleCardsAu(self, auid: 'str 加上au前缀'): 370 | "根据au号获取音乐信息,在专栏引用站内音乐时使用" 371 | return self.articleCardsCvid(auid) 372 | 373 | def articleCardsPw(self, pwid: 'str 加上pw前缀'): 374 | "根据au号获取会员购信息,在专栏引用会员购时使用" 375 | return self.articleCardsCvid(pwid) 376 | 377 | def articleMangas(self, mcid: 'int 不加mc前缀'): 378 | "根据mc号获取漫画信息,在专栏引用站内漫画时使用" 379 | url = f'https://api.bilibili.com/x/article/mangas?id={mcid}&cross_domain=true' 380 | return self.__session.get(url).json() 381 | 382 | def articleCardsLv(self, lvid: 'str 加上lv前缀'): 383 | "根据lv号获取直播信息,在专栏引用站内直播时使用" 384 | return self.articleCardsCvid(lvid) 385 | 386 | def articleCreateVote(self, vote): 387 | "创建一个投票" 388 | ''' 389 | vote = { 390 | "title": "投票标题", 391 | "desc": "投票说明", 392 | "type": 0, #0为文字投票,1为图片投票 393 | "duration": 604800,#投票时长秒,604800为一个星期 394 | "options":[ 395 | { 396 | "desc": "选项1", 397 | "cnt": 0,#不知道什么意思 398 | "idx": 1, #选项序号,第一个选项为1 399 | #"img_url": "http://i0.hdslb.com/bfs/album/d74e83cf96a9028eb3e280d5f877dce53760a7e2.jpg",#仅图片投票需要 400 | }, 401 | { 402 | "desc": "选项2", 403 | "cnt": 0, 404 | "idx": 2, #选项序号,第二个选项为2 405 | #"img_url": "" 406 | } 407 | ] 408 | } 409 | ''' 410 | post_data = { 411 | "info": vote, 412 | "csrf": self.__bili_jct 413 | } 414 | return self.__session.post(url, post_data).json() 415 | 416 | def videoPreupload(self, filename, filesize): 417 | "申请上传,返回上传信息" 418 | from urllib.parse import quote 419 | name = quote(filename) 420 | url = f'https://member.bilibili.com/preupload?name={name}&size={filesize}&r=upos&profile=ugcupos%2Fbup&ssl=0&version=2.8.9&build=2080900&upcdn=bda2&probe_version=20200628' 421 | return self.__session.get(url).json() 422 | 423 | def videoUploadId(self, url, auth): 424 | "向上传地址申请上传,得到上传id等信息" 425 | return self.__session.post(f'{url}?uploads&output=json', headers={"X-Upos-Auth": auth}).json() 426 | 427 | def videoUpload(self, url, auth, upload_id, data, chunk, chunks, start, total): 428 | "上传视频分块" 429 | size = len(data) 430 | end = start + size 431 | content = self.__session.put(f'{url}?partNumber={chunk+1}&uploadId={upload_id}&chunk={chunk}&chunks={chunks}&size={size}&start={start}&end={end}&total={total}', data=data, headers={"X-Upos-Auth": auth}) 432 | return True if content.text == "MULTIPART_PUT_SUCCESS" else False 433 | 434 | def videoUploadInfo(self, url, auth, parts, filename, upload_id, biz_id): 435 | "查询上传视频信息" 436 | from urllib.parse import quote 437 | name = quote(filename) 438 | return self.__session.post(f'{url}?output=json&name={name}&profile=ugcupos%2Fbup&uploadId={upload_id}&biz_id={biz_id}', json={"parts":parts}, headers={"X-Upos-Auth": auth}).json() 439 | 440 | def videoRecovers(self, fns: '视频编号'): 441 | "查询以前上传的视频信息" 442 | url = f'https://member.bilibili.com/x/web/archive/recovers?fns={fns}' 443 | return self.__session.get(url=url).json() 444 | 445 | def videoTags(self, title: '视频标题', filename: "上传后的视频名称", typeid="", desc="", cover="", groupid=1, vfea=""): 446 | "上传视频后获得推荐标签" 447 | from urllib.parse import quote 448 | url = f'https://member.bilibili.com/x/web/archive/tags?typeid={typeid}&title={quote(title)}&filename=filename&desc={desc}&cover={cover}&groupid={groupid}&vfea={vfea}' 449 | return self.__session.get(url=url).json() 450 | 451 | def videoAdd(self, videoData:"dict 视频参数"): 452 | "发布视频" 453 | url = f'https://member.bilibili.com/x/vu/web/add?csrf={self.__bili_jct}' 454 | return self.__session.post(url, json=videoData).json() 455 | 456 | def videoPre(self): 457 | "视频预操作" 458 | url = 'https://member.bilibili.com/x/geetest/pre' 459 | return self.__session.get(url=url).json() 460 | 461 | def videoDelete(self, aid, geetest_challenge, geetest_validate, geetest_seccode): 462 | "删除视频" 463 | url = 'https://member.bilibili.com/x/web/archive/delete' 464 | post_data = { 465 | "aid": aid, 466 | "geetest_challenge": geetest_challenge, 467 | "geetest_validate": geetest_validate, 468 | "geetest_seccode": geetest_seccode, 469 | "success": 1, 470 | "csrf": self.__bili_jct 471 | } 472 | return self.__session.post(url, post_data).json() 473 | 474 | @staticmethod 475 | def activityList(plat='2', mold=0, http=3, start_page=1, end_page=10): 476 | "获取B站活动列表,生成器" 477 | session = requests.session() 478 | url = f'https://www.bilibili.com/activity/page/list?plat={plat}&mold={mold}&http={http}&page={start_page}' 479 | list = session.get(url).json()["data"]["list"] 480 | while len(list): 481 | for x in list: 482 | yield x 483 | if start_page == end_page: 484 | break 485 | start_page += 1 486 | url = f'https://www.bilibili.com/activity/page/list?plat={plat}&mold={mold}&http={http}&page={start_page}' 487 | list = session.get(url).json()["data"]["list"] 488 | session.close() 489 | 490 | @staticmethod 491 | def activityAll(): 492 | "获取B站活动列表" 493 | url = 'https://member.bilibili.com/x/app/h5/activity/videoall' 494 | return requests.get(url).json() 495 | 496 | def activityAddTimes(self, sid: 'str 活动sid', action_type: 'int 操作类型'): 497 | "增加B站活动的参与次数" 498 | url = 'https://api.bilibili.com/x/activity/lottery/addtimes' 499 | post_data = { 500 | "sid": sid, 501 | "action_type": action_type, 502 | "csrf": self.__bili_jct 503 | } 504 | #响应例子{"code":75405,"message":"获得的抽奖次数已达到上限","ttl":1} 505 | return self.__session.post(url, post_data).json() 506 | 507 | def activityDo(self, sid: 'str 活动sid', type: 'int 操作类型'): 508 | "参与B站活动" 509 | #B站有时候举行抽奖之类的活动,活动页面能查出活动的sid 510 | post_data = { 511 | "sid": sid, 512 | "type": type, 513 | "csrf": self.__bili_jct 514 | } 515 | #响应例子{"code":75415,"message":"抽奖次数不足","ttl":1,"data":null} 516 | return self.__session.post('https://api.bilibili.com/x/activity/lottery/do', post_data).json() 517 | 518 | def activityMyTimes(self, sid: 'str 活动sid'): 519 | "获取B站活动次数" 520 | url = f'https://api.bilibili.com/x/activity/lottery/mytimes?sid={sid}' 521 | #响应例子{"code":0,"message":"0","ttl":1,"data":{"times":0}} 522 | return self.__session.get(url=url).json() 523 | 524 | def xliveGetAward(self, platform="android"): 525 | "B站直播模拟客户端打开宝箱领取银瓜子" 526 | url = f'https://api.live.bilibili.com/lottery/v1/SilverBox/getAward?platform={platform}' 527 | return self.__session.get(url).json() 528 | 529 | def xliveGetCurrentTask(self, platform="android"): 530 | "B站直播模拟客户端获取时间宝箱" 531 | url = f'https://api.live.bilibili.com/lottery/v1/SilverBox/getCurrentTask?platform={platform}' 532 | return self.__session.get(url).json() 533 | 534 | def xliveGiftBagList(self): 535 | "B站直播获取背包礼物" 536 | url = 'https://api.live.bilibili.com/xlive/web-room/v1/gift/bag_list' 537 | return self.__session.get(url=url).json() 538 | 539 | def xliveGetRecommendList(self): 540 | "B站直播获取首页前10条直播" 541 | url = f'https://api.live.bilibili.com/relation/v1/AppWeb/getRecommendList' 542 | return self.__session.get(url=url).json() 543 | 544 | def xliveBagSend(self, biz_id, ruid, bag_id, gift_id, gift_num, storm_beat_id=0, price=0, platform="pc"): 545 | "B站直播送出背包礼物" 546 | url = 'https://api.live.bilibili.com/gift/v2/live/bag_send' 547 | post_data = { 548 | "uid": self.__uid, 549 | "gift_id": gift_id, #背包里的礼物id 550 | "ruid": ruid, #up主的uid 551 | "send_ruid": 0, 552 | "gift_num": gift_num, #送礼物的数量 553 | "bag_id": bag_id, #背包id 554 | "platform": platform, #平台 555 | "biz_code": "live", 556 | "biz_id": biz_id, #房间号 557 | #"rnd": rnd, #直播开始时间 558 | "storm_beat_id": storm_beat_id, 559 | "price": price, #礼物价格 560 | "csrf": self.__bili_jct 561 | } 562 | return self.__session.post(url,post_data).json() 563 | 564 | def xliveGetRoomInfo(self, room_id: 'int 房间id'): 565 | "B站直播获取房间信息" 566 | url = f'https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom?room_id={room_id}' 567 | return self.__session.get(url=url).json() 568 | 569 | def xliveWebHeartBeat(self, biz_id, last=11, platform="web"): 570 | "B站直播 直播间心跳" 571 | import base64 572 | hb = base64.b64encode(f'{last}|{biz_id}|1|0'.encode('utf-8')).decode() 573 | url = f'https://live-trace.bilibili.com/xlive/rdata-interface/v1/heartbeat/webHeartBeat?hb={hb}&pf={platform}' 574 | return self.__session.get(url).json() 575 | 576 | def xliveHeartBeat(self): 577 | "B站直播 心跳(大约2分半一次)" 578 | url = f'https://api.live.bilibili.com/relation/v1/Feed/heartBeat' 579 | return self.__session.get(url).json() 580 | 581 | def xliveUserOnlineHeart(self): 582 | "B站直播 用户在线心跳(很少见)" 583 | url = f'https://api.live.bilibili.com/User/userOnlineHeart' 584 | post_data = { 585 | "csrf": self.__bili_jct 586 | } 587 | content = self.__session.post(url, post_data) 588 | return self.__session.post(url, post_data).json() 589 | 590 | def mangaClockIn(self, platform="android"): 591 | "模拟B站漫画客户端签到" 592 | url = "https://manga.bilibili.com/twirp/activity.v1.Activity/ClockIn" 593 | post_data = { 594 | "platform": platform 595 | } 596 | return self.__session.post(url, post_data).json() 597 | 598 | def mangaGetWallet(self, platform="web"): 599 | "获取钱包信息" 600 | url = f'https://manga.bilibili.com/twirp/user.v1.User/GetWallet?platform={platform}' 601 | #{"code":0,"msg":"","data":{"remain_coupon":0,"remain_gold":0,"first_reward":false,"point":"1270","first_bonus_percent":0,"bonus_percent":0,"unusable_gold":0,"remain_item":0,"remain_tickets":0,"remain_discount":0,"account_level":0}} 602 | # 劵的数量 金币数量 是否第一次充值 积分 603 | return self.__session.post(url, json={}).json() 604 | 605 | def mangaComrade(self, platform="web"): 606 | "站友日漫画卷兑换" 607 | url = f'https://manga.bilibili.com/twirp/activity.v1.Activity/Comrade?platform={platform}' 608 | #{"code":0,"msg":"","data":{"now":"2020-09-20T21:10:38+08:00","received":0,"active":0,"lottery":0,"svip":1}} 609 | return self.__session.post(url, json={}).json() 610 | 611 | def mangaGetEpisodeBuyInfo(self, ep_id: int, platform="web"): 612 | "获取漫画购买信息" 613 | url = f'https://manga.bilibili.com/twirp/comic.v1.Comic/GetEpisodeBuyInfo?platform={platform}' 614 | post_data = { 615 | "ep_id": ep_id 616 | } 617 | return self.__session.post(url, json=post_data).json() 618 | 619 | def mangaBuyEpisode(self, ep_id: int, buy_method=1, coupon_id=0, auto_pay_gold_status=0, platform="web"): 620 | "购买漫画" 621 | url = f'https://manga.bilibili.com/twirp/comic.v1.Comic/BuyEpisode?&platform={platform}' 622 | post_data = { 623 | "buy_method": buy_method, 624 | "ep_id": ep_id 625 | } 626 | #{"buy_method":2,"ep_id":283578,"coupon_id":2064528,"auto_pay_gold_status":2} 627 | if coupon_id: 628 | post_data["coupon_id"] = coupon_id 629 | if auto_pay_gold_status: 630 | post_data["auto_pay_gold_status"] = auto_pay_gold_status 631 | 632 | #{"code":1,"msg":"没有足够的卡券使用次数,请刷新重试。","data":{"auto_use_item":""}} 633 | return self.__session.post(url, json=post_data).json() 634 | 635 | def mangaGetTopic(self, page_num=1, platform='phone'): 636 | "B站漫画app活动中心列表" 637 | url = 'https://manga.bilibili.com/twirp/comic.v1.Comic/Topic' 638 | post_data = { 639 | "page_num": page_num, 640 | "platform": platform 641 | } 642 | return self.__session.post(url, post_data).json() 643 | 644 | def mangaListFavorite(self, page_num=1, page_size=50, order=1, wait_free=0, platform='web'): 645 | "B站漫画追漫列表" 646 | url = 'https://manga.bilibili.com/twirp/bookshelf.v1.Bookshelf/ListFavorite?platform={platform}' 647 | post_data = { 648 | "page_num": page_num, 649 | "page_size": page_size, 650 | "order": order, 651 | "wait_free": wait_free 652 | } 653 | return self.__session.post(url, json=post_data).json() 654 | 655 | def mangaPayBCoin(self, pay_amount: int, product_id=1, platform='web'): 656 | "B币购买漫画" 657 | url = f'https://manga.bilibili.com/twirp/pay.v1.Pay/PayBCoin?platform={platform}' 658 | post_data = { 659 | "pay_amount": pay_amount, 660 | "product_id": product_id 661 | } 662 | #{"code":0,"msg":"","data":{"id":"1600656017507211119"}} 663 | return self.__session.post(url, json=post_data).json() 664 | 665 | def mangaGetBCoin(self, platform='web'): 666 | "获取B币与漫读劵的信息" 667 | url = f'https://manga.bilibili.com/twirp/pay.v1.Pay/GetBCoin?platform={platform}' 668 | #{"code":0,"msg":"","data":{"amount":0,"exchange_rate":100,"first_max_coin":18800,"first_bonus_percent":0,"bonus_percent":0,"coupon_rate":2,"coupon_exp":30,"point_rate":200,"coin_amount":0,"coupon_amount":0,"is_old_version":true}} 669 | return self.__session.post(url, json={}).json() 670 | 671 | def mangaGetCoupons(self, not_expired=True, page_num=1, page_size=50, tab_type=1, platform='web'): 672 | "获取账户中的漫读劵信息" 673 | url = f'https://manga.bilibili.com/twirp/user.v1.User/GetCoupons?platform={platform}' 674 | post_data = { 675 | "not_expired": not_expired, 676 | "page_num": page_num, 677 | "page_size": page_size, 678 | "tab_type": tab_type 679 | } 680 | #{"code":0,"msg":"","data":{"total_remain_amount":4,"user_coupons":[{"ID":2093696,"remain_amount":2,"expire_time":"2020-10-21 10:40:17","reason":"B币兑换","type":"福利券","ctime":"2020-09-21 10:40:17","total_amount":2,"limits":[],"type_num":7,"will_expire":0,"discount":0,"discount_limit":0,"is_from_card":0},{"ID":2093703,"remain_amount":2,"expire_time":"2020-10-21 10:47:43","reason":"B币兑换","type":"福利券","ctime":"2020-09-21 10:47:43","total_amount":2,"limits":[],"type_num":7,"will_expire":0,"discount":0,"discount_limit":0,"is_from_card":0}],"coupon_info":{"new_coupon_num":0,"coupon_will_expire":0,"rent_will_expire":0,"new_rent_num":0,"discount_will_expire":0,"new_discount_num":0,"month_ticket_will_expire":0,"new_month_ticket_num":0,"silver_will_expire":0,"new_silver_num":0,"remain_item":0,"remain_discount":0,"remain_coupon":4,"remain_silver":0}}} 681 | return self.__session.post(url, json=post_data).json() 682 | 683 | def mangaDetail(self, comic_id: int, device='pc', platform='web'): 684 | "获取漫画信息" 685 | url = f'https://manga.bilibili.com/twirp/comic.v1.Comic/ComicDetail?device={device}&platform={platform}' 686 | post_data = { 687 | "comic_id": comic_id 688 | } 689 | return self.__session.post(url, json=post_data).json() 690 | 691 | def mangaGetPoint(self): 692 | "获取漫画积分" 693 | url = f'https://manga.bilibili.com/twirp/pointshop.v1.Pointshop/GetUserPoint' 694 | return self.__session.post(url, json={}).json() 695 | 696 | def mangaShopList(self): 697 | "漫画积分商城列表" 698 | url = f'https://manga.bilibili.com/twirp/pointshop.v1.Pointshop/ListProduct' 699 | return self.__session.post(url, json={}).json() 700 | 701 | def mangaShopExchange(self, product_id: int, point: int, product_num=1): 702 | "漫画积分商城兑换" 703 | url = f'https://manga.bilibili.com/twirp/pointshop.v1.Pointshop/Exchange' 704 | post_data = { 705 | "product_id": product_id, 706 | "point": point, 707 | "product_num": product_num 708 | } 709 | return self.__session.post(url, json=post_data).json() 710 | 711 | def mangaImageToken(self, urls=[], device='pc', platform='web'): 712 | "获取漫画图片token" 713 | url = f'https://manga.bilibili.com/twirp/comic.v1.Comic/ImageToken?device={device}&platform={platform}' 714 | post_data = { 715 | "urls": json.dumps(urls) 716 | } 717 | return self.__session.post(url, json=post_data).json() 718 | 719 | def mangaImageIndex(self, ep_id: int, device='pc', platform='web'): 720 | "获取漫画图片列表" 721 | url = f'https://manga.bilibili.com/twirp/comic.v1.Comic/GetImageIndex?device={device}&platform={platform}' 722 | post_data = { 723 | "ep_id": ep_id 724 | } 725 | return self.__session.post(url, json=post_data).json() 726 | 727 | def mangaGetImageBytes(self, url: str): 728 | "获取漫画图片" 729 | return self.__session.get(url).content 730 | 731 | def vipPrivilegeMy(self): 732 | "B站大会员权益列表" 733 | url = 'https://api.bilibili.com/x/vip/privilege/my' 734 | #{"code":0,"message":"0","ttl":1,"data":{"list":[{"type":1,"state":1,"expire_time":1601481599},{"type":2,"state":0,"expire_time":1601481599}]}} 735 | return self.__session.get(url).json() 736 | 737 | def vipPrivilegeReceive(self, type=1): 738 | "领取B站大会员权益" 739 | url = 'https://api.bilibili.com/x/vip/privilege/receive' 740 | post_data = { 741 | "type": type, 742 | "csrf": self.__bili_jct 743 | } 744 | #{"code":69801,"message":"你已领取过该权益","ttl":1} 745 | return self.__session.post(url, data=post_data).json() 746 | 747 | @staticmethod 748 | def webView(bvid: str): 749 | "通过bv号获取视频信息" 750 | url = f'https://api.bilibili.com/x/web-interface/view?bvid={bvid}' 751 | return requests.get(url,headers={"User-Agent": "Mozilla/5.0","Referer": "https://www.bilibili.com/"}).json() 752 | 753 | @staticmethod 754 | def webStat(aid: int): 755 | "通过av号获取视频信息" 756 | url = f'https://api.bilibili.com/x/web-interface/archive/stat?aid={aid}' 757 | return requests.get(url,headers={"User-Agent": "Mozilla/5.0","Referer": "https://www.bilibili.com/"}).json() 758 | 759 | @staticmethod 760 | def playList(bvid='', aid=0): 761 | "获取播放列表" 762 | if bvid: 763 | url = f'https://api.bilibili.com/x/player/pagelist?bvid={bvid}' 764 | elif aid: 765 | url = f'https://api.bilibili.com/x/player/pagelist?bvid={aid}' 766 | return requests.get(url).json() 767 | 768 | @staticmethod 769 | def epPlayList(ep_or_ss: str): 770 | "获取番剧播放列表" 771 | import re 772 | url = f'https://www.bilibili.com/bangumi/play/{ep_or_ss}' 773 | text = requests.get(url, headers={'User-Agent':'Mozilla/5.0'}).text 774 | find = re.findall(r'window.__INITIAL_STATE__=({.*});\(function\(\)', text, re.S) 775 | return json.loads(find[0]) 776 | 777 | def webPlayUrl(self, cid=0, aid=0, bvid='', epid=0, qn=16): 778 | "获取番剧播放地址(普通视频请用playerUrl方法)" 779 | url = 'https://api.bilibili.com/pgc/player/web/playurl' 780 | data = {"qn":qn} 781 | if cid: 782 | data["cid"] = cid 783 | if aid: 784 | data["avid"] = aid 785 | if bvid: 786 | data["bvid"] = bvid 787 | if epid: 788 | data["ep_id"] = epid 789 | #{'code': -10403, 'message': '抱歉您所在地区不可观看!'} 790 | #{'code': -10403, 'message': '大会员专享限制'} 791 | return self.__session.get(url, params=data).json() 792 | 793 | def playerUrl(self, cid: int, aid=0, bvid='', qn=16, reverse_proxy=''): 794 | "获取视频播放地址" 795 | if reverse_proxy: 796 | url = reverse_proxy 797 | else: 798 | url = 'https://api.bilibili.com/x/player/playurl' 799 | #data = {"qn":qn,"cid":cid,'fnval':80} 800 | data = {"qn":qn,"cid":cid} 801 | if aid: 802 | data["avid"] = aid 803 | if bvid: 804 | data["bvid"] = bvid 805 | return self.__session.get(url, params=data).json() 806 | 807 | @staticmethod 808 | def videoGetPart(url: str, start, end): 809 | "下载视频分段" 810 | headers = {"Range":f'bytes={start}-{end}',"Referer": "https://www.bilibili.com/"} 811 | return requests.get(url, headers=headers).content 812 | 813 | @staticmethod 814 | def dmList(oid: int): 815 | "获得弹幕xml" 816 | url = f'https://api.bilibili.com/x/v1/dm/list.so?oid={oid}' 817 | content = requests.get(url) 818 | content.encoding = 'utf-8' 819 | return content.text 820 | 821 | @staticmethod 822 | def dmHistory(oid: int, data: str): 823 | "获得历史弹幕xml" 824 | url = f'https://api.bilibili.com/x/v2/dm/history?type=1&oid={oid}&date={data}' 825 | content = requests.get(url) 826 | content.encoding = 'utf-8' 827 | return content.text 828 | -------------------------------------------------------------------------------- /通过 Serverless 实现 B 站定时签到/models/Manga.py: -------------------------------------------------------------------------------- 1 | from models.Biliapi import BiliWebApi 2 | 3 | class Manga(object): 4 | "B站漫画下载类" 5 | def __init__(self, comic_id=0, cookieData=None): 6 | if cookieData: 7 | BiliWebApi.__init__(self, cookieData) 8 | else: 9 | import requests 10 | session = requests.session() 11 | session.headers.update({"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36","Referer": "https://www.bilibili.com/",'Connection': 'keep-alive'}) 12 | self._BiliWebApi__session = session 13 | if comic_id: 14 | self.__manga_detail = BiliWebApi.mangaDetail(self, comic_id)["data"] 15 | self.__comic_id = self.__manga_detail["id"] 16 | self.__manga_detail["ep_list"].sort(key=lambda elem: elem["ord"]) 17 | else: 18 | self.__manga_detail = None 19 | 20 | def setComicId(comic_id: int): 21 | "设置当前漫画id" 22 | self.__manga_detail = BiliWebApi.mangaDetail(self, comic_id)["data"] 23 | self.__comic_id = self.__manga_detail["id"] 24 | self.__manga_detail["ep_list"].sort(key=lambda elem: elem["ord"]) 25 | 26 | def getIndex(self): 27 | "获取漫画章节列表" 28 | return self.__manga_detail["ep_list"] 29 | 30 | def getTitle(self): 31 | "获取漫画名称" 32 | return self.__manga_detail["title"] 33 | 34 | def getAuthors(self): 35 | "获取漫画作者名称(数组)" 36 | return self.__manga_detail["author_name"] 37 | 38 | def getCover(self): 39 | "获取漫画封面图片链接" 40 | return self.__manga_detail["vertical_cover"] 41 | 42 | def getNum(self): 43 | "获取漫画章节数量" 44 | return self.__manga_detail["last_ord"] 45 | 46 | def getDownloadList(self, ep_id: int): 47 | "获取漫画章节下载列表" 48 | data = BiliWebApi.mangaImageIndex(self, ep_id)["data"]["images"] 49 | url_list = [x["path"] for x in data] 50 | data = BiliWebApi.mangaImageToken(self, url_list)["data"] 51 | url_list = [f'{x["url"]}?token={x["token"]}' for x in data] 52 | return url_list 53 | 54 | def download(self, ep_id: int, path: str): 55 | "下载一个章节" 56 | import os 57 | if not os.path.exists(path): 58 | os.mkdir(path) 59 | 60 | import requests 61 | _s = requests.session() 62 | 63 | list = self.getDownloadList(ep_id) 64 | n = 0 65 | for x in list: 66 | n += 1 67 | with open(f'{path}/{n:0>2}.jpg', 'wb') as f: 68 | f.write(_s.get(x).content) 69 | 70 | def downloadAll(self, path): 71 | "下载漫画所有可下载章节" 72 | import os 73 | if not os.path.exists(path): 74 | os.mkdir(path) 75 | title = self.getTitle() 76 | if path[-1] == '/': 77 | path = f'{path}{title}' 78 | else: 79 | path = f'{path}/{title}' 80 | if not os.path.exists(path): 81 | os.mkdir(path) 82 | print(f'开始下载漫画 "{title}"') 83 | bq = len(str(self.getNum())) 84 | for x in self.getIndex(): 85 | name = x["title"] 86 | if name.replace(' ', '') == '': 87 | name = x["short_title"] 88 | if not x["is_locked"]: 89 | self.download(x['id'], f'{path}/{x["ord"]:0>{bq}}-{name}') 90 | print(f'{x["ord"]:0>{bq}}-{name} 下载完成') 91 | else: 92 | print(f'{x["ord"]:0>{bq}}-{name} 目前需要解锁') 93 | 94 | -------------------------------------------------------------------------------- /通过 Serverless 实现 B 站定时签到/models/PushMessage.py: -------------------------------------------------------------------------------- 1 | class PushMessage(object): 2 | "消息推送" 3 | def __init__(self, title, SCKEY=None, email=None): 4 | "初始化" 5 | self.__title = title 6 | self.__message = "" 7 | self.__send_serverchan = f'https://sc.ftqq.com/{SCKEY}.send' if SCKEY else None 8 | self.__send_email = f'http://liuxingw.com/api/mail/api.php?address={email}' if email else None 9 | self.__title = title 10 | self.__message = "" 11 | 12 | def sendText(self, text, desp): 13 | "发送消息" 14 | import requests 15 | if self.__send_email: 16 | requests.get(self.__send_email, params={"name": text,"certno": desp.replace("\n","
    ")}) 17 | 18 | if self.__send_serverchan: 19 | requests.post(self.__send_serverchan, data={"text": text,"desp": desp}) 20 | 21 | def addMsg(self, msg, newLine=True): 22 | "添加要推送的消息" 23 | self.__message = f"{self.__message}{msg}\n" if newLine else f"{self.__message}{msg}" 24 | 25 | def pushMessage(self): 26 | "推送已经添加消息" 27 | self.sendText(self.__title, self.__message) 28 | 29 | def setMsg(self, msg): 30 | "设置要推送的消息" 31 | self.__message = msg 32 | 33 | def getMsg(self): 34 | "获取要推送的消息" 35 | return self.__message 36 | 37 | def setTitle(self, title): 38 | "设置消息标题" 39 | self.__title = title 40 | 41 | def getTitle(self): 42 | "获取当前消息标题" 43 | return self.__title 44 | -------------------------------------------------------------------------------- /通过 Serverless 实现 B 站定时签到/models/Video.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from models.Biliapi import BiliWebApi 3 | import time 4 | 5 | class VideoUploader(object): 6 | "B站视频上传类" 7 | videoPreupload = BiliWebApi.videoPreupload 8 | videoUploadId = BiliWebApi.videoUploadId 9 | videoUpload = BiliWebApi.videoUpload 10 | videoUploadInfo = BiliWebApi.videoUploadInfo 11 | videoRecovers = BiliWebApi.videoRecovers 12 | videoTags = BiliWebApi.videoTags 13 | videoAdd = BiliWebApi.videoAdd 14 | videoPre = BiliWebApi.videoPre 15 | videoDelete = BiliWebApi.videoDelete 16 | #本类只继承BiliWebApi中与Video上传有关的方法 17 | def __init__(self, cookieData, title="", desc="", dtime=0, tag=[], copyright=2, tid=174, source="", cover="",desc_format_id=0, subtitle={"open":0,"lan":""}): 18 | "创建一个B站视频上传类" #简介 19 | BiliWebApi.__init__(self, cookieData) 20 | self.__data = { 21 | "copyright":copyright, 22 | "videos":[], 23 | "source":source, 24 | "tid":tid, #分区,174为生活,其他分区 25 | "cover":cover, #封面图片,可由recovers方法得到视频的帧截图 26 | "title":title, 27 | "tag":"", 28 | "desc_format_id":desc_format_id, 29 | "desc":desc, 30 | "dynamic":"", 31 | "subtitle":subtitle 32 | } 33 | if dtime and dtime - int(time.time()) > 14400: 34 | self.__data["dtime"] = dtime 35 | 36 | for i in range(len(tag)): 37 | if (i == len(tag) - 1): 38 | self.__data["tag"] += tag[i] 39 | else: 40 | self.__data["dynamic"] += f'{tag[i]},' 41 | self.__data["tag"] += f'#{tag[i]}#' 42 | 43 | def uploadFile(self, filepath: '视频路径', fsize=8388608): 44 | "上传本地视频文件,返回视频信息dict" 45 | import os, math 46 | path,name = os.path.split(filepath)#分离路径与文件名 47 | 48 | with open(filepath,'rb') as f: 49 | size = f.seek(0, 2) #获取文件大小 50 | chunks = math.ceil(size / fsize) #获取分块数量 51 | 52 | retobj = self.videoPreupload(name, size) #申请上传 53 | auth = retobj["auth"] 54 | endpoint = retobj["endpoint"] 55 | biz_id = retobj["biz_id"] 56 | upos_uri = retobj["upos_uri"][6:] 57 | rname = os.path.splitext(upos_uri[5:])[0] 58 | url = f'https:{endpoint}{upos_uri}' #视频上传路径 59 | 60 | retobj = self.videoUploadId(url, auth) 61 | upload_id = retobj["upload_id"] #得到上传id 62 | 63 | #开始上传 64 | parts = [] #分块信息 65 | f.seek(0, 0) 66 | for i in range(chunks): #单线程分块上传,官方支持三线程 67 | data = f.read(fsize) #一次读取一个分块大小 68 | self.videoUpload(url, auth, upload_id, data, i, chunks, i*fsize, size)#上传分块 69 | parts.append({"partNumber":i+1,"eTag":"etag"}) #添加分块信息,partNumber从1开始 70 | #print(f'{i} / {chunks}')#输出上传进度 71 | 72 | preffix = os.path.splitext(name)[0] 73 | retobj = self.videoUploadInfo(url, auth, parts, name, upload_id, biz_id) 74 | if (retobj["OK"] == 1): 75 | return {"title": preffix, "filename": rname, "desc": ""} 76 | return {"title": preffix, "filename": "", "desc": ""} 77 | 78 | def submit(self): 79 | if self.__data["title"] == "": 80 | self.__data["title"] = self.__data["videos"][0]["title"] 81 | retobj = self.videoAdd(self.__data) 82 | if retobj["code"] == 0: 83 | self.__submit = retobj["data"] 84 | else: 85 | self.__submit = retobj 86 | return self.__submit 87 | 88 | def delete(self): 89 | "立即撤销本视频的发布(会丢失硬币)" 90 | aid = self.__submit["aid"] 91 | retobj = self.videoPre() 92 | challenge = retobj["data"]["challenge"] 93 | gt = retobj["data"]["gt"] 94 | return (self.videoDelete(aid, challenge, gt, f'{gt}%7Cjordan')["code"] == 0) 95 | 96 | def recovers(self, upvideo: "由uploadFile方法返回的dict"): 97 | "返回官方生成的封面,刚上传可能获取不到" 98 | return self.videoRecovers(upvideo["filename"]) 99 | 100 | def getTags(self, upvideo: "由uploadFile方法返回的dict"): 101 | "返回官方推荐的tag" 102 | return self.videoTags(upvideo["title"], upvideo["filename"]) 103 | 104 | def add(self, upvideo: "由uploadFile方法返回的dict"): 105 | "添加已经上传的视频" 106 | self.__data["videos"].append(upvideo) 107 | 108 | def clear(self): 109 | "清除已经添加的视频" 110 | self.__data["videos"] = [] 111 | 112 | def setDtime(self, dtime: int): 113 | "设置延时发布时间,距离提交大于4小时,格式为10位时间戳" 114 | if dtime - int(time.time()) > 14400: 115 | self.__data["dtime"] = dtime 116 | 117 | def setTitle(self, title: str): 118 | "设置标题" 119 | self.__data["title"] = title 120 | 121 | def setDesc(self, desc: str): 122 | "设置简介" 123 | self.__data["desc"] = desc 124 | 125 | def setTag(self, tag: []): 126 | "设置标签,tag为数组" 127 | tagstr = "" 128 | dynamic = "" 129 | for i in range(len(tag)): 130 | if (i == len(tag) - 1): 131 | tagstr += tag[i] 132 | else: 133 | tagstr += f'{tag[i]},' 134 | dynamic += f'#{tag[i]}#' 135 | 136 | self.__data["tag"] = tagstr 137 | self.__data["dynamic"] = dynamic 138 | 139 | def setCopyright(self, copyright=2): 140 | "设置copyright" 141 | self.__data["copyright"] = copyright 142 | 143 | def setTid(self, tid=174): 144 | "设置视频分区" 145 | self.__data["tid"] = tid 146 | 147 | def setTitle(self, tid=174): 148 | "设置标题" 149 | self.__data["tid"] = tid 150 | 151 | def setSource(self, source=""): 152 | "设置转载原地址" 153 | self.__data["source"] = source 154 | 155 | def setCover(self, cover=""): 156 | "设置视频封面url" 157 | self.__data["cover"] = cover 158 | 159 | def setDescFormatId(self, desc_format_id=0): 160 | "设置desc_format_id" 161 | self.__data["desc_format_id"] = desc_format_id 162 | 163 | def setSubtitle(self, subtitle={"open":0,"lan":""}): 164 | "设置subtitle" 165 | self.__data["subtitle"] = subtitle 166 | 167 | class VideoDownloader(object): 168 | '''B站视频下载类''' 169 | class __videos(object): 170 | class __videostream(object): 171 | def __init__(self, name: str,url: str, resolution: str, size: int): 172 | self.__name = name 173 | self.__url = url 174 | self.__resolution = resolution 175 | self.__size = size 176 | 177 | def __repr__(self): 178 | return f'' 179 | 180 | def __str__(self): 181 | return f'filename={self.__name} ; resolution={self.__resolution} ; size={self.__size / 1024 / 1024:0.2f}MB' 182 | 183 | def download(self, path='', callback=None): 184 | '''下载当前视频流''' 185 | if path != '': 186 | if path[-1] == '/': 187 | path = f'{path}{self.__name}' 188 | else: 189 | path = f'{path}/{self.__name}' 190 | else: 191 | path = self.__name 192 | 193 | from models.aria2py import Aria2Py 194 | aria2 = Aria2Py() 195 | ret = aria2.addUri(self.__url, {'max-connection-per-server':8,'referer': "https://www.bilibili.com","header":["User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)"],'out':path}) 196 | gid = ret["result"] 197 | while aria2.tellStatus(gid, ["status","completedLength","totalLength"])["result"]["status"] == 'waiting': 198 | time.sleep(2) 199 | while True: 200 | time.sleep(2) 201 | ret = aria2.tellStatus(gid, ["status","completedLength","totalLength"])["result"] 202 | if callback: 203 | if int(ret["totalLength"])!=0: 204 | callback(float(ret["completedLength"])/float(ret["totalLength"])) 205 | if ret["status"] != 'active': 206 | if ret["status"] != 'complete': 207 | raise Exception(f'下载失败(status: {ret["status"]})') 208 | break 209 | if callback: 210 | if int(ret["totalLength"])==0: 211 | continue 212 | callback(float(ret["completedLength"])/float(ret["totalLength"])) 213 | 214 | def __init__(self, subtitle, bvid='', cid=0, epid=''): 215 | self.__title = subtitle.replace('/',' ') 216 | self.__bvid = bvid 217 | self.__cid = cid 218 | 219 | def __repr__(self): 220 | return f'' 221 | 222 | def __str__(self): 223 | return self.__title 224 | 225 | def getTitle(self): 226 | '''获取当前视频标题''' 227 | return self.__title 228 | 229 | def allStream(self, cookieData:dict =None, reverse_proxy='', force_use_proxy=False): 230 | ''' 231 | 获取所有视频流 232 | cookieData dict :包含"SESSDATA"值的字典,模拟用户登录 233 | reverse_proxy str :B站接口代理地址 234 | force_use_proxy bool :强制使用代理地址(默认请求失败才尝试代理地址) 235 | ''' 236 | biliapi = BiliWebApi(cookieData) 237 | if force_use_proxy: 238 | RP = reverse_proxy 239 | data = biliapi.playerUrl(cid=self.__cid, bvid=self.__bvid, reverse_proxy=RP) 240 | if data["code"] != 0: 241 | raise Exception(f'解析失败,请尝试使用会员账号(错误信息:{data["message"]})') 242 | else: 243 | RP = '' 244 | data = biliapi.playerUrl(cid=self.__cid, bvid=self.__bvid, reverse_proxy=RP) 245 | if data["code"] != 0: 246 | if reverse_proxy == '': 247 | raise Exception(f'解析失败,请尝试使用代理或会员账号(错误信息:{data["message"]})') 248 | else: 249 | RP = reverse_proxy 250 | data = biliapi.playerUrl(cid=self.__cid, bvid=self.__bvid, reverse_proxy=RP) 251 | if data["code"] != 0: 252 | print(self.__bvid, self.__cid) 253 | raise Exception(f'解析失败,请尝试更换代理地区或使用会员账号(错误信息:{data["message"]})') 254 | 255 | accept_quality = data["data"]["accept_quality"] 256 | accept_description = data["data"]["accept_description"] 257 | ret = [] 258 | for ii in range(len(accept_quality)): 259 | data = biliapi.playerUrl(cid=self.__cid, bvid=self.__bvid, qn=accept_quality[ii], reverse_proxy=RP)["data"] 260 | if data["quality"] != accept_quality[ii]: 261 | continue 262 | if 'flv' in data["format"]: 263 | ret.append(self.__videostream(f'{self.__title}.flv', data["durl"][0]["url"].replace('http:','https:'),accept_description[ii],data["durl"][0]["size"])) 264 | else: 265 | ret.append(self.__videostream(f'{self.__title}.mp4', data["durl"][0]["url"].replace('http:','https:'),accept_description[ii],data["durl"][0]["size"])) 266 | return ret 267 | 268 | def __init__(self, url: str): 269 | self.set(url) 270 | 271 | def all(self): 272 | '''取得当前所有视频(分P)''' 273 | if self.__type == 1: 274 | list = BiliWebApi.playList(self.__bvid)["data"] 275 | return [self.__videos(x["part"], self.__bvid, x["cid"]) for x in list] 276 | elif self.__type == 2: 277 | return [self.__videos(x[0], x[1], x[2]) for x in self.__eplist] 278 | else: 279 | return [] 280 | 281 | def set(self, url: str): 282 | ''' 283 | 解析视频 284 | url str: BV,av,ep,ss号以及包含这些号的网址 285 | ''' 286 | import re 287 | self.__type = 0 288 | find = re.findall('(BV|av|ep|ss)([0-9 a-z A-Z]*)', url) 289 | if len(find): 290 | if find[0][0] == 'BV': 291 | self.__bvid = f'BV{find[0][1]}' 292 | self.__title = BiliWebApi.webView(self.__bvid)["data"]["title"] 293 | self.__type = 1 294 | elif find[0][0] == 'av': 295 | self.__bvid = self.av2bv(find[0][1]) 296 | self.__title = BiliWebApi.webView(self.__bvid)["data"]["title"] 297 | self.__type = 1 298 | elif find[0][0] == 'ep' or find[0][0] == 'ss': 299 | data = BiliWebApi.epPlayList(find[0][0] + find[0][1]) 300 | self.__title = data["mediaInfo"]["title"] 301 | self.__eplist = [[f'{x["titleFormat"]} {x["longTitle"]}', x["bvid"], x["cid"]] for x in data["epList"]] 302 | self.__type = 2 303 | else: 304 | raise Exception("不支持的参数") 305 | 306 | def getTitle(self): 307 | '''获取标题''' 308 | return self.__title 309 | 310 | @staticmethod 311 | def bv2av(bvid: str): 312 | '''B站bv号转av号''' 313 | return BiliWebApi.webView(bvid)["data"]["aid"] 314 | 315 | @staticmethod 316 | def av2bv(aid: int): 317 | "B站av号转bv号" 318 | return BiliWebApi.webStat(aid)["data"]["bvid"] -------------------------------------------------------------------------------- /通过 Serverless 实现 B 站定时签到/models/aria2py.py: -------------------------------------------------------------------------------- 1 | import json, time 2 | import subprocess 3 | import os 4 | 5 | DEFAULT_HOST = 'localhost' 6 | DEFAULT_PORT = 6800 7 | DEFAULT_SECRET = '' 8 | 9 | class Aria2Py(object): 10 | '''Aria2下载器''' 11 | def __init__(self, 12 | host=DEFAULT_HOST, 13 | port=DEFAULT_PORT, 14 | secret=DEFAULT_SECRET, 15 | session=None, 16 | remote=False 17 | ): 18 | self.server_uri = f'http://{host}:{port}/jsonrpc' 19 | self.secret = secret 20 | import requests 21 | self.session = requests.session() 22 | 23 | if not remote: 24 | if self.isAria2Running(): 25 | return 26 | if not Aria2Py.isAria2Installed(): 27 | raise Exception('未找到aria2') 28 | 29 | cmd = f'aria2c' \ 30 | ' --enable-rpc' \ 31 | f' --rpc-listen-port {port}' \ 32 | ' --continue' \ 33 | ' --max-concurrent-downloads=20' \ 34 | ' --max-connection-per-server=10' \ 35 | ' --rpc-max-request-size=1024M' 36 | 37 | if not session is None: 38 | cmd += f' --input-file={session}' \ 39 | ' --save-session-interval=60' \ 40 | f' --save-session={session}' 41 | 42 | subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) 43 | 44 | try: 45 | data = self.getGlobalStat() 46 | if 'error' in data: 47 | raise Exception(f'连接aria2失败({data["error"]["Unauthorized"]})') 48 | except: 49 | raise Exception('连接aria2失败') 50 | 51 | def sendJsonRPC(self, data): 52 | '''发送RPC请求''' 53 | return self.session.post(self.server_uri, data=data).json() 54 | 55 | def getRPCBody(self, method, params=[]): 56 | '''创建RPC请求''' 57 | uid = '14515821564dsfdvzxvdf' 58 | if self.secret: 59 | params.insert(0, f'token:{self.secret}') 60 | j = json.dumps({ 61 | 'jsonrpc': '2.0', 62 | 'id': uid, 63 | 'method': method, 64 | 'params': params 65 | }) 66 | return j 67 | 68 | def addUri(self, uris, options=None, position=None): 69 | ''' 70 | 添加一个HTTP(S)/FTP/BitTorrent Magnet下载任务. 71 | uris: list, 链接数组 72 | options: dict, 附加参数 73 | position: int, 队列里的位置 74 | ''' 75 | params = [[uris]] 76 | if options: 77 | params.append(options) 78 | if position: 79 | params.append(position) 80 | return self.sendJsonRPC(data=self.getRPCBody('aria2.addUri', params)) 81 | 82 | def remove(self, gid): 83 | ''' 84 | 移除下载任务. 85 | gid: string, 任务GID. 86 | ''' 87 | params = [gid] 88 | return self.sendJsonRPC(data=self.getRPCBody('aria2.remove', params)) 89 | 90 | def forceRemove(self, gid): 91 | ''' 92 | 强制移除下载任务. 93 | gid: string, 任务GID. 94 | ''' 95 | params = [gid] 96 | return self.sendJsonRPC(data=self.getRPCBody('aria2.forceRemove', params)) 97 | 98 | def pause(self, gid): 99 | ''' 100 | 暂停任务. 101 | gid: string, GID. 102 | return: GID. 103 | ''' 104 | params = [gid] 105 | return self.sendJsonRPC(data=self.getRPCBody('aria2.pause', params)) 106 | 107 | def pauseAll(self): 108 | ''' 109 | 暂停全部任务. 110 | return: 成功返回OK. 111 | ''' 112 | return self.sendJsonRPC(data=self.getRPCBody('aria2.pauseAll')) 113 | 114 | def forcePause(self, gid): 115 | ''' 116 | 强制暂停任务. 117 | gid: string, GID. 118 | ''' 119 | params = [gid] 120 | return self.sendJsonRPC(data=self.getRPCBody('aria2.forcePause', params)) 121 | 122 | def forcePauseAll(self): 123 | ''' 124 | 强制暂停全部任务. 125 | return: 成功返回OK. 126 | ''' 127 | return self.sendJsonRPC(data=self.getRPCBody('aria2.forcePauseAll')) 128 | 129 | def unpause(self, gid): 130 | ''' 131 | 解除任务暂停状态. 132 | gid: string, GID. 133 | ''' 134 | params = [gid] 135 | return self.sendJsonRPC(data=self.getRPCBody('aria2.unpause', params)) 136 | 137 | def unpauseAll(self): 138 | ''' 139 | 解除所有任务暂停状态. 140 | return: 成功返回OK. 141 | ''' 142 | return self.sendJsonRPC(data=self.getRPCBody('aria2.unpauseAll')) 143 | 144 | def tellStatus(self, gid, keys=None): 145 | ''' 146 | 返回下载进度. 147 | gid: string, GID. 148 | keys: list, 需要返回的参数列表. 149 | ''' 150 | params = [gid] 151 | if keys: 152 | params.append(keys) 153 | return self.sendJsonRPC(data=self.getRPCBody('aria2.tellStatus', params)) 154 | 155 | def tellActive(self, keys=None): 156 | ''' 157 | 返回活跃的任务. 158 | keys: list, 需要返回的参数列表. 159 | ''' 160 | params = [] 161 | if keys: 162 | params.append(keys) 163 | return self.sendJsonRPC(data=self.getRPCBody('aria2.tellActive', params)) 164 | 165 | def getGlobalStat(self): 166 | ''' 167 | 获得全局参数比如上传或下载速度. 168 | return: The method response is of type struct and contains following keys. 169 | ''' 170 | return self.sendJsonRPC(data=self.getRPCBody('aria2.getGlobalStat')) 171 | 172 | @staticmethod 173 | def isAria2Installed(): 174 | '''Aria2是否已经安装''' 175 | if os.path.exists('aria2c'): 176 | return True 177 | if os.path.exists('aria2c.exe'): 178 | return True 179 | for cmdpath in os.environ['PATH'].split(':'): 180 | if os.path.isdir(cmdpath) and 'aria2c' in os.listdir(cmdpath): 181 | return True 182 | return False 183 | 184 | def isAria2Running(self): 185 | '''Aria2是否已经启动并能成功连接''' 186 | ret = True 187 | try: 188 | data = self.getGlobalStat() 189 | if 'error' in data: 190 | ret = False 191 | except: 192 | ret = False 193 | return ret -------------------------------------------------------------------------------- /通过 Serverless 实现 B 站定时签到/serverless.yml: -------------------------------------------------------------------------------- 1 | #组件信息 2 | app: bilibili 3 | component: scf # (必填) 引用 component 的名称,当前用到的是 tencent-scf 组件 4 | name: BiliExp # (必填) 创建的实例名称,请修改成您的实例名称 5 | 6 | #B站自动领取经验 7 | inputs: 8 | name: ${app}-BiliExp #函数名称 9 | #namespace: bilibili 10 | src: ./ #代码路径 11 | handler: BiliExp.main #入口 12 | runtime: Python3.6 # 云函数运行时的环境 13 | region: ap-guangzhou # 云函数所在区域 14 | description: 'B站自动签到' 15 | timeout: 60 16 | events: # 触发器 17 | - timer: # 定时触发器 18 | name: #触发器名称,默认timer-${name}-${stage} 19 | parameters: 20 | cronExpression: '0 30 15 * * * *' 21 | enable: true --------------------------------------------------------------------------------