歌词数据抓取与解析

在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&notice=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 NPMGIT

解码后是一个特别长的字符串:

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,当歌曲播放的时候,让歌词也跟着播放,然后在回调函数中 改变我们的 变量。 在歌词列表中通过这个变量的改变高亮对应的行

© All Rights Reserved            updated 2017-12-28 02:49:41

results matching ""

    No results matching ""