为Music.Xuchen.Wang写一个文章自动生成器

我从小英文歌就听的很多,之前开了一个音乐推荐的博客,每天一更,记录下近期喜欢听的歌。但是每天要编辑半天,复制歌曲名歌手名歌曲链接专辑图片歌词等等让我越来越不耐烦,终于下定决心写一个自动生成器,通过这首歌在网易云音乐的ID和Github上高人提供的API,自动生成出歌曲推荐文章。

1. 环境

  • PHP 5.5.12
  • SQLite 3.7.13

2. 需求

2.1. 我提供什么

  1. 一首歌的网易云ID
  2. 歌曲分类
  3. 放在标题尾部的几个字的介绍
  4. 一段稍微详细点的介绍

2.2. 我需要什么

随便打开一个Music.Xuchen.Wang的音乐推荐界面,如:https://music.xuchen.wang/?id=99,可以看到,我需要为这个页面提供:

  1. 歌曲MP3直链用来在线播放和下载
  2. 一个重新组合好的MainArtist - Title (Ft. FeatArtist1,FeatArtist2...)的标题
  3. 网易云页面
  4. 专辑封面图片展示
  5. 歌词
  6. 每个歌手的名字用来生成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. 总结

以上我需要的数据就组织完了,分别为:

  1. 歌曲MP3直链用来在线播放和下载 :$sDirectLink
  2. 一个重新组合好的MainArtist - Title (Ft. FeatArtist1,FeatArtist2...)的标题 :$sRealTitle
  3. 网易云页面 :$sPage
  4. 专辑封面图片展示 :$sAlbumPicUrl
  5. 歌词 :$sRealLyric
  6. 每个歌手的名字用来生成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条路可以走:

  1. 研究博客系统提供的API,使用插件提供的接口添加文章
  2. 写一个Chrome插件或者TamperMonkey,在新建文章界面自动填入(我雪出的点子)
  3. 直接对博客系统的数据库下手

我最后选择了第三种,原因第一点是我没有兴趣研究这个博客所用系统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

标签: PHP

精彩评论
  1. fz fz

    真tm吊!

  2. 水水的老兔 水水的老兔

    真nm屌!

  3. R-Stalker R-Stalker

    真tmd屌!

  4. 王老板就是屌,水个留言!

发表评论: