MENU

快手 视频图集解析 API 实现方法

May 4, 2025 • Read: 77 • Code

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


特别注意:user-agent要设置成手机代理,不然多次请求会出现验证码

短链接格式:https://v.kuaishou.com/xxxxxx
接口地址:https://api.kuaishouzt.com/rest/zt/show/web/xxx
关键字段:photo.manifest.adaptationSet.representation.url

以下有两种方法,原文仅供参考。

方法一

  • 首先还是获取一个快手短视频或者图集的链接,演示如下:
https://v.kuaishou.com/dlyk1
  • 然后传到后端,后端会获取到其重定向到地址,但是考虑到有可能会重定向几次,所以做了一些处理,代码如下:
async getFinalRedirectUrl(url, maxRedirects = 5) {
    let currentUrl = url;
    let redirectCount = 0;
    
    while (redirectCount < maxRedirects) {
        try {
            const response = await this.curl(currentUrl, {
                method: "GET",
                dataType: "text",
                followRedirect: false,
            });

            if (response.headers.location) {
                currentUrl = new URL(response.headers.location, currentUrl).href;
                console.log(`重定向 ${redirectCount + 1}: ${currentUrl}`);
                redirectCount++;
            } else {
                // 没有更多重定向,返回当前URL
                return currentUrl;
            }
        } catch (error) {
            console.error(`重定向过程中出错 (${currentUrl}):`, error);
            throw error;
        }
    }

    console.warn(`达到最大重定向次数 (${maxRedirects})`);
    return currentUrl;
}
  • 获取到的原地址应该如下:
https://v.m.chenzhongtech.com/fw/photo/3xnh6zqupyhfm5e?cc=share_copylink&followRefer=151&shareMethod=TOKEN&docId=9&kpn=KUAISHOU&subBiz=BROWSE_SLIDE_PHOTO&photoId=3xnh6zqupyhfm5e&shareId=18116220443971&shareToken=X44fHQnInAjW1py&shareResourceType=PHOTO_OTHER&userId=3xw7it7sxf6k7pe&shareType=1&et=1_i%2F2007717946053474769_scn0&shareMode=APP&efid=0&originShareId=18116220443971&appType=1&shareObjectId=5241064204127827041&shareUrlOpened=0&timestamp=1729591920122
  • 研究域名可以得到其中的 ID ,也就是链接中的 3xnh6zqupyhfm5e ,然后进行下一步,构造移动版网页URL,如下:
const 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",
    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-Language": "zh-CN,zh;q=0.9,en;q=0.8",
};

const mobileUrl = `https://c.kuaishou.com/fw/photo/${photoId}`;

// 发送请求获取信息
const response = await this.curl(mobileUrl, {
    headers: this.headers,
    dataType: "text",
});

console.log("响应数据:", response);
  • 最后就是得到响应后的处理了
parseContentInfo(html) {
    const jsonMatch = html.match(
        /<script>window\.INIT_STATE = (.*?)<\/script>/
    );
    if (!jsonMatch) return null;

    try {
        const initState = JSON.parse(jsonMatch[1]);

        // 找到包含内容信息的键
        const contentInfoKey = Object.keys(initState).find(
            (key) => key.startsWith("tusjoh") && (initState[key].photo || initState[key].atlas)
        );

        console.log("内容信息键:", contentInfoKey);

        if (!contentInfoKey) {
            console.error("无法找到内容信息");
            return null;
        }

        const contentInfo = initState[contentInfoKey];
        console.log("内容信息:", contentInfo);

        const isAtlas = !!contentInfo.atlas;
        const photoInfo = contentInfo.photo;
        const atlasInfo = contentInfo.atlas;

        const baseInfo = {
            type: isAtlas ? "image_set" : "video",
            author: photoInfo.userName,
            uid: photoInfo.userId,
            avatar: photoInfo.headUrls[0].url,
            like_count: photoInfo.likeCount,
            comment_count: photoInfo.commentCount,
            time: photoInfo.timestamp,
            title: photoInfo.caption,
            cover: photoInfo.coverUrls[0].url,
            view_count: photoInfo.viewCount,
            share_count: photoInfo.shareCount || 0,
            platform: "kuaishou",
        };

        if (isAtlas) {
            baseInfo.images = atlasInfo.list.map((path, index) => ({
                url: `https://${atlasInfo.cdn[0]}${path}`,
                width: atlasInfo.size[index].w,
                height: atlasInfo.size[index].h,
            }));
        } else {
            baseInfo.url = photoInfo.mainMvUrls[0].url;
            baseInfo.duration = photoInfo.duration;
        }

        return baseInfo;
    } catch (error) {
        console.error("解析JSON数据时出错:", error);
        return null;
    }
}

