声明:本篇仅基于兴趣以及技术研究而对B站曾经发生过的抢楼事件背后相关技术原理进行研究而写。请不要将其作为私利而对B站以及B站用户体验造成影响!谢谢合作!若本文对B站及其用户带来困扰,请联系本人删除本文。

  虽然说是技术研究,但实际上并没有什么太深的东西在里面,你只需要懂一点http协议的请求格式、懂python、会使用python requests package就能完成这个简单的任务了。

  如果你不懂python,还是先简单了解一下配置环境以及语法再来继续下面内容。

  好了,如果你想要简单浏览并了解一点http协议知识,可以试试阅读这两篇:https://blog.csdn.net/a19881029/article/details/14002273、https://blog.csdn.net/Stream__/article/details/78604937。

  关于本文使用的python库:见 http://docs.python-requests.org/zh_CN/latest/

  开始吧。

  首先你肯定得有一个浏览器,我推荐Chrome —— 个人喜好。最好还有一个PyCharm或者其他python编辑器,如果你只喜欢用python自带的命令行工具也行。

  然后说一点,现在B站为了防止抢楼,把番剧下所有视频的评论区都合并了(一些番剧貌似并没有这样做,例如哆啦A梦),以前每一个视频下都会有对应的评论区,现在所有视频的评论全部在一起的。。。所以现在就算要抢也只有对新番第一集抢楼可能才有‘意义’了。

  然后有许多语言工具都可以进行B站的抢楼,比如使用python+phantomjs+selenium、js、Java、C++等,由于我学习能力与水平有限,没用过js、Java进行过爬虫,C++的话自己正在仿照python的requests决定尽力写一个好用的C++ http库。

  嘛,这篇还是相当于用便利的requests来做一个爬虫小教学以及学习如何使用除get外的http动词。

  工具都准备好了,让我们进入主题吧:

    这里随便选了一部老番《D.C.Ⅱ S.S.》又称《初音岛》作为测试。

    来到番剧剧集页面,先F12准备监控一会儿发送请求服务器返回的数据包:

    当我们要进行这个任务的时候,我们必须要先知道:我们该向什么地方发送的请求?难道就直接对番剧页面发送就可以了吗?如果有做过网站的经验就会知道,一个网站的前端展示页面基本上都是通过 js + ajax 等通过后台的业务逻辑调用数据库中的数据加载到对应的jsp文件中的html标签中自动生成的。比如评论区,肯定有一个 post 的API接口来接受用户发送的数据,并将数据存入到数据库中,然后展示页面 + js + 数据库 + 后台业务逻辑等一套服务,最后我们用户才能在前端中看到丰富的内容,才能看到实时更新的数据,说实时更新不太对,但总之你每次刷新页面,网站后台就会做这些事情。

    这些有什么用呢?至少我知道了当我在B站评论区编辑好要发送的消息并点击发送评论的时候,肯定是通过一个特地编写好的接口来post data,而这个post接口的url会在我们点击按钮的动作后显示在浏览器的网络监控中,所以,我们要找到这个接口的url就要先发送一个消息试一试:

    最好就是在浏览器加载完该页面的数据后按F12打开监控台,这样比较干净,点击发表评论后,很快就可以注意到我们的动作的回馈,点一下看看内容:

    显然,它是通过http post动词来提交的,从中我找到了这个add接口的url:

    到这里已经可以宣告结束了。注意到Request Headers中就是我们需要自定义添加的请求头内容,然后,很关键的地方就是发送内容,它一般在Chrome监控台最下面:

    oid对应当前视频的av号,这样才能确定是对哪一个视频进行的评论,可以在视频页面这里获取它:

    这里type的含义不重要,message显然是我们要发送的东西,注意message如何是中文,那么将会进行url编码,可以参考:http://www.w3school.com.cn/tags/html_ref_urlencode.html

    后面几个除csrf外的含义在这里也并不重要。。。

    ps:刚才我对新番Comic Girls用同样的csrf试了一下,成功了:

    B站评论区效果:

    但其实我更觉得比较侥幸的是,B站的发送评论并没有做非常复杂的验证...我们在python中使用它们:

data = {
'oid': '2458871',
'type': '1',
'message': 'test~',
'plat': '1',
'jsonp': 'jsonp',
'csrf': 'da9c3263c011ee0969ce383e8d799f05'
}

    然后利用浏览器中的Request Headers写好我们的请求头:

headers = {
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Content-Length': 'xxx', # 这里对应的就是我们的data数据的长度
'Cookie': 'xxx',
'Host': 'api.bilibili.com',
'Referer': 'https://www.bilibili.com/bangumi/play/ep86125',
'User-Agent': 'xxx',
}

    这里上面注释中的长度可以这样获取,来到浏览器:

    点击蓝色区域变为:

    当然你可以肉眼一个个字符的数,但我还是选择复制一下这段字符串,然后到粘贴到PyCharm中,选中粘贴上去的那段字符串,然后蓝色框住的部分就是字符串的长度,也就是这里我们的Content-Length的长度:

    但这是测试过后得到了add中的data信息才能知道长度,如果对一部没有add信息的视频,还是自己将data转换为浏览器中的拼接参数格式的字符串,然后用python len算一下字符串长度:

s = str(data).replace(': ', '=').replace(',', '&')\
.replace('\'', '').replace(' ', '')\
.replace('{', '').replace('}', '')
content_length = len(s)
print(s)
print(content_length)

    或者直接len(str(data)),因为Content-Length对于填写并不严格,但虽然Content-Length对于填写并不严格,但就算随便填写长度也必须要比实际长度大,因为这样从推断上请求内容就应该是损坏的。而且注意一些字符以及中文字符的url编码格式,比如空格的为%20,但基本上都是%xx的格式,看url编码格式中英文字符也是%xx,但实际上也并不需要编码,所以长度需要注意下。。。

    删除这段字符串,开始使用requests post:

r = request.post(url, data=data, headers=headers, timeout=1, )
print(r.status_code)
print(r.json()) # r.text

    值得注意的一点是,有些接口的请求内容是data参数,data参数仅仅就是转换为字符串,而还有一些接口的请求内容格式是json,这时就只需要将参数data改为使用json即可:

r = request.post(url, json=json)

    详细的可以对准post方法 Ctrl + 左键 看看源码可能会更有帮助。

    ps:扩展一下,这里以上面的client发送post请求为例,它的http请求格式是这样的,其中c表示客户端,s表示服务端,(:)表示开始,(:!)表示结束:

c >>					        POST /x/v2/reply/add HTTP1/1\r\n
c >>(Request headers:) User-agent: xxx\r\n
c >> Accept: application/json, text/javascript, */*; q=0.01\r\n
c >> Accept-Encoding': 'gzip, deflate, br\r\n
c >> Accept-Language': 'zh-CN,zh;q=0.9\r\n
c >> Connection: keep-alive\r\n
c >> Content-Length: xxx\r\n
c >> Cookie: xxx\r\n
c >> Host: api.bilibili.com\r\n
c >> Referer: https://www.bilibili.com/bangumi/play/ep86125\r\n
c >>(Request headers:!) \r\n
c >>(body:)    oid=xxx&type=1&message=xxx&plat=1&jsonp=jsonp&csrf=xxx...\r\n
c >>(body:!)
s >>(:)
...
s >>(:!)
...

    注意请求格式以及整个过程底层做了哪些事:

      1.请求方法与请求资源路径之间有空格,资源路径与协议版本之间有空格每一句后面加上\r\n,一个换行符一个换行表示该行结束;

      2.请求头中key与value之间有冒号,冒号后面一定要加上空格,最后一个换行符一个换行表示该行结束;

      3.请求头的结束输入以一行\r\n表示结束;

      4.请求头结束后的下面才是请求body部分;

      5.请求body结束以后,后面就是服务端返回的响应格式内容;

      6.最后再返回请求的资源。

    到这里,这样就完成了发送评论。

  既然我们可以发送评论,那么我们又如何通过代码来删除呢?

  我们先在页面中删除一条刚才对视频的评论测试一下,找到删除的接口:

  可以看到B站删除的接口为:https://api.bilibili.com/x/v2/reply/del,而且也是通过post的方式进行的。

  再看看发送删除请求需要什么内容:

  注意到 rpid ,这不就是刚才发表评论时返回的json中出现过的吗,于是按照之前的发送方法,再通过删除接口写一个删除的方法:

  由于之前发送的已经被删除了,前面的补充中还测试过《Comic Girls》,所以这里用她来做删除测试:

from requests import Session

request = Session()

url = 'https://api.bilibili.com/x/v2/reply/del'

data = {
'oid': '21683499',
'type': '1',
'rpid': '788555414',
'jsonp': 'jsonp',
'csrf': 'da9c3263c011ee0969ce383e8d799f05'
} content_length = len(str(data).replace(': ', '=').replace(',', '&').replace('\'', '')) headers = {
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Content-Length': str(content_length),
'Cookie': 'xxx',
'Host': 'api.bilibili.com',
'Referer': 'https://www.bilibili.com/bangumi/play/ep86125',
'User-Agent': 'xxx',
} r = request.post(url, data=data, headers=headers, timeout=1, )
print(r.status_code)
print(r.json()) # r.text

  运行结果:

  B站页面上看看,发现没了:

  之前补充中发表的评论是:emmmm...

  对比一下楼层数也是正确的。其实我以为会用到delete动词的,但没想到B站的删除评论方法还是post。

  最后,实际上我写完之后发现我的方法有问题,首先,新番第一集的oid如何获得?或许可以想办法通过新番时间表来获取,我去新番时间表中找了找,但没看到给出oid的文件。。。所以还是需要从其他角度找找解决办法,我觉得可以考虑从主页上获取到新番信息的链接,因为记得新番在开播前会有提醒,可惜我以前貌似从来没有点进去看过会是什么样子的。。。但假如这时就已经有页面了只是没有集数也好办,就不停的检测页面的变化,如何发现有了oid那就同时发送消息,emmm...

  总之csrf目前应该可以固定不变的使用,只要找到oid,对新视频及时进行(qiang)评论(lou)的问题就解决了。

  还有许多细节还得要自己去体会~ 我一直想要下载B站的视频,但一直没能完成。。。

  2018-05-22 02:01:08 ps:关于如何获取csrf,可以参考我这篇中最后提到的方法。

  # 额外花絮。。。

  之前不太懂的时候,我很sb的试过这样写:

from requests import Session

request = Session()

url = 'https://api.bilibili.com/x/v2/reply/add'

data = {
'oid': '2458871',
'type': '1',
'message': 'test~',
'plat': '1',
'jsonp': 'jsonp',
'csrf': 'da9c3263c011ee0969ce383e8d799f05'
} r = request.post(url, data=data, auth=('user', 'pw'), timeout=1, )
print(r.status_code)
print(r.json()) # r.text

  显然被残酷的拒绝了:

  因为这个接口并不是用于登录的接口,所以肯定不可能登录上的。。。

python学习 —— post请求方法的应用的更多相关文章

  1. Python学习8——魔法方法、特性和迭代器

    Python中很多名称比较古怪,开头和结尾都是两个下划线.这样的拼写表示名称有特殊意义,因此绝不要在程序中创建这样的名称.这样的名称中大部分都是魔法(方法)的名称.如果你的对象实现了这些方法,他们将在 ...

  2. http学习--常用请求方法和响应状态码

    常用的http请求方法: GET方法:请求服务器资源,并返回 POST方法:向指定资源提交数据进行处理请求(比如说表单,上传文件等).数据被包含在请求体中.POST请求可能会导致新的资源建立或已有资源 ...

  3. http协议学习 —— post请求方法提交application/x-www-form-urlencoded类型的数据格式

    先推荐一篇很不错的文章:https://imququ.com/post/four-ways-to-post-data-in-http.html 说一下,如果是自己编写底层,那么要注意了,不能只有提交数 ...

  4. Python学习之魔法方法

    Python中会看到前后都加双下划线的函数名,例如 __init__(self),这类写法在Python中具有特殊的含义.如果对象使用了这类方法中的某一个,那么这个方法将会在特殊的情况下被执行,然而几 ...

  5. SpringMVC 学习笔记(请求方法的返回值和参数)

    在用注解对配置 处理器时,一般是一个方法处理一个请求,不同方法的返回类型有着不同的意义. 返回值为 ModelAndView 类型 ModelAndView 是Model 和 View 的一个集合类型 ...

  6. Python学习--两种方法爬取网页图片(requests/urllib)

    实际上,简单的图片爬虫就三个步骤: 获取网页代码 使用正则表达式,寻找图片链接 下载图片链接资源到电脑 下面以博客园为例子,不同的网站可能需要更改正则表达式形式. requests版本: import ...

  7. Python学习之read()方法

    read([size [,chars [,firstline]]]) 含义: 从文本流(io.TextIOWrapper)中解码数据并返回字符串对象.

  8. Python中http请求方法库汇总

    最近在使用python做接口测试,发现python中http请求方法有许多种,今天抽点时间把相关内容整理,分享给大家,具体内容如下所示: 一.python自带库----urllib2 python自带 ...

  9. 【python学习笔记】9.魔法方法、属性和迭代器

    [python学习笔记]9.魔法方法.属性和迭代器 魔法方法:xx, 收尾各有两个下划线的方法 __init__(self): 构造方法,创建对象时候自动执行,可以为其增加参数, 父类构造方法不会被自 ...

随机推荐

  1. css的理解 ----footrt固定在底部

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. 静态方法使用synchronized修饰.

    package seday10;/** * @author xingsir * 静态方法若使用synchronized修饰,这个方法一定具有同步效果.静态方法上使用的同步监视器对象为这个类的" ...

  3. C++指针和引用及区别

    1.变量 首先最重要的,variable的定义,当你申明一个变量的时候,计算机会将指定的一块内存空间和变量名进行绑定:这个定义很简单,但其实很抽象,例如:int x = 5; 这是一句最简单的变量赋值 ...

  4. Duizi and Shunzi HDU

    Duizi and Shunzi Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  5. HDU2121 Ice_cream’s world II (最小树形图)

    在建图的时候对原图进行加边 建立一个超级源点~ #include<cstdio> #include<algorithm> #include<cstring> usi ...

  6. VS2017编写c/c++汇编函数并调用

    首先在VS里面创建个空项目,然后添加汇编文件 .asm,    右键asm文件属性  --- 常规,改成下图的设置  , 从生成中排除改为否, 项类型改为自定义生成工具 然后点确定. 再次右键asm文 ...

  7. robotframework初始化时有返回值怎么处理

    方法一:set suite variable/set global variable 假设执行add school class会返回一个id,这个id在后面的脚本中还要使用. 因为初始化时只能有一个关 ...

  8. mybatis--一对多关联

    今天来介绍mybatis的一对多关联 (1)首先创建数据库mybatisonetomany,并创建数据库表post和user,并向其中插入一定的数据: create database mybatiso ...

  9. Atcoder Beginner Contest153E(完全背包)

    完全背包,价值取题意代价的最小值 #define HAVE_STRUCT_TIMESPEC #include<bits/stdc++.h> using namespace std; ],b ...

  10. samba对外开放的端口

    前言搭建samba的时候,如果是在内网\测试环境中,可以直接关闭防火墙,但是如果是在外网情况下,需要对防火墙开放某些端口.开放的具体步骤,下面我们来看. 操作步骤1.添加端口 firewall-cmd ...