MENU

最右 视频解析 API 实现方法

May 7, 2025 • Read: 62 • Code

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


技术分析

最右平台的API相对封闭,官方文档不完善,主要通过抓包和逆向工程方法进行实现。完整的解析流程包含以下步骤:

  • 分享链接解析,提取内容ID(pid)
  • 调用API获取内容详情数据
  • 解析响应数据,提取无水印资源URL
  • 构建标准化响应结构

技术栈采用 uniCloud 云函数,配合 uni-cloud-router 框架实现接口路由和服务解耦,保持各平台解析功能的独立性与接口一致性

技术实现详解

技术实现详解

针对最右平台分享链接格式 https://share.xiaochuankeji.cn/hybrid/share/post?pid=xxx...,采用URL对象方法提取参数:

extractShareId(url) {
    try {
        const urlObj = new URL(url);
        const pid = urlObj.searchParams.get('pid');
        return pid;
    } catch (error) {
        console.error("提取分享ID失败:", error);
        return null;
    }
}

API请求实现

内容详情获取需通过 POST 方式请求,数据结构如下:

async getContentData(shareId) {
    try {
        const api = "https://share.xiaochuankeji.cn/planck/share/post/detail_h5";

        // 构造请求数据
        const postData = {
            "pid": parseInt(shareId) // 转成数字类型
        };

        const response = await this.curl(api, {
            method: 'POST',
            headers: this.headers,
            data: postData,
            dataType: "json"
        });

        // 验证响应
        const responseData = response.data;
        if (!responseData || responseData.ret!== 1) {
            console.error("API返回错误:", responseData);
            return null;
        }

        return responseData.data;
    } catch (error) {
        console.error("获取内容详情失败:", error);
        return null;
    }
}

资源数据结构解析

数据结构设计中,视频资源通过图片ID关联,这是一个关键的技术点:

// 获取基本信息
const title = this.safeGet(post, 'content', '');
const author = this.safeGet(post, 'member.name', '');
const imgs = this.safeGet(post, 'imgs', []);

// 获取图片ID
let imgId = null;
if (imgs && imgs.length > 0) {
    imgId = String(imgs[0].id);
}

// 通过图片ID获取视频URL
let videoUrl = '';
if (imgId && post.videos && post.videos[imgId]) {
    videoUrl = post.videos[imgId].url;
}

这种设计利用图片ID作为视频对象键名的方式,有效关联了视频与其封面图,是最右平台API的特殊之处

图片资源处理

针对最右多种尺寸的图片资源,实现了优先级获取策略:

// 构造图片URL数组
const imageUrls = [];

if (imgs && imgs.length > 0) {
    for (const img of imgs) {
        // 优先使用中等尺寸的webp格式
        if (img.urls && img.urls['540_webp'] && img.urls['540_webp'].urls) {
            imageUrls.push({
                url: img.urls['540_webp'].urls[0],
                width: img.width || 0,
                height: img.height || 0
            });
        } 
        // 备选原图格式
        else if (img.urls && img.urls.origin && img.urls.origin.urls) {
            imageUrls.push({
                url: img.urls.origin.urls[0],
                width: img.width || 0,
                height: img.height || 0
            });
        }
    }
}

健壮性处理

为提高服务稳定性,实现了安全属性获取方法,有效避免因API变更导致的引用错误:

safeGet(obj, path, defaultValue = '') {
    return path.split('.').reduce((acc, part) => {
        if (acc && typeof acc === 'object' && part in acc) {
            return acc[part];
        }
        return defaultValue;
    }, obj);
}

完整服务实现

const { Service } = require("uni-cloud-router");