PS:快手的数据有一个很长的键,我们需要找到它,然后在进行处理,代码中的 contentInfoKey 就是起到这个作用,不过以后会不会变就不知道了,主要是方法,变了以后再改即可。

// 找到包含内容信息的键:tusjoh
const contentInfoKey = Object.keys(initState).find(
    (key) => 
        key.startsWith("tusjoh") && (initState[key].photo || initState[key].atlas)
);

通过 php 编写完整的:

原始版

<?php
class KuaishouParser {
    private $maxRedirects = 5;
    private $userAgent = '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';

    /**
     * 获取最终重定向URL
     * @param string $url 原始URL
     * @return string 最终URL
     */
    public function getFinalRedirectUrl($url) {
        $redirectCount = 0;
        $currentUrl = $url;

        while ($redirectCount < $this->maxRedirects) {
            $ch = curl_init();
            curl_setopt_array($ch, [
                CURLOPT_URL => $currentUrl,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_FOLLOWLOCATION => false,
                CURLOPT_HEADER => true,
                CURLOPT_NOBODY => true,
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_USERAGENT => $this->userAgent
            ]);

            $response = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $headers = $this->parseHeaders($response);
            curl_close($ch);

            if ($httpCode >= 300 && $httpCode < 400 && isset($headers['Location'])) {
                $currentUrl = $this->resolveUrl($headers['Location'], $currentUrl);
                echo "重定向url " . ": $currentUrl\n";
                $redirectCount++;
            } else {
                return $currentUrl;
            }
        }

        echo "警告:达到最大重定向次数 ({$this->maxRedirects})\n";
        return $currentUrl;
    }

    /**
     * 解析响应头
     * @param string $response 响应内容
     * @return array 头信息数组
     */
    private function parseHeaders($response) {
        $headers = [];
        $headerLines = explode("\r\n", substr($response, 0, strpos($response, "\r\n\r\n")));
        foreach ($headerLines as $line) {
            if (strpos($line, ': ') !== false) {
                list($key, $value) = explode(': ', $line, 2);
                $headers[$key] = $value;
            }
        }
        return $headers;
    }

    /**
     * 解析URL相对路径
     * @param string $relative 相对路径
     * @param string $base 基础URL
     * @return string 绝对URL
     */
    private function resolveUrl($relative, $base) {
        if (parse_url($relative, PHP_URL_SCHEME) != '') {
            return $relative;
        }
        $baseParts = parse_url($base);
        $path = preg_replace('#/[^/]*$#', '', $baseParts['path']);
        if ($relative[0] == '/') {
            $path = '';
        }
        $absolute = $baseParts['scheme'] . '://' . $baseParts['host'];
        if (isset($baseParts['port'])) {
            $absolute .= ':' . $baseParts['port'];
        }
        $absolute .= $path . '/' . $relative;
        $absolute = preg_replace('#/(\./)+#', '/', $absolute);
        return preg_replace('#/[^/]+/\.\./#', '/', $absolute);
    }

    /**
     * 提取内容ID
     * @param string $url 最终URL
     * @return string|null 内容ID
     */
    private function extractPhotoId($url) {
        $path = parse_url($url, PHP_URL_PATH);
        if (preg_match('#/photo/([a-zA-Z0-9]+)#', $path, $matches)) {
            return $matches[1];
        }
        return null;
    }

