API仅供学习交流使用,禁止用于商业用途、违法用途等,否则后果自负!
其实都是大差不差的,首先获取到小红书的分享链接,然后重定向到原地址,然后直接请求这个地址,获取到网页HTML,直接从中提取,这里比抖音快手还要简单一些。支持图集/视频。
具体代码如下:
async getRedirectUrl(url) {
try {
const response = await this.curl(url, {
method: "GET",
headers: this.headers,
followRedirect: false,
});
return this.safeGet(response, 'headers.location', url);
} catch (error) {
console.error("获取重定向URL时出错:", error);
throw error;
}
}
async getHtml(url) {
try {
const response = await this.curl(url, {
headers: this.headers,
dataType: "text",
});
return this.safeGet(response, 'data', null);
} catch (error) {
console.error("获取网页内容失败:", error);
return null;
}
}
parseHtml(html) {
const jsonMatch = html.match(
/<script>window\.__INITIAL_STATE__=(.*?)<\/script>/
);
if (!jsonMatch || jsonMatch.length < 2) {
console.error("无法找到笔记信息");
return null;
}
try {
let jsonString = jsonMatch[1].replace(/undefined/g, "null");
const data = JSON.parse(jsonString);
const noteId = Object.keys(
this.safeGet(data, 'note.noteDetailMap', {})
)[0];
if (!noteId) {
console.error("无法找到笔记ID");
return null;
}
const noteData = this.safeGet(
data,
`note.noteDetailMap.${noteId}.note`,
null
);
if (!noteData) {
console.error("无法获取笔记数据");
return null;
}
const result = {
title: this.safeGet(noteData, 'title', ''),
desc: this.safeGet(noteData, 'desc', ''),
type: this.safeGet(noteData, 'type', ''),
user: {
nickname: this.safeGet(noteData, 'user.nickname', ''),
avatar: this.safeGet(noteData, 'user.avatar', ''),
userId: this.safeGet(noteData, 'user.userId', ''),
},
time: this.safeGet(noteData, 'time', ''),
likes: this.safeGet(noteData, 'interactInfo.likedCount', '0'),
comments: this.safeGet(noteData, 'interactInfo.commentCount', '0'),
collects: this.safeGet(noteData, 'interactInfo.collectedCount', '0'),
view_count: this.safeGet(noteData, 'interactInfo.viewCount', '0'),
share_count: this.safeGet(noteData, 'interactInfo.shareCount', '0'),
platform: "xiaohongshu",
};
if (noteData.type === "video") {
result.video = {
url: this.safeGet(
noteData,
'video.media.stream.h264.0.masterUrl',
''
),
cover: this.safeGet(noteData, 'video.cover.url', ''),
};
} else {
result.images = this.safeGet(noteData, 'imageList', [])
.map((img) => ({
url: this.safeGet(img, 'urlDefault', '') ||
this.safeGet(img, 'url', ''),
width: this.safeGet(img, 'width', 0),
height: this.safeGet(img, 'height', 0),
}));
}
return result;
} catch (error) {
console.error("解析笔记信息失败:", error);
return null;
}
}
// 辅助方法:将字符串解析为数字
parseNumber(value) {
if (typeof value === "number") return value;
if (!value) return 0;
const num = parseInt(value.replace(/[^0-9]/g, ""));
return isNaN(num) ? 0 : num;
}
safeGet(obj, path, defaultValue = '') {
return path.split('.').reduce((acc, part) => {
if (acc && typeof acc === 'object' && part in acc) {
return acc[part];
}
return defaultValue;
}, obj);
}
编译后的php版:
<!--小红书解析-->
<?php
header("Content-Type: application/json; charset=utf-8");
header("Access-Control-Allow-Origin: *");
class RedBookParser {
private $headers = [
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
'Referer: https://www.xiaohongshu.com/',
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8'
];
public function getRedirectUrl($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_exec($ch);
$location = '';
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) === 302) {
$location = curl_getinfo($ch, CURLINFO_REDIRECT_URL);
}
curl_close($ch);
return $location ?: $url;
}
public function getHtml($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$html = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("获取网页失败,状态码:{$httpCode}");
}
return $html;
}
public function parseHtml($html) {
if (!preg_match('/<script>window\.__INITIAL_STATE__=(.*?)<\/script>/s', $html, $matches)) {
throw new Exception("无法找到笔记信息");
}
$jsonString = str_replace('undefined', 'null', $matches[1]);
$data = json_decode($jsonString, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception("JSON解析失败:" . json_last_error_msg());
}
$noteDetailMap = $this->safeGet($data, 'note.noteDetailMap', []);
if (empty($noteDetailMap)) {
throw new Exception("无法找到笔记ID");
}
reset($noteDetailMap);
$noteId = key($noteDetailMap);
$noteData = $this->safeGet($data, "note.noteDetailMap.{$noteId}.note", []);
if (empty($noteData)) {
throw new Exception("无法获取笔记数据");
}
$result = [
'title' => $this->safeGet($noteData, 'title', ''),
'desc' => $this->safeGet($noteData, 'desc', ''),
'type' => $this->safeGet($noteData, 'type', ''),
'user' => [
'nickname' => $this->safeGet($noteData, 'user.nickname', ''),
'avatar' => $this->safeGet($noteData, 'user.avatar', ''),
'userId' => $this->safeGet($noteData, 'user.userId', ''),
],
'time' => $this->safeGet($noteData, 'time', ''),
'likes' => $this->parseNumber($this->safeGet($noteData, 'interactInfo.likedCount', '0')),
'comments' => $this->parseNumber($this->safeGet($noteData, 'interactInfo.commentCount', '0')),
'collects' => $this->parseNumber($this->safeGet($noteData, 'interactInfo.collectedCount', '0')),
'view_count' => $this->parseNumber($this->safeGet($noteData, 'interactInfo.viewCount', '0')),
'share_count' => $this->parseNumber($this->safeGet($noteData, 'interactInfo.shareCount', '0')),
'platform' => 'xiaohongshu'
];
if ($noteData['type'] === 'video') {
$result['video'] = [
'url' => $this->safeGet($noteData, 'video.media.stream.h264.0.masterUrl', ''),
'cover' => $this->safeGet($noteData, 'video.cover.url', '')
];
} else {
$result['images'] = array_map(function($img) {
return [
'url' => $this->safeGet($img, 'urlDefault', '') ?: $this->safeGet($img, 'url', ''),
'width' => $this->parseNumber($this->safeGet($img, 'width', 0)),
'height' => $this->parseNumber($this->safeGet($img, 'height', 0))
];
}, $this->safeGet($noteData, 'imageList', []));
}
return $result;
}
private function safeGet($obj, $path, $default = '') {
$keys = explode('.', $path);
$current = $obj;
foreach ($keys as $key) {
if (is_array($current) && array_key_exists($key, $current)) {
$current = $current[$key];
} else {
return $default;
}
}
return $current ?: $default;
}
private function parseNumber($value) {
if (is_numeric($value)) return (int)$value;
if (empty($value)) return 0;
$num = preg_replace('/[^0-9]/', '', $value);
return is_numeric($num) ? (int)$num : 0;
}
}
try {
if (!isset($_GET['url'])) {
throw new Exception("请提供分享链接参数(?url=链接)");
}
$parser = new RedBookParser();
$redirectUrl = $parser->getRedirectUrl($_GET['url']);
$html = $parser->getHtml($redirectUrl);
$result = $parser->parseHtml($html);
// 关键修改:添加JSON_UNESCAPED_SLASHES禁止斜杠转义
echo json_encode([
'code' => 200,
'data' => $result
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
} catch (Exception $e) {
echo json_encode([
'code' => 500,
'message' => $e->getMessage()
], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}
?>