这次实现的具体功能是通过多个B站API获取JSON格式的粉丝数,播放量等数据,转化格式后上传到指定的SQL服务器上,方便日
后数据分析。
既然都有服务器能跑数据库了,那肯定是PHP写个脚本来做这件事更方便啊!
所以,只是练习,练习!!!
这个代码可以完美和B站粉丝数显示器兼容,也就是说可以同时在OLED输出显示的时候上传数据到后台,以后可以回溯。
别问我为什么要记录粉丝数,就算我现在不知道,以后可能就突然会用到。。
比较重要的代码,比如要修改的地方之类的在源码中有标出。
JSON获取数据并格式化参考B站UP主 会飞的阿林 分享的代码。OLED输出,以及SQL通讯的部分 由好友 Sniper滑稽 完成。当然FKUN也有贡献,比如,修改了串口里和屏幕里显示的文字,照猫画虎的增加了一些Push内容,完成了服务器域名转化IP的DNS适配 完。
好了,接下来就是全部源码。在文章末尾会对一些开始遇到的问题和比较容易出错的地方进行探讨。
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <Wire.h>
#include <WiFiUdp.h>
#include <Ethernet.h>
#include <MySQL_Connection.h>
#include <MySQL_Cursor.h>
#include <Dns.h>
char deviceid[] = "fkun_esp_8266_02";
char hostname[] = "www.yourdomain.com"; //server host
char user[] = "*******"; // Mysql的用户名
char password[] = "*******"; // 登陆Mysql的密码
char INSERT_SQL[] = "INSERT INTO ******.****** (***, ***, ***, ***) VALUES (%s, %d, %d, %d)";
char ssid[] = "****"; // WiFi名
char pass[] = "*****"; // WiFi密码
String biliuid = "8515147"; //UID
String biliname = "F_KUN" ; //Name
String responserelation;
String responsespace;
int Follower = 0;
int View = 0;
int Uid =0;
const unsigned long HTTP_TIMEOUT = 5000;
IPAddress server_ip;
WiFiClient client;
HTTPClient http;
WiFiUDP udp;
MySQL_Connection conn((Client *)&client);
MySQL_Cursor* cursor;
DNSClient dns_client;
void setup()
{
Serial.begin(115200);
while (!Serial); // 等待端口的释放
Serial.println("Device ID:");
Serial.print(deviceid);
Serial.printf("\nConnecting to WiFi");
WiFi.begin(ssid, pass); // 连接WiFi
while (WiFi.status() != WL_CONNECTED)
{ // 如果WiFi没有连接,一直循环打印点
delay(500);
Serial.print(".");
}
Serial.printf("\nConnected to %s", ssid);
Serial.print("\nLocal IP address: ");
Serial.print(WiFi.localIP()); // 打印开发板的IP地址
Serial.print("\nDevice MAC:");
Serial.println(WiFi.macAddress());
Serial.printf("DNS Server getting IP from hostname: %s", hostname);
dns_client.begin(Ethernet.dnsServerIP());
WiFi.hostByName(hostname, server_ip);
Serial.print("\nDNS Process Successed !");
Serial.print("\nServer IP address: ");
Serial.println(server_ip);
Serial.print("Connecting to Database... \n");
if (conn.connect(server_ip, 3306, user, password)) // 连接数据库
Serial.print("Start getting Data!");
else
Serial.print("Connection to Server FAILED !!!");
cursor = new MySQL_Cursor(&conn); // 创建一个数据库游标实例
}
bool getJson()
{
bool r = false;
http.begin("http://api.bilibili.com/x/relation/stat?vmid=" + biliuid); //relation api
int httpCoderelation = http.GET();
if (httpCoderelation > 0) {
if (httpCoderelation == HTTP_CODE_OK) {
responserelation = http.getString();
r = true;
}
} else {
Serial.printf("[HTTP] GET JSON failed, error: %s\n", http.errorToString(httpCoderelation).c_str());
r = false;
}
http.end();
http.begin("http://api.bilibili.com/x/space/upstat?mid=" + biliuid); //space api
int httpCodespace = http.GET();
if (httpCodespace > 0) {
if (httpCodespace == HTTP_CODE_OK) {
responsespace = http.getString();
r = true;
}
} else {
Serial.printf("[HTTP] GET JSON failed, error: %s\n", http.errorToString(httpCodespace).c_str());
r = false;
}
http.end();
return r;
}
bool parseJson(String json)
{
const size_t capacity = JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + 70;
DynamicJsonDocument doc(capacity);
deserializeJson(doc, json);
int code = doc["code"];
const char *message = doc["message"];
if (code != 0) {
Serial.print("[API]Code:");
Serial.print(code);
Serial.print(" Message:");
Serial.println(message);
return false;
}
JsonObject data = doc["data"];
unsigned long data_mid = data["mid"];
int data_follower = data["follower"];
if (data_mid == 0) {
delay(500);
Serial.println("[JSON] FORMAT ERROR");
return false;
}
Follower = data_follower;
Uid = data_mid;
return true;
}
void parseJson1(String json)
{
const size_t capacity = JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + 70;
DynamicJsonDocument doc(capacity);
deserializeJson(doc, json);
int code = doc["code"];
const char *message = doc["message"];
JsonObject data = doc["data"];
JsonObject archive = data["archive"];
int data_view = archive["view"];
View = data_view;
}
void bilibilidata()
{
Serial.print("\nData from Bilibili API: \n");
Serial.print(" UID: ");
Serial.println(Uid);
Serial.print(" follower: ");
Serial.println(Follower);
Serial.print(" View: ");
Serial.println(View);
}
void pushsql()
{
char query[128];
sprintf(query, INSERT_SQL, biliuid.c_str(), Follower, View ,deviceid);
Serial.print("Ready to Push: ");
Serial.println(query);
MySQL_Cursor *cur_mem = new MySQL_Cursor(&conn);
cur_mem->execute(query);
Serial.print("Data Pushed !");
delete cur_mem;
}
void loop()
{
if (WiFi.status() == WL_CONNECTED)
{
if (getJson())
{
if (parseJson(responserelation))
{
parseJson1(responsespace);
bilibilidata();
pushsql();
}
}
Serial.print("\nWaiting for next Push... 5min remaining!");
delay(600000);
}
}
分开来说吧,先说B站数据获取的时候遇到的障碍,我非常作死的想在get粉丝数的同时get播放量,可这两个数据并不在同一个API里,也就是要分开获取。开始的想法是,既然都是解JSON,那就把两个API返回的数据放在一起然后解析。显然这样的尝试是失败的,于是就继续尝试仿照粉丝数去解析播放量。原作中用了bool函数,当时并不知道为什么这么用,也并不知道是干什么的,光复制就完事儿了。结果就是冲突了,最后是新开一个void解决了这个问题。
Get到数据之后显示到屏幕上也花了不少心思,从一开始照着图接线未果,去学习了针脚定义,并重新定义了针脚,又是看了很多遍demo最后把显示的部分解决了。但本文代码中已经去除了全部display的语句,所以不详细说明这期间遇到的问题了。
然后就是最烦人的数据库了,讲真我一点没学过数据库。记得开始架环境的时候,内存太少,没开swap,数据库界面能出来但是登陆不了。后来瞎搞给整上了。当时的需求就是建立这个博客,Typecho是动态博客,所以后台需要数据库支援。当时建库的时候Typecho自动用脚本就把底下的表都建好了,不需要我任何多余的操作。
但这次不一样了,真的要底层通讯了。
契机是Sniper正在为毕业设计准备的项目,基于8266的温湿度,气体传感器加门锁加灯加时钟的聚合体,然后有独立OLED屏幕显示内容,并物联网,可以在手机上对其进行一些操作。我表示想尝试用数据库作为中转,这样板子本身不会吃很大压力,数据也有地方长期储存,可以做可视化之类的。于是就开整,刚好在网上找到了很相似的代码,甚至用的是同款传感器,稍微动了一下就实现了上传数据。但这也是得益于传感器的库非常友好的对数据的格式进行了处理,可以直接上传。粉丝数什么的稍微会有点麻烦。
这里列举我们之前出错的几个细节。上传数据库的格式,INSERT 代码的使用,想要上传数据首先要找到表,其方法是在目录之间加点,例如 INSERT INTO MainDATABASE.ChildDATABASE。紧接着就是后面的数据,这个要看数据库的设置,对于不同的数据类型要设置不同的VALUES。对于例如uid这种string返回值,要特别的写成biliuid.c_str()这样解释。
如果是VPS上跑的数据库问题不大,用固定IP直接连接就可以。但怕的就是在自己家里搭的环境,动态IP可以通过DDNS绑定到域名,但这边代码里,直接把IP的值改成hostname是行不通的。开始想的办法就是ping,在前期测试的时候就是手动用cmd去ping域名,返回IP,但如果长期让板子自己运行,一旦IP发生变化,服务就歇逼了。查了一下,8266有ping库,可是这库似乎只是测试通讯是否正常,返回值只是判断是否联通,并不能给出地址。
那么后来找到了调用DNS的解决方案,注意,不是mDNS,这个是让板子做DNS服务器去解析域名的。比如把局域网的IP弄成好看的域名(DNS劫持2333)然而我们真正用的DNS藏在Ethenet这个库里,调用的时候也非常蛋疼。要知道我们8266用的是wifi连接,一切就比较复杂。这方面的讨论在国外的论坛上非常多,凭借我高中偶尔不及格但却应用自如的英语,非常顺畅的看完了他们的交流。最后找到了非常奇怪的解决方案。
dns_client.begin(Ethernet.dnsServerIP());
WiFi.hostByName(hostname, server_ip);
就是这两句,看着就奇怪,明明Wifi这个库里是没有DNS代码的,结果竟然这样联合起来用,非常迷惑,但就是可以解决问题。
后面的计划大概是去多写点PHP,然后能把数据直接在网页上可视化就最好了。毕竟,搞这些现在也只能装逼嘛,要大家都看得到才叫真的装逼。(笑)
好的。我看到你装逼了。
咦嘻嘻