使用Python快速实现抖音无水印视频自动下载微信机器人

前言

最近短视频是越来越火,也出现了很多自媒体公司。刚好我有个朋友就是做短视频这块的业务,因为前期发展需要,避免不了使用很多营销号通过搬运别人视频的方式来吸引一些粉丝。比如很火的抖音短视频,搬运一般需要的是没有水印的视频,所以就产生一个去水印的服务。说是去水印倒不如说是下载无水印的视频,因为本身并没有涉及到任何去水印的技术,仅仅是根据接口返回的数据里面提取出来无水印视频的下载地址然后下载回来就完事儿了。这篇水文就来快速实现一个自动下载抖音无水印视频的微信机器人。

市面上有很多提供下载抖音无水印视频的服务,一般包月也就20块钱左右,实际上原理非常简单,希望朋友们看完这篇文章能自己动手实现下载抖音无水印视频的功能。

声明

本文并不是为了鼓励大家完全去搬运别人的视频,毕竟别人辛苦拍摄出来的视频,如果你喜欢他的视频,你可以下载回来自己观看,未征得视频原作者同意前请不要肆意传播,这是对视频拍摄者最基本的尊重。

分析

抖音短视频App本身除了app客户端以外,还有web网页端,虽然功能比较简陋,都是看视频是足够了。市面上大多数人下载无水印视频基本上都是基于web端,而在观察了很多开源项目之后发现,原理其实非常简单,之所以能够下载无水印视频,是因为抖音web端的机制本身就有问题。

抖音短视频的web端,在看视频的时候都是会带一个抖音的logo,如果直接抓取这个视频地址,下载回来的视频也是带有logo的,不过可以直接替换视频地址里面的参数就可以实现无水印。

既然拿到了视频地址,那么直接下载不就完事了么?No,下载视频必须带上一个签名,否则是拒绝下载的,那么只要拿到这个签名就可以了。

网上很多开源代码用了一个很好的办法,直接执行官方计算签名的js,把结果输出这样就得到了签名字符串,所以就直接拿来用。

办法虽好,但是博主今天写的并不是这种办法,而是一种更加简单的方法,都不用计算签名,几行代码就搞定。

说了这么多,还是说说整个流程吧,首先打开抖音App,随便找一个视频,点右下角那个转发分享按钮

微信图片_20191129121839.png - 大小: 2.81 MB - 尺寸: 1000 x 2167 - 点击打开新窗口浏览全图

在弹出的菜单里面选择 [复制链接]

微信图片_20191129121911.png - 大小: 1.76 MB - 尺寸: 1000 x 2167 - 点击打开新窗口浏览全图
这个时候就拿到了这个短视频的一个链接

#在抖音,记录美好生活#屏幕前的女孩,愿你往后余生,遇见的都是温暖。#这也太好看了吧 都给我用这个特效 https://v.douyin.com/CtCgMT/ 复制此链接,打开【抖音短视频】,直接观看视频!
这个链接是可以直接通过浏览器打开视频的,我们把这个链接放到电脑上通过浏览器打开注意url地址栏,已经重定向到一个新的地址
https://www.iesdouyin.com/share/video/6747483623986908429/?region=CN&mid=6714175953607740173&u_code=145f873fm&titleType=title&utm_source=copy_link&utm_campaign=client_share&utm_medium=android&app=aweme
2019-11-29-11-24-57屏幕截图.jpg - 大小: 796.23 KB - 尺寸: 1570 x 884 - 点击打开新窗口浏览全图
这个新的地址里面包含真实的视频id为 6747483623986908429 还有一个mid猜测是这个视频的媒体id为 6714175953607740173 其他的参数先不用管,使用开发者工具查看页面元素的视频播放地址发现一个url为
https://aweme.snssdk.com/aweme/v1/playwm/?s_vid=93f1b41336a8b7a442dbf1c29c6bbc560f673350224950d4f1ad574051b6150ff882f3e79be07a356f310ec5292afc7323c3edec04e9e40016866270650f2d06&line=0
2019-11-29-11-29-57屏幕截图.jpg - 大小: 1 MB - 尺寸: 1570 x 1061 - 点击打开新窗口浏览全图
这个url地址里面应该就是播放视频的媒体流,我们使用开发者工具隐藏头部那个打开app的按钮,然后点击播放,这个时候我们看到视频上是有logo水印的
2019-11-29-11-32-25屏幕截图.jpg - 大小: 1.01 MB - 尺寸: 1570 x 1061 - 点击打开新窗口浏览全图
这个地址肯定不是我们想要的视频地址,再仔细观察这个地址里面有个playwm,注意,这里是个关键点,其他开发者大多数就是从这里入手的,只需要把playwm改成play就得到了无水印的视频地址,我们改完之后再点击播放,是不是视频就没有水印了
2019-11-29-11-35-41屏幕截图.jpg - 大小: 1.01 MB - 尺寸: 1570 x 1061 - 点击打开新窗口浏览全图
这看起来也太简单了吧,完全没有技术含量啊,难道博主我这么快就水完一篇文章了?不不不,博主我不是那样的人!!

