抖音上下载的视频通常都会加上作者账号水印,或者有些视频直接不开放下载。如果需要下载无水印的视频该怎么如何操作呢?
其实思路很简单,我们只需要找到原始视频地址就可以了。
通常我们在我们抖音视频中选择复制链接,会得到一个类似下图所示的链接。
访问这个地址会发现被302重定向到一个类似这样的url
https://www.iesdouyin.com/share/video/6901990942908124431/?region=CN&mid=6798390707091573516&u_code=i3g58h16&titleType=title&did=MS4wLjABAAAAuaorE6aYRkmwcoa3QreMSLRIA9sWQDpEQPRByLXSoyA&iid=MS4wLjABAAAADuPZL4CgoFrcLpcrtq1sf89qhSbZG7CCSGyKGbePPPWTIdVx6dS252KnzTNkQP_Q&with_sec_did=1&utm_source=copy_link&utm_campaign=client_share&utm_medium=android&app=aweme&scheme_type=1
打开chrome控制台,发现页面发起了这样一个请求
https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=6901990942908124431
这个请求返回了一个json串,里面包含了我们需要的原始视频地址,音频地址等等各种信息
// 20210905191510
// https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids=6901990942908124431
{
"item_list": [
{
"share_url": "https://www.iesdouyin.com/share/video/6901990942908124431/?region=&mid=6798390707091573516&u_code=48&titleType=title&did=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&iid=MS4wLjABAAAANwkJuWIRFOzg5uCpDRpMj4OX-QryoDgn-yYlXQnRwQQ&with_sec_did=1",
"text_extra": [
{
"end": 15,
"type": 1,
"hashtag_name": "旅行大玩家",
"hashtag_id": 1608054515739651,
"start": 9
}
],
"image_infos": null,
"is_preview": 0,
"group_id": 6901990942908124000,
"is_live_replay": false,
"desc": "我要路过你了。 #旅行大玩家",
"author": {
"avatar_larger": {
"uri": "1080x1080/tos-cn-avt-0015/01e9fdc06ca5b20e0083ea392f6fe786",
"url_list": [
"https://p6.douyinpic.com/img/tos-cn-avt-0015/01e9fdc06ca5b20e0083ea392f6fe786~c5_1080x1080.jpeg?from=116350172",
"https://p11.douyinpic.com/img/tos-cn-avt-0015/01e9fdc06ca5b20e0083ea392f6fe786~c5_1080x1080.jpeg?from=116350172",
"https://p9.douyinpic.com/img/tos-cn-avt-0015/01e9fdc06ca5b20e0083ea392f6fe786~c5_1080x1080.jpeg?from=116350172"
]
},
"avatar_thumb": {
"uri": "100x100/tos-cn-avt-0015/01e9fdc06ca5b20e0083ea392f6fe786",
"url_list": [
"https://p11.douyinpic.com/img/tos-cn-avt-0015/01e9fdc06ca5b20e0083ea392f6fe786~c5_100x100.jpeg?from=116350172",
"https://p29.douyinpic.com/img/tos-cn-avt-0015/01e9fdc06ca5b20e0083ea392f6fe786~c5_100x100.jpeg?from=116350172",
"https://p3.douyinpic.com/img/tos-cn-avt-0015/01e9fdc06ca5b20e0083ea392f6fe786~c5_100x100.jpeg?from=116350172"
]
},
"avatar_medium": {
"uri": "720x720/tos-cn-avt-0015/01e9fdc06ca5b20e0083ea392f6fe786",
"url_list": [
"https://p26.douyinpic.com/img/tos-cn-avt-0015/01e9fdc06ca5b20e0083ea392f6fe786~c5_720x720.jpeg?from=116350172",
"https://p9.douyinpic.com/img/tos-cn-avt-0015/01e9fdc06ca5b20e0083ea392f6fe786~c5_720x720.jpeg?from=116350172",
"https://p11.douyinpic.com/img/tos-cn-avt-0015/01e9fdc06ca5b20e0083ea392f6fe786~c5_720x720.jpeg?from=116350172"
]
},
"unique_id": "",
"platform_sync_info": null,
"short_id": "2219249505",
"nickname": "一朵小森林",
"signature": "摄影师,治愈系旅行博主。\n接写真,情侣,旅拍跟拍。短视频出售,定制。\n📩yanglinpt",
"geofencing": null,
"policy_version": null,
"type_label": null,
"uid": "4419667439400716",
"followers_detail": null
},
"forward_id": "0",
"promotions": null,
"long_video": null,
"duration": 10332,
"geofencing": null,
"statistics": {
"comment_count": 131140,
"digg_count": 2393061,
"play_count": 0,
"share_count": 135917,
"aweme_id": "6901990942908124431"
},
"share_info": {
"share_weibo_desc": "#在抖音,记录美好生活#我要路过你了。 #旅行大玩家",
"share_desc": "在抖音,记录美好生活",
"share_title": "我要路过你了。 #旅行大玩家"
},
"video_text": null,
"aweme_id": "6901990942908124431",
"cha_list": [
{
"view_count": 0,
"cid": "1608054515739651",
"cha_name": "旅行大玩家",
"desc": "带活动话题投稿,价值万元dou+、上亿流量等你瓜分,快和抖音旅行一起暑期神游吧!活动规则点击:https://aweme.snssdk.com/magic/eco/runtime/release/60fe2fc950440c032a3bbd7e?appType=douyin&magic_source=search",
"type": 2,
"is_commerce": false,
"user_count": 0,
"connect_music": null,
"cover_item": {
"uri": "douyin-admin-obj/180c18f991d04a59c7b7baafcc6e8c57",
"url_list": [
"https://p3-dy.bytecdn.cn/live/100x100/douyin-admin-obj/180c18f991d04a59c7b7baafcc6e8c57.jpg",
"https://p9-dy.bytecdn.cn/live/100x100/douyin-admin-obj/180c18f991d04a59c7b7baafcc6e8c57.jpg",
"https://p9-dy.bytecdn.cn/live/100x100/douyin-admin-obj/180c18f991d04a59c7b7baafcc6e8c57.jpg"
]
},
"hash_tag_profile": "douyin-admin-obj/180c18f991d04a59c7b7baafcc6e8c57"
}
],
"author_user_id": 4419667439400716,
"label_top_text": null,
"images": null,
"music": {
"id": 6798390707091574000,
"mid": "6798390707091573516",
"author": "温柔的岁月",
"cover_large": {
"uri": "1080x1080/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6",
"url_list": [
"https://p26.douyinpic.com/img/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6~c5_1080x1080.jpeg?from=116350172",
"https://p29.douyinpic.com/img/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6~c5_1080x1080.jpeg?from=116350172",
"https://p6.douyinpic.com/img/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6~c5_1080x1080.jpeg?from=116350172"
]
},
"cover_medium": {
"uri": "720x720/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6",
"url_list": [
"https://p3.douyinpic.com/img/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6~c5_720x720.jpeg?from=116350172",
"https://p11.douyinpic.com/img/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6~c5_720x720.jpeg?from=116350172",
"https://p26.douyinpic.com/img/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6~c5_720x720.jpeg?from=116350172"
]
},
"play_url": {
"uri": "https://sf3-cdn-tos.douyinstatic.com/obj/ies-music/1659763355067443.mp3",
"url_list": [
"https://sf3-cdn-tos.douyinstatic.com/obj/ies-music/1659763355067443.mp3",
"https://sf6-cdn-tos.douyinstatic.com/obj/ies-music/1659763355067443.mp3"
]
},
"duration": 10,
"title": "@温柔的岁月创作的原声",
"cover_hd": {
"uri": "1080x1080/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6",
"url_list": [
"https://p26.douyinpic.com/img/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6~c5_1080x1080.jpeg?from=116350172",
"https://p29.douyinpic.com/img/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6~c5_1080x1080.jpeg?from=116350172",
"https://p6.douyinpic.com/img/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6~c5_1080x1080.jpeg?from=116350172"
]
},
"cover_thumb": {
"uri": "168x168/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6",
"url_list": [
"https://p3.douyinpic.com/img/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6~c5_168x168.jpeg?from=116350172",
"https://p26.douyinpic.com/img/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6~c5_168x168.jpeg?from=116350172",
"https://p11.douyinpic.com/img/tos-cn-avt-0015/5c58a7121b884d996c5b677c82766dc6~c5_168x168.jpeg?from=116350172"
]
},
"position": null,
"status": 1
},
"comment_list": null,
"video_labels": null,
"create_time": 1606994997,
"video": {
"width": 1280,
"dynamic_cover": {
"uri": "tos-cn-p-0015/ea1d7868cf7a496ebbbc3f67a9acb400_1606994998",
"url_list": [
"https://p3.douyinpic.com/obj/tos-cn-p-0015/ea1d7868cf7a496ebbbc3f67a9acb400_1606994998?from=4257465056_large",
"https://p29.douyinpic.com/obj/tos-cn-p-0015/ea1d7868cf7a496ebbbc3f67a9acb400_1606994998?from=4257465056_large",
"https://p26.douyinpic.com/obj/tos-cn-p-0015/ea1d7868cf7a496ebbbc3f67a9acb400_1606994998?from=4257465056_large"
]
},
"origin_cover": {
"uri": "tos-cn-p-0015/06e9dcdb0ede4a19aaddabd983df72f7_1606994998",
"url_list": [
"https://p9.douyinpic.com/tos-cn-p-0015/06e9dcdb0ede4a19aaddabd983df72f7_1606994998~tplv-dy-360p.jpeg?from=4257465056&s=&se=false&sh=&sc=&l=202109051915100102121481502C609810&biz_tag=feed_cover",
"https://p29.douyinpic.com/tos-cn-p-0015/06e9dcdb0ede4a19aaddabd983df72f7_1606994998~tplv-dy-360p.jpeg?from=4257465056&s=&se=false&sh=&sc=&l=202109051915100102121481502C609810&biz_tag=feed_cover",
"https://p3.douyinpic.com/tos-cn-p-0015/06e9dcdb0ede4a19aaddabd983df72f7_1606994998~tplv-dy-360p.jpeg?from=4257465056&s=&se=false&sh=&sc=&l=202109051915100102121481502C609810&biz_tag=feed_cover"
]
},
"has_watermark": true,
"bit_rate": null,
"duration": 10332,
"play_addr": {
"uri": "v0d00f730000bv4coanfom5kn499oqbg",
"url_list": [
"https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0d00f730000bv4coanfom5kn499oqbg&ratio=720p&line=0"
]
},
"cover": {
"uri": "tos-cn-p-0015/cd2c543889fe456ca390864df50234bf",
"url_list": [
"https://p26.douyinpic.com/img/tos-cn-p-0015/cd2c543889fe456ca390864df50234bf~c5_300x400.jpeg?from=4257465056_large",
"https://p9.douyinpic.com/img/tos-cn-p-0015/cd2c543889fe456ca390864df50234bf~c5_300x400.jpeg?from=4257465056_large",
"https://p3.douyinpic.com/img/tos-cn-p-0015/cd2c543889fe456ca390864df50234bf~c5_300x400.jpeg?from=4257465056_large"
]
},
"height": 720,
"ratio": "720p",
"vid": "v0d00f730000bv4coanfom5kn499oqbg"
},
"risk_infos": {
"warn": false,
"type": 0,
"content": ""
},
"aweme_type": 4
}
],
"extra": {
"now": 1630840510000,
"logid": "202109051915100102121481502C609810"
},
"status_code": 0
}
在响应结果中找到video字段,有一个play_addr,看名字应该就是视频地址了,把这个url拷贝到浏览器中试一下,发现可以正常播放,但是仍然有水印
https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0d00f730000bv4coanfom5kn499oqbg&ratio=720p&line=0
目前看来这条路好像被堵死了,但是我们仔细分析一下这个url,会发现其中playwm后面的wm像是一个单词的缩写,没错就是水印watermark的缩写。我们修改一下这个url,将playwm改为play,尝试一下能否播放,结果神奇的事情发生了,视频播放成功,而且视频中没有水印。
所以我们往回推,只要调用iteminfo这个接口,就能获得我们想要的原始视频地址,这个接口只有一个参数item_ids。这个参数的值该如何获取呢?还记得最前面吗,我们访问抖音的分享链接时,302重定向到了一个url,这个url中就包含了这个id的值。
至此,我们完整的思路有了,第一步访问分享的原始链接,取得302重定向后的url,从url中截取id,将id传入刚才的iteminfo接口,从接口的响应结果中解析出视频地址。将视频地址中的playwm替换成play就大功告成了。
完整代码的后端代码如下
import json
import requests
from flask import Flask,render_template,jsonify,request
from uuid import uuid4
from flask_cors import CORS
app= Flask(__name__)
CORS(app)
@app.route("/",methods=["GET","POST"])
def homepage():
return render_template("dyqt.html")
#定义前端页面
@app.route('/douyin',methods=['GET','POST'])
def douyin():
url =request.form.get("oldurl")
ua = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1"
headers = {"User-Agent":ua}
r = requests.get(url,headers=headers).url
# 获取重定向后的url
midurl ="https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids="+r[38:57]
#使用切片截取id后传递iteminfo接口
nr = requests.get(midurl,headers=headers)
res = nr.json()
#获取接口返回的内容,接口返回的是json格式的数据,先将其反序列化为python字典
urllist =res["item_list"][0]["video"]["play_addr"]["url_list"][0]
#获取视频地址
desc = res["item_list"][0]["desc"]
musicUrl = res["item_list"][0]["music"]["play_url"]["uri"]
newurl = urllist.replace("wm","")
#替换url中"playwm"为"play"
reurl = requests.get(newurl,headers=headers).url
#发起请求得到跳转后的视频地址
return jsonify({"videoUrl":reurl,"desc":desc,"musicUrl":musicUrl})
#将结果封装成json格式返回
if __name__=='__main__':
app.run(host='0.0.0.0',port = 5000,debug=True)
简单解释一下这段代码,我们用到了requests模块,使用它来模拟发送请求,需要注意的是,需要加上手机UA,因为PC端的请求逻辑稍有区别,我们前面这段分析都是建立在手机访问的情况下。
然后用到了flask来做server,因为我们需要把原始的分享链接提交过去(注意 ,原始链接是类似这段 https://v.douyin.com/djnUPYe/) 。
前端我不太会弄,就简单做了一个输入框和一个提交按钮,通过ajax将url传递给服务端
qyqt.html关键代码如下:
<html>
<head>
<meta name="referrer" content="no-referrer"/>
<script src="{{url_for('static',filename='jquery.min.js')}}"></script>
</head>
<body>
<form method="POST">
<p>
<h1>输入分享链接:</h1>
</p>
<p><input type="text" name="oldurl" id="oldurl"/></p>
<p><input type="button" value="提交" name="Submit" id="Submit"/></p>
<p id="newurl" name="newurl"></p>
</form>
<script>
$(function(){
$("#Submit").click(function()
{
$.ajax({
url:'/douyin',
async:false,
type:'POST',
data:{oldurl:$("#oldurl").val()},
dataType:'json',
success: function(data,status){
// alert(1);
var nurl =data.videoUrl;
$("#newurl").text(nurl)
},
error: function(e){
console.log("ajax请求失败");
}
})
})
})
</script>
</body>
</html>
顺便提一下另一种思路,使用selenium无头模式请求url,通过xpath获取视频的播放地址。
目前的xpath路径为
//*[@id="root"]/div/div[2]/div[1]/div[1]/div[1]/div[1]/div/div[1]/div[2]/div/video
不过xpath存在不稳定的情况,如果页面布局有变动就会失效,所以还是推荐使用第一种方式,使用接口来获取.