前言

浏览知乎的时候发现知乎里的视频没有提供保存到本地的功能。搜索了一波解决方法后,发现有个答主做的小程序应该是最佳解决方案,不过答主并没有给出实现方式。正好最近在学小程序开发,所以就研究了一下,做了一个叫"积木小盒"的微信小程序,将知乎视频下载功能实现了。

使用

仅需两步:
1.
在知乎回答页点击右上角,出现弹出框,再点击复制链接

2.
打开"积木小盒"微信小程序,点击知乎视频。粘贴链接后,点击视频下载,即可爬取知乎回答页的所有视频并自动保存到本地相册。

功能分析

使用小程序十分方便,一些其他解决方案比如在pc端打开网页查找元素,找到相应视频链接再下载,操作上其实比不上小程序。一方面对于非开发的小白用户来说,使用控制台查看元素其实还是有一点成本的。另一方面,这种方式需要借助pc端,无法全部在手机上操作完成,大部分用户的使用习惯还是会更倾向于在手机上便捷操作。

原理

知乎视频下载的实现原理其实和在网页中查看元素,找到视频链接下载视频的操作是一样的。它的实现分为两步:1.利用cheerio爬取视频链接 2.使用小程序下载和保存视频。很显然,第一步才是实现的关键。

1. 利用cheerio爬取视频链接

由于微信小程序访问的api地址受白名单限制,所以爬取操作都放在node.js服务端进行。我们只需要实现一个接口,传入复制的知乎链接,返回爬取的该网页链接下所有视频的下载地址。这样在爬取阶段我们只需要将服务端域名 https://www.readingblog.cn 添加到访问白名单中,而不是将各式各样的知乎域名添加到小程序白名单中。

接下来让我们看看服务端是怎么实现的:

const axios = require('axios');
const cheerio = require('cheerio');

const videoUrlPrefix = 'https://lens.zhihu.com/api/v4/videos/';

exports.urlFetchAll = async (pageUrl) => {
    const pageRes = await axios.get(pageUrl);
    const videoSrcList = [];
    if (pageRes.status === 200) {
        $ = cheerio.load(pageRes.data);
        const videoBox = $('.video-box');
        for (let i = 0, len = videoBox.length; i < len; i ++) {
            const videoPageUrl = $(videoBox[i]).attr('href');
            const params = videoPageUrl.split('/');
            const videoId = params[params.length - 1];
            const videoRes = await axios.get(`${videoUrlPrefix}${videoId}`);
            if (videoRes.status === 200) {
                const videoSrc = videoRes.data.playlist.LD.play_url;
                videoSrcList.push(videoSrc);
            }
        }
    }
    return videoSrcList;
};

还是比较容易看出它的实现思路的,这里使用了页面爬取和接口爬取相结合的方式。由于知乎网页使用了js动态渲染,爬取页面没有能够直接爬取到视频元素的链接。所以在页面爬取阶段,我们其实只获取了它的videoId。经过对整个页面元素的过滤、筛选,我们发现带video-box类名的href属性会直接带上videoId,我们直接爬取它。再通过抓包分析,我们发现只要向 https://lens.zhihu.com/api/v4... 接口传入videoId就可以返回视频的下载地址,所以通过知乎链接爬取视频下载地址的接口就实现了。

2. 使用小程序将视频保存到本地

实现了爬取接口整个功能主体就完成了,剩下的我们在微信小程序调用这个接口,传入用户输入的知乎链接地址,获取视频下载地址。再调用wx的api,downloadFile和saveVideoToPhotosAlbum分别下载和保存视频。

Page({
  data: {
    zhihuPageUrl: '',
  },
  handleUrlChange(e) {
    this.setData({
      zhihuPageUrl: e.detail.value
    })
  },
  downloadVideo() {
    const pageUrl = this.data.zhihuPageUrl;
    if (!this.data.zhihuPageUrl) {
      wx.showToast({
        title:'链接为空!',
        icon:'none',
        duration:2000
      });
      return;
    }
    wx.request({
      url: vedioFetchUrl,
      data: {
        pageUrl,
      },
      header: {
        'content-type': 'application/json'
      },
      success: (res) => {
        res = res.data;
        if (res.code === 1) {
          const total = res.data.length;
          wx.showToast({
            title:`开始下载!`,
            icon:'success',
            duration:2000
          });
          res.data.forEach((url, index) => {
            wx.downloadFile({
              url,
              success: (res) => {
                const filePath = res.tempFilePath;
                wx.saveVideoToPhotosAlbum({
                  filePath: filePath,
                  success() {
                    wx.showToast({
                      title:`${index + 1}/${total}下载成功!`,
                      icon:'success',
                      duration:2000
                    });
                  }
                })
              }
            })
          })
        } else {
          wx.showToast({
            title:'请求失败!',
            icon:'none',
            duration:2000
          });
        }
      }
    });
  },
})

"积木小盒"微信小程序


小番茄
67 声望5 粉丝