看似非常简单,只需要得到前面的s_vid参数就可以了,查看代码后发现,这个s_vid是一个类似签名的东西,由web页面的js生成,至于生成的方法就不多说了,网上很多人都已经给出了方法。

这不是本文的重点,本文并不是通过这个方法下载无水印的视频,而是使用更简单的方法。

捷径

打开开发者工具中的Network,找到item_ids的那个接口

2019-11-29-11-26-16屏幕截图.jpg - 大小: 788.12 KB - 尺寸: 1570 x 1061 - 点击打开新窗口浏览全图
观察返回的json数据,整理一下如下所示(在此感谢 https://tool.lu/json )

{
  "status_code": 0,
  "item_list": [
    {
      "video_labels": null,
      "comment_list": null,
      "label_top_text": null,
      "desc": "屏幕前的女孩,愿你往后余生,遇见的都是温暖。#这也太好看了吧 都给我用这个特效",
      "cha_list": null,
      "video": {
        "cover": {
          "uri": "tos-cn-p-0015/f33863b72f03401fb01d68786c5d382e",
          "url_list": [
            "https://p1-dy.byteimg.com/img/tos-cn-p-0015/f33863b72f03401fb01d68786c5d382e~c5_300x400.jpeg?from=2563711402_large",
            "https://p3-dy.byteimg.com/img/tos-cn-p-0015/f33863b72f03401fb01d68786c5d382e~c5_300x400.jpeg?from=2563711402_large",
            "https://p9-dy.byteimg.com/img/tos-cn-p-0015/f33863b72f03401fb01d68786c5d382e~c5_300x400.jpeg?from=2563711402_large"
          ]
        },
        "ratio": "540p",
        "play_addr_lowbr": {
          "uri": "v0200ffa0000bmhu16581shqv3ocmidg",
          "url_list": [
            "https://aweme.snssdk.com/aweme/v1/play/?video_id=v0200ffa0000bmhu16581shqv3ocmidg&line=0&ratio=540p&media_type=4&vr_type=0&improve_bitrate=0&is_play_url=1",
            "https://api.amemv.com/aweme/v1/play/?video_id=v0200ffa0000bmhu16581shqv3ocmidg&line=1&ratio=540p&media_type=4&vr_type=0&improve_bitrate=0&is_play_url=1"
          ]
        },
        "duration": 12712,
        "play_addr": {
          "uri": "v0200ffa0000bmhu16581shqv3ocmidg",
          "url_list": [
            "https://aweme.snssdk.com/aweme/v1/play/?video_id=v0200ffa0000bmhu16581shqv3ocmidg&line=0&ratio=540p&media_type=4&vr_type=0&improve_bitrate=0&is_play_url=1",
            "https://api.amemv.com/aweme/v1/play/?video_id=v0200ffa0000bmhu16581shqv3ocmidg&line=1&ratio=540p&media_type=4&vr_type=0&improve_bitrate=0&is_play_url=1"
          ]
        },
        "height": 1280,
        "width": 720,
        "dynamic_cover": {
          "uri": "tos-cn-p-0015/6c79aeb39ac14431b72381c9e3ea129b",
          "url_list": [
            "https://p1-dy.byteimg.com/obj/tos-cn-p-0015/6c79aeb39ac14431b72381c9e3ea129b?from=2563711402_large",
            "https://p3-dy.byteimg.com/obj/tos-cn-p-0015/6c79aeb39ac14431b72381c9e3ea129b?from=2563711402_large",
            "https://p9-dy.byteimg.com/obj/tos-cn-p-0015/6c79aeb39ac14431b72381c9e3ea129b?from=2563711402_large"
          ]
        },
        "origin_cover": {
          "uri": "large/tos-cn-p-0015/72a8ad8cd52f48ed819c5d273df96874",
          "url_list": [
            "http://p3-dy.byteimg.com/large/tos-cn-p-0015/72a8ad8cd52f48ed819c5d273df96874.jpeg?from=2563711402_large",
            "http://p1-dy.byteimg.com/large/tos-cn-p-0015/72a8ad8cd52f48ed819c5d273df96874.jpeg?from=2563711402_large",
            "http://p9-dy.byteimg.com/large/tos-cn-p-0015/72a8ad8cd52f48ed819c5d273df96874.jpeg?from=2563711402_large"
          ]
        },
        "download_addr": {
          "uri": "v0200ffa0000bmhu16581shqv3ocmidg",
          "url_list": [
            "https://aweme.snssdk.com/aweme/v1/play/?video_id=v0200ffa0000bmhu16581shqv3ocmidg&line=0&ratio=540p&watermark=1&media_type=4&vr_type=0&improve_bitrate=0&logo_name=aweme",
            "https://api.amemv.com/aweme/v1/play/?video_id=v0200ffa0000bmhu16581shqv3ocmidg&line=1&ratio=540p&watermark=1&media_type=4&vr_type=0&improve_bitrate=0&logo_name=aweme"
          ]
        },
        "has_watermark": true,
        "bit_rate": null,
        "vid": "v0200ffa0000bmhu16581shqv3ocmidg"
      },
      "duration": 12712,
      "image_infos": null,
      "video_text": null,
      "promotions": null,
      "long_video": null,
      "aweme_id": "6747483623986908429",
      "statistics": {
        "comment_count": 27,
        "digg_count": 70,
        "aweme_id": "6747483623986908429"
      },
      "text_extra": null,
      "position": null,
      "uniqid_position": null,
      "geofencing": null
    }
  ],
  "extra": {
    "now": 1574998475000,
    "logid": "2019112911343501001404602000C59A64"
  }
}

观察数据发现两个关键参数 play_addr和donwload_addr这两个地址看起来甚是眼熟,好像跟视频播放地址有那么一丝丝相似,先不管了,替换一下看看

2019-11-29-11-48-00屏幕截图.jpg - 大小: 974.82 KB - 尺寸: 1570 x 1061 - 点击打开新窗口浏览全图
通过实验发现返回的地址里面play_addr的确可以播放,而且没有水印,而download_addr是有水印的。

至此我们已经可以直接从接口拿到无水印视频地址了。

那么接下来就是看请求这个接口需要哪些参数了,接口完整地址如下

https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=6747483623986908429&dytk=da461b821c2dcb9b0393eab92afc11e561006d3758ec869bb9b121de4f665ca3
很好,请求这个接口地址只需要一个item_ids和一个dytk,item_ids也就是url地址栏里面的视频id,dytk也很容易拿到,在网页上点击右键,查看网页源代码,在最底下就有
2019-11-29-11-57-15屏幕截图.jpg - 大小: 922.68 KB - 尺寸: 1083 x 1080 - 点击打开新窗口浏览全图
好了,基本工作已经完成,代码写起来。

代码

根据以上需求分析,只需要使用requests模拟请求就可以完成,步骤如下:

1、打开分享的链接 https://v.douyin.com/xxxx/

2、拿到重定向的url地址,并进行判断(这里判断的目的是因为分享视频作者个人页面的url格式也是这样,但是重定向之后的url不一样,分享单个视频重定向之后有/share/video/,而作者个人页面的是/share/user/)

3、从返回的页面html代码中提取dytk,然后从url中提取视频id,接着模拟请求接口

4、拿到无水印地址后模拟请求下载无水印视频

5、结合itchat库和阿里云oss实现下载完自动上传云端oss保存,这样直接通过远程url地址就可以下载查看。

from ipaddress import ip_address
from contextlib import closing
import requests
import itchat
import random
import oss2
import time
import sys
import re


# 生成x位随机字符串
def create_random_string(x):
    chars = "abcdefghijklmnABCDEFGHIJKLMNXYZopqrstuvwxyz"
    random_string = random.sample(chars, x)
    return "".join(random_string)


def create_headers():
    rip = ip_address('0.0.0.0')
    while rip.is_private:
        rip = ip_address('.'.join(map(str, (random.randint(0, 255) for _ in range(4)))))
    headers = {
        'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
        'accept-encoding': 'gzip, deflate, br',
        'accept-language': 'zh-CN,zh;q=0.9',
        'pragma': 'no-cache',
        'cache-control': 'no-cache',
        'upgrade-insecure-requests': '1',
        'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1',
        'X-Real-IP': str(rip),
        'X-Forwarded-For': str(rip),
    }
    return headers


def do_download(video_url, v_info):
    size = 0
    headers = create_headers()
    with closing(requests.get(video_url, headers=headers, stream=True)) as response:
        chunk_size = 1024
        content_size = int(response.headers['content-length'])
        if response.status_code == 200:
            local_file = "%s/Download/%s.mp4" % (sys.path[0], v_info.get("video_author") + '-[' + v_info.get("video_name") +']')
            with open(local_file, 'wb') as file:
                for data in response.iter_content(chunk_size=chunk_size):
                    file.write(data)
                    size += len(data)
                    file.flush()

            reset_name = str(time.strftime('%Y%m%d%H%M%S')) + create_random_string(5) + ".mp4"
            remote_path = "auto-upload" + time.strftime('/%Y-%m-%d/') + reset_name
            try:
                auth = oss2.Auth("ossid", "osskey")
                bucket = oss2.Bucket(auth, 'https://xxxx.aliyuncs.com', 'xxxx')
                bucket.put_object_from_file(remote_path, local_file)
                oss_path_url = "https://xxxx.oss-cn-shenzhen.aliyuncs.com/" + remote_path
                result = "成功"
            except Exception as e:
                result = "视频上传云端失败,请微信联系开发者"
                oss_path_url = "无"
        else:
            result = "下载视频失败,请微信联系开发者"
            oss_path_url = "无"
    return '[作者]:%s\n[描述]:%s\n[大小]:%0.2f MB\n[状态]:%s\n[下载地址]:%s' % (
            v_info.get("video_author"),
            v_info.get("video_name"),
            content_size / chunk_size / 1024,
            result,
            oss_path_url
    )


@itchat.msg_register(itchat.content.TEXT)
def text_reply(msg):
    return deal_wx_msg(msg)


@itchat.msg_register(itchat.content.TEXT, isGroupChat=True)
def text_reply(msg):
    return deal_wx_msg(msg)


def deal_wx_msg(msg):
    if "v.douyin.com" in msg.text:
        complete_url = "https://v.douyin.com/%s/" % (msg.text.split("/")[3])
        html_first_response = requests.get(url=complete_url, headers=create_headers())
        redirect_url = html_first_response.url
        html_content = html_first_response.content.decode("utf8")
        if "share/video" in redirect_url:
            dytk = re.findall(r'dytk: "(.+?)"', html_content)[0]
            video_id = re.findall(r"video\/(.+?)\/\?region", redirect_url)[0]
            video_info_url = "https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=%s&dytk=%s" % (video_id, dytk)
            video_json = requests.get(url=video_info_url).json()
            if len(video_json.get("item_list")) > 0:
                video_clear_url = video_json.get("item_list")[0].get("video").get("play_addr").get("url_list")[0]
                video_name = video_json.get("item_list")[0].get("desc")
                video_author = re.findall(r'<p class="user-info-name">(.+?)<', html_content)[0]
                video_info = {"video_name": video_name, "video_author": video_author}
                return do_download(video_clear_url, video_info)
            else:
                return "未检测到视频信息"
        else:
            return ""


itchat.auto_login(hotReload=True, enableCmdQR=2)
itchat.run()
代码中涉及到oss这块的信息已去除,请自行替换成自己的信息。代码实现的效果功能如图所示
微信图片_20191129122015.png - 大小: 624.57 KB - 尺寸: 1000 x 1186 - 点击打开新窗口浏览全图
微信个人消息和群消息都可以,当然还可以实现@某个人的功能,itchat非常强大。

总结

不知道这种方法能用多久,这应该是最简单的方法了吧。

本文链接:https://www.92ez.com/?action=show&id=23506
!!! 转载请先联系non3gov@gmail.com授权并在显著位置注明作者和原文链接 !!! 小黑屋
提示:技术文章有一定的时效性,请先确认是否适用你当前的系统环境。

上一篇: 如何通过家里的路由器代理上Youtube(动态VPS代理同理)

访客评论
目前还没有人评论,您发表点看法?
发表评论

评论内容 (必填):