MENU

抖音 视频图集解析 API 实现方法

May 7, 2025 • Read: 65 • Code

API仅供学习交流使用,禁止用于商业用途、违法用途等,否则后果自负!


如何获取到抖音短视频或图集的链接就不说了,这里以获取到一个短视频或图集链接开始,例如:

https://v.douyin.com/iSMn612Y/

这是一个抖音图集链接,获取到图片后我们需要获取到链接的重定向地址,代码如下:

/**
 * 异步获取重定向URL
 * @param {string} url - 初始URL
 * @returns {Promise<string>} - 最终的重定向URL
 */
async getRedirectUrl(url) {
    try {
        // 输出日志,表明开始获取重定向URL
        console.log("开始获取重定向URL:", url);

        // 发起GET请求,不跟随重定向
        const response = await this.curl(url, {
            method: "GET",
            dataType: "text",
            followRedirect: false
        });

        // 输出重定向响应的状态码
        console.log("重定向响应状态码:", response.status);
        // 输出重定向响应的头部信息
        console.log("重定向响应头:", response.headers);

        // 从响应头中获取重定向URL,如果没有则使用原始URL
        const redirectUrl = response.headers.location || url;

        // 输出最终的重定向URL
        console.log("最终重定向URL:", redirectUrl);

        // 返回最终的重定向URL
        return redirectUrl;
    } catch (error) {
        // 输出获取重定向URL时的错误信息
        console.error("获取重定向URL时出错:", error);
        // 抛出错误
        throw error;
    }
}

这样我们会获取到图集的详细地址,如下:

https://www.iesdouyin.com/share/note/7413711897830116608/?from_ssr=1&did=MS4wLjABAAAA_ZRjnl9xURp2Nui6mSbdBZD-zKuxZ_4XCrORVrlZnOyHvVKSTMMbXZ7WEc7q938F&mid=7386529472516376593&ts=1729489810&region=CN&share_sign=EZcWgkQkqoMwZxaIuvyxXW0TN2cWPoFVkJJ8ZkaxKZU-&tt_from=share_to&with_sec_did=1&from_aid=1128&titleType=title&utm_source=share_to&utm_medium=ios&activity_info=%7B%22social_share_time%22:%221729491670%22,%22social_author_id%22:%223971887811007315%22,%22social_share_id%22:%223971887811007315_1729491670%22,%22social_share_user_id%22:%223971887811007315%22%7D&timestamp=1729491669&share_version=310700&u_code=43k49fd35049&iid=MS4wLjABAAAAnlTijq4kdbAmlGgBbFVcvoPYhMp5oMFVOQh6-bg_fGd-_GapZ0AE5mNge6ItbVJ8&utm_campaign=client_share&app=aweme&schema_type=37

挺长的,但是我们只需要两个参数就行了,一个是链接中的 note 然后就是后面的 ID,其中 note 主要是为了区分短视频和图集的,然后重要的就是视频的ID,得到分类和ID后,就可以开始进行下一步了

// 构造请求头
const headers = {
    "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 Edg/122.0.0.0"
};

// 发送请求获取信息
const response = await this.curl(
    `https://www.iesdouyin.com/share/${isImage ? "note" : "video"}/${id}`,
    {
        headers: headers,
        dataType: "text"
    }
);

这样视频或图集的信息就藏在这个响应中,但是数据很乱,只要找到需要的参数就行了

// 解析响应数据
const pattern = /window\._ROUTER_DATA\s*=\s*(.*?)<\/script>/s;
const matches = response.data.match(pattern);

if (!matches || !matches[1]) {
    return {
        code: 201,
        msg: "解析失败:无法匹配信息"
    };
}

const info = JSON.parse(matches[1].trim());
console.log("info:", info);

return isImage ? this.parseImageData(info) : this.parseVideoData(info);

上述代码中可以得到原始数据,然后图片和视频的分别进行解析得到数据,如下:

