一、C++之获取B站粉丝信息
1.1、写这个例子的初衷
通过一个多星期,将C++基础学习了一遍,想通过一个例子来整合一下自己所学到的东西,刚好我计划这段时间制作一个B站的粉丝信息计数器(主要是拿C语言来写的),但是后面想想,也想通过C++来实现,刚好拿来练练手。
1.2、获取使用到的知识点
这里面使用到网络socket的知识,但是不是很复杂,只需要自行百度C++中如何使用TCP编程即可。
1.3、获取原理
现在我们之讲原理,并不是最终版本,因为最终版本是需要有一定可靠性的,比如断网、网络恢复后又该如何恢复访问,所以这一节先教大家获取到自己想要的信息。
第一步:先打开我们的B站用户界面:
我们最终获取的就是圈出来的那四个信息。
然后按F12(有一些的小伙伴如果F12被占用其他功能,可以加上Fn+F12),调取网页代码出来。
按照下面两个步骤。
输入st后,点击一下空白处,就会出现圈出来的内容,然后点一下他,出现右边的代码信息。
然后获取到这个网址:
打开新的网址,把蓝色的区域删掉
最终就得到我们需要的两个信息,就是你关注的人和关注的人。
剩余两个信息视频播放数和点赞数在另外一个网址那里。
他的获取方式和上一条获取方式一样。
在这个网址可以获取到视频播放数和点赞人数,那么到这里,获取这四个信息的操作就已经知道,接下来就是写代码去验证。
1.4、C++获取
因为网页使用的html语言,是基于TCP网络的,所以我们可以通过TCP来模拟访问。
网络上有很多讲解C++的TCP的使用,在这里我不深入进去,只是教大家如何使用。
因为html使用的是域名访问,可是TCP是建立在ip地址的访问,所以我们第一步先解决,将域名转为ip,代码如下:
#include <winsock2.h>
#include <ws2def.h>
#include <stdlib.h>
#include <iostream>
#pragma comment(lib,"ws2_32.lib")
#define HOST_NAME "api.bilibili.com"
#define PORT 80
using namespace std;
int main(void)
{
/*这里主要是加载上面那个库做的初始化*/
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0){
return -1;
}
/*获取IP地址*/
struct hostent *hptr;
char ip[32];
if ((hptr = gethostbyname(HOST_NAME)) == NULL)
{
cout << " gethostbyname error for host:" << HOST_NAME << endl;
return -1;
}
cout << "official hostname:" << hptr->h_name << endl;
switch (hptr->h_addrtype)
{
case AF_INET:
case AF_INET6:
memset(ip,0x00,32);
strcpy(ip, inet_ntoa(*(struct in_addr *)*hptr->h_addr_list));
cout << ip << endl;
break;
default:
cout << "unknown address type" <<endl;
return -2;
break;
}
return 0;
}
我们访问的B站信息网址是"api.bilibili.com",所以运行结果如在:
这样我们就可以得到转化后的ip地址了。
获取到IP地址,就可以尝试连接到服务器上。
#include <winsock2.h>
#include <ws2def.h>
#include <stdlib.h>
#include <iostream>
#pragma comment(lib,"ws2_32.lib")
#define HOST_NAME "api.bilibili.com"
#define PORT 80
using namespace std;
int main(void)
{
/*这里主要是加载上面那个库做的初始化*/
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0){
return -1;
}
/*获取IP地址*/
struct hostent *hptr;
char ip[32];
if ((hptr = gethostbyname(HOST_NAME)) == NULL)
{
cout << " gethostbyname error for host:" << HOST_NAME << endl;
return -1;
}
cout << "official hostname:" << hptr->h_name << endl;
switch (hptr->h_addrtype)
{
case AF_INET:
case AF_INET6:
memset(ip,0x00,32);
strcpy(ip, inet_ntoa(*(struct in_addr *)*hptr->h_addr_list));
cout << ip << endl;
break;
default:
cout << "unknown address type" << endl;
return -2;
break;
}
/*连接到服务器*/
SOCKET socket_fd;
SOCKADDR_IN seraddr;
socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socket_fd == INVALID_SOCKET){
cout << "invalid socket!" << endl;
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(PORT);
seraddr.sin_addr.S_un.S_addr = inet_addr(ip);
if (connect(socket_fd, (SOCKADDR *)&seraddr, sizeof(seraddr)) == SOCKET_ERROR){
cout << "connect err!" << endl;
closesocket(socket_fd);
return -2;
}
cout << "tcp connect OK" << endl;
return 0;
}
这部分的代码增加了连接到服务器的部分代码。
运行结果:
这里已经连接上服务器,接下来就是发送请求了。
这里的发送请求,就有点门路在里面,我们不能乱发,只能模拟浏览器发送,否则会被服务器返回403之类的错误。
我们打开我们之前的网页,选择我们第一个右键后选择copy然后选择request headers。
新建一个文本,然后粘贴进去。
最终我们选择如下内容:
为了避免服务器禁止我们访问,所以我们需要模拟浏览器的请求头的部分信息,来伪装成为浏览器。
所以定一个字符串:
char req[] = "GET /x/relation/stat?vmid=378528692 HTTP/1.1\r\nAccept: */*\r\nHost: api.bilibili.com\r\nConnection: keep-alive\r\n\r\n";
这里要注意两点,每一行都得添加一个换行"\r\n",最后一行要多加"\r\n"一个换行符,具体请看代码。
之后通过向服务器请求,就可以得到我们想要的数据。
#include <winsock2.h>
#include <ws2def.h>
#include <stdlib.h>
#include <iostream>
#pragma comment(lib,"ws2_32.lib")
#define HOST_NAME "api.bilibili.com"
#define PORT 80
#define BUFSIZE 1024
using namespace std;
int main(void)
{
/*这里主要是加载上面那个库做的初始化*/
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0){
return -1;
}
/*获取IP地址*/
struct hostent *hptr;
char ip[32];
if ((hptr = gethostbyname(HOST_NAME)) == NULL)
{
cout << " gethostbyname error for host:" << HOST_NAME << endl;
return -1;
}
cout << "official hostname:" << hptr->h_name << endl;
switch (hptr->h_addrtype)
{
case AF_INET:
case AF_INET6:
memset(ip,0x00,32);
strcpy(ip, inet_ntoa(*(struct in_addr *)*hptr->h_addr_list));
cout << ip << endl;
break;
default:
cout << "unknown address type" << endl;
return -2;
break;
}
/*连接到服务器*/
SOCKET socket_fd;
SOCKADDR_IN seraddr;
socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socket_fd == INVALID_SOCKET){
cout << "invalid socket!" << endl;
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(PORT);
seraddr.sin_addr.S_un.S_addr = inet_addr(ip);
if (connect(socket_fd, (SOCKADDR *)&seraddr, sizeof(seraddr)) == SOCKET_ERROR){
cout << "connect err!" << endl;
closesocket(socket_fd);
return -2;
}
cout << "tcp connect OK" << endl;
/*发送请求*/
char strResponse[BUFSIZE];
char req[] = "GET /x/relation/stat?vmid=378528692 HTTP/1.1\r\nAccept: */*\r\nHost: api.bilibili.com\r\nConnection: keep-alive\r\n\r\n";
int req_len = strlen(req);
int result = 0;
req_len = strlen(req);
result = send(socket_fd, req, req_len, 0);
if (result == SOCKET_ERROR){
cout << "send err!" << endl;
closesocket(socket_fd);
return -1;
}
//开始接受服务器返回来的数据
result = recv(socket_fd, strResponse, BUFSIZE - 1, 0);
if (result > 0){
strResponse[result] = '\0';
cout << strResponse << endl;
}
else{
cout << "recv err!" << endl;
closesocket(socket_fd);
return -2;
}
return 0;
}
运行结果:
这个就是我们最终访问得到服务器的数据,只是这里我们还没解释出来而已。
这里获取到你关注的人数和关注你的人数,接下来就是视频播放量和点赞数了。
同理,也可以得到视频播放量的数据。
1.5、最终代码
#include <winsock2.h>
#include <ws2def.h>
#include <stdlib.h>
#include <iostream>
#pragma comment(lib,"ws2_32.lib")
#define HOST_NAME "api.bilibili.com"
#define PORT 80
#define BUFSIZE 1024
using namespace std;
int main(void)
{
/*这里主要是加载上面那个库做的初始化*/
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0){
return -1;
}
/*获取IP地址*/
struct hostent *hptr;
char ip[32];
if ((hptr = gethostbyname(HOST_NAME)) == NULL)
{
cout << " gethostbyname error for host:" << HOST_NAME << endl;
return -1;
}
cout << "official hostname:" << hptr->h_name << endl;
switch (hptr->h_addrtype)
{
case AF_INET:
case AF_INET6:
memset(ip,0x00,32);
strcpy(ip, inet_ntoa(*(struct in_addr *)*hptr->h_addr_list));
cout << ip << endl;
break;
default:
cout << "unknown address type" << endl;
return -2;
break;
}
/*连接到服务器*/
SOCKET socket_fd;
SOCKADDR_IN seraddr;
socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socket_fd == INVALID_SOCKET){
cout << "invalid socket!" << endl;
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(PORT);
seraddr.sin_addr.S_un.S_addr = inet_addr(ip);
if (connect(socket_fd, (SOCKADDR *)&seraddr, sizeof(seraddr)) == SOCKET_ERROR){
cout << "connect err!" << endl;
closesocket(socket_fd);
return -2;
}
cout << "tcp connect OK" << endl;
/*发送请求*/
char strResponse[BUFSIZE];
char req[] = "GET /x/relation/stat?vmid=378528692 HTTP/1.1\r\nAccept: */*\r\nHost: api.bilibili.com\r\nConnection: keep-alive\r\n\r\n";
int req_len = strlen(req);
int result = 0;
req_len = strlen(req);
result = send(socket_fd, req, req_len, 0);
if (result == SOCKET_ERROR){
cout << "send err!" << endl;
closesocket(socket_fd);
return -1;
}
//开始接受服务器返回来的数据
result = recv(socket_fd, strResponse, BUFSIZE - 1, 0);
if (result > 0){
strResponse[result] = '\0';
cout << strResponse << endl;
}
else{
cout << "recv err!" << endl;
closesocket(socket_fd);
return -2;
}
char req_like[] = "GET /x/space/upstat?mid=378528692 HTTP/1.1\r\nAccept: */*\r\nHost: api.bilibili.com\r\nConnection: keep-alive\r\n\r\n";
int req_like_len = strlen(req_like);
result = send(socket_fd, req_like, req_like_len, 0);
if (result == SOCKET_ERROR){
cout << "send err!" << endl;
closesocket(socket_fd);
return -1;
}
//开始接受服务器返回来的数据
result = recv(socket_fd, strResponse, BUFSIZE - 1, 0);
if (result > 0){
strResponse[result] = '\0';
cout << strResponse << endl;
}
else{
cout << "recv err!" << endl;
closesocket(socket_fd);
return -2;
}
return 0;
}
运行结果:
当然以上只是教大家获取的原理,这样肯定是不可能跑的,第一,因为数据还没提取出来,第二因为他的可靠性太差了,网络一中断,便再也无法恢复过来。
所以下一篇我们来慢慢完善他,不知道大家明白这个原理没,如果那里不明白的可以评论。