    /**
     * 解析内容信息
     * @param string $html 页面HTML
     * @return array|null 解析结果
     */
    public function parseContentInfo($html) {
        if (preg_match('#<script>window\.INIT_STATE = (.*?)</script>#is', $html, $matches)) {
            try {
                $initState = json_decode($matches[1], true);
                if (json_last_error() !== JSON_ERROR_NONE) {
                    return null;
                }

                $contentInfoKey = null;
                foreach (array_keys($initState) as $key) {
                    if (preg_match('/^tusjoh/', $key) && (isset($initState[$key]['photo']) || isset($initState[$key]['atlas']))) {
                        $contentInfoKey = $key;
                        break;
                    }
                }

                if (!$contentInfoKey) {
                    return null;
                }

                $contentInfo = $initState[$contentInfoKey];
                $isAtlas = isset($contentInfo['atlas']);
                $photoInfo = $contentInfo['photo'] ?? [];
                $atlasInfo = $contentInfo['atlas'] ?? [];

                $baseInfo = [
                    'type' => $isAtlas ? 'image_set' : 'video',
                    'author' => $photoInfo['userName'] ?? '',
                    'uid' => $photoInfo['userId'] ?? '',
                    'avatar' => $this->getFirstUrl($photoInfo['headUrls'] ?? []),
                    'like_count' => $photoInfo['likeCount'] ?? 0,
                    'comment_count' => $photoInfo['commentCount'] ?? 0,
                    'time' => $photoInfo['timestamp'] ?? 0,
                    'title' => $photoInfo['caption'] ?? '',
                    'cover' => $this->getFirstUrl($photoInfo['coverUrls'] ?? []),
                    'view_count' => $photoInfo['viewCount'] ?? 0,
                    'share_count' => $photoInfo['shareCount'] ?? 0,
                    'platform' => 'kuaishou'
                ];

                if ($isAtlas) {
                    $baseInfo['images'] = [];
                    foreach (($atlasInfo['list'] ?? []) as $index => $path) {
                        $cdn = $atlasInfo['cdn'][0] ?? '';
                        $size = $atlasInfo['size'][$index] ?? [];
                        $baseInfo['images'][] = [
                            'url' => "https://$cdn$path",
                            'width' => $size['w'] ?? 0,
                            'height' => $size['h'] ?? 0
                        ];
                    }
                } else {
                    $baseInfo['url'] = $this->getFirstUrl($photoInfo['mainMvUrls'] ?? []);
                    $baseInfo['duration'] = $photoInfo['duration'] ?? 0;
                }

                return $baseInfo;
            } catch (Exception $e) {
                error_log("解析JSON错误: " . $e->getMessage());
                return null;
            }
        }
        return null;
    }

    /**
     * 获取数组中第一个URL
     * @param array $urls URL数组
     * @return string|null 第一个URL
     */
    private function getFirstUrl($urls) {
        if (!empty($urls) && isset($urls[0]['url'])) {
            return $urls[0]['url'];
        }
        return null;
    }

    /**
     * 主解析方法
     * @param string $inputUrl 输入的分享URL
     * @return array 解析结果
     */
    public function parse($inputUrl) {
        // 获取最终重定向URL
        $finalUrl = $this->getFinalRedirectUrl($inputUrl);
        
        // 提取内容ID
        $photoId = $this->extractPhotoId($finalUrl);
        if (!$photoId) {
            return ['error' => '无法提取内容ID'];
        }

        // 构造移动版URL
        $mobileUrl = "https://c.kuaishou.com/fw/photo/{$photoId}";

        // 获取移动版页面内容
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $mobileUrl,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_USERAGENT => $this->userAgent,
            CURLOPT_HTTPHEADER => [
                'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
                'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8'
            ]
        ]);
        $html = curl_exec($ch);
        curl_close($ch);

        if (!$html) {
            return ['error' => '获取页面内容失败'];
        }

        // 解析内容信息
        $result = $this->parseContentInfo($html);
        if (!$result) {
            return ['error' => '解析内容信息失败'];
        }

        return $result;
    }
}

// 使用示例
if (isset($_GET['url'])) {
    $parser = new KuaishouParser();
    $result = $parser->parse(urldecode($_GET['url']));
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} else {
    header('Content-Type: text/plain; charset=utf-8');
    echo "请提供URL参数,示例:?url=https://v.kuaishou.com/dlyk1";
}
?>

请输入图片描述

修改版(去除了反斜线)

<?php
class KuaishouParser {
    private $maxRedirects = 5;
    private $userAgent = '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';

    /**
     * 获取最终重定向URL
     * @param string $url 原始URL
     * @return string 最终URL
     */
    public function getFinalRedirectUrl($url) {
        $redirectCount = 0;
        $currentUrl = $url;

        while ($redirectCount < $this->maxRedirects) {
            $ch = curl_init();
            curl_setopt_array($ch, [
                CURLOPT_URL => $currentUrl,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_FOLLOWLOCATION => false,
                CURLOPT_HEADER => true,
                CURLOPT_NOBODY => true,
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_USERAGENT => $this->userAgent
            ]);

            $response = curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $headers = $this->parseHeaders($response);
            curl_close($ch);

            if ($httpCode >= 300 && $httpCode < 400 && isset($headers['Location'])) {
                $currentUrl = $this->resolveUrl($headers['Location'], $currentUrl);
                $redirectCount++;
            } else {
                return $this->cleanUrl($currentUrl);
            }
        }

        return $this->cleanUrl($currentUrl);
    }

