项目地址是:https://www.chenshiyang.com/dytk
接下来我们分析下源码简要看下实现原理。
实现原理
该项目不需要使用模型(models), 最核心的只有两个页面:一个主页面(home)展示包含下载url地址的表单,一个下载页面(download)处理表单请求,并展示去水印后的视频文件地址及文件大小,以及用于手机预览的二维码。
对应两个核心页面的路由如下所示,每个url对应一个视图函数。
# urls.py
1
2
3
4
5
6
7
8
|
from django.urls import path
from web.views import home, download
urlpatterns = [
path( 'home' , home),
path( 'downloader' , download),
]
|
#web/urls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
from django.http import httpresponse
from django.shortcuts import render, redirect
# create your views here.
from common.utils import format_duration, load_media
from common.douyin import dy
def home(request):
"""首页"""
return render(request, 'home.html' )
def download(request):
"""下载"""
url = request.post.get( 'url' , none)
assert url ! = none
dy = dy()
data = dy.parse(url)
mp4_path, mp4_content_length = load_media(data[ 'mp4' ], 'mp4' )
mp3_path, mp3_content_length = load_media(data[ 'mp3' ], 'mp3' )
realpath = ' '.join([' https: / / www.chenshiyang.com', mp4_path])
print ( 'realpath---------------------' , realpath)
if len (data[ 'desc' ].split( '#' )) > 2 :
topic = data[ 'desc' ].split( '#' )[ 2 ].rstrip( '#' )
return render(request, 'download.html' , locals ())
|
可以看出通过home页面表单提交过来的下载url会交由download函数处理。common模块的douyin.py中定义的dy类负责对url继续解析,爬取相关视频地址,通过自定义utils.py中的load_media方法下载文件,并返回文件路径以及文件大小。
由于解析下载url,从抖音爬取数据的代码都封装到dy类里了,所以我们有必要贴下这个类的代码。另外,我们还需要贴下load_media这个方法的代码。
# common/douyin.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
# -*- coding: utf-8 -*-
# @time : 2020-07-03 13:10
# @author : chenshiyang
# @email : chenshiyang@blued.com
# @file : douyin.py
# @software: pycharm
import re
from urllib.parse import urlparse
import requests
from common.utils import format_duration
class dy( object ):
def __init__( self , app = none):
self .app = app
if app is not none:
self .init_app(app)
self .headers = {
'accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9' ,
# 'accept-encoding': 'gzip, deflate, br',
'accept-language' : 'zh-cn,zh;q=0.9' ,
'cache-control' : 'no-cache' ,
'cookie' : 'sid_guard=2e624045d2da7f502b37ecf72974d311%7c1591170698%7c5184000%7csun%2c+02-aug-2020+07%3a51%3a38+gmt; uid_tt=0033579d9229eec4a4d09871dfc11271; sid_tt=2e624045d2da7f502b37ecf72974d311; sessionid=2e624045d2da7f502b37ecf72974d311' ,
'pragma' : 'no-cache' ,
'sec-fetch-dest' : 'document' ,
'sec-fetch-mode' : 'navigate' ,
'sec-fetch-site' : 'none' ,
'sec-fetch-user' : '?1' ,
'upgrade-insecure-requests' : '1' ,
'user-agent' : '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'
}
self .domain = [ 'www.douyin.com' ,
'v.douyin.com' ,
'www.snssdk.com' ,
'www.amemv.com' ,
'www.iesdouyin.com' ,
'aweme.snssdk.com' ]
def init_app( self , app):
self .app = app
def parse( self , url):
share_url = self .get_share_url(url)
share_url_parse = urlparse(share_url)
if share_url_parse.netloc not in self .domain:
raise exception( "无效的链接" )
dytk = none
vid = re.findall(r '\/share\/video\/(\d*)' , share_url_parse.path)[ 0 ]
match = re.search(r '\/share\/video\/(\d*)' , share_url_parse.path)
if match:
vid = match.group( 1 )
response = requests.get(
share_url,
headers = self .headers,
allow_redirects = false)
match = re.search( 'dytk: "(.*?)"' , response.text)
if match:
dytk = match.group( 1 )
if vid:
return self .get_data(vid, dytk)
else :
raise exception( "解析失败" )
def get_share_url( self , url):
response = requests.get(url,
headers = self .headers,
allow_redirects = false)
if 'location' in response.headers.keys():
return response.headers[ 'location' ]
elif '/share/video/' in url:
return url
else :
raise exception( "解析失败" )
def get_data( self , vid, dytk):
url = f "https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids={vid}&dytk={dytk}"
response = requests.get(url, headers = self .headers, )
result = response.json()
if not response.status_code = = 200 :
raise exception( "解析失败" )
item = result.get( "item_list" )[ 0 ]
author = item.get( "author" ).get( "nickname" )
mp4 = item.get( "video" ).get( "play_addr" ).get( "url_list" )[ 0 ]
cover = item.get( "video" ).get( "cover" ).get( "url_list" )[ 0 ]
mp4 = mp4.replace( "playwm" , "play" )
res = requests.get(mp4, headers = self .headers, allow_redirects = true)
mp4 = res.url
desc = item.get( "desc" )
mp3 = item.get( "music" ).get( "play_url" ).get( "url_list" )[ 0 ]
data = dict ()
data[ 'mp3' ] = mp3
data[ 'mp4' ] = mp4
data[ 'cover' ] = cover
data[ 'nickname' ] = author
data[ 'desc' ] = desc
data[ 'duration' ] = format_duration(item.get( "duration" ))
return data
|
从代码你可以看到返回的data字典里包括了mp3和mp4源文件地址,以及视频的封面,作者昵称及描述等等。
接下来你可以看到load_media方法爬取了视频到本地,并提供了新的path和大小。
#common/utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
# -*- coding: utf-8 -*-
# @time : 2020-06-29 17:26
# @author : chenshiyang
# @email : chenshiyang@blued.com
# @file : utils.py
# @software: pycharm
import os
import time
import requests
def format_duration(duration):
"""
格式化时长
:param duration 毫秒
"""
total_seconds = int (duration / 1000 )
minute = total_seconds / / 60
seconds = total_seconds % 60
return f '{minute:02}:{seconds:02}'
suffixes = { 1000 : [ 'kb' , 'mb' , 'gb' , 'tb' , 'pb' , 'eb' , 'zb' , 'yb' ],
1024 : [ 'kib' , 'mib' , 'gib' , 'tib' , 'pib' , 'eib' , 'zib' , 'yib' ]}
def approximate_size(size, a_kilobyte_is_1024_bytes = true):
'''convert a file size to human-readable form.
keyword arguments:
size -- file size in bytes
a_kilobyte_is_1024_bytes -- if true (default), use multiples of 1024
if false, use multiples of 1000
returns: string
'''
if size < 0 :
raise valueerror( 'number must be non-negative' )
multiple = 1024 if a_kilobyte_is_1024_bytes else 1000
for suffix in suffixes[multiple]:
size / = multiple
if size < multiple:
return '{0:.1f} {1}' . format (size, suffix)
raise valueerror( 'number too large' )
def do_load_media(url, path):
"""
对媒体下载
:param url: 多媒体地址
:param path: 文件保存路径
:return: none
"""
try :
headers = {
"user-agent" : "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" }
pre_content_length = 0
# 循环接收视频数据
while true:
# 若文件已经存在,则断点续传,设置接收来需接收数据的位置
if os.path.exists(path):
headers[ 'range' ] = 'bytes=%d-' % os.path.getsize(path)
res = requests.get(url, stream = true, headers = headers)
content_length = int (res.headers[ 'content-length' ])
# 若当前报文长度小于前次报文长度,或者已接收文件等于当前报文长度,则可以认为视频接收完成
if content_length < pre_content_length or (
os.path.exists(path) and os.path.getsize(path) = = content_length):
break
pre_content_length = content_length
# 写入收到的视频数据
with open (path, 'ab' ) as file :
file .write(res.content)
file .flush()
print ( 'receive data,file size : %d total size:%d' % (os.path.getsize(path), content_length))
return approximate_size(content_length, a_kilobyte_is_1024_bytes = false)
except exception as e:
print ( '视频下载异常:{}' . format (e))
def load_media(url, path):
basepath = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
# 生成13位时间戳
suffixes = str ( int ( round (time.time() * 1000 )))
path = ' '.join([' / media / ', path, ' / ', ' .'.join([suffixes, path])])
targetpath = ''.join([basepath, path])
content_length = do_load_media(url, targetpath)
return path, content_length
def main(url, suffixes, path):
load_media(url, suffixes, path)
if __name__ = = "__main__" :
# url = 'https://aweme.snssdk.com/aweme/v1/play/?video_id=v0200fe70000br155v26tgq06h08e0lg&ratio=720p&line=0'
# suffixes = 'test'
# main(url, suffixes, 'mp4',)
print (approximate_size( 3726257 , a_kilobyte_is_1024_bytes = false))
|
接下来我们看下模板, 这个没什么好说的。
# templates/home.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
{ % extends "base.html" % }
{ % block content % }
<div class = "jumbotron custom-jum no-mrg" >
<div class = "container" >
<div class = "row" >
<div class = "col-md-12" >
<div class = "center" >
<div class = "home-search" >
<h1>抖音无水印视频下载器< / h1>
<h2>将抖音无水印视频下载到mp4和mp3< / h2>
< / div>
<div class = "form-home-search" >
<form id = "form_download" action = 'https://www.chenshiyang.com/dytk/downloader' method = 'post' >
<div class = "input-group col-lg-10 col-md-10 col-sm-10" >
< input name = "url" class = "form-control input-md ht58" placeholder = "输入抖音视频 url ..." type = "text"
required = " " value=" ">
<span class = "input-group-btn" ><button class = "btn btn-primary input-md btn-download ht58" type = "submit"
id = "btn_submit" >下载< / button>< / span>
< / div>
< / form>
< / div>
< / div>
< / div>
< / div>
< / div>
< / div>
< / div>
{ % endblock % }
|
# templates/download.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
{ % extends "base.html" % }
{ % block content % }
<div class = "page-content" >
<div class = "container" >
<div class = "row" >
<div class = "col-lg-12 col-centered" >
<div class = "ads mrg-bt20 text-center" >
<ins class = "adsbygoogle" style = "display:inline-block;width:728px;height:90px"
data - ad - client = "ca-pub-2984659695526033" data - ad - slot = "5734284394" >< / ins>
< / div>
<div class = "card" >
<div class = "row" >
<div class = "col-md-4 col-sm-4" >
<a href = "{{mp4_path}}" rel = "external nofollow" rel = "external nofollow" data - toggle = "modal" class = "card-aside-column img-video"
style = "height: 252px; background: url("{{data.cover}}") 0% 0% / cover;" title = "">
<span class = "btn-play-video" ><i class = "glyphicon glyphicon-play" >< / i>< / span>
<p class = "time-video" id = "time" >{{data.duration}}< / p>
< / a>
<h5>作者: {{data.nickname}}< / h5>
<h5><a href = "#" rel = "external nofollow" >{{topic}} <i class = "open-new-window" >< / i>< / a>< / h5>
<p class = "card-text" >{{data.desc}}< / p>
< / div>
<div class = "col-md-8 col-sm-8 col-table" >
<table class = "table" >
<thead>
<tr>
<th> format < / th>
<th>size< / th>
<th>downloads< / th>
< / tr>
< / thead>
<tbody>
<tr>
<td>mp4< / td>
<td>{{mp4_content_length}}< / td>
<td>
<a href = "{{mp4_path}}" rel = "external nofollow" rel = "external nofollow" class = "btn btn-download" download = "">下载< / a>
< / td>
< / tr>
<tr>
<td>mp3< / td>
<td>{{mp3_content_length}}< / td>
<td>
<a href = "{{mp3_path}}" rel = "external nofollow" class = "btn btn-download" download = "">下载< / a>
< / td>
< / tr>
< / tbody>
< / table>
< / div>
< / div>
< / div>
<div class = "card card-qrcode" >
<div class = "row" >
<div class = "col-md-12 qrcode" >
<div class = "text-center" >
<p class = "qrcode-p" >扫描下面的二维码直接下载到您的智能手机或平板电脑!< / p>
< / div>
< / div>
<div class = "col-md-4 col-centered qrcode" >
<div id = "qrcode" title = "{{realpath}}" >
<script src = "/static/js/qrcode.min.js" >< / script>
<script type = "text/javascript" >
new qrcode(document.getelementbyid( "qrcode" ), {
text: "{{realpath}}" ,
width: 120 ,
height: 120 ,
correctlevel: qrcode.correctlevel.l
});
< / script>
< / div>
< / div>
< / div>
< / div>
< / div>
< / div>
< / div>
< / div>
{ % endblock % }
|
完整源码地址:
https://github.com/tinysheepyang/python_api。
以上就是django实现在线无水印抖音视频下载(附源码及地址)的详细内容,更多关于django 无水印抖音视频下载的资料请关注服务器之家其它相关文章!
原文链接:https://mp.weixin.qq.com/s?__biz=MjM5OTMyODA4Nw==&mid=2247484839&idx=1&sn=7682815ebf82cf8bf2e4e773f2960d1b