您当前的位置:首页 > IT编程 > python
| C语言 | Java | VB | VC | python | Android | TensorFlow | C++ | oracle | 学术与代码 | cnn卷积神经网络 | gnn | 图像修复 | Keras | 数据集 | Neo4j | 自然语言处理 | 深度学习 | 医学CAD | 医学影像 | 超参数 | pointnet | pytorch | 异常检测 | Transformers | 情感分类 | 知识图谱 |

自学教程:Django实现在线无水印抖音视频下载(附源码及地址)

51自学网 2021-10-30 22:41:39
  python
这篇教程Django实现在线无水印抖音视频下载(附源码及地址)写得很实用,希望能帮到您。

项目地址是:https://www.chenshiyang.com/dytk

接下来我们分析下源码简要看下实现原理。

实现原理

该项目不需要使用模型(models), 最核心的只有两个页面:一个主页面(home)展示包含下载url地址的表单,一个下载页面(download)处理表单请求,并展示去水印后的视频文件地址及文件大小,以及用于手机预览的二维码。

对应两个核心页面的路由如下所示,每个url对应一个视图函数。

# urls.py

from django.urls import pathfrom web.views import home, downloadurlpatterns = [    path('home', home),    path('downloader', download),]

#web/urls.py

from django.http import HttpResponsefrom django.shortcuts import render, redirect# Create your views here.from common.utils import format_duration, load_mediafrom common.DouYin import DYdef 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

# -*- coding: utf-8 -*-# @Time    : 2020-07-03 13:10# @Author  : chenshiyang# @Email   : chenshiyang@blued.com# @File    : DouYin.py# @Software: PyCharmimport refrom urllib.parse import urlparseimport requestsfrom common.utils import format_durationclass 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

# -*- coding: utf-8 -*-# @Time    : 2020-06-29 17:26# @Author  : chenshiyang# @Email   : chenshiyang@blued.com# @File    : utils.py# @Software: PyCharmimport osimport timeimport requestsdef 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_lengthdef 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

{% 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

{% 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(&quot;{{data.cover}}&quot;) 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 无水印抖音视频下载的资料请关注51zixue.net其它相关文章!


Django给表单添加honeypot验证增加安全性
Python 如何安装Selenium(推荐)
万事OK自学网:51自学网_软件自学网_CAD自学网自学excel、自学PS、自学CAD、自学C语言、自学css3实例,是一个通过网络自主学习工作技能的自学平台,网友喜欢的软件自学网站。