介绍
QQ音乐或网易云音乐的分享短连接,通过解析html内容,解析出来音频下载地址
说明
1:有IP被封的风险,如有特别需要,请自行配置代理
2:此 demo 简单易懂,如有问题,欢迎留言讨论
代码实现
<?php
namespace xFramework\Plugin\Music;
use xFramework\Plugin\Request\Curl;
class Music {
public static $wPath = X_ROOT_PATH.'/Cache/Music/wyy/'; #本地网易云音乐的保存路径
public static $qPath = X_ROOT_PATH.'/Cache/Music/qq/'; #本地QQ音乐的保存路径
public static $wOutPlayUrl = 'http://music.163.com/song/media/outer/url?';
public static $wSearchUrl = 'http://music.163.com/api/search/get/web?csrf_token=';
public static $songDataFormat = [ #自己构建的返回参数
'song_id'=>'','title'=>'','img'=>'','song_url'=>'','origin_song_url'=>'','down'=>0, 'singers'=>[], 'album'=>''
];
/**
* 解析网易云分享音乐url, 短链 示例:http://163cn.tv/Nt4J43
*/
public static function getNeteaseSong($htmlContent, $shareUrl, $userId = 0){
$songs = self::$songDataFormat;
$res = $htmlContent ? $htmlContent : file_get_contents($shareUrl);
if (empty($res)) {
return $songs;
}
try{
$dom = new \DOMDocument();
$dom->loadHTML($res);
$script = $dom->getElementsByTagName('script');
$metas = $dom->getElementsByTagName('meta');
foreach ($script as $key => $value) {
$content = $value->textContent;
if (false !== strpos($content, 'window.REDUX_STATE')) { //从script里解析出来
$contentArr = explode('window.REDUX_STATE = ', $content);
$realContent = rtrim(trim($contentArr[1]), ';');
$songData = json_decode($realContent, true);
if ($songData) {
$songs['singers'] = $songData['Song']['ar'] ?? [];
$songs['title'] = $songData['Song']['name'] ?? '';
$songs['song_id'] = $songData['Song']['id'] ?? 0;
$songs['img'] = $songData['Song']['al']['picUrl'] ?? '';
$songs['song_url'] = self::$wOutPlayUrl.'id='.$songs['song_id'].'.mp3';
$songs['parse_type'] = 'script';
$songs['album'] = $songData['Song']['al']['name'] ?? '';
}
}
}
if (empty($songs['title']) && empty($songs['song_id'])) {//script 解析不出来再去meta里解析
foreach ($metas as $key => $value) {
$content = $value->getAttribute('content');
if ($value->getAttribute('property') == 'og:title') {
$songs['title'] = $content;
}
if ($value->getAttribute('property') == 'og:image') {
$songs['img'] = $content;
}
if ($value->getAttribute('property') == 'og:music:artist') {
$songs['singers'] = [['name'=>$content]];
}
if ($value->getAttribute('property') == 'og:url') {
$songs['song_url'] = $content;
$songs['song_id'] = explode('id=', $content)[1] ?? '';
}
if ($value->getAttribute('property') == 'og:music:album') {
$songs['album'] = $content;
}
}
if ($songs['song_id']) {
$songs['parse_type'] = 'meta';
}
}
if ($songs['song_url']) {
$headers = self::getUrlInfo($songs['song_url'], 'netease');
if (!empty($headers)) {
$urlInfo = parse_url($headers['url']);
$songs['origin_song_url'] = $songs['song_url'];
$songs['song_url'] = $headers['url'];
if ($urlInfo['host'] == 'm10.music.126.net') {//这个要特殊处理,有时会403错误而下载不下来数据
$songs['song_url'] = $urlInfo['scheme'].'://m11.music.126.net'.$urlInfo['path'];
}
if ($songs['parse_type'] == 'meta') {
$songs['down'] = 1;
}else{
if ($headers['content_type'] == 'audio/mpeg' || false !== strpos($headers['content_type'], 'audio')) {
$songs['down'] = 1;
}
}
}
}
}catch(\Expection $e){
$songs['error_msg'] = $e->getMessage();
}
return $songs;
}
/**
* 根据分享短连接获取到songmid 示例:https://c6.y.qq.com/base/fcgi-bin/u?__=WLAmdz0wQfXi
*/
public static function getQQMusicSong($htmlContent, $shareUrl, $userId = 0){
$songs = $songs = self::$songDataFormat;
$res = $htmlContent ? $htmlContent : file_get_contents($shareUrl);
if (empty($res)) {
return $songs;
}
try{
$dom = new \DOMDocument();
$dom->loadHTML($res);
$scripts = $dom->getElementsByTagName('script');
foreach ($scripts as $key => $value) {
$content = $value->textContent;
if (false !== strpos($content, 'window.__ssrFirstPageData__')) {
$realArr = explode("window.__ssrFirstPageData__", $content);
$songData = explode(" =", $realArr[1])[1] ?? [];
if ($songData) {
$songData = json_decode($songData, true);
$songs['song_id'] = $songData['songList'][0]['id'] ?? 0;
$songs['mid'] = $songData['songList'][0]['mid'] ?? 0;
$songs['title'] = $songData['songList'][0]['title'] ?? '';
$songs['img'] = $songData['metaData']['image'] ?? '';
$songs['down'] = $songData['songList'][0]['action']['down'] == true ? 1 : 0;
$songs['song_url'] = $songData['songList'][0]['url'] ?? '';
$songs['singers'] = $songData['songList'][0]['singer'] ?? [];
$songs['album'] = $songData['songList'][0]['album']['name'] ?? '';
}
}
}
if ($songs['song_url']) {
$headers = self::getUrlInfo($songs['song_url'], 'qq');
if (!empty($headers)) {
$urlInfo = parse_url($headers['url']);
$songs['origin_song_url'] = $songs['song_url'];
$songs['song_url'] = $headers['url'];
if ($headers['content_type'] == 'audio/mpeg' || false !== strpos($headers['content_type'], 'audio')) {
$songs['down'] = 1;
}
}
}
}catch(\Expection $e){
$songs['error_msg'] = $e->getMessage();
}
return $songs;
}
/**
* 网易云音乐通过歌曲名或者歌手搜索歌曲列表
*/
public static function searchWangyiyunMusic($name){
$curl = curl_init();
$post_data = 'hlpretag=<span class="s-fc7">&hlposttag=</span>&s=' . $name . '&type=1&offset=0&total=true&limit=' . $limit;
curl_setopt($curl, CURLOPT_URL, self::$wSearchUrl);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$header = array(
'Host: music.163.com',
'Origin: http://music.163.com',
'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36',
'Content-Type: application/x-www-form-urlencoded',
'Referer: http://music.163.com/search/',
);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
$src = curl_exec($curl);
curl_close($curl);
return $src;
}
/**
* 下载音乐
*/
public static function download($filePath, $url, $songType){
$headers = self::getCurlHeaders($url, $songType);
$ch = curl_init(); // 启动一个CURL会话
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //从证书中检查SSL加密算法是否存在
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($ch);
//关闭URL请求
curl_close($ch);
file_put_contents($filePath, $result);
}
/**
* 解析解析出来的url信息
*/
public static function getUrlInfo($url, $type, $timeout = 30) {
$urlInfo = [];
$headers = self::getCurlHeaders($url, $type);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_HTTP_VERSION, 2);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //从证书中检查SSL加密算法是否存在
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);//允许请求的链接跳转
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_USERAGENT, 'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36');
$content = curl_exec($ch);
if(!curl_errno($ch)) {
$urlInfo = curl_getinfo($ch);//获取最终请求的url地址
}
curl_close($ch);
return $urlInfo;
}
/**
* 获取公共的请求头部信息
*/
public static function getCurlHeaders($url, $type){
$headers = [];
$headers[] = 'User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36';
$headers[] = 'Accept: application/json, text/plain, */*';
$headers[] = 'Upgrade-Insecure-Requests: 1';
$urlInfo = parse_url($url);
switch ($type) {
case 'netease':
if (false !== strpos($urlInfo['host'], 'music.126.net')) {
$headers[] = 'Host: '.$urlInfo['host'];
$headers[] = 'Origin: '.$urlInfo['scheme'].'://'.$urlInfo['host'];
}else{
$headers[] = 'Host: music.163.com';
$headers[] = 'Origin: http://music.163.com';
}
break;
case 'qq':
$headers[] = 'Host: '.$urlInfo['host'];
$headers[] = 'Origin: '.$urlInfo['scheme'].'://'.$urlInfo['host'];
break;
default:
break;
}
return $headers;
}
/**
* 获取URL的headers信息
*/
public static function getUrlHeaders12($url){
$return = [];
$url = trim($url);
if (empty($url)) {
return $return;
}
$content = stream_context_set_default([
'ssl' => [
'verify_peer' => false,
'verify_peer_name' => false,
],
'http'=>[
'method'=>'HEAD'
]
]);
$headers = get_headers($url,1,$content);
return $headers;
}
/**
* 获取歌词
*/
public function getLyric($songId, $songType){
$lricData = '';
switch ($songType) {
case 'netease':
$postData = ['id'=>$songId,'os'=>'linux','lv' => -1,'kv' => -1,'tv' => -1];
$url = 'http://music.163.com/api/song/lyric';
break;
default:
break;
}
if (!empty($url)) {
$curl = new Curl();
$result = $curl->curl_request($url, $postData, 'urlencoded', 'POST', self::getCurlHeaders($url, $songType));
if (isset($result['code']) && $result['code'] == 200) {
$lricData = $result['lrc']['lyric'] ?? '';
}
}
return $lricData;
}
}