// 解析视频数据
parseVideoData(videoInfo) {
    const itemData = this.safeGet(videoInfo, 'loaderData.video_(id)/page.videoInfoRes.item_list.0', {});

    // 尝试获取无水印视频链接
    let videoUrl = this.getNoWatermarkVideoUrl(itemData);

    return {
        code: 200,
        msg: "解析成功",
        data: {
            type: "video",
            author: this.safeGet(itemData, 'author.nickname', ''),
            uid: this.safeGet(itemData, 'author.unique_id', ''),
            avatar: this.safeGet(itemData, 'author.avatar_medium.url_list.0', ''),
            like: this.safeGet(itemData, 'statistics.digg_count', 0),
            time: this.safeGet(itemData, 'create_time', ''),
            title: this.safeGet(itemData, 'desc', ''),
            cover: this.safeGet(itemData, 'video.cover.url_list.0', ''),
            url: videoUrl,
            music: {
                author: this.safeGet(itemData, 'music.author', ''),
                avatar: this.safeGet(itemData, 'music.cover_large.url_list.0', '')
            },
            view_count: this.safeGet(itemData, 'statistics.play_count', 0),
            share_count: this.safeGet(itemData, 'statistics.share_count', 0),
            platform: "douyin"
        }
    };
}

// 尝试获取无水印视频链接
getNoWatermarkVideoUrl(itemData) {
    let videoUrl = '';

    // 方法1:尝试从 play_addr 中获取无水印链接
    const playAddr = this.safeGet(itemData, 'video.play_addr', {});
    videoUrl = this.safeGet(playAddr, 'url_list.0', '');

    // 如果获取到的 URL 包含 "playwm",尝试将其替换为 "play"
    if (videoUrl.includes('playwm')) {
        videoUrl = videoUrl.replace('playwm', 'play');
    }

    // 方法2:如果上面的方法失败,尝试从 download_addr 获取
    if (!videoUrl) {
        const downloadAddr = this.safeGet(itemData, 'video.download_addr', {});
        videoUrl = this.safeGet(downloadAddr, 'url_list.0', '');
    }

    // 方法3:如果还是失败,使用原始的 play_addr
    if (!videoUrl) {
        videoUrl = this.safeGet(itemData, 'video.play_addr.url_list.0', '');
    }

    return videoUrl;
}

上面是视频具体的解析方案,然后下面是图片的解析方案

parseImageData(imageInfo) {
    const noteData = this.safeGet(imageInfo, 'loaderData.note_(id)/page.videoInfoRes.item_list.0', null);

    if (!noteData) {
        return {
            code: 404,
            msg: "未找到图集信息"
        };
    }

    const imageList = noteData.images || [];

    return {
        code: 200,
        msg: "解析成功",
        data: {
            type: "image_set",
            author: this.safeGet(noteData, 'author.nickname', ''),
            uid: this.safeGet(noteData, 'author.unique_id', ''),
            avatar: this.safeGet(noteData, 'author.avatar_medium.url_list.0', ''),
            like_count: this.safeGet(noteData, 'statistics.digg_count', 0),
            comment_count: this.safeGet(noteData, 'statistics.comment_count', 0),
            time: noteData.create_time,
            title: noteData.desc,
            cover: this.safeGet(imageList, '0.url_list.0', ''),
            images: imageList.map((img) => ({
                url: this.safeGet(img, 'url_list.0', ''),
                width: img.width,
                height: img.height
            })),
            view_count: this.safeGet(noteData, 'statistics.play_count', 0),
            share_count: this.safeGet(noteData, 'statistics.share_count', 0),
            platform: "douyin"
        }
    };
}

当然其实得到参数后你想怎么处理就怎么处理了,所以到这里就结束了。最后代码中safeGet是一个用于代码?.的函数,因为测试 node18 支持,但是 node12 好像不支持?.

// 安全获取对象属性
safeGet(obj, path, defaultValue = '') {
    return path.split('.').reduce((acc, part) => {
        if (acc && typeof acc === 'object' && part in acc) {
            return acc[part];
        }
        return defaultValue;
    }, obj);
}


Archives QR Code Tip
QR Code for this page
Tipping QR Code