歌词数据抓取与解析
在pc端: https://y.qq.com/n/yqq/song/001Qu4I30eVFYb.html 页面找到获取歌词的请求
https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric.fcg?nobase64=1&musicid=102636799&callback=jsonp1&g_tk=5381&jsonpCallback=jsonp1&loginUin=0&hostUin=0&format=jsonp&inCharset=utf8&outCharset=utf-8¬ice=0&platform=yqq&needNewCode=0
获取到了链接,照旧,这个链接是不能直接获取到的,需要更改里面的请求头。使用到开发时候的代理
build/dev-server.js
apiRoutes.get('/lyric', function (req, res) {
// 两个地址都可以获取到歌词
// var url = 'https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric.fcg'
var url = 'https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg'
axios.get(url, {
headers: {
referer: 'https://c.y.qq.com/',
host: 'c.y.qq.com'
},
params: req.query
}).then((response) => {
var ret = response.data
if (typeof ret === 'string') {
var reg = /^\w+\(({[^()]+})\)$/
var matches = ret.match(reg)
if (matches) {
ret = JSON.parse(matches[1])
}
}
res.json(ret)
}).catch((e) => {
console.log(e)
})
})
上面获取到的data 是这样的结构:
MusicJsonCallback({"retcode":0,"code":0,"subcode":0,"lyric":"W3RpOua8lOWRmFxxxxxxxxxx","trans":""})
获取到的是一个字符串,使用正则把外面的function去掉
var reg = /^\w+\(({[^()]+})\)$/
var matches = ret.match(reg)
if (matches) {
ret = JSON.parse(matches[1])
}
编写请求类 src/api/song.js
import {commonParams} from './config'
import axios from 'axios'
// 需要使用歌曲id获取到歌曲的歌词
export function getLyric(mid) {
const url = '/api/lyric'
const data = Object.assign({}, commonParams, {
songmid: mid,
platform: 'yqq',
hostUin: 0,
needNewCode: 0,
categoryId: 10000000,
pcachetime: +new Date(),
format: 'json'
})
return axios.get(url, {
params: data
}).then((res) => {
return Promise.resolve(res.data)
})
}
在播放列表中随意测试
getLyric(this.currentSong.mid).then(res => {
console.log(res)
})
这个时候我们发现获取到的歌词 居然是一个base64字符串
1. base64 歌词解码
这里使用到一个第三方库 js-base64
NPM 、GIT
解码后是一个特别长的字符串:
Base64.decode(res.lyric); // 解码
[ti:演员]
[ar:薛之谦]
[al:绅士]
[by:]
[offset:0]
[00:00.56]演员 - 薛之谦
[00:02.42]词:薛之谦
[00:03.99]曲:薛之谦
[00:05.38]编曲:郑伟/张宝宇
[00:07.45]制作人:赵英俊
[00:09.09]合声:赵英俊
[00:10.48]录音师:王晓海
[00:11.74]
[00:12.34]混音师:鲍锐
[00:13.88]母带处理工程师:鲍锐
[00:16.10]
[00:21.25]简单点
[00:22.21]
...
解析之后,我们可以面向对象,把这个获取歌词的操作放到 song 类里面
2. 初步封装歌词到song类
src/common/js/song.js
import { getLyric } from '@/api/song.js'
import { Base64 } from 'js-base64'
import { ERR_OK } from 'api/config'
export default class Song {
/**
*
* @param id
* @param mid
* @param singer 歌手
* @param name
* @param album 专辑名称
* @param duration 歌曲长度
* @param image 图片
* @param url 歌曲的真实请求路径
*/
constructor ({id, mid, singer, name, album, duration, image, url}) {
this.id = id
this.mid = mid
this.singer = singer
this.name = name
this.album = album
this.duration = duration
this.image = image
this.url = url
}
getLyric () {
getLyric(this.mid).then(res => {
if (res.retcode === ERR_OK) {
this.lyric = Base64.decode(res.lyric)
console.log(this.lyric)
}
})
}
}
测试,在歌曲变化的时候,调用 getLyric方法 src/components/player/player.vue
this.$nextTick(() => {
this.$refs.audio.play()
this.currentSong.getLyric()
})
测试成功后,进行优化
getLyric () {
// 因为歌曲在变化时候会调用获取歌词的操作,这里优化一下,不用每次都去请求
if (this.lyric) {
return Promise.resolve(this.lyric)
}
// 且使用 Promise 来封装异步操作
return new Promise((resolve, reject) => {
getLyric(this.mid).then(res => {
if (res.retcode === ERR_OK) {
this.lyric = Base64.decode(res.lyric)
resolve(this.lyric)
} else {
reject('no lyric')
}
})
})
}
优化后的测试
this.currentSong.getLyric().then(lyric => {
console.log(lyric)
})
3. lyric-parser 歌词处理库
https://github.com/ustbhuangyi/lyric-parser
使用这个库来进行歌词的解析.使用方式这里摘抄一点上面官网链接中的
import Lyric from 'lyric-parser'
let lyric = new Lyric(lyricStr, handler)
function hanlder({lineNum, txt}){
// this hanlder called when lineNum change
}
歌词的基本界面是这样的。
只展示该部分的html代码和css,js,dom结构是在唱片cd的右侧,到时候他们两个可以相互滑动切换。 基本的歌词列表如下
<div class="middle-r">
<div class="lyric-wrapper">
<div v-if="currentLyric">
<p
class="text"
v-for="(line,index) in currentLyric.lines">
{{line.txt}}</p>
</div>
</div>
</div>
.middle-r {
display inline-block
vertical-align top
width 100%
height 100%
overflow hidden
.lyric-wrapper {
width 80%
margin 0 auto
overflow hidden
text-align center
.text {
line-height 32px
color $color-text-l
font-size $font-size-medium
&.current {
color $color-text
}
}
}
}
currentLyric 是通过lyric-parser
构造出的一个实例
this.currentLyric = new Lyric(lyric, this.handleLyric)
处理函数如下
handleLyric ({lineNum, txt}) {
console.log(lineNum, '=========', txt)
}
4. 歌词列表高亮当前时间的歌词
歌词应该随着歌曲的播放而高亮对应的歌词,这个在lyric-parser
中已经帮我们处理好了。
<div class="middle-r">
<div class="lyric-wrapper">
<div v-if="currentLyric">
<p ref="lyricLine"
class="text"
:class="{'current' : currentLineNum == index}"
v-for="(line,index) in currentLyric.lines">
{{line.txt}}</p>
</div>
</div>
</div>
getLyric () {
this.currentSong.getLyric().then(lyric => {
this.currentLyric = new Lyric(lyric, this.handleLyric)
if (this.playing) {
this.currentLyric.play()
}
})
},
handleLyric ({lineNum, txt}) {
console.log(lineNum, '=========', txt)
this.currentLineNum = lineNum
}
我们在data中定义一个变量currentLineNum
,当歌曲播放的时候,让歌词也跟着播放,然后在回调函数中 改变我们的 变量。 在歌词列表中通过这个变量的改变高亮对应的行