一、C++获取B站粉丝数信息
1.1、获取原理
请参考我的上一篇文章:
(十一)C++之获取B站用户的粉丝数信息(一)。
1.2、讲一下程序思路
下面是我在写代码前构思一下整体思路。
我会创建两个类,一个类是专门处理网络问题,另外一个类是处理数据问题。
网络从初始化到链接发送数据,有四个地方容易出错:
1、加载lib出错。
2、获取IP出错。
3、没法连接到服务器。
4、发送数据出错、或者接受数据出错。
所以我们必须针对这四种情况做出处理。
第二个类主要是创建数据处理的问题,每个人的处理方式可能都不一样,所以我将他作为纯虚函数来写,这样就可以更好的兼容性问题了。
1.3、代码
这份代码我已经上传到CSDN中,可以点击下载, 代码链接。哈作为一点辛苦费,我设置了1个积分,如果你不嫌麻烦的话,可以粘贴下面的代码。
主要有五个文件:
先创建第一个类Network:
.h文件如下:
#pragma once
#include <winsock2.h>
#include <ws2def.h>
#include <stdlib.h>
#include <time.h>
#include <iostream>
#include "GetData.h"
#pragma comment(lib,"ws2_32.lib")
#define DEVICE_ONLINE 0 //设备在线
#define DEVICE_OFFLINE -1 //设备离线
#define DEVICE_NO_IP -2 //没有获取到ip
#define DEVICE_NO_INIT -3 //没有初始化 基于window下的网络编程
#define NETWORK_OFFLINE -1 //网络离线
#define NETWORK_ONLINE 0 //网络在线
struct fan_struct{
int device_status; //设备状态
int device_real_status;// 通过改参数来确定最终的设备是否离线
int follower; //关注你的人数
int following; //你关注的人数
int likes; //点赞人数
int view; //所有视频观看次数
};
class Network
{
public:
Network(const char *puid);
~Network();
void fan_init(void);
int lib_init(void);
int domain_to_ip(void);
void init(void);
int network_connect(void);
void socket_connect(void);
int network_get_fan(void);
void socket_online(void);
void socket_other(void);
void run(void);
private:
char *puid; //UID
char ip[20]; //ip地址
SOCKET socket_fd; //网络句柄
struct fan_struct fan; //粉丝数据结构体
GetData *pdata; //父类数据处理指针
GetJson get_data; //这个是我自己实现获取数据的对象,然后将父类的指针指向子类的对象,就可以完成接口的形成
};
.cpp文件如下:
#define _CRT_SECURE_NO_WARNINGS
#include "Network.h"
#define HOST_NAME "api.bilibili.com"
#define PORT 80
#define BUFSIZE 1024
#define REFRESH_TIME_MS 2000 //默认2秒钟刷新一次
#define DETECTION_TIME_MS 10000 //默认10秒钟检测网络是否恢复
using namespace std;
Network::Network(const char *puid)
{
int len = strlen(puid);
this->puid = new char[len+1];
if (this->puid == NULL){
cout << "get uid err" << endl;
return ;
}
strcpy(this->puid, puid);
memset(this->ip,0x00,20);
this->socket_fd = 0;
//初始化粉丝结构体
fan_init();
//将创建的对象指向父类指针
pdata = &get_data;
}
Network::~Network()
{
if (this->puid != NULL){
delete this->puid;
this->puid = NULL;
}
}
/*
* description:初始化fan结构体
* para:无
* return:无
*/
void Network::fan_init(void)
{
this->fan.device_real_status = NETWORK_OFFLINE;
this->fan.device_status = DEVICE_NO_INIT;
this->fan.follower = 0;
this->fan.following = 0;
this->fan.likes = 0;
this->fan.view = 0;
}
/*
* description:socket库的一些初始化
* para:无
* return:
* 0:成功 其他:失败
*/
int Network::lib_init(void)
{
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0){
return -1;
}
return 0;
}
/*
* description:将域名转为ip地址
* para:无
* return:
* 0:成功 其他:失败
*/
int Network::domain_to_ip(void)
{
struct hostent *hptr;
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(this->ip, 0x00, 32);
strcpy(this->ip, inet_ntoa(*(struct in_addr *)*hptr->h_addr_list));
cout << this->ip << endl;
break;
default:
cout << "unknown address type" << endl;
return -2;
break;
}
return 0;
}
/*
* description:网络整体初始化
* para:无
* return:无
*/
void Network::init(void)
{
int ret = 0;
ret = lib_init();
if (ret < 0){
cout << "socket init err" << endl;
this->fan.device_status = DEVICE_NO_INIT;
return;
}
ret = domain_to_ip();
if (ret < 0){
cout << "ip get err" << endl;
this->fan.device_status = DEVICE_NO_IP;
return;
}
this->fan.device_status = DEVICE_OFFLINE;
}
/*
* description:连接到服务器
* para:无
* return:
* 0:成功 其他:失败
*/
int Network::network_connect(void)
{
SOCKADDR_IN seraddr;
this->socket_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (this->socket_fd == INVALID_SOCKET){
printf("invalid socket!\r\n");
return -1;
}
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(PORT);
seraddr.sin_addr.S_un.S_addr = inet_addr(this->ip);
if (connect(this->socket_fd, (SOCKADDR *)&seraddr, sizeof(seraddr)) == SOCKET_ERROR){
printf("connect err!\r\n");
closesocket(this->socket_fd);
return -2;
}
return 0;
}
/*
* description:整体网络的连接
* para:无
* return:无
*/
void Network::socket_connect(void)
{
int ret = 0;
//只有是离线才处理,其他模式说明没有过
if (this->fan.device_status == DEVICE_OFFLINE){
ret = network_connect();
if (ret < 0){
cout << "connect socket err" << endl;
return;
}
cout << "connect TCP ok" << endl;
this->fan.device_status = DEVICE_ONLINE;
}
}
/*
* description:获取粉丝信息
* para:无
* return:
* 0:成功 其他:失败
*/
int Network::network_get_fan(void)
{
char req[200];
int req_len = 0;
int result;
char strResponse[BUFSIZE] = { 0 };
char ret_cout = 0;
int follower, following, view, likes;
//先请求关注人数
memset(req, 0x00, 200);
this->pdata->fill_information(this->puid, req, MODE_FLOWER);//拼凑出发送头部信息
#ifdef DEBUG
cout << req << endl;
#endif
req_len = strlen(req);
result = send(this->socket_fd, req, req_len, 0);
if (result == SOCKET_ERROR){
return -1;
}
result = recv(this->socket_fd, strResponse, BUFSIZE - 1, 0);
if (result > 0){
#ifdef DEBUG
cout << strResponse<<endl;
#endif
strResponse[result] = '\0';
ret_cout += this->pdata->explanation_fan(strResponse, &follower, &following);
}
else{
return -2;
}
//求点赞数和观看数
memset(req, 0x00, 200);
this->pdata->fill_information(this->puid, req, MODE_VIEW);
req_len = strlen(req);
#ifdef DEBUG
cout << req << endl;
#endif
result = send(this->socket_fd, req, req_len, 0);
if (result == SOCKET_ERROR){
return -3;
}
result = recv(this->socket_fd, strResponse, BUFSIZE - 1, 0);
if (result > 0){
#ifdef DEBUG
cout << strResponse<<endl;
#endif
strResponse[result] = '\0';
ret_cout += this->pdata->explanation_view(strResponse, &view, &likes);
}
else{
return -4;
}
if (ret_cout == 0){
this->fan.follower = follower;
this->fan.following = following;
this->fan.view = view;
this->fan.likes = likes;
}
return ret_cout;
}
/*
* description:在线处理粉丝数据信息
* para:无
* return:无
*/
void Network::socket_online(void)
{
int ret = 0;
static int err_cout = 0;
time_t rawtime;
struct tm *info;
ret = network_get_fan();
if (ret < 0){
err_cout++;
if (err_cout >= 2){ //超过三次都失败可以判断为暂时离线,因为有可能是服务器踢掉的情况。
printf("get fan err\r\n");
closesocket(this->socket_fd);
this->fan.device_status = DEVICE_OFFLINE;
return;
}
}
else {
err_cout = 0;
time(&rawtime);
info = localtime(&rawtime);
cout << asctime(info) << endl;
cout << "你关注的人数:" << this->fan.following;
cout << " 关注你的人数:" << this->fan.follower;
cout << " 视频总播放量:" << this->fan.view;
cout << " 点赞数:" << this->fan.likes << endl;
}
Sleep(REFRESH_TIME_MS);
}
/*
* description:网络出错后,统一在这里处理
* para:无
* return:无
*/
void Network::socket_other(void)
{
int ret = 0;
switch (this->fan.device_status){
case DEVICE_NO_INIT:
ret = lib_init();
if (ret == 0){
this->fan.device_status = DEVICE_NO_IP;
}
else{
break;
}
case DEVICE_NO_IP:
ret = domain_to_ip();
if (ret == 0){
this->fan.device_status = DEVICE_OFFLINE;
}
else{
break;
}
case DEVICE_OFFLINE:
ret = network_connect();
if (ret == 0){
this->fan.device_status = DEVICE_ONLINE;
}
break;
default:
break;
}
Sleep(DETECTION_TIME_MS);
}
/*
* description:最终运行的函数
* para:无
* return:无
*/
void Network::run(void)
{
int real_offline_cout = 0; //主要是统计真实的掉线次数
socket_connect();
while (1){
cout << "network status:" << this->fan.device_real_status << endl;
if (this->fan.device_status == DEVICE_ONLINE){
socket_online();
real_offline_cout = 0;
this->fan.device_real_status = NETWORK_ONLINE;
}
else{
socket_other();
real_offline_cout++;
if (real_offline_cout >= 3){ //超过三次,就真的判断为设备真正的离线了
real_offline_cout = 3;
this->fan.device_real_status = NETWORK_OFFLINE;
}
}
}
}
第二个类GetData:
.h文件如下:
#pragma once
#define MODE_FLOWER 0 //因为关注人数和点赞人数是分开请求的,所以需要两种模式来生成对应的访问信息
#define MODE_VIEW 1
class GetData
{
public:
GetData();
~GetData();
//进行数据拼接
void fill_information(char *puid, char *pdata, char mode);
//提供两个获取数据的方法,这两个方法是我根据我自己的想法来实现的,如果你自己有更好的可以自己实现
virtual int explanation_fan(char *precv, int *pfollower, int *pfollwing) = 0;
virtual int explanation_view(char *precv, int *pview, int *plikes) = 0;
private:
};
class GetJson :public GetData
{
public:
GetJson();
~GetJson();
virtual int explanation_fan(char *precv, int *pfollower, int *pfollwing);
virtual int explanation_view(char *precv, int *pview, int *plikes);
private:
};
.cpp文件如下:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <iostream>
#include "GetData.h"
GetData::GetData()
{
}
GetData::~GetData()
{
}
/*
* description:填充请求信息
* para:
* puid:B站用户对应的UID信息
* pdata:最后生成的信息
* mode:请求信息类型
* return:无
*/
void GetData::fill_information(char *puid, char *pdata, char mode)
{
char fill_data2[] = " HTTP/1.1\r\nAccept: */*\r\nHost: api.bilibili.com\r\nConnection: keep-alive\r\n\r\n";
char fill_flower_data1[] = "GET /x/relation/stat?vmid=";
char fill_view_data1[] = "GET /x/space/upstat?mid=";
//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";
//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";
if (mode == MODE_FLOWER){
sprintf(pdata, "%s%s%s", fill_flower_data1, puid, fill_data2);
}
else if (mode == MODE_VIEW){
sprintf(pdata, "%s%s%s", fill_view_data1, puid, fill_data2);
}
}
//---------------------------------------------------------------------------------------
GetJson::GetJson()
{
}
GetJson::~GetJson()
{
}
/*
* description:解释粉丝数和你关注的人数
* para:
* precv :请求返回的数据
* pfollower :粉丝数
* pfollwing :你关注的人数
* return:
* 0:成功 小于0:失败
*/
int GetJson::explanation_fan(char *precv, int *pfollower, int *pfollwing)
{
char *pdata = NULL;
char *pdata_end = NULL;
char data_tmp[10];
pdata = strstr(precv, "\"following\":");
if (pdata == NULL){
return -1;
}
else {
pdata_end = strstr(pdata, ",\"");
if (pdata_end == NULL){
return -2;
}
else{
memset(data_tmp, 0x00, 10);
memcpy(data_tmp, pdata + 12, pdata_end - pdata - 12);
*pfollwing = atoi(data_tmp);
}
}
pdata = strstr(precv, "\"follower\":");
if (pdata == NULL){
return -3;
}
else {
pdata_end = strstr(pdata, "}}");
if (pdata_end == NULL){
return -4;
}
else{
memset(data_tmp, 0x00, 10);
memcpy(data_tmp, pdata + 11, pdata_end - pdata - 11);
*pfollower = atoi(data_tmp);
}
}
return 0;
}
/*
* description:解释观看数和点赞数
* para:
* precv :请求返回的数据
* pview :观看数
* plikes :点赞数
* return:
* 0:成功 小于0:失败
*/
int GetJson::explanation_view(char *precv, int *pview, int *plikes)
{
char *pdata = NULL;
char *pdata_end = NULL;
char data_tmp[10];
pdata = strstr(precv, "\"view\":");
if (pdata == NULL){
return -1;
}
else {
pdata_end = strstr(pdata, "},");
if (pdata_end == NULL){
return -2;
}
else{
memset(data_tmp, 0x00, 10);
memcpy(data_tmp, pdata + 7, pdata_end - pdata - 7);
*pview = atoi(data_tmp);
}
}
pdata = strstr(precv, "\"likes\":");
if (pdata == NULL){
return -3;
}
else {
pdata_end = strstr(pdata, "}}");
if (pdata_end == NULL){
return -4;
}
else{
memset(data_tmp, 0x00, 10);
memcpy(data_tmp, pdata + 8, pdata_end - pdata - 8);
*plikes = atoi(data_tmp);
}
}
return 0;
}
main.cpp文件如下:
#include "Network.h"
int main(void)
{
Network net("250858633"); //传递进去你想要查询的up主的UID即可获取得到你想要得数据
net.init();
net.run();
return 0;
}
这个是华农兄弟的B站的UID号。
最终的运行结果如下:
1.4、一些关键点解释
其实这里最主要的就是判断有没有掉线,之前我遇到过,设备会出现周期性的访问不到数据,其实就是所谓的掉线。
掉线有三种情况,第一种就是服务器把我给踢了,第二种运营商限制(需要增加心跳防止老化),第三种就是我自己断开。
第三种肯定排除了,因为我没有这不操作,第二种的话,如果是运营商的因为链路老化,这个解决办法很好解决,就是将刷新频率调大一点,但是我发现还是存在周期性无法访问,那么就剩第一种,访问一段时间后被服务器给踢了,看下面这种图,周期性访问不到数据,但是在下一次尝试重连服务器发现又可以连接上去了。
所以我们不能一旦访问不到数据就认为是网络出问题了,有可能是被服务器给踢掉了。
所以我在结构体里面设置两个变量,一个是真正的网络标志位,另外一个是设备标志位,设备离线不代表网络出问题。
但是如果网络出问题了,设备一定会离线。
我具体处理的方式清查开代码,其他的都没什么好说的了,逻辑问题,主要是不断访问,判断有没有出现问题,如果超过三次连续的没有拿到数据,在尝试重新登录,如果尝试三次重新登录都失败,那么判断为网络离线。