首页
关于
友情链接
推荐
百度一下
腾讯视频
百度一下
腾讯视频
Search
1
【笔记】用Javascript实现椭圆曲线加密算法
13 阅读
2
Hackergame 2022 个人题解
11 阅读
3
欢迎使用 Typecho
10 阅读
4
【博客折腾记录】l2d看板娘相关~
5 阅读
5
中国科学技术大学第六届信息安全大赛(HACKERGAME2019自我总结)
4 阅读
默认分类
登录
Search
标签搜索
Note
CPP
C
JavaScript
Math
CTF
Bilibili
Python
Docker
php
RSA
ECC
Crypto
Blog
Bash
FPGA
GAMES
Homework
依言 - Eyan
累计撰写
34
篇文章
累计收到
1
条评论
首页
栏目
默认分类
页面
关于
友情链接
推荐
百度一下
腾讯视频
百度一下
腾讯视频
搜索到
2
篇与
的结果
2020-04-21
B站直播弹幕获取 - 用python写一个B站弹幕姬吧
前言关于这个小项目的由来。最开始是想要利用b站的弹幕进行一些互动之类的。原本也有想过可以利用现有的弹幕姬做个插件来解决的,但无奈不会C#,所以只能自己研究b站的弹幕协议。后来有写过一个C++版本的,不过有一些小问题,这在后文中会提到。开码一丶利用 POST 方式获取 B 站直播弹幕参考:【python】b站直播弹幕获取首先,随便打开一个b站的直播页面,按F12打开控制台,点进“网络(Network)”标签,刷新一下,然后审计一下里面的内容,可以找到“gethistory”这个文件里面就是我们要的弹幕了。实际上,仔细观察便不难发现,请求 gethistory 的时候返回的是请求时最近的10条历史弹幕,不过根据这些就可以写出来一个简易的弹幕姬了。具体做法就是每隔一定的时间请求一次,然后与上次的请求做对比。不同的部分就是这段时间新发的弹幕了,这样就可以对弹幕进行一些操作了。我们点进“headers”标签:有了这些我们就可以开写一个弹幕姬了。虽然headers很乱,不过实际上我们在请求弹幕的时候并不需要这么多headers,具体哪些headers是必要的可以用实验试出来,不过具体过程和结果我就直接略去了。最后的代码可以参考:B站直播弹幕爬取或我自己写的C++版本:【笔记/学习】c++实现b站弹幕姬(代码有点长而且不是本文的重点这里就不放了)注:之前的时候获取弹幕的URL是:https://api.live.bilibili.com/ajax/msg,不过我写这篇文再去复现的时候发现这个URL已经没了,经过观察发现变成了 https://api.live.bilibili.com/xlive/web-room/v1/dM/gethistory 截至写文时二者都能用。不过这些都不是重点了。二丶利用 WebSocket 获取 B 站弹幕前文利用 POST 的方式获取B站弹幕。这种方法虽然简单,但也有不方便的地方。我设的时间间隔为3s,但如果3s内发送的弹幕数量超过了10条,这种方法就会丢失一部分的弹幕,而如果简单的减小时间间隔,不仅会占用更多的网络资源,如果太过频繁的话还可能会被封IP。而 HTTP 请求的这种缺陷也正好就是 WebSocket 的出现所为了解决的问题。事实上,我们在看B站直播的时候正是通过 WebSocket 的方式与服务器通信的。让我们继续打开 F12 :这个 sub 就是与弹幕服务器通信的 WebSocket 啦。点进 Message 标签,会看见一大堆东西。不过我们并不需要自己去研究这个通信协议,在 Github 上已经有了B站的API可以直接使用。弹幕WS协议API文档使用 JavaScript 写的,不过这并不妨碍我们移植一个 Python 版本的。由 API 可知,我们与服务器进行通信所发送的数据大多是 json 的数据,偶尔还会有 zlib 数据。所以我们自然需要导入这两个包。我们与服务器使用 WebSocket 进行通信,但原生 Python 并不能直接发送 WebSocket,我们自然也不可能使用 socket 去造轮子。不过好在已经有很多好用的 WebSocket 的库可供我们使用了。目前常用的 WebSocket 库有: websocket-client, websockets, aiowebsocket 三个。其中 websocket-client 是同步的,因为我们在收弹幕的同时还得要发送心跳包才能不被服务器断开连接,使用异步io会方便一些。所以不用他。另外两个我也都试过。感觉上 aiowebsocket 更稳定一些,所以这里我们使用这个库。安装:pip install aiowebsocket除了 aiowebsocket 要安装外,其他的库都是 python 自带的,直接导入就行了。自然不要忘了用了异步操作要加上 asyncio 库哦。import asyncio import zlib from aiowebsocket.converses import AioWebSocket import json之后我们写好入口函数:if __name__ == '__main__': remote = 'wss://broadcastlv.chat.bilibili.com:2245/sub' try: asyncio.get_event_loop().run_until_complete(startup(remote)) except KeyboardInterrupt as exc: pring('Quit.')remote 自然就是API中弹幕服务器的地址了。然后是startup()roomid = '5322' data_raw='000000{headerLen}0010000100000007000000017b22726f6f6d6964223a{roomid}7d' data_raw=data_raw.format(headerLen=hex(27+len(roomid))[2:], roomid=''.join(map(lambda x:hex(ord(x))[2:],list(roomid)))) async def startup(url): async with AioWebSocket(url) as aws: converse = aws.manipulator await converse.send(bytes.fromhex(data_raw)) tasks=[receDM(converse), sendHeartBeat(converse)] await asyncio.wait(tasks)在连接到弹幕服务器后必须先发一个数据包写出进入的房间,否则连接会被断开。这里我是直接从浏览器抄的。数据包中的包长度必须要正确,所以这里要计算一下包长度。然后这里先把接受弹幕的 receDM() 和发送心跳包的 sendHeartBeat 先写好。接下来是这两个函数:hb = '00000010001000010000000200000001' async def sendHeartBeat(websocket): while True: await asyncio.sleep(30) await websocket.send(bytes.fromhex(hb)) print('[Notice] Sent HeartBeat.') async def receDM(websocket): while True: recv_text = await websocket.receive() printDM(recv_text)B站的弹幕服务器是如果70秒没有心跳就断开连接,这里是30s发送一次。因为只需要发一个没有内容的数据包就行了,所以这里也是直接从浏览器抄的。。对于接收到的数据包的处理比较复杂,这里我们单独写一个函数来处理它。首先,由API我们可以看到每个数据包的头部是怎样的:位置0-34-56-78-1112-1516-说明数据包长度数据包头部长度协议版本操作类型数据包头部长度数据包内容不过这些内容我们并不都需要用到。下面上代码,具体说明在注释中写了:# 将数据包传入: def printDM(data): # 获取数据包的长度,版本和操作类型 packetLen = int(data[:4].hex(),16) ver = int(data[6:8].hex(),16) op = int(data[8:12].hex(),16) # 有的时候可能会两个数据包连在一起发过来,所以利用前面的数据包长度判断, if(len(data)>packetLen): printDM(data[packetLen:]) data=data[:packetLen] # 有时会发送过来 zlib 压缩的数据包,这个时候要去解压。 if(ver == 2): data = zlib.decompress(data) printDM(data) print('db3') return # ver 为1的时候为进入房间后或心跳包服务器的回应。op 为3的时候为房间的人气值。 if(ver == 1): if(op == 3): print('[RENQI] {}'.format(int(data[16:].hex(),16))) return # ver 不为2也不为1目前就只能是0了,也就是普通的 json 数据。 # op 为5意味着这是通知消息,cmd 基本就那几个了。 if(op==5): try: jd = json.loads(data[16:].decode('utf-8', errors='ignore')) if(jd['cmd']=='DANMU_MSG'): print('[DANMU] ', jd['info'][2][1], ': ', jd['info'][1]) elif(jd['cmd']=='SEND_GIFT'): print('[GITT]',jd['data']['uname'], ' ', jd['data']['action'], ' ', jd['data']['num'], 'x', jd['data']['giftName']) elif(jd['cmd']=='LIVE'): print('[Notice] LIVE Start!') elif(jd['cmd']=='PREPARING'): print('[Notice] LIVE Ended!') else: print('[OTHER] ', jd['cmd']) except Exception as e: pass这样,一个简单的弹幕姬就完成了!三丶试试搞一些其他的事情吧!至此,我们已经有了一个可以在控制台输出弹幕内容的弹幕姬了。不过,这并不是结束,有了这个我们就可以利用弹幕搞事情了(先放个简单的功能:把弹幕保存至本地先到如下时间:import time然后我们把前面的print()函数改掉:def log(typ, body): with open('D:/danmu.txt','a') as fd: fd.write(time.strftime("[%H:%M:%S] ", time.localtime())) fd.write(body+'\n')这样就可以爬取弹幕到本地了。利用SAPI朗读弹幕利用 SAPI 朗读需要导入相应的包:import win32com.client然后改写log函数:def log(typ, body): speak = win32com.client.Dispatch("SAPI.SpVoice") #创建发声对象 speak.Speak(body) #使用发生对象读取文字 with open('D:/danmu.txt','a') as fd: fd.write(time.strftime("[%H:%M:%S] ", time.localtime())) fd.write(body+'\n')来使python自动朗读弹幕。一般 Windows 都可以直接使用,不能用的话再上网查吧。。利用聊天机器人实现自动聊天首先打开b站一个直播间,发条弹幕截下包:照着参考,可以大致写出一份发送弹幕的python脚本import requests import time form_data = { 'color': '65532', 'fontsize': '25', 'mode': '1', 'msg': 'test', 'rnd': int(time.time()), 'roomid': '1136753', 'csrf_token': 'cce335cbfa5bfd292a049b813175bd12', 'csrf': 'cce335cbfa5bfd292a049b813175bd12' } # 设置cookie值帮助我们在发送弹幕的时候,服务器识别我们的身份 cookie = { '你的cookie(上图红色部分)' } res = requests.post('https://api.live.bilibili.com/msg/send', cookies=cookie, data=form_data) print (res.status_code)上面的 csrf 和 csrf_token 在使用的时候最好也换成自己的。然后可以去注册图灵机器人/思知机器人等API(不推荐图灵,之前还好,后来一去看感觉有点贵),申请到 appid。具体可以参考我以前写的这篇文章最后代码差不多是这样:def talk(msg): form_data = { 'color': '65532', 'fontsize': '25', 'mode': '1', 'msg': 'test', 'rnd': int(time.time()), 'roomid': roomid, 'csrf_token': 'cce335cbfa5bfd292a049b813175bd12', 'csrf': 'cce335cbfa5bfd292a049b813175bd12' } cookie = { '你的cookie(上图红色部分)' } payload = { 'appid': '你的appid', 'userid': '1234', } payload['spoken'] = msg res1 = requests.get("https://api.ownthink.com/bot", params=payload) form_data['msg'] = res1.json()['data']['info']['text'] res2 = requests.post('https://api.live.bilibili.com/msg/send', cookies=cookie, data=form_data)好啦,去直播间发条弹幕看看效果:注意使用的时候加上限制不要对机器人的回复再去回复就行了。。。后记嗯。。。暂时就先写这么多吧,还有什么要补充的以后再说吧。。源码什么的之后再传吧。。
2020年04月21日
2 阅读
0 评论
0 点赞
2020-04-10
Python爬虫 - 统计自己读过小说的字数
写在前面的废话没错,这个爬虫的确只是我想统计下自己读小说的速度和自己已经读了多少小说写的,可以爬一些小说的数据,不能用来爬小说本身。不过稍加改进可以实现更多的功能,我会在之后的文章实现其他的功能。话说我都好久没有发过文章了啊,所以就来水一篇博客了(x)正文一、使用工具和目标网站的分析使用的工具,恩,如题,我使用的是 python3.7,其他版本问题应该也不大。我用到的包有三个:requests 用于发出http请求来获取数据re 用来匹配正则表达式的(我没有使用 HTML 解析器)time然后是网站的分析了。这次我拿 SF轻小说 http://book.sfacg.com/ 这个网站开刀。对网站分析就不难整理接下来的思路了。网站自带收藏夹功能,而访问别人的收藏夹并不需要登录,因而在之后爬取数据的时候不需要模拟登录,可以比较方便地爬取数据。收藏夹内有各本小说的标题,作者,链接等数据,在小说页就可以看到字数以及更多我们想要的数据了。因此我们可以在阅读时把小说添加进收藏夹内,之后统计整个收藏夹就可以得到我们想要的数据了。二、开工!随便打开一个火袋页:就可以看到里面的基本信息了。比如这个火袋的 URL 是: http://p.sfacg.com/p/1892478/1/不难得出,前面的 http://p.sfacg.com/p/ 就是每个火袋共有的前缀了。 1892478 是这个火袋的id, 最后的1/是页码,第一页默认不会有这个页码,这里是我加的。默认一页现实10本书,可以根据上面显示的总共的小说数计算出总页数。虽然最下面可以选择每页数量,但是有最大限制,当总数过多时就起不到作用了。所以这里就按照默认的数量来。开 f12,分析下html:先找到小说的总数。然后可以看到,一个 <ul class="content_comment cover" eid="xxxx">就是一本小说了。点开标签还可以看到这本小说的封面标题作者等信息,不过这些我们都可以等进入详情页再去爬取。SF轻小说的每本小说详情页的格式为: http://book.sfacg.com/Novel/xxx/ 这后面的xxx就是前面的 eid 了。我们先试试把这个火袋里的所有小说爬出来:三、开爬!代码写出来大概是这个样子:import requests import re import time favlist = 'http://p.sfacg.com/p/1892478/' headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'} req = requests.get(favlist, headers=headers) pat_getcount = re.compile(r'<a href="javascript:void\(0\)">全部小说\(([0-9]*)\)</a>') pat_getnovel = re.compile(r'<ul class="content_comment cover" eid="([0-9]*)">') content = req.content.decode('utf-8') pages = int(pat_getcount.findall(content)[0]) pages = pages // 10 + 2 for page in range(1,pages): req = requests.get(favlist+str(page)+'/', headers=headers) content = req.content.decode('utf-8') eid_list = pat_getnovel.findall(content) print(eid_list)运行一下看看:可以看到,这个火袋里所有小说的id都被爬下来了,之后只要拼接下字符串就可以得到每本小说的url了。然后简单讲解下:首先是三个包的导入,收藏夹地址和header的定义,就不用多讲了。然后是正则表达式的定义。从html里可以找到相应的规律。因为小说的数量和eid都是数字,所以用 ([0-9]*) 来代替。写的时候注意下给原有的括号加上转义就行了。总页数的计算可以用 总页数 = 小说总数 // 每页的小说数 + 1 来计算。代码中是 +2 而不是 +1 是为了后面的 range(1, pages) 方便一些所以这样的。然后就是后面的遍历了。根据刚才算出来的页数遍历每一页,然后从每页提取相应的id,后面用id去访问详情页就可以了。这里有一点,在开始获取总页数之后,第一页是不用再访问一遍的,不过为了方便些,我还是把第一页放到底下的for里了。最后储存小说id的list也不会去打印了,之后会把所有的id放到一个list里然后去遍历的。三、继续分析接着刚才,随便打开一本小说的详情页看看:可以看到小说的详情了,至少我想要的字数信息就在这里啦!url的格式也很清楚了,最后的那串数字就是eid了:base_url = 'http://book.sfacg.com/Novel/' for eid in eid_list: req = requests.get(base_url+eid+'/', headers=headers)按f12看看源码:字数很好找,不过如果直接匹配 字数:([0-9]*) 的话会匹配到多个,加上前面的 span 标签就可以了。对于标题,同理,加上前面的 h1 标签就行了。涉及到多行的时候使用 \s 匹配空白字符。pat_gettitle = re.compile(r'<h1 class="title">\s*<span class="text">(.*?)</span>') pat_getchara = re.compile(r'<span class="text">字数:([0-9]*)字')最后别忘了用一个变量储存下总字数。最后的代码差不多是这样的:import requests import re import time favlist = 'http://p.sfacg.com/p/1892478/' headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'} req = requests.get(favlist, headers=headers) pat_getcount = re.compile(r'<a href="javascript:void\(0\)">全部小说\(([0-9]*)\)</a>') pat_getnovel = re.compile(r'<ul class="content_comment cover" eid="([0-9]*)">') content = req.content.decode('utf-8') pages = int(pat_getcount.findall(content)[0]) pages = pages // 10 + 2 eid_list=[] for page in range(1,pages): req = requests.get(favlist+str(page)+'/', headers=headers) content = req.content.decode('utf-8') eid_list += pat_getnovel.findall(content) pat_gettitle = re.compile(r'<h1 class="title">\s*<span class="text">(.*?)</span>') pat_getchara = re.compile(r'<span class="text">字数:([0-9]*)字') base_url = 'http://book.sfacg.com/Novel/' all_character = 0 for eid in eid_list: req = requests.get(base_url+eid+'/', headers=headers) content = req.content.decode('utf-8') title = pat_gettitle.findall(content)[0] chara = pat_getchara.findall(content)[0] print(title, chara) all_character += int(chara) print('All count of character: ', all_character) 运行下看看:成功!总结这次算是成功搞定了这个项目,对这个网站的目录页和小说详情页都有访问。虽然这次我只是想算下字数,但之后只要稍加改动便可以实现更多更复杂的效果,整体还是不错的,而且以后写其他代码时的参考性也很高。这次的代码涉及到了requests库的使用,正则表达式还有html的审计等相关知识,以后想复习的时候可以回来参考下。什么,你说我time库没有用?time库相关主要是用来计算时间以及通过sleep防止请求过快被封的情况的。你要问什么时候在哪用哪个相关的函数?这个我就先不讲了,自己研究吧2333(x)
2020年04月10日
3 阅读
0 评论
0 点赞