koa+vue3+echarts分析B站综合热门前100的数据

581 阅读2分钟
image.png

最近又在重温nodejs,光看没用,还是得写点东西加深印象,然后又想爬点什么,这就盯上了B站热门,爬了几天数据,分析出了怎么才会上榜、什么类型的视频上榜的较多。

在线预览, 当天没数据的话,底部左边选一下时间
源码地址

接口分析

打开B站热榜,往下滑动,会发现快到底部时就会请求一些接口

动画.gif api.bilibili.com/x/web-inter…
api.bilibili.com/x/web-inter…
api.bilibili.com/x/web-inter…

image.png 可以发现这个接口的数据,就是对应热榜的数据,参数ps就是pagesize,pn就是pagenum,每次20条数据,这里我只取到pn=5也就是100条数据进行了分析。

字段分析

从页面观察对应返回的数据,我只取了下面这些字段的数据

[
  {key: 'aid', label: 'aid', isShow: false},
  {key: 'bvid', label: 'bvid', isShow: false},
  {key: 'cid', label: 'cid', isShow: false}, // 三个id到时候嵌入B站视频需要
  {key: 'title', label: '标题', isShow: true, cType: 'href', width: '180px'},
  {key: 'title_url', label: '视频地址', isShow: false},
  {key: 'owner_name', label: '作者', isShow: true, cType: 'href', width: '120px'},
  {key: 'owner_url', label: '作者连接', isShow: false},
  {key: 'vdesc', label: '视频描述', isShow: false},
  {key: 'tname', label: '视频分类', isShow: true},
  {key: 'pic', label: '视频图片', isShow: false, cType: 'img'},
  {key: 'ctime', label: '创建时间', isShow: false},
  {key: 'pubdate', label: '发布时间', isShow: true},
  {key: 'rcmd_reason', label: '推荐原因', isShow: true},
  {key: 'videos', label: '视频数', isShow: false},
  {key: 'copyright', label: '版权所有', isShow: false},
  {key: 'view', label: '播放数', isShow: true},
  {key: 'like_count', label: '点赞数', isShow: true},
  {key: 'favorite', label: '收藏数', isShow: true},
  {key: 'coin', label: '投币数', isShow: false},
  {key: 'share', label: '分享数', isShow: false},
  {key: 'data_date', label: '数据时间', isShow: true},
]

数据读写

请求接口用的是axios,对数据进行整理以后保存到json文件方便后续的读取。

const dates = dayjs(new Date).format('YYYYMMDD')
const fileName = `popular/bili_popular_${dates}.json`
const requests = Array.from({ length: 5 }).map((_, idx) => 
   axios.get(`https://api.bilibili.com/x/web-interface/popular?ps=20&pn=${idx + 1}`)
)
const results = await Promise.all(requests)
results.map(rst => {
   dList = dList.concat(rst.data.data.list.map(list => formatPopularData(list)))
})

fs.writeFile(fileName, JSON.stringify(dList), 'utf-8', (err) => {
   if (err) console.log(err)
})

function formatPopularData(data){
  return {
    aid: data.aid,
    bvid: data.bvid,
    cid: data.cid,
    title: data.title,
    title_url: data.short_link_v2,
    owner_name: data.owner.name,
    owner_url: `https://space.bilibili.com/${data.owner.mid}`,
    vdesc: data.desc,
    tname: data.tname,
    pic: data.pic,
    ctime: dayjs(data.ctime*1000).format('YYYY-MM-DD'),
    pubdate: dayjs(data.pubdate*1000).format('YYYY-MM-DD'),
    rcmd_reason: data.rcmd_reason.content,
    videos: data.videos,
    copyright: data.copyright,
    view: data.stat.view,
    like_count: data.stat.like,
    favorite: data.stat.favorite,
    coin: data.stat.coin,
    share: data.stat.share,
    data_date: dayjs(new Date()).format('YYYY-MM-DD'),
  }
}

读取数据

function getFileData(dates, ctx) {
  const fileName = `popular/bili_popular_${dates}.json`
  const exist = fs.existsSync(fileName);
  let fileData = []
  if (exist) {
    const results = fs.readFileSync(fileName, 'utf8');
    console.log(`读取${fileName}文件成功`)
    fileData = JSON.parse(results)
  }
  return fileData
}

可视化展示

用的是Vue3 + Echarts5,展示了热榜分类的词云、热榜分类的前20的条形图、推荐原因的饼图。

vue3按需引入Echarts

安装echarts

npm install echarts --save

可单独创建一个useEcharts.js文件

// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from 'echarts/core';
// 引入柱状图图表,图表后缀都为 Chart
import { PieChart, BarChart } from 'echarts/charts';
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import {
  TitleComponent,
  TooltipComponent,
  GridComponent,
  DatasetComponent,
  TransformComponent,
  ToolboxComponent,
  LegendComponent,
  GraphicComponent
} from 'echarts/components';
// 标签自动布局、全局过渡动画等特性
import { LabelLayout, UniversalTransition } from 'echarts/features';
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { CanvasRenderer } from 'echarts/renderers';
// 注册必须的组件
echarts.use([
  TitleComponent,
  TooltipComponent,
  GridComponent,
  DatasetComponent,
  TransformComponent,
  PieChart,
  BarChart,
  LabelLayout,
  UniversalTransition,
  CanvasRenderer,
  ToolboxComponent,
  LegendComponent,
  GraphicComponent
]);

export default echarts

使用

import echarts from '@/utils/useEcharts.js'

let wordChartDiv = echarts.init(document.getElementById('wordChartDiv'))
wordChartDiv.setOption({...})

视频分类词云

这里的分类数据取得是当前数据所有的分类,下面的条形图取得是前20的分类

image.png

Echarts5已经没有自带的词云了,需要额外引入词云

npm install echarts-wordcloud
import 'echarts-wordcloud';
{
    title: {
      text: '热榜分类词云',
      top: 6,
      left: 6
    },
    tooltip: {
      trigger: "item",
      backgroundColor: 'rgba(255,255,255,0.98)',
      borderColor: 'rgba(0,0,0,0)',
    },
    graphic: getGraphic(seriesData),
    series: [
      {
        type: "wordCloud", // 词云类型
        gridSize: 5, // 文字间隙
        sizeRange: [12, 40], // 文字大小范围
        rotationRange: [-90, 90], // 文字旋转角度范围
        layoutAnimation: true, // 生成动画
        shape: "circle", // 词云展示的形状
        left: '0',
        top: '0',
        width: '100%',
        height: '100%',
        textStyle: {
          fontFamily: 'sans-serif',
          fontWeight: 'bold',
          // 生成随机颜色
          color: function () {
            return 'rgb(' + [
              Math.round(Math.random() * 160),
              Math.round(Math.random() * 160),
              Math.round(Math.random() * 160)
            ].join(',') + ')';
          }
        },
        // hover时文字的样式
        emphasis: {
          shadowBlur: 10,
          shadowColor: "#333",
        },
        data: seriesData,
      },
    ],
  }

视频分类条形图

之前总是在网上看到那种数据增长,排名交替的条形图的动画,想着自己也搞一个,就取了热榜分类前20的数据进行动画展示 动画.gif

没了解之前还以为这种东西会很复杂,做了之后发现,就是配置几个动画的属性,然后循环更改数据

option={
    ...
    yAxis: {
      animationDuration: 300,
      animationDurationUpdate: 300,
    },
    animationDuration: 0,
    animationDurationUpdate: 3000,
    animationEasing: 'linear',
    animationEasingUpdate: 'linear',
    ...
}
async function animationBar(datas, barValueTotal) {
  let curIdx = 1, chartData=null
  let interval = setInterval(function () {
    run()
  }, 3000);

  function run(){
    if (datas.length > 1) {
      chartData = datas[curIdx++]
      if (!chartData) {
        clearInterval(interval)
        interval = null
        return
      }
      barChart.setOption(getPopularBarOptions(chartData.barYAxisDatas, chartData.barSeriesData, barValueTotal))
    }
  }
  run()
}

推荐原因饼图

这里取的是所有数据的推荐原因,基本就是百万播放

image.png

因为取得是所有的数据推荐原因,所以图表的legend会很多,就需要采用翻页的形式展示,也就是配置一个属性type: scroll

legend: {
  type: 'scroll',
  orient: 'vertical', // 水平还是垂直
},

中间的文字展示配置

series:{
    ...
    label: {
      show: false, // 不hover图表时不展示
      formatter: '{b}: {c}({d}%)',
      position: 'center'
    },
    // hover图表时展示的样式
    emphasis: {
      label: {
        show: true,
        fontSize: 18,
        fontWeight: 'bold'
      }
    },
    ...
}

嵌入B站视频

image.png 表格使用的是Element Plus的table,嵌入视频就只需要一个iframe即可,src就需要数据中的aid、bid、cid

//player.bilibili.com/player.html?aid=${row.aid}&bvid=${row.bid}&cid=${row.cid}&page=1&high_quality=1
<iframe
  :src="`//player.bilibili.com/player.html?aid=${row.aid}&bvid=${row.bid}&cid=${row.cid}&page=1&high_quality=1`"
  width="60%" height="360" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"
  sandbox="allow-top-navigation allow-same-origin allow-forms allow-scripts"> </iframe>

数据分析

现在的数据量不是很多,结果也不是那么准确,基本排名靠前的视频分类大概就是搞笑手游单机游戏美食日常等,能有过万点赞、百万播放的基本就能上热门。等数据量再大点的时候再看看什么分类多。