    /**
     * 解析响应头
     * @param string $response 响应内容
     * @return array 头信息数组
     */
    private function parseHeaders($response) {
        $headers = [];
        $headerText = substr($response, 0, strpos($response, "\r\n\r\n"));
        foreach (explode("\r\n", $headerText) as $i => $line) {
            if ($i === 0) {
                $headers['http_code'] = $line;
            } else {
                list($key, $value) = explode(': ', $line);
                $headers[$key] = $value;
            }
        }
        return $headers;
    }

    /**
     * 解析URL相对路径
     * @param string $relative 相对路径
     * @param string $base 基础URL
     * @return string 绝对URL
     */
    private function resolveUrl($relative, $base) {
        if (parse_url($relative, PHP_URL_SCHEME) != '') {
            return $relative;
        }
        $baseParts = parse_url($base);
        $path = preg_replace('#/[^/]*$#', '', $baseParts['path']);
        if ($relative[0] == '/') {
            $path = '';
        }
        $absolute = $baseParts['scheme'] . '://' . $baseParts['host'];
        if (isset($baseParts['port'])) {
            $absolute .= ':' . $baseParts['port'];
        }
        $absolute .= $path . '/' . $relative;
        $absolute = preg_replace('#/(\./)+#', '/', $absolute);
        return preg_replace('#/[^/]+/\.\./#', '/', $absolute);
    }

    /**
     * 提取内容ID
     * @param string $url 最终URL
     * @return string|null 内容ID
     */
    private function extractPhotoId($url) {
        $path = parse_url($url, PHP_URL_PATH);
        if (preg_match('#/photo/([a-zA-Z0-9]+)#', $path, $matches)) {
            return $matches[1];
        }
        return null;
    }

    /**
     * 解析内容信息
     * @param string $html 页面HTML
     * @return array|null 解析结果
     */
    public function parseContentInfo($html) {
        if (preg_match('#<script>window\.INIT_STATE\s*=\s*(.*?)</script>#is', $html, $matches)) {
            try {
                $initState = json_decode($matches[1], true);
                if (json_last_error() !== JSON_ERROR_NONE) {
                    return null;
                }

                $contentInfoKey = null;
                foreach (array_keys($initState) as $key) {
                    if (preg_match('/^tusjoh/', $key) && (isset($initState[$key]['photo']) || isset($initState[$key]['atlas']))) {
                        $contentInfoKey = $key;
                        break;
                    }
                }

                if (!$contentInfoKey) {
                    return null;
                }

                $contentInfo = $initState[$contentInfoKey];
                $isAtlas = isset($contentInfo['atlas']);
                $photoInfo = $contentInfo['photo'] ?? [];
                $atlasInfo = $contentInfo['atlas'] ?? [];

                $baseInfo = [
                    'type' => $isAtlas ? 'image_set' : 'video',
                    'author' => $photoInfo['userName'] ?? '',
                    'uid' => $photoInfo['userId'] ?? '',
                    'avatar' => $this->cleanUrl($this->getFirstUrl($photoInfo['headUrls'] ?? [])),
                    'like_count' => $photoInfo['likeCount'] ?? 0,
                    'comment_count' => $photoInfo['commentCount'] ?? 0,
                    'time' => $photoInfo['timestamp'] ?? 0,
                    'title' => $photoInfo['caption'] ?? '',
                    'cover' => $this->cleanUrl($this->getFirstUrl($photoInfo['coverUrls'] ?? [])),
                    'view_count' => $photoInfo['viewCount'] ?? 0,
                    'share_count' => $photoInfo['shareCount'] ?? 0,
                    'platform' => 'kuaishou'
                ];

                if ($isAtlas) {
                    $baseInfo['images'] = [];
                    foreach (($atlasInfo['list'] ?? []) as $index => $path) {
                        $cdn = $atlasInfo['cdn'][0] ?? '';
                        $size = $atlasInfo['size'][$index] ?? [];
                        $baseInfo['images'][] = [
                            'url' => $this->cleanUrl("https://$cdn$path"),
                            'width' => $size['w'] ?? 0,
                            'height' => $size['h'] ?? 0
                        ];
                    }
                } else {
                    $baseInfo['url'] = $this->cleanUrl($this->getFirstUrl($photoInfo['mainMvUrls'] ?? []));
                    $baseInfo['duration'] = $photoInfo['duration'] ?? 0;
                }

                return $baseInfo;
            } catch (Exception $e) {
                return null;
            }
        }
        return null;
    }

