import requests
import os
import re
import json
from urllib.parse import quote


class DouyinDownloader:
    def __init__(self):
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
            "Referer": "https://www.douyin.com/",
            "Cookie": "enter_pc_once=1; UIFID_TEMP=d4579b5b1721ffdd22d8a6ff378781159e3b4e5a1248a83fc8c708ec2606b7053c073054c053cd86e6df9eff908b5e8cc7308d5cf8e28836855eb7431df2db81f07e2b561dcb4887426de73bb39200f8; s_v_web_id=verify_mcncsn3o_giJLQI7A_BJss_4Dba_AgiU_DUcJJRqiZSK4; fpk1=U2FsdGVkX1+3p1KZvxr4f5dBOl/0MkQXGN8DAYIQsoe1OX0o9lvz32QZkQMcEf8eCWEoJ6iPvv2We6YBFEJJXg==; fpk2=d2ad6785d256851dd366703bdc61aa61; UIFID=d4579b5b1721ffdd22d8a6ff378781159e3b4e5a1248a83fc8c708ec2606b7051aef51bb3919fc554c6c77d02bfdfba068dc71cfd0f9620f2d0c2580e96994cf3ed494a014905d9f1aeb7d8ba5f0428bb959b923c5e6d8f7b4554b2207fc3299f39e491ad0c331e0484ee64647036a784363ea86bacf1e86427fe4eea44f1caa2dbedc515f3a39e50a2dc6b0cb9b2befb2ba970b616230bf7374f57fed39e68e; passport_csrf_token=bee3e7bacf078268e4ff3ad62366b799; passport_csrf_token_default=bee3e7bacf078268e4ff3ad62366b799; __security_mc_1_s_sdk_crypt_sdk=4c83a43c-471f-bc6a; bd_ticket_guard_client_web_domain=2; __ac_nonce=0687c920b0048d47bc9e4; __ac_signature=_02B4Z6wo00f015BNW8wAAIDDp2mfkuxdxk-QbV9AAIx43c; douyin.com; device_web_cpu_core=4; device_web_memory_size=8; architecture=amd64; dy_swidth=1536; dy_sheight=864; stream_recommend_feed_params=%22%7B%5C%22cookie_enabled%5C%22%3Atrue%2C%5C%22screen_width%5C%22%3A1536%2C%5C%22screen_height%5C%22%3A864%2C%5C%22browser_online%5C%22%3Atrue%2C%5C%22cpu_core_num%5C%22%3A4%2C%5C%22device_memory%5C%22%3A8%2C%5C%22downlink%5C%22%3A8.45%2C%5C%22effective_type%5C%22%3A%5C%224g%5C%22%2C%5C%22round_trip_time%5C%22%3A100%7D%22; strategyABtestKey=%221752994328.133%22; xgplayer_user_id=214342160910; xg_device_score=6.925510555910728; SEARCH_RESULT_LIST_TYPE=%22single%22; volume_info=%7B%22volume%22%3A0.6%2C%22isMute%22%3Afalse%2C%22isUserMute%22%3Afalse%7D; stream_player_status_params=%22%7B%5C%22is_auto_play%5C%22%3A0%2C%5C%22is_full_screen%5C%22%3A0%2C%5C%22is_full_webscreen%5C%22%3A0%2C%5C%22is_mute%5C%22%3A1%2C%5C%22is_speed%5C%22%3A1%2C%5C%22is_visible%5C%22%3A0%7D%22; csrf_session_id=f7e8683b119eef6a8f3e7ae23149a086; download_guide=%222%2F20250720%2F0%22; passport_assist_user=Cj14_XIZNq5l_enyVjBsQAwzQAosggtxHOiGnL1MIXdsRJcK0ksITm2FoEJAffQyHyCwQxWHoWkGw_OI_cTHGkoKPAAAAAAAAAAAAABPQeJXCQeaeJDH9Bn138-hpUTzX5h1ufyQN3cPHJAaSdrgAFAdz0EnjWWn9ls7EJCQhxDVnPcNGImv1lQgASIBAx4Odhk%3D; n_mh=7OJlO0csQE8crlhIOZfI8Js24pwvbOZcmySM5toWEdM; sid_guard=a0f8e4da928ec31679a6c11a2c9d06fa%7C1752994522%7C5184000%7CThu%2C+18-Sep-2025+06%3A55%3A22+GMT; uid_tt=3096bec7ff59b1b5d36a8af6326bf654; uid_tt_ss=3096bec7ff59b1b5d36a8af6326bf654; sid_tt=a0f8e4da928ec31679a6c11a2c9d06fa; sessionid=a0f8e4da928ec31679a6c11a2c9d06fa; sessionid_ss=a0f8e4da928ec31679a6c11a2c9d06fa; session_tlb_tag=sttt%7C10%7CoPjk2pKOwxZ5psEaLJ0G-v_________VWoOHjdZ5tfur1IS7UQlszxhBlmnsg8nb2vc7RWrdh4U%3D; is_staff_user=false; sid_ucp_v1=1.0.0-KDY1ZDM0MDdiZDBkM2YwMmUyZjNhZDM3M2U1ODZiYzY4YTQ5YTFjM2UKHwiDmvqAgAMQ2qXywwYY7zEgDDDW3tDbBTgHQPQHSAQaAmxmIiBhMGY4ZTRkYTkyOGVjMzE2NzlhNmMxMWEyYzlkMDZmYQ; ssid_ucp_v1=1.0.0-KDY1ZDM0MDdiZDBkM2YwMmUyZjNhZDM3M2U1ODZiYzY4YTQ5YTFjM2UKHwiDmvqAgAMQ2qXywwYY7zEgDDDW3tDbBTgHQPQHSAQaAmxmIiBhMGY4ZTRkYTkyOGVjMzE2NzlhNmMxMWEyYzlkMDZmYQ; login_time=1752994522792; SelfTabRedDotControl=%5B%7B%22id%22%3A%227014734846131963934%22%2C%22u%22%3A81%2C%22c%22%3A0%7D%2C%7B%22id%22%3A%227289833578178578489%22%2C%22u%22%3A673%2C%22c%22%3A0%7D%2C%7B%22id%22%3A%227206568810336225341%22%2C%22u%22%3A82%2C%22c%22%3A0%7D%2C%7B%22id%22%3A%227355146212943366194%22%2C%22u%22%3A255%2C%22c%22%3A0%7D%2C%7B%22id%22%3A%227363953707760764967%22%2C%22u%22%3A218%2C%22c%22%3A0%7D%2C%7B%22id%22%3A%227126409804771231758%22%2C%22u%22%3A2174%2C%22c%22%3A0%7D%2C%7B%22id%22%3A%227178470125828311095%22%2C%22u%22%3A406%2C%22c%22%3A0%7D%2C%7B%22id%22%3A%227208771683937683496%22%2C%22u%22%3A124%2C%22c%22%3A0%7D%2C%7B%22id%22%3A%227215857424987588664%22%2C%22u%22%3A277%2C%22c%22%3A0%7D%2C%7B%22id%22%3A%227014734432817678343%22%2C%22u%22%3A313%2C%22c%22%3A0%7D%2C%7B%22id%22%3A%227212479892523321404%22%2C%22u%22%3A593%2C%22c%22%3A0%7D%2C%7B%22id%22%3A%227271182930221729803%22%2C%22u%22%3A307%2C%22c%22%3A0%7D%2C%7B%22id%22%3A%227265913477733025846%22%2C%22u%22%3A146%2C%22c%22%3A0%7D%2C%7B%22id%22%3A%227312793857073907752%22%2C%22u%22%3A166%2C%22c%22%3A0%7D%5D; _bd_ticket_crypt_cookie=482235c92857a5fbd461032eefb5a849; __security_mc_1_s_sdk_sign_data_key_web_protect=c46fd730-4dc2-b0de; __security_mc_1_s_sdk_cert_key=9d06974e-4fea-8eff; __security_server_data_status=1; publish_badge_show_info=%220%2C0%2C0%2C1752994541207%22; ttwid=1%7Cq1Ewg4oAV8tbbnV5l5Xe3yc8kQS3LbWTxJ_n2HNB1FU%7C1752994565%7Ce2ac47e138fe38dfcd108cd0a44026b809e73d2ac2b5834349b7b4367daf9a93; biz_trace_id=38b12bf9; odin_tt=ed44bc4d4f94bb64966b82a65821445e26ae3d4d5e39db5b8d377573a8dd238876b65b5bb81332f6aa9664bdd8bd8b04fa1c42c1569dab407b28f2fe6b795b7d; passport_fe_beating_status=false; home_can_add_dy_2_desktop=%220%22; IsDouyinActive=true; bd_ticket_guard_client_data=eyJiZC10aWNrZXQtZ3VhcmQtdmVyc2lvbiI6MiwiYmQtdGlja2V0LWd1YXJkLWl0ZXJhdGlvbi12ZXJzaW9uIjoxLCJiZC10aWNrZXQtZ3VhcmQtcmVlLXB1YmxpYy1rZXkiOiJCREx0bVhDRE9ERTh3cHZ3dlZ5UHZHNXFyb1R1VnZyNXZIMWFGSThDOVlXMVhhemovL3V6S3hwTVFVc3lCM0NtYmI0cEU3SC81SHNTMkpFT3kwbmE4aGM9IiwiYmQtdGlja2V0LWd1YXJkLXdlYi12ZXJzaW9uIjoyfQ%3D%3D; FOLLOW_LIVE_POINT_INFO=%22MS4wLjABAAAA-IXxjHZlHw0CDM7vh0SQLLCDDgNxVqC-QOmlgwkVExc%2F1753027200000%2F0%2F0%2F1752995335706%22; FOLLOW_NUMBER_YELLOW_POINT_INFO=%22MS4wLjABAAAA-IXxjHZlHw0CDM7vh0SQLLCDDgNxVqC-QOmlgwkVExc%2F1753027200000%2F0%2F1752994735709%2F0%22"  # 运行时手动输入Cookie
        }
        self.session = requests.Session()

    def clean_filename(self, text):
        """清理文件名中的非法字符"""
        return re.sub(r'[\\/:"*?<>|]', '', text).strip() or "未命名"

    def search_videos(self, keyword, count=10):
        """搜索抖音视频"""
        try:
            url = "https://www.douyin.com/aweme/v1/web/general/search/single/"
            params = {
                "device_platform": "webapp",
                "aid": "6383",
                "channel": "channel_pc_web",
                "search_channel": "aweme_general",
                "keyword": keyword,
                "search_source": "normal_search",
                "offset": "0",
                "count": str(count),
                "pc_client_type": "1",
            }

            resp = self.session.get(url, headers=self.headers, params=params, timeout=10)
            resp.raise_for_status()
            return resp.json().get("data", [])
        except Exception as e:
            print(f"搜索失败: {str(e)}")
            return []

    def download_video(self, video_url, filename):
        """下载单个视频"""
        try:
            os.makedirs("douyin_videos", exist_ok=True)
            resp = self.session.get(video_url, headers=self.headers, stream=True, timeout=30)
            resp.raise_for_status()

            filepath = f"douyin_videos/{filename}.mp4"
            with open(filepath, "wb") as f:
                for chunk in resp.iter_content(chunk_size=1024 * 1024):
                    if chunk:
                        f.write(chunk)
            return True
        except Exception as e:
            print(f"下载失败[{filename}]: {str(e)}")
            return False

    def run(self):
        """主交互流程"""
        # 1. 输入搜索关键词
        keyword = input("\n请输入要搜索的视频名称: ").strip()
        if not keyword:
            print("搜索关键词不能为空!")
            return

        # 2. 搜索并显示结果
        print("\n正在搜索...")
        videos = self.search_videos(keyword, count=20)  # 先获取20条结果
        if not videos:
            print("未找到相关视频!")
            return

        # 3. 显示可下载列表
        print("\n找到以下视频:")
        aweme_list = []
        for idx, item in enumerate(videos[:20]):  # 最多显示20条
            if "aweme_info" in item:
                video = item["aweme_info"]
                desc = self.clean_filename(video.get("desc", f"视频{idx + 1}"))
                print(f"[{idx + 1}] {desc}")
                aweme_list.append(video)

        # 4. 选择下载数量
        try:
            max_download = min(len(aweme_list), 20)
            count = int(input(f"\n请输入要下载的数量(1-{max_download}): "))
            count = max(1, min(count, max_download))
        except:
            print("输入无效,默认下载3个")
            count = 3

        # 5. 开始下载
        print(f"\n开始下载{count}个视频...")
        success = 0
        for video in aweme_list[:count]:
            desc = self.clean_filename(video.get("desc", ""))
            video_url = video["video"]["play_addr"]["url_list"][0]

            print(f"正在下载: {desc}.mp4", end="", flush=True)
            if self.download_video(video_url, desc):
                print(" [✓]")
                success += 1
            else:
                print(" [×]")

        print(f"\n下载完成!成功下载 {success}/{count} 个视频到 douyin_videos 文件夹")


if __name__ == "__main__":
    print("抖音视频下载器 (输入视频名称搜索下载)")
    print("=" * 40)
    DouyinDownloader().run()







在这里插入图片描述

在这里插入图片描述

Logo

加入社区!打开量化的大门,首批课程上线啦!

更多推荐