module.exports = class ZuiyouService extends Service {
    constructor(ctx) {
        super(ctx);
        this.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",
            "Accept": "application/json",
            "Content-Type": "application/json",
            "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
        };
    }

    async parse() {
        try {
            const url = this.ctx.data.url;
            if (!url) {
                return { code: 400, msg: "请提供最右分享链接" };
            }
            console.log("开始解析最右URL:", url);

            // 提取分享ID
            const shareId = this.extractShareId(url);
            if (!shareId) {
                return { code: 400, msg: "无法从URL中提取分享ID" };
            }
            console.log("提取到分享ID:", shareId);

            // 获取内容详情
            const contentData = await this.getContentData(shareId);
            if (!contentData) {
                return { code: 201, msg: "无法获取内容详情" };
            }

            // 解析内容数据
            return this.parseContentData(contentData);
        } catch (error) {
            console.error("解析过程中出错:", error);
            return {
                code: 500,
                msg: `解析失败: ${error.message}`,
                data: null
            };
        }
    }

    extractShareId(url) {
        try {
            // 从查询参数中提取pid
            const urlObj = new URL(url);
            const pid = urlObj.searchParams.get('pid');
            return pid;
        } catch (error) {
            console.error("提取分享ID失败:", error);
            return null;
        }
    }

    async getContentData(shareId) {
        try {
            // 参考最右的API请求
            const api = "https://share.xiaochuankeji.cn/planck/share/post/detail_h5";
            console.log("请求API:", api);

            // 构造请求数据
            const postData = {
                "pid": parseInt(shareId)
            };

            // 发送POST请求
            const response = await this.curl(api, {
                method: 'POST',
                headers: this.headers,
                data: postData,
                dataType: "json"
            });

            // 检查API响应
            const responseData = response.data;
            console.log(responseData);
            if (!responseData || responseData.ret!== 1) {
                console.error("API返回错误:", responseData);
                return null;
            }

            return responseData.data;
        } catch (error) {
            console.error("获取内容详情失败:", error);
            return null;
        }
    }

    parseContentData(data) {
        try {
            // 提取帖子信息
            const post = this.safeGet(data, 'post', {});
            if (!post) {
                return {
                    code: 404,
                    msg: "未找到内容信息",
                    data: null
                };
            }

            // 获取基本信息
            const title = this.safeGet(post, 'content', '');
            const author = this.safeGet(post, 'member.name', '');
            const imgs = this.safeGet(post, 'imgs', []);

            // 获取第一张图片的ID
            let imgId = null;
            if (imgs && imgs.length > 0) {
                imgId = String(imgs[0].id);
            }

            // 获取视频URL
            let videoUrl = '';
            if (imgId && post.videos && post.videos[imgId]) {
                videoUrl = post.videos[imgId].url;
            }

            // 构造图片URL数组
            const imageUrls = [];
            if (imgs && imgs.length > 0) {
                for (const img of imgs) {
                    if (img.urls && img.urls['540_webp'] && img.urls['540_webp'].urls) {
                        imageUrls.push({
                            url: img.urls['540_webp'].urls[0],
                            width: img.width || 0,
                            height: img.height || 0
                        });
                    } else if (img.urls && img.urls.origin && img.urls.origin.urls) {
                        imageUrls.push({
                            url: img.urls.origin.urls[0],
                            width: img.width || 0,
                            height: img.height || 0
                        });
                    }
                }
            }

            // 构造返回数据
            const isVideo = videoUrl!== '';
            const result = {
                code: imgId? 200 : 201,
                msg: imgId? "解析成功" : "未找到媒体内容",
                data: {
                    author: author,
                    uid: this.safeGet(post, 'member.id', ''),
                    avatar: this.safeGet(post, 'member.avatar_urls.origin.urls.0', ''),
                    like_count: this.safeGet(post, 'like_count', 0),
                    comment_count: this.safeGet(post, 'comment_count', 0),
                    time: this.formatTime(this.safeGet(post, 'create_time', 0)),
                    title: title,
                    platform: "zuiyou"
                }
            };

            if (isVideo) {
                // 视频内容
                result.data.type = "video";
                result.data.cover = imageUrls.length > 0? imageUrls[0].url : '';
                result.data.url = videoUrl;
                result.data.video_url = videoUrl;
            } else if (imageUrls.length > 0) {
                // 图片内容
                result.data.type = "image_set";
                result.data.cover = imageUrls[0].url;
                result.data.images = imageUrls;
            } else {
                // 纯文本内容
                result.data.type = "text";
            }

            return result;
        } catch (error) {
            console.error("解析内容数据失败:", error);
            return {
                code: 500,
                msg: "解析内容数据失败",
                data: null
            };
        }
    }

    formatTime(timestamp) {
        if (!timestamp) return '';
        // 将秒级时间戳转换为毫秒级
        if (timestamp.toString().length === 10) {
            timestamp = timestamp * 1000;
        }
        return new Date(timestamp).toISOString();
    }

    safeGet(obj, path, defaultValue = '') {
        return path.split('.').reduce((acc, part) => {
            if (acc && typeof acc === 'object' && part in acc) {
                return acc[part];
            }
            return defaultValue;
        }, obj);
    }
};
    

关键技术总结

  • 链接解析技术:应用URL对象进行高效参数提取
  • API逆向分析:确定接口、请求方式及数据结构
  • 特殊数据结构处理:解析视频与图片ID关联机制
  • 资源优化策略:优先选择体积较小的webp格式图片
  • 健壮性设计:实现全链路异常处理和安全属性读取

接口规范与集成

针对多平台统一解析需求,设计了一致的接口规范:

  • 请求方式:POST
  • 请求参数:{ url: "分享链接" }
  • 响应格式:标准化JSON结构

性能与稳定性优化

  • 缓存策略:针对热门内容实现结果缓存
  • 异步处理:解决大文件处理的性能问题
  • 异常处理:完善错误捕获和日志记录
  • 资源选择:根据场景智能选择资源质量


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