    /**
     * 获取数组中第一个URL
     * @param array $urls URL数组
     * @return string|null 第一个URL
     */
    private function getFirstUrl($urls) {
        if (!empty($urls) && isset($urls[0]['url'])) {
            return $urls[0]['url'];
        }
        return null;
    }

    /**
     * 清理URL中的反斜杠
     * @param string $url 原始URL
     * @return string 清理后的URL
     */
    private function cleanUrl($url) {
        if (is_array($url)) {
            return array_map([$this, 'cleanUrl'], $url);
        }
        return $url ? str_replace('\\', '', $url) : $url;
    }

    /**
     * 主解析方法
     * @param string $inputUrl 输入的分享URL
     * @return array 解析结果
     */
    public function parse($inputUrl) {
        $finalUrl = $this->getFinalRedirectUrl($inputUrl);
        $photoId = $this->extractPhotoId($finalUrl);
        
        if (!$photoId) {
            return ['error' => '无法提取内容ID'];
        }

        $mobileUrl = "https://c.kuaishou.com/fw/photo/{$photoId}";
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $mobileUrl,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_USERAGENT => $this->userAgent,
            CURLOPT_HTTPHEADER => [
                'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
                'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8'
            ]
        ]);
        $html = curl_exec($ch);
        curl_close($ch);

        if (!$html) {
            return ['error' => '获取页面内容失败'];
        }

        $result = $this->parseContentInfo($html);
        if (!$result) {
            return ['error' => '解析内容信息失败'];
        }

        // 深度清理结果中的所有URL
        array_walk_recursive($result, function(&$value, $key) {
            if (is_string($value) && (strpos($key, 'url') !== false || strpos($value, 'http') === 0)) {
                $value = str_replace('\\', '', $value);
            }
        });

        return $result;
    }
}

