我从小英文歌就听的很多,之前开了一个音乐推荐的博客,每天一更,记录下近期喜欢听的歌。但是每天要编辑半天,复制歌曲名歌手名歌曲链接专辑图片歌词等等让我越来越不耐烦,终于下定决心写一个自动生成器,通过这首歌在网易云音乐的ID和Github上高人提供的API,自动生成出歌曲推荐文章。
1. 环境
- PHP 5.5.12
- SQLite 3.7.13
2. 需求
2.1. 我提供什么
- 一首歌的网易云ID
- 歌曲分类
- 放在标题尾部的几个字的介绍
- 一段稍微详细点的介绍
2.2. 我需要什么
随便打开一个Music.Xuchen.Wang的音乐推荐界面,如:https://music.xuchen.wang/?id=99,可以看到,我需要为这个页面提供:
- 歌曲MP3直链用来在线播放和下载
- 一个重新组合好的MainArtist - Title (Ft. FeatArtist1,FeatArtist2...)的标题
- 网易云页面
- 专辑封面图片展示
- 歌词
- 每个歌手的名字用来生成Tag
2.3. 要能做到什么
在我输入[我提供什么]的4点之后,直接发布这篇文章。
3. 入口页面
入口页面只需要一个form,里面包含我要提供的4点就行了。反正是我自己才能用到,我也没用任何前端库什么的,直接一个form拉了4个input完事。
界面:
4. 处理页面
4.1. 选轮子
干这种事肯定不能从轮子造起,去Github上搜索了一番,网易云API的轮子挺多,其实我本来是想找个Golang的轮子锻炼锻炼golang水平的,可惜没有合适的,最后选了一个看起来比较简单的PHP的轮子,在此感谢该API作者metowolf:https://github.com/metowolf/NeteaseCloudMusicApi
4.2. 轮子提供了什么
通过API,我们可以轻松的通过网易云歌曲id获取到这首歌对应的歌手、专辑ID、专辑名、歌词、专辑图片等需要的信息,这些信息API将其以Json格式良好的打包好了,我们只需要拿来就用即可。如这首Jhene Aiko feat Swae Lee的《Savita》http://music.163.com/#/m/song?id=531777634,通过网易云页面的连接可以看到id为531777634。根据API使用说明,我们直接
$api = new NeteaseMusicAPI();
$result = $api->detail("531777634");
就获取到了这首歌的详细信息。
这样拉下来是个json字符串,我们通过json_decode()函数将其转换成一个对象,再重新指定下JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES三个参数来方便观察这个json里有什么信息。
<?php
header('Content-type: application/json; charset=UTF-8');
require_once 'NeteaseMusicAPI_mini.php';
$api = new NeteaseMusicAPI();
$result = $api->detail("531777634");
$data=json_decode($result);
echo json_encode($data,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
?>
输出如图:[图1]
可以看到这里有我们需要的歌曲名(songs->name)、歌手(songs-ar>name)、专辑ID(songs->al->id),
通过专辑ID再去查询
$albumresult = $api->album($sAlbumId);
$albumdata=json_decode($albumresult);
可以看到$albumdata->picUrl就是我们需要的专辑图片的Url:[图2]
再通过
$lyricresult = $api->lyric($sId);
$lyricdata=json_decode($lyricresult);
[图3]
可以看到我们需要的歌词信息。
同时,网易云有这样一条规则,MP3外链为
http://music.163.com/song/media/outer/url?id=(ID).mp3
这样,我需要的所有信息我其实都已获得,但是需要重新组织一下以符合我的需求。
4.3. 组织数据
我们按照“我需要什么”中的顺序来一一处理:
4.3.1. 1和3:歌曲MP3直链和网易云页面
这个非常好解决,我们已经有了ID,直接替换ID即可
//直链
$sDirectLink="http://music.163.com/song/media/outer/url?id=".$sId.".mp3";
//网易云页面
$sPage="https://music.163.com/#/song?id=".$sId;
4.3.2. 2和6:一个重新组合好的标题和歌手Tag
从第一张detail(ID)的图可以看到,返回的Json数据并没有MainArtist和FeatArtist的区分,但第一个肯定是MainArtist。直接一个循环重新组织好歌手数据:
//Tag
$sTag=$sMainArtist;
//歌手数量
$sArtistCnt=count($data->songs[0]->ar);
//如果有Feat歌手:修改歌曲名加上Feat;修改Tag加上Feat歌手
if ($sArtistCnt>1) {
$sTitle=$sTitle." (Ft.";
for ($curI=0;$curI<$sArtistCnt-1;$curI++) {
$sFeatArtist[$curI]=$data->songs[0]->ar[$curI+1]->name;
if ($curI!=0) {
$sTitle=$sTitle.",";
}
$sTitle=$sTitle." ".$sFeatArtist[$curI];
$sTag=$sTag.",".$sFeatArtist[$curI];
}
$sTitle=$sTitle.")";
}
$sRealTitle = $sMainArtist." - ".$sTitle." ".$_POST["intro"];
4.3.3. 4:专辑封面图片展示
从上面第二张album的图可以看到,$albumdata->album->picUrl就是图片地址,不过这个地址有点瑕疵,它带上了http,直接用会让我的https站点提示不安全,我们手动处理一下,去掉http:,使用Protocol-relative URL让链接直接//开头。
$sAlbumPicUrl=substr($albumdata->album->picUrl,5);
4.3.4. 5:歌词
歌词的处理是唯一比较麻烦的地方,从上面第三张lyric的图可以看到,歌词是[时间戳]文本形式,而且英文歌曲原歌词和翻译是分开的。
我处理的办法是先遍历一遍英文歌词,将其转换为Map(php里就是array)的形式: $line[时间戳]=歌词。然后如果有翻译的中文歌词,则再遍历一次中文歌词,将中文歌词加至每项结尾处。最后一个循环输出$line的value。
在将一个字符串提取出[时间戳]的过程中,不可避免的使用到了正则表达式,好在还比较简单,我雪还帮了我一把.
对应代码如下:
$line=array();
//将歌词以时间戳为key,歌词内容为value分解成map
for ($curI=0;$curI<count($sEngLyc);$curI++) {
//匹配到时间戳,后半句排除只有时间戳没有实际歌词的情况
//PREG_OFFSET_CAPTURE:如果传递了这个标记,对于每一个出现的匹配返回时会附加字符串偏移量(相对于目标字符串的)。 注意:这会改变填充到matches参数的数组,使其每个元素成为一个由 第0个元素是匹配到的字符串,第1个元素是该匹配字符串 在目标字符串subject中的偏移量。
//http://php.net/manual/zh/function.preg-match.php
if (preg_match("/\[.*\]/",$sEngLyc[$curI],$matches,PREG_OFFSET_CAPTURE)==1) {
if (strlen($sEngLyc[$curI])>strlen($matches[0][0])) {
$time=substr($sEngLyc[$curI],$matches[0][1],strlen($matches[0][0]));
$bar=substr($sEngLyc[$curI],$matches[0][1]+strlen($matches[0][0]));
// echo $time." wtf ".$bar."<br>";
$line[$time]=$bar;
}
}
}
//如果此歌曲有中文翻译,按时间戳为key将中文歌词跟在每行尾
if (property_exists($lyricdata->tlyric,"lyric")) {
$sTlyric=$lyricdata->tlyric->lyric;
$sChnLyc=explode("\n",$sTlyric);
// var_dump($sChnLyc);
for ($curI=0;$curI<count($sChnLyc);$curI++) {
if (preg_match("/\[.*\]/",$sChnLyc[$curI],$matches,PREG_OFFSET_CAPTURE)==1) {
// echo $sChnLyc[$curI]." wtf ".$curI." wtf ".$matches[0][0]."<br>";
if (strlen($sChnLyc[$curI])>strlen($matches[0][0])) {
$time=substr($sChnLyc[$curI],$matches[0][1],strlen($matches[0][0]));
// echo $time." ".gettype($time)."<br>";
$chnBar=substr($sChnLyc[$curI],$matches[0][1]+strlen($matches[0][0]));
// echo $time." ".$chnBar."<br>";
// echo array_key_exists($time,$line);
if (array_key_exists($time,$line)) {
$line[$time]=$line[$time]."</p><p>".$chnBar;
}
}
}
}
//var_dump($line);
}
//将歌词map再组合成string
$sRealLyric=implode("</p><p>",$line);
//var_dump($sRealLyric);
4.3.5. 总结
以上我需要的数据就组织完了,分别为:
- 歌曲MP3直链用来在线播放和下载 :$sDirectLink
- 一个重新组合好的MainArtist - Title (Ft. FeatArtist1,FeatArtist2...)的标题 :$sRealTitle
- 网易云页面 :$sPage
- 专辑封面图片展示 :$sAlbumPicUrl
- 歌词 :$sRealLyric
- 每个歌手的名字用来生成Tag :$sTag
4.4. 整理一个模板
有了信息,我肯定需要一个模板用来塞数据,直接从以前的文章随便找一篇,将需要替换的部分做好@标记方便替换,如:
这样我在处理页面中先引用模板,再使用类似
//替换模板中对应内容
$templatestr = str_replace("@MainArtist",$sMainArtist,$templatestr);
$templatestr = str_replace("@Title",$sTitle,$templatestr);
$templatestr = str_replace("@DirectLink",$sDirectLink,$templatestr);
$templatestr = str_replace("@NeteasePage",$sPage,$templatestr);
$templatestr = str_replace("@Lyric",$sRealLyric,$templatestr);
$templatestr = str_replace("@AlbumPicUrl",$sAlbumPicUrl,$templatestr);
$templatestr = str_replace("@Description",$_POST["des"],$templatestr);
即可完成替换。
这样,一篇文章已经自动生成好了,通过用几个textarea输出,我们可以得到这样的页面:
这样我可以手工复制进博客后台,但是我觉得明显无法接受,既然已经干了肯定要避免所有的手工操作,做到全自动发布文章。
5. 发布文章
要做到自动发布文章,我有以下3条路可以走:
- 研究博客系统提供的API,使用插件提供的接口添加文章
- 写一个Chrome插件或者TamperMonkey,在新建文章界面自动填入(我雪出的点子)
- 直接对博客系统的数据库下手
我最后选择了第三种,原因第一点是我没有兴趣研究这个博客所用系统Z-Blog的文档,粗略过了一遍后我觉得质量不是很高。第二种虽然可行但我更喜欢尽量避开依赖于Chrome插件。第三种比较暴力。
其实过程还是很简单的,ZBlog数据库中zbp_post表就是保存文章的,和替换模板一样,我们先写好insert into字符串模板:
$dbPostInsStr=<<<INSSTR
insert into zbp_post (log_ID,log_CateID,log_AuthorID,log_Tag,log_Status,log_Type,log_IsTop,
log_isLock,log_Title,log_Intro,log_Content,log_PostTime,log_CommNums,log_ViewNums,log_Alias,log_Template,log_Meta)
values (@nextPostID,@logCateID,1,"@logTag",0,0,0,0,"@logTitle","",'@logContent',@logPostTime,0,0,"","","");
INSSTR;
然后对对应的字段替换即可。
同样,zbp_tag表也是,我们需要先检测该歌手是否存在,不存在加入Tag表即可。
//生成Tag字段
for ($curI=0;$curI<$sArtistCnt;$curI++) {
$ArtistName=$data->songs[0]->ar[$curI]->name;
//检查是否已有该歌手Tag
$dbTagSelStr='select tag_ID from zbp_tag where tag_Name="'.$ArtistName.'"';
$results = $dbConn->query($dbTagSelStr);
$row = $results->fetchArray();
// var_dump($row);
//如果歌手已存在将其TagID加入
if (!empty($row)) {
$logTag=$logTag."{".$row[0]."}";
//歌手对应文章数量+1
$dbUpdateTagCnt='update zbp_tag set tag_Count=tag_Count+1 where tag_Name="'.$ArtistName.'"';
$dbConn->exec($dbUpdateTagCnt);
}
//如果歌手不存在于Tag列表
else {
//获取下一TagID
$dbGetNextTagIdStr='SELECT tag_id FROM zbp_tag order BY tag_id desc LIMIT 0,1';
$results = $dbConn->query($dbGetNextTagIdStr);
$row = $results->fetchArray();
// var_dump($row);
$nextTagID=$row[0]+1;
$dbTagInsStr='insert into zbp_tag (tag_ID,tag_Name,tag_Order,tag_Count) values ('.$nextTagID.',"'.$ArtistName.'",0,1)';
// echo $dbTagInsStr."<br>";
$dbConn->exec($dbTagInsStr);
$logTag=$logTag."{".$nextTagID."}";
}
// echo $logTag;
}
title: 为Music.Xuchen.Wang写一个文章自动生成器
time: 2018-08-22 17:32
tags: PHP
category: Study
真tm吊!
真nm屌!
真tmd屌!
王老板就是屌,水个留言!
我擦,张总好啊
想想你的文章写的特别好https://www.ea55.com/
哈哈哈,写的太好了https://www.cscnn.com/