// 使用示例
if (isset($_GET['url'])) {
    $parser = new KuaishouParser();
    $result = $parser->parse(trim(urldecode($_GET['url'])));
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($result, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
} else {
    header('Content-Type: text/plain; charset=utf-8');
    echo "请提供URL参数,示例:?url=https://v.kuaishou.com/dlyk1";
}
?>

请输入图片描述

方法二

用Java语言实现快手视频、及图文解析的代码:

  • 代码示例
import cn.hutool.core.map.MapUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.net.HttpCookie;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 - @description: 快手视频解析
 */
@Slf4j
public class Analysis {
    private static final String XG = "/";

    /**
     * 快手链接请求解析
     * @param url 链接地址
     * @return list
     */
    private List<String> dealVideoKs(String url) {
        try {
            // 正则表达式提取链接
            url = getPattern(url);
            // 链接格式化
            url = dealCharStrLast(url);

            // 设置为浏览器格式的请求头
            HashMap<String, String> headers = MapUtil.newHashMap();
            headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36");

            HttpResponse httpResponse = HttpUtil.createGet(url).addHeaders(headers).execute();
            if (httpResponse != null && httpResponse.getStatus() == 302) {
                url = httpResponse.header("Location");
            }

            HttpResponse httpRes = HttpUtil.createGet(url).addHeaders(headers).execute();
            if (httpRes != null) {
                if (httpRes.getStatus() == 302) {
                    // 视频解析
                    return videoUrlKs(httpRes, headers);
                } else if (httpRes.getStatus() == 200) {
                    // 图片解析
                    return imageUrlKs(httpRes);
                }
            }
        } catch (Exception e) {
            log.error("视频解析失败!{}", (Object) e.getStackTrace());
        }
        return new ArrayList<>();
    }

    /**
     * 图片解析
     * @param httpRes 请求
     * @return list
     */
    private List<String> imageUrlKs(HttpResponse httpRes) {
        Document parse = Jsoup.parse(httpRes.body());
        Elements script = parse.select("script");
        String data = "";

        for (Element element : script) {
            if (element.data().contains("window.INIT_STATE =")) {
                data = element.data();
                break;
            }
        }

        if (StringUtils.isNotBlank(data)) {
            String trim = data.split("window.INIT_STATE =")[1].trim();
            JSONObject object = JSON.parseObject(trim, JSONObject.class);
            Iterator<String> iterator = object.keySet().iterator();
            JSONObject atlas = null;

            while (iterator.hasNext()) {
                String key = iterator.next();
                String value = object.get(key).toString();
                JSONObject obj = JSON.parseObject(value, JSONObject.class);
                if (obj.containsKey("fid") && obj.containsKey("atlas")) {
                    atlas = obj.getJSONObject("atlas");
                    break;
                }
            }

            if (atlas != null) {
                // 获取域名
                String host = atlas.getJSONArray("cdn").getStr(0);
                if (!host.contains("https://")) {
                    host = "https://" + host;
                }

                // 获取图片
                List<String> list = new ArrayList<>();
                JSONArray array = atlas.getJSONArray("list");
                for (Object a : array) {
                    list.add(host + a);
                }
                return list;
            }
        }
        return new ArrayList<>();
    }

    /**
     * 快手视频地址解析
     * @param httpRes 请求
     * @param headers 请求头
     * @return list
     */
    private List<String> videoUrlKs(HttpResponse httpRes, HashMap<String, String> headers) throws Exception {
        String cookie = "";
        List<HttpCookie> cookies = httpRes.getCookies();
        if (cookies != null && !cookies.isEmpty()) {
            String didv = "didv", did = "did";
            for (HttpCookie hc : cookies) {
                if (didv.equals(hc.getName())) {
                    didv = hc.getValue();
                }
                if (did.equals(hc.getName())) {
                    did = hc.getValue();
                }
            }
            cookie = "didv=" + didv + "; did=" + did;
        }

        // url链接参数
        Map<String, String> map = getMapUrl(httpRes.header("Location"));

        // 拼接快手视频地址
        String videoUrl = "https://live.kuaishou.com/live_api/profile/feedbyid?photoId=" + map.get("photoId") + "&principalId=" + map.get("userId");

        // 设置cookie参数
        headers.put("Cookie", cookie);

        HttpResponse execute = HttpUtil.createGet(videoUrl).addHeaders(headers).execute();
        JSONObject object = JSON.parseObject(execute.body(), JSONObject.class);
        JSONObject currentJson = object.getJSONObject("data").getJSONObject("currentWork");
        String playUrl = currentJson.getStr("playUrl");
        if (StringUtils.isNotBlank(playUrl)) {
            return Collections.singletonList(playUrl);
        }
        return new ArrayList<>();
    }

    /**
     * url链接参数
     * @param url 链接
     * @return map
     */
    private Map<String, String> getMapUrl(String url) throws Exception {
        Map<String, String> map = new HashMap<>();
        // 解析请求
        String[] split = new URL(url).getQuery().split("&");
        for (String s : split) {
            String[] userStr = s.split("=");
            if (userStr.length > 1) {
                map.put(userStr[0], userStr[1]);
            }
        }
        return map;
    }

    /**
     * 正则表达式提取链接
     * @param url 地址
     * @return string
     */
    private static String getPattern(String url) {
        // 定义正则表达式,并创建Pattern对象
        Pattern pattern = Pattern.compile("https?://\\S+");
        // 创建Matcher对象
        Matcher matcher = pattern.matcher(url);
        // 使用List来存储匹配的链接
        List<String> urlList = new ArrayList<>();
        // 循环查找匹配的链接
        while (matcher.find()) {
            // 将匹配的链接添加到列表中
            urlList.add(matcher.group());
        }
        if (!urlList.isEmpty()) {
            url = urlList.get(0);
        }
        return url;
    }

    /**
     * 如果最后一个字符是 / 的话,则去掉 / 字符
     * @param str 需要处理的链接
     * @return string
     */
    private String dealCharStrLast(String str) {
        if (StringUtils.isNotBlank(str)) {
            String last = str.substring(str.length() - 1);
            if (XG.equals(last)) {
                str = str.substring(0, str.length() - 1);
            }
        }
        return str;
    }
}
  • 测试
public static void main(String[] args) {
    String url = "https://v.kuaishou.com/4eQnbu";
    List<String> urlList = dealVideoKs(url);
    System.out.println(urlList);
}
  • 使用依赖
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.32</version>
</dependency>
<dependency>
    <groupId>com.alibaba.fastjson2</groupId>
    <artifactId>fastjson2</artifactId>
    <version>2.0.52</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.26</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.17.2</version>
</dependency>
  • 执行第二步测试得到的结果,复制到浏览器便可下载


Last Modified: May 7, 2025
Archives QR Code Tip
QR Code for this page
Tipping QR Code