
Xhs Note Creator
- 61 installs
- 263 repo stars
- Updated July 22, 2026
- zrt-ai-lab/opencode-skills
Helps with ai & agent building tasks during AI-assisted development.
About
xhs-note-creator is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- xhs-note-creator
- AI & Agent Building
- AI-coding skill
Xhs Note Creator by the numbers
- 61 all-time installs (skills.sh)
- Ranked #6,230 of 16,556 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/zrt-ai-lab/opencode-skills --skill xhs-note-creatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 61 |
|---|---|
| repo stars | ★ 263 |
| Last updated | July 22, 2026 |
| Repository | zrt-ai-lab/opencode-skills ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
小红书笔记创作技能
这个技能用于创建专业的小红书笔记素材,包括内容撰写、图片卡片生成和笔记发布。
使用场景
- 用户需要创建小红书笔记时
- 用户提供资料需要转化为小红书风格内容时
- 用户需要生成精美的图片卡片用于发布时
工作流程
第一步:撰写小红书笔记内容
根据用户需求和提供的资料,创作符合小红书风格的内容:
标题要求
- 不超过 20 字
- 吸引眼球,制造好奇心
- 可使用数字、疑问句、感叹号增强吸引力
- 示例:「5个让效率翻倍的神器推荐!」「震惊!原来这样做才对」
正文要求
- 使用良好的排版,段落清晰
- 点缀少量 Emoji 增加可读性(每段 1-2 个即可)
- 使用简短的句子和段落
- 结尾给出 SEO 友好的 Tags 标签(5-10 个相关标签)
第二步:生成 Markdown 文档
注意:这里生成的 Markdown 文档是用于渲染卡片的,必须专门生成,禁止直接使用上一步的笔记正文内容。
Markdown 文件,文件应包含:
1. YAML 头部元数据(封面信息):
---
emoji: "🚀" # 封面装饰 Emoji
title: "大标题" # 封面大标题(不超过15字)
subtitle: "副标题文案" # 封面副标题(不超过15字)
---2. 用于渲染卡片的 Markdown 文本内容:
- 当待渲染内容必须严格切分为独立的数张图片时,可使用
---分割线主动将正文分隔为多个卡片段落(每个段落文本控制在 200 字左右),输出图片时使用参数-m separator - 当待渲染内容无需严格分割,生成正常 Markdown 文本即可,跟下方分页模式参数规则按需选择
完整 Markdown 文档内容示例:
---
emoji: "💡"
title: "5个效率神器让工作效率翻倍"
subtitle: "对着抄作业就好了,一起变高效"
---
# 📝 神器一:Notion
> 全能型笔记工具,支持数据库、看板、日历等多种视图...
## 特色功能
- 特色一
- 特色二
# ⚡ 神器二:Raycast
\`\`\`
可使用代码块来增加渲染后图片的视觉丰富度
\`\`\`
## 推荐原因
- 原因一
- 原因二
- ……
# 🌈 神器三:Arc
全新理念的浏览器,侧边栏标签管理...
...
第三步:渲染图片卡片
将 Markdown 文档渲染为图片卡片。使用以下脚本渲染:
python scripts/render_xhs.py <markdown_file> [options]- 默认输出目录为当前工作目录
- 生成的图片包括:封面(cover.png)和正文卡片(card_1.png, card_2.png, ...)
渲染参数(Python)
| 参数 | 简写 | 说明 | 默认值 |
|---|---|---|---|
--output-dir | -o | 输出目录 | 当前工作目录 |
--theme | -t | 排版主题 | default |
--mode | -m | 分页模式 | separator |
--width | -w | 图片宽度 | 1080 |
--height | 图片高度(dynamic 下为最小高度) | 1440 | |
--max-height | dynamic 最大高度 | 4320 | |
--dpr | 设备像素比(清晰度) | 2 |
排版主题(--theme)
default:默认简约浅灰渐变背景(#f3f3f3 -> #f9f9f9)playful-geometric:活泼几何(Memphis)neo-brutalism:新粗野主义botanical:植物园自然professional:专业商务retro:复古怀旧terminal:终端命令行sketch:手绘素描
分页模式(--mode)
separator:按---分隔符分页(适合内容已手动控量)auto-fit:固定尺寸下自动缩放文字,避免溢出/留白(适合封面+单张图片但尺寸固定的情况)auto-split:按渲染后高度自动切分分页(适合切分不影响阅读的长文内容)dynamic:根据内容动态调整图片高度(注意:图片最高 4320,字数超过 550 的不建使用此模式)
常用示例
# 1) 默认主题 + 手动分隔分页
python scripts/render_xhs.py content.md -m separator
# 2) 固定 1080x1440,自动缩放文字,尽量填满画面
python scripts/render_xhs.py content.md -m auto-fit
# 3) 自动切分分页(推荐:内容长短不稳定)
python scripts/render_xhs.py content.md -m auto-split
# 4) 动态高度(允许不同高度卡片)
python scripts/render_xhs.py content.md -m dynamic --max-height 4320
# 5) 切换主题
python scripts/render_xhs.py content.md -t playful-geometric -m auto-splitNode.js 渲染(可选)
node scripts/render_xhs.js content.md -t default -m separatorNode.js 参数与 Python 基本一致:--output-dir/-o、--theme/-t、--mode/-m、--width/-w、--height、--max-height、--dpr。
第四步:发布小红书笔记(可选)
使用发布脚本将生成的图片发布到小红书:
python scripts/publish_xhs.py --title "笔记标题" --desc "笔记描述" --images card_1.png card_2.png cover.png前置条件:
1. 需配置小红书 Cookie:
XHS_COOKIE=your_cookie_string_here2. Cookie 获取方式:
- 在浏览器中登录小红书(https://www.xiaohongshu.com)
- 打开开发者工具(F12)
- 在 Network 标签中查看请求头的 Cookie
图片规格说明
封面卡片
- 尺寸比例:3:4(小红书推荐比例)
- 基准尺寸:1080×1440px
- 包含:Emoji 装饰、大标题、副标题
- 样式:渐变背景 + 圆角内容区
正文卡片
- 尺寸比例:3:4
- 基准尺寸:1080×1440px
- 支持:标题、段落、列表、引用、代码块、图片
- 样式:白色卡片 + 渐变背景边框
技能资源
脚本文件
scripts/render_xhs.py- Python 渲染脚本scripts/render_xhs.js- Node.js 渲染脚本scripts/publish_xhs.py- 小红书发布脚本
资源文件
assets/cover.html- 封面 HTML 模板assets/card.html- 正文卡片 HTML 模板assets/styles.css- 共用样式表
注意事项
1. Markdown 文件应保存在工作目录,渲染后的图片也保存在工作目录 2. 技能目录 (md2Redbook/) 仅存放脚本和模板,不存放用户数据 3. 图片尺寸会根据内容自动调整,但保持 3:4 比例 4. Cookie 有有效期限制,过期后需要重新获取 5. 发布功能依赖 xhs 库,需要安装:pip install xhs
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1080">
<title>小红书卡片</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Noto Sans SC', 'Source Han Sans CN', 'PingFang SC', 'Microsoft YaHei', sans-serif;
width: 1080px;
min-height: 1440px;
overflow: hidden;
background: transparent;
}
.card-container {
width: 1080px;
min-height: 1440px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
position: relative;
padding: 50px;
overflow: hidden;
}
.card-inner {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 60px;
min-height: calc(1440px - 100px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
}
/* Markdown 内容样式 */
.card-content {
color: #475569;
font-size: 42px;
line-height: 1.7;
}
.card-content h1 {
font-size: 72px;
font-weight: 700;
color: #1e293b;
margin-bottom: 40px;
line-height: 1.3;
}
.card-content h2 {
font-size: 56px;
font-weight: 600;
color: #334155;
margin: 50px 0 25px 0;
line-height: 1.4;
}
.card-content h3 {
font-size: 48px;
font-weight: 600;
color: #475569;
margin: 40px 0 20px 0;
}
.card-content p {
margin-bottom: 35px;
}
.card-content strong {
font-weight: 700;
color: #1e293b;
}
.card-content em {
font-style: italic;
color: #6366f1;
}
.card-content a {
color: #6366f1;
text-decoration: none;
border-bottom: 2px solid #6366f1;
}
.card-content ul,
.card-content ol {
margin: 30px 0;
padding-left: 60px;
}
.card-content li {
margin-bottom: 20px;
line-height: 1.6;
}
.card-content blockquote {
border-left: 8px solid #6366f1;
padding-left: 40px;
background: #f1f5f9;
padding-top: 25px;
padding-bottom: 25px;
padding-right: 30px;
margin: 35px 0;
color: #64748b;
font-style: italic;
border-radius: 0 12px 12px 0;
}
.card-content blockquote p {
margin: 0;
}
.card-content code {
background: #f1f5f9;
padding: 6px 16px;
border-radius: 8px;
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
font-size: 38px;
color: #6366f1;
}
.card-content pre {
background: #1e293b;
color: #e2e8f0;
padding: 40px;
border-radius: 16px;
margin: 35px 0;
overflow-x: visible;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
font-size: 36px;
line-height: 1.5;
}
.card-content pre code {
background: transparent;
color: inherit;
padding: 0;
font-size: inherit;
}
.card-content img {
max-width: 100%;
height: auto;
border-radius: 16px;
margin: 35px auto;
display: block;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.card-content hr {
border: none;
height: 2px;
background: #e2e8f0;
margin: 50px 0;
}
/* Tags 标签样式 */
.tags-container {
margin-top: 50px;
padding-top: 30px;
border-top: 2px solid #e2e8f0;
}
.tag {
display: inline-block;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: white;
padding: 12px 28px;
border-radius: 30px;
font-size: 34px;
margin: 10px 15px 10px 0;
font-weight: 500;
}
/* 信息卡片样式 */
.info-card {
margin: 40px 0;
padding: 40px 50px;
background: linear-gradient(135deg, rgba(99, 102, 241, 0.12) 0%, rgba(139, 92, 246, 0.12) 100%);
border: 2px solid rgba(99, 102, 241, 0.2);
border-radius: 20px;
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.1);
}
/* 页码样式 */
.page-number {
position: absolute;
bottom: 80px;
right: 80px;
font-size: 36px;
color: rgba(255, 255, 255, 0.8);
font-weight: 500;
}
</style>
</head>
<body>
<div class="card-container">
<div class="card-inner">
<div class="card-content">
{{CONTENT}}
</div>
</div>
<div class="page-number">{{PAGE_NUMBER}}</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1080, height=1440">
<title>小红书封面</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Noto Sans SC', 'Source Han Sans CN', 'PingFang SC', 'Microsoft YaHei', sans-serif;
width: 1080px;
height: 1440px;
overflow: hidden;
}
.cover-container {
width: 1080px;
height: 1440px;
background: linear-gradient(180deg, #f3f3f3 0%, #f9f9f9 100%);
position: relative;
overflow: hidden;
}
.cover-inner {
position: absolute;
width: 950px;
height: 1310px;
left: 65px;
top: 65px;
background: #F3F3F3;
border-radius: 25px;
display: flex;
flex-direction: column;
padding: 80px 85px;
}
.cover-emoji {
font-size: 180px;
line-height: 1.2;
margin-bottom: 50px;
}
.cover-title {
font-weight: 900;
font-size: 130px;
line-height: 1.4;
background: linear-gradient(180deg, #111827 0%, #4B5563 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
flex: 1;
display: flex;
align-items: flex-start;
word-break: break-all;
}
.cover-subtitle {
font-weight: 350;
font-size: 72px;
line-height: 1.4;
color: #000000;
margin-top: auto;
}
</style>
</head>
<body>
<div class="cover-container">
<div class="cover-inner">
<div class="cover-emoji">{{EMOJI}}</div>
<div class="cover-title">{{TITLE}}</div>
<div class="cover-subtitle">{{SUBTITLE}}</div>
</div>
</div>
</body>
</html>
活泼几何风格 💜
这是 Playful Geometric 风格的示例,灵感来自 Memphis 设计。
糖果般的色彩搭配,硬边框与圆角的对比,让内容更有活力!
特点
- 紫色、粉色、黄色的俏皮配色
- 硬阴影贴纸效果
- 非对称圆角设计
---
新粗野主义风格 ⚡
Neo-Brutalism 风格:RAW. LOUD. UNAPOLOGETIC.
厚重的黑色边框,高饱和度色块,这就是反设计美学!
视觉特点
- 5px 以上的粗边框
- 8-10px 的硬阴影
- 荧光黄与电光青的撞色
---
植物园风格 🌿
Botanical 风格带来自然清新的感觉。
森林绿与米白的搭配,仿佛置身于植物园中
设计理念
- 柔和的绿色系配色
- 自然有机的圆角
- 温暖的米色背景
---
更多风格选择
还有更多精美风格等你探索:
1. Professional - 专业商务风格 2. Retro - 复古怀旧风格 3. Terminal - 终端命令行风格 4. Sketch - 手绘素描风格
使用命令:
python render_xhs.py example.md -t playful-geometric#小红书模板 #排版设计 #内容创作
/* 小红书卡片样式 */
/* CSS 变量定义 */
:root {
--primary-color: #ff2442;
--primary-light: #ff6b81;
--text-primary: #1e293b;
--text-secondary: #334155;
--text-muted: #64748b;
--text-light: #475569;
--background-light: #f8fafc;
--background-gray: #f1f5f9;
--border-color: #e2e8f0;
--border-radius: 25px;
--card-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
--font-family: 'Source Han Sans CN', 'PingFang SC', 'Microsoft YaHei', -apple-system, sans-serif;
}
/* 全局样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-family);
background: transparent;
color: var(--text-secondary);
line-height: 1.6;
}
/* ========================
封面卡片样式
======================== */
.cover-container {
width: 1080px;
height: 1440px;
background: linear-gradient(180deg, #f3f3f3 0%, #f9f9f9 100%);
position: relative;
overflow: hidden;
}
.cover-inner {
position: absolute;
width: 950px;
height: 1310px;
left: 65px;
top: 65px;
background: #F3F3F3;
border-radius: var(--border-radius);
display: flex;
flex-direction: column;
padding: 60px 85px;
}
.cover-emoji {
font-size: 180px;
line-height: 1.2;
margin-bottom: 40px;
}
.cover-title {
font-weight: 900;
font-size: 130px;
line-height: 1.35;
background: linear-gradient(180deg, #111827 0%, #4B5563 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: auto;
word-break: break-all;
}
.cover-subtitle {
font-weight: 350;
font-size: 72px;
line-height: 1.4;
color: #000000;
margin-top: auto;
padding-bottom: 20px;
}
/* ========================
正文卡片样式
======================== */
.card-container {
width: 1080px;
min-height: 1440px;
background: linear-gradient(180deg, #f3f3f3 0%, #f9f9f9 100%);
position: relative;
padding: 50px;
overflow: hidden;
}
.card-inner {
background: rgba(255, 255, 255, 0.95);
border-radius: 20px;
padding: 60px;
min-height: calc(1440px - 100px);
box-shadow: var(--card-shadow);
backdrop-filter: blur(10px);
}
/* Markdown 内容样式 */
.card-content {
color: var(--text-light);
font-size: 42px;
line-height: 1.7;
}
.card-content h1 {
font-size: 72px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 40px;
line-height: 1.3;
}
.card-content h2 {
font-size: 56px;
font-weight: 600;
color: var(--text-secondary);
margin: 50px 0 25px 0;
line-height: 1.4;
}
.card-content h3 {
font-size: 48px;
font-weight: 600;
color: var(--text-light);
margin: 40px 0 20px 0;
}
.card-content p {
margin-bottom: 35px;
}
.card-content strong {
font-weight: 700;
color: var(--text-primary);
}
.card-content em {
font-style: italic;
color: var(--primary-color);
}
.card-content a {
color: var(--primary-color);
text-decoration: none;
border-bottom: 2px solid var(--primary-color);
}
.card-content ul,
.card-content ol {
margin: 30px 0;
padding-left: 60px;
}
.card-content li {
margin-bottom: 20px;
line-height: 1.6;
}
.card-content blockquote {
border-left: 8px solid var(--primary-color);
padding-left: 40px;
background: var(--background-gray);
padding-top: 25px;
padding-bottom: 25px;
margin: 35px 0;
color: var(--text-muted);
font-style: italic;
border-radius: 0 12px 12px 0;
}
.card-content blockquote p {
margin: 0;
}
.card-content code {
background: var(--background-gray);
padding: 6px 16px;
border-radius: 8px;
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
font-size: 38px;
color: var(--primary-color);
}
.card-content pre {
background: var(--text-primary);
color: #e2e8f0;
padding: 40px;
border-radius: 16px;
margin: 35px 0;
overflow-x: visible;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
font-size: 36px;
line-height: 1.5;
}
.card-content pre code {
background: transparent;
color: inherit;
padding: 0;
font-size: inherit;
}
.card-content img {
max-width: 100%;
height: auto;
border-radius: 16px;
margin: 35px auto;
display: block;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.card-content hr {
border: none;
height: 2px;
background: var(--border-color);
margin: 50px 0;
}
/* Tags 标签样式 */
.card-content .tags {
margin-top: 50px;
padding-top: 30px;
border-top: 2px solid var(--border-color);
}
.card-content .tag {
display: inline-block;
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-light) 100%);
color: white;
padding: 12px 28px;
border-radius: 30px;
font-size: 34px;
margin: 10px 15px 10px 0;
font-weight: 500;
}
/* 信息卡片样式 */
.info-card {
margin: 40px 0;
padding: 40px 50px;
background: linear-gradient(135deg, rgba(99, 102, 241, 0.12) 0%, rgba(139, 92, 246, 0.12) 100%);
border: 2px solid rgba(99, 102, 241, 0.2);
border-radius: 20px;
box-shadow: 0 4px 20px rgba(99, 102, 241, 0.1);
}
.info-card.success {
background: linear-gradient(135deg, rgba(16, 185, 129, 0.12) 0%, rgba(5, 150, 105, 0.12) 100%);
border-color: rgba(16, 185, 129, 0.2);
}
.info-card.warning {
background: linear-gradient(135deg, rgba(245, 158, 11, 0.12) 0%, rgba(217, 119, 6, 0.12) 100%);
border-color: rgba(245, 158, 11, 0.2);
}
.info-card.error {
background: linear-gradient(135deg, rgba(239, 68, 68, 0.12) 0%, rgba(220, 38, 38, 0.12) 100%);
border-color: rgba(239, 68, 68, 0.2);
}
/* 页码样式 */
.page-number {
position: absolute;
bottom: 80px;
right: 80px;
font-size: 36px;
color: rgba(255, 255, 255, 0.8);
font-weight: 500;
}
/* ========================
渐变背景预设
======================== */
.bg-gradient-1 {
background: linear-gradient(180deg, #3450E4 0%, #D266DA 100%);
}
.bg-gradient-2 {
background: linear-gradient(180deg, #f3f3f3 0%, #f9f9f9 100%);
}
.bg-gradient-3 {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
}
.bg-gradient-4 {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.bg-gradient-5 {
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
}
.bg-gradient-6 {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
}
.bg-gradient-7 {
background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%);
}
.bg-gradient-8 {
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
}
/* ============================================
Botanical - 植物园风格
自然柔和,清新淡雅
适配小红书卡片渲染
============================================ */
/* 配色定义
背景色: #F9FAF6 (淡绿白)
前景色: #2D3B36 (深绿灰)
主绿色: #4A7C59 (森林绿)
浅绿: #8FBC8F (淡绿)
棕色: #8B7355 (木质棕)
米色: #E8E4DC (暖米白)
*/
.card-inner {
background-color: #f9faf6;
}
.card-content {
color: #2d3b36;
font-size: 42px;
line-height: 1.8;
}
/* 标题样式 */
.card-content h1 {
font-size: 72px;
font-weight: 700;
color: #4a7c59;
margin-bottom: 40px;
line-height: 1.3;
padding-bottom: 20px;
border-bottom: 4px solid #8fbc8f;
}
.card-content h2 {
font-size: 56px;
font-weight: 600;
color: #3d5a48;
margin: 50px 0 25px 0;
line-height: 1.4;
padding-left: 20px;
border-left: 6px solid #4a7c59;
}
.card-content h3 {
font-size: 48px;
font-weight: 600;
color: #4a7c59;
margin: 40px 0 20px 0;
}
/* 段落 */
.card-content p {
margin-bottom: 35px;
}
/* 粗体 */
.card-content strong,
.card-content b {
font-weight: 700;
color: #2d3b36;
background-color: rgba(143, 188, 143, 0.3);
padding: 0.1em 0.3em;
border-radius: 4px;
}
/* 斜体 */
.card-content em,
.card-content i {
font-style: italic;
color: #4a7c59;
}
/* 链接 */
.card-content a {
color: #4a7c59;
text-decoration: none;
border-bottom: 2px solid #8fbc8f;
transition: all 0.2s;
}
/* 列表 */
.card-content ul,
.card-content ol {
margin: 30px 0;
padding-left: 60px;
}
.card-content li {
margin-bottom: 20px;
line-height: 1.6;
}
.card-content li::marker {
color: #4a7c59;
}
/* 引用块 */
.card-content blockquote {
margin: 35px 0;
padding: 30px 40px;
background-color: #e8e4dc;
color: #3d5a48;
border-left: 6px solid #4a7c59;
border-radius: 0 12px 12px 0;
}
.card-content blockquote p {
margin: 0;
font-style: italic;
}
/* 行内代码 */
.card-content code {
font-family: 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 38px;
background-color: #e8e4dc;
color: #8b7355;
padding: 6px 16px;
border-radius: 6px;
}
/* 代码块 */
.card-content pre {
margin: 35px 0;
padding: 40px;
background-color: #2d3b36;
color: #e8e4dc;
border-radius: 12px;
overflow-x: visible;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
}
.card-content pre code {
background-color: transparent;
color: inherit;
padding: 0;
font-size: 36px;
line-height: 1.5;
}
/* 分割线 */
.card-content hr {
margin: 50px 0;
border: none;
height: 3px;
background: linear-gradient(90deg, transparent, #8fbc8f, transparent);
}
/* 图片 */
.card-content img {
display: block;
max-width: 100%;
height: auto;
border-radius: 12px;
margin: 35px auto;
box-shadow: 0 4px 20px rgba(45, 59, 54, 0.1);
}
/* Tags 标签样式 */
.tags-container {
margin-top: 50px;
padding-top: 30px;
border-top: 2px solid #e8e4dc;
}
.tag {
display: inline-block;
background-color: #4a7c59;
color: white;
padding: 12px 28px;
border-radius: 30px;
font-size: 34px;
margin: 10px 15px 10px 0;
font-weight: 500;
}
/* ============================================
Default - 默认风格
小红书原生风格,渐变紫色背景
适配小红书卡片渲染
============================================ */
/* 配色定义
背景色: #FFFFFF (白色卡片)
前景色: #475569 (中性灰)
主色: #6366f1 (靛蓝紫)
浅紫: #8b5cf6 (紫罗兰)
灰色: #64748b (石板灰)
边框: #e2e8f0 (浅灰边框)
*/
.card-content {
color: #475569;
background-color: #ffffff;
font-size: 42px;
line-height: 1.7;
}
/* 标题样式 */
.card-content h1 {
font-size: 72px;
font-weight: 700;
color: #1e293b;
margin-bottom: 40px;
line-height: 1.3;
}
.card-content h2 {
font-size: 56px;
font-weight: 600;
color: #334155;
margin: 50px 0 25px 0;
line-height: 1.4;
}
.card-content h3 {
font-size: 48px;
font-weight: 600;
color: #475569;
margin: 40px 0 20px 0;
}
/* 段落 */
.card-content p {
margin-bottom: 35px;
}
/* 粗体 */
.card-content strong,
.card-content b {
font-weight: 700;
color: #1e293b;
}
/* 斜体 */
.card-content em,
.card-content i {
font-style: italic;
color: #6366f1;
}
/* 链接 */
.card-content a {
color: #6366f1;
text-decoration: none;
border-bottom: 2px solid #6366f1;
}
/* 列表 */
.card-content ul,
.card-content ol {
margin: 30px 0;
padding-left: 60px;
}
.card-content li {
margin-bottom: 20px;
line-height: 1.6;
}
/* 引用块 */
.card-content blockquote {
border-left: 8px solid #6366f1;
padding-left: 40px;
background: #f1f5f9;
padding-top: 25px;
padding-bottom: 25px;
padding-right: 30px;
margin: 35px 0;
color: #64748b;
font-style: italic;
border-radius: 0 12px 12px 0;
}
.card-content blockquote p {
margin: 0;
}
/* 行内代码 */
.card-content code {
background: #f1f5f9;
padding: 6px 16px;
border-radius: 8px;
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
font-size: 38px;
color: #6366f1;
}
/* 代码块 */
.card-content pre {
background: #1e293b;
color: #e2e8f0;
padding: 40px;
border-radius: 16px;
margin: 35px 0;
overflow-x: visible;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
font-size: 36px;
line-height: 1.5;
}
.card-content pre code {
background: transparent;
color: inherit;
padding: 0;
font-size: inherit;
}
/* 图片 */
.card-content img {
max-width: 100%;
height: auto;
border-radius: 16px;
margin: 35px auto;
display: block;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
/* 分割线 */
.card-content hr {
border: none;
height: 2px;
background: #e2e8f0;
margin: 50px 0;
}
/* Tags 标签样式 */
.tags-container {
margin-top: 50px;
padding-top: 30px;
border-top: 2px solid #e2e8f0;
}
.tag {
display: inline-block;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: white;
padding: 12px 28px;
border-radius: 30px;
font-size: 34px;
margin: 10px 15px 10px 0;
font-weight: 500;
}
/* ============================================
Neo-Brutalism - 新粗野主义风格
RAW. LOUD. UNAPOLOGETIC.
厚重黑色边框 + 硬阴影 + 高饱和色块
适配小红书卡片渲染
============================================ */
/* 配色定义
背景色: #FFFDF5 (奶油白)
主黑色: #000000 (纯黑)
强调红: #FF4757 (热辣红)
强调黄: #FECA57 (荧光黄)
强调青: #00D2D3 (电光青)
强调紫: #A29BFE (柔和紫)
*/
.card-inner {
background-color: #fffdf5;
/* 纸张纹理感 */
background-image: repeating-linear-gradient(
0deg,
transparent,
transparent 1px,
rgba(0, 0, 0, 0.008) 1px,
rgba(0, 0, 0, 0.008) 2px
);
}
.card-content {
color: #000000;
font-size: 42px;
line-height: 1.7;
}
/* 标题样式 */
.card-content h1 {
font-size: 72px;
font-weight: 900;
padding: 0.4em 0.6em;
background-color: #feca57;
color: #000000;
border: 5px solid #000000;
box-shadow: 8px 8px 0 #000000;
text-transform: uppercase;
margin-bottom: 40px;
line-height: 1.15;
}
.card-content h2 {
font-size: 56px;
font-weight: 900;
padding: 0.35em 0.6em;
background-color: #ffffff;
color: #000000;
border: 4px solid #000000;
border-left: 10px solid #ff4757;
box-shadow: 6px 6px 0 #000000;
margin: 50px 0 25px 0;
line-height: 1.15;
}
.card-content h3 {
font-size: 48px;
font-weight: 900;
padding: 0.25em 0.5em;
color: #000000;
background-color: #00d2d3;
border: 4px solid #000000;
box-shadow: 4px 4px 0 #000000;
display: inline-block;
margin: 40px 0 20px 0;
line-height: 1.15;
}
/* 段落 */
.card-content p {
margin-bottom: 35px;
}
/* 加粗 - 黄色高亮块 */
.card-content strong,
.card-content b {
font-weight: 900;
color: #000000;
background-color: #feca57;
padding: 0.08em 0.25em;
border: 2px solid #000000;
}
/* 斜体 - 红色下划线 */
.card-content em,
.card-content i {
font-style: italic;
color: #000000;
border-bottom: 3px solid #ff4757;
}
/* 链接 - 青色底纹+粗下划线 */
.card-content a {
color: #000000;
text-decoration: none;
background-color: #00d2d3;
padding: 0.08em 0.2em;
border-bottom: 4px solid #000000;
font-weight: 600;
}
/* 列表 */
.card-content ul,
.card-content ol {
margin: 30px 0;
padding-left: 60px;
}
.card-content ul {
list-style-type: square;
}
.card-content li {
margin-bottom: 20px;
line-height: 1.6;
}
.card-content li::marker {
color: #ff4757;
font-weight: 900;
}
/* 引用块 - 紫色色块+硬阴影 */
.card-content blockquote {
margin: 35px 0;
padding: 0.85em 1.1em;
background-color: #a29bfe;
color: #000000;
border: 5px solid #000000;
border-left: 12px solid #ff4757;
box-shadow: 8px 8px 0 #000000;
}
.card-content blockquote p {
margin: 0;
font-style: normal;
}
/* 行内代码 - 黄色背景 */
.card-content code {
font-family: 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 38px;
background-color: #feca57;
color: #000000;
padding: 0.18em 0.45em;
border: 3px solid #000000;
font-weight: 600;
}
/* 代码块 - 黑色背景+红色阴影 */
.card-content pre {
margin: 35px 0;
padding: 40px;
background-color: #000000;
border: 5px solid #000000;
box-shadow: 10px 10px 0 #ff4757;
overflow-x: visible;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
}
.card-content pre code {
background-color: transparent;
color: #ffffff;
padding: 0;
border: none;
font-size: 36px;
line-height: 1.55;
font-weight: 400;
}
/* 分割线 - 粗犷几何 */
.card-content hr {
margin: 50px 0;
border: none;
height: 8px;
background-color: #000000;
box-shadow: 5px 5px 0 #ff4757;
}
/* 图片 - 厚边框框架 */
.card-content img {
display: block;
max-width: 100%;
height: auto;
border: 5px solid #000000;
box-shadow: 8px 8px 0 #000000;
margin: 35px auto;
}
/* Tags 标签样式 */
.tags-container {
margin-top: 50px;
padding-top: 30px;
border-top: 5px solid #000000;
}
.tag {
display: inline-block;
background-color: #ff4757;
color: white;
padding: 12px 28px;
font-size: 34px;
margin: 10px 15px 10px 0;
font-weight: 800;
border: 3px solid #000000;
box-shadow: 4px 4px 0 #000000;
text-transform: uppercase;
}
/* ============================================
Playful Geometric - 活泼几何风格
Memphis 设计与现代波普艺术的融合
适配小红书卡片渲染
============================================ */
/* 配色定义
背景色: #FFFDF5 (温暖奶油白 - 纸质感)
前景色: #1E293B (石板色 800)
主紫色: #8B5CF6 (Vivid Violet - 品牌主色)
粉色: #F472B6 (Hot Pink - 俏皮活力)
黄色: #FBBF24 (Amber/Yellow - 乐观积极)
薄荷绿: #34D399 (Emerald/Mint - 清新感)
*/
.card-inner {
background-color: #fffdf5;
/* 几何点阵背景装饰 */
background-image: radial-gradient(circle, #e2e8f0 1.5px, transparent 1.5px);
background-size: 24px 24px;
}
.card-content {
color: #1e293b;
font-size: 42px;
line-height: 1.7;
}
/* 标题样式 */
.card-content h1 {
font-size: 72px;
font-weight: 800;
padding: 0.4em 0.65em;
background-color: #8b5cf6;
color: #ffffff;
border: 3px solid #1e293b;
border-radius: 0 28px 0 28px;
box-shadow: 6px 6px 0 #1e293b;
margin-bottom: 40px;
line-height: 1.25;
}
.card-content h2 {
font-size: 56px;
font-weight: 800;
padding: 0.3em 0.6em;
background-color: #fffdf5;
color: #7c3aed;
border: 3px solid #1e293b;
border-left: 10px solid #8b5cf6;
border-radius: 0 20px 20px 0;
box-shadow: 5px 5px 0 #f472b6;
margin: 50px 0 25px 0;
line-height: 1.25;
}
.card-content h3 {
font-size: 48px;
font-weight: 800;
padding: 0.25em 0.65em;
color: #1e293b;
background-color: #fbbf24;
border: 2px solid #1e293b;
border-radius: 9999px;
display: inline-block;
box-shadow: 4px 4px 0 #1e293b;
margin: 40px 0 20px 0;
line-height: 1.25;
}
/* 段落 */
.card-content p {
margin-bottom: 35px;
}
/* 粗体 - 黄色高亮贴纸效果 */
.card-content strong,
.card-content b {
font-weight: 700;
color: #1e293b;
background-color: #fbbf24;
padding: 0.1em 0.3em;
border-radius: 6px;
box-shadow: 2px 2px 0 rgba(30, 41, 59, 0.2);
}
/* 斜体 - 紫色强调 */
.card-content em,
.card-content i {
font-style: italic;
color: #8b5cf6;
font-weight: 500;
}
/* 链接 - 活泼几何风格 */
.card-content a {
color: #8b5cf6;
text-decoration: none;
font-weight: 500;
background-color: rgba(139, 92, 246, 0.1);
padding: 0.08em 0.25em;
border-radius: 5px;
border-bottom: 2px solid #8b5cf6;
}
/* 列表 */
.card-content ul,
.card-content ol {
margin: 30px 0;
padding-left: 60px;
}
.card-content li {
margin-bottom: 20px;
line-height: 1.6;
}
.card-content li::marker {
color: #8b5cf6;
font-weight: 700;
}
/* 引用块 - 气泡贴纸风格 */
.card-content blockquote {
margin: 35px 0;
padding: 0.8em 1.1em 0.8em 1.6em;
background-color: #f472b6;
color: #1e293b;
border: 3px solid #1e293b;
border-left: 10px solid #fbbf24;
border-radius: 0 28px 28px 0;
box-shadow: 6px 6px 0 #1e293b;
}
.card-content blockquote p {
margin: 0;
font-style: normal;
}
/* 行内代码 - 黄色贴纸 */
.card-content code {
font-family: 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 38px;
background-color: #fbbf24;
color: #1e293b;
padding: 0.15em 0.5em;
border: 2px solid #1e293b;
border-radius: 8px;
}
/* 代码块 - 深色背景配紫色硬阴影 */
.card-content pre {
margin: 35px 0;
padding: 40px;
background-color: #1e293b;
border: 3px solid #1e293b;
border-radius: 20px;
box-shadow: 8px 8px 0 #8b5cf6;
overflow-x: visible;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
}
.card-content pre code {
background-color: transparent;
color: #ffffff;
padding: 0;
border: none;
border-radius: 0;
font-size: 36px;
line-height: 1.55;
}
/* 分割线 - 彩虹几何装饰条 */
.card-content hr {
margin: 50px 0;
border: none;
height: 10px;
background: repeating-linear-gradient(
90deg,
#8b5cf6 0px,
#8b5cf6 24px,
#f472b6 24px,
#f472b6 48px,
#fbbf24 48px,
#fbbf24 72px,
#34d399 72px,
#34d399 96px
);
border-radius: 9999px;
}
/* 图片 - 贴纸框架风格 */
.card-content img {
display: block;
max-width: 100%;
height: auto;
border: 4px solid #1e293b;
border-radius: 20px;
box-shadow: 8px 8px 0 #1e293b;
margin: 35px auto;
}
/* Tags 标签样式 */
.tags-container {
margin-top: 50px;
padding-top: 30px;
border-top: 5px solid #fbbf24;
}
.tag {
display: inline-block;
background-color: #8b5cf6;
color: white;
padding: 12px 28px;
border-radius: 9999px;
font-size: 34px;
margin: 10px 15px 10px 0;
font-weight: 600;
border: 2px solid #1e293b;
box-shadow: 3px 3px 0 #1e293b;
}
/* ============================================
Professional - 专业商务风格
简洁、稳重、可读性强
适配小红书卡片渲染
============================================ */
/* 配色定义
背景色: #FFFFFF (纯白)
前景色: #1A202C (深灰黑)
主蓝色: #2563EB (专业蓝)
浅蓝: #DBEAFE (淡蓝背景)
灰色: #64748B (中性灰)
边框: #E2E8F0 (浅灰边框)
*/
.card-inner {
background-color: #ffffff;
}
.card-content {
color: #1a202c;
font-size: 42px;
line-height: 1.75;
}
/* 标题样式 */
.card-content h1 {
font-size: 72px;
font-weight: 700;
color: #1a202c;
margin-bottom: 40px;
line-height: 1.3;
padding-bottom: 20px;
border-bottom: 3px solid #2563eb;
}
.card-content h2 {
font-size: 56px;
font-weight: 600;
color: #1e40af;
margin: 50px 0 25px 0;
line-height: 1.4;
}
.card-content h3 {
font-size: 48px;
font-weight: 600;
color: #374151;
margin: 40px 0 20px 0;
}
/* 段落 */
.card-content p {
margin-bottom: 35px;
color: #374151;
}
/* 粗体 */
.card-content strong,
.card-content b {
font-weight: 700;
color: #1a202c;
}
/* 斜体 */
.card-content em,
.card-content i {
font-style: italic;
color: #2563eb;
}
/* 链接 */
.card-content a {
color: #2563eb;
text-decoration: none;
border-bottom: 2px solid #93c5fd;
}
/* 列表 */
.card-content ul,
.card-content ol {
margin: 30px 0;
padding-left: 60px;
}
.card-content li {
margin-bottom: 20px;
line-height: 1.6;
color: #374151;
}
.card-content li::marker {
color: #2563eb;
}
/* 引用块 */
.card-content blockquote {
margin: 35px 0;
padding: 30px 40px;
background-color: #dbeafe;
color: #1e40af;
border-left: 5px solid #2563eb;
border-radius: 0 8px 8px 0;
}
.card-content blockquote p {
margin: 0;
}
/* 行内代码 */
.card-content code {
font-family: 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 38px;
background-color: #f1f5f9;
color: #0f172a;
padding: 6px 16px;
border-radius: 6px;
}
/* 代码块 */
.card-content pre {
margin: 35px 0;
padding: 40px;
background-color: #0f172a;
color: #e2e8f0;
border-radius: 12px;
overflow-x: visible;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
}
.card-content pre code {
background-color: transparent;
color: inherit;
padding: 0;
font-size: 36px;
line-height: 1.5;
}
/* 分割线 */
.card-content hr {
margin: 50px 0;
border: none;
height: 2px;
background-color: #e2e8f0;
}
/* 图片 */
.card-content img {
display: block;
max-width: 100%;
height: auto;
border-radius: 8px;
margin: 35px auto;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
/* Tags 标签样式 */
.tags-container {
margin-top: 50px;
padding-top: 30px;
border-top: 2px solid #e2e8f0;
}
.tag {
display: inline-block;
background-color: #2563eb;
color: white;
padding: 12px 28px;
border-radius: 6px;
font-size: 34px;
margin: 10px 15px 10px 0;
font-weight: 500;
}
/* ============================================
Retro - 复古怀旧风格
温暖的米色调和复古排版
适配小红书卡片渲染
============================================ */
/* 配色定义
背景色: #FDF6E3 (复古米黄)
前景色: #5C4033 (棕褐色)
主色: #D35400 (复古橙)
浅橙: #F39C12 (暖黄)
深棕: #8B4513 (马鞍棕)
米色: #F5DEB3 (小麦色)
*/
.card-inner {
background-color: #fdf6e3;
}
.card-content {
color: #5c4033;
font-size: 42px;
line-height: 1.8;
}
/* 标题样式 */
.card-content h1 {
font-size: 72px;
font-weight: 700;
color: #d35400;
margin-bottom: 40px;
line-height: 1.3;
border-bottom: 4px double #d35400;
padding-bottom: 15px;
}
.card-content h2 {
font-size: 56px;
font-weight: 600;
color: #8b4513;
margin: 50px 0 25px 0;
line-height: 1.4;
}
.card-content h3 {
font-size: 48px;
font-weight: 600;
color: #a0522d;
margin: 40px 0 20px 0;
font-style: italic;
}
/* 段落 */
.card-content p {
margin-bottom: 35px;
}
/* 粗体 */
.card-content strong,
.card-content b {
font-weight: 700;
color: #8b4513;
}
/* 斜体 */
.card-content em,
.card-content i {
font-style: italic;
color: #d35400;
}
/* 链接 */
.card-content a {
color: #d35400;
text-decoration: underline;
text-decoration-style: wavy;
text-underline-offset: 4px;
}
/* 列表 */
.card-content ul,
.card-content ol {
margin: 30px 0;
padding-left: 60px;
}
.card-content li {
margin-bottom: 20px;
line-height: 1.6;
}
.card-content li::marker {
color: #d35400;
}
/* 引用块 */
.card-content blockquote {
margin: 35px 0;
padding: 30px 40px;
background-color: #f5deb3;
color: #5c4033;
border-left: 6px solid #d35400;
font-style: italic;
}
.card-content blockquote p {
margin: 0;
}
/* 行内代码 */
.card-content code {
font-family: 'Courier New', Courier, monospace;
font-size: 38px;
background-color: #f5deb3;
color: #8b4513;
padding: 6px 16px;
border-radius: 4px;
}
/* 代码块 */
.card-content pre {
margin: 35px 0;
padding: 40px;
background-color: #5c4033;
color: #fdf6e3;
border: 3px solid #8b4513;
overflow-x: visible;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
}
.card-content pre code {
background-color: transparent;
color: inherit;
padding: 0;
font-size: 36px;
line-height: 1.5;
}
/* 分割线 */
.card-content hr {
margin: 50px 0;
border: none;
height: 3px;
background: repeating-linear-gradient(
90deg,
#d35400,
#d35400 10px,
transparent 10px,
transparent 20px
);
}
/* 图片 */
.card-content img {
display: block;
max-width: 100%;
height: auto;
margin: 35px auto;
border: 4px solid #8b4513;
box-shadow: 4px 4px 0 #d35400;
}
/* Tags 标签样式 */
.tags-container {
margin-top: 50px;
padding-top: 30px;
border-top: 3px double #d35400;
}
.tag {
display: inline-block;
background-color: #d35400;
color: #fdf6e3;
padding: 12px 28px;
border-radius: 4px;
font-size: 34px;
margin: 10px 15px 10px 0;
font-weight: 600;
border: 2px solid #8b4513;
}
/* ============================================
Sketch - 手绘素描风格
纸张质感、手写字体效果
适配小红书卡片渲染
============================================ */
/* 配色定义
背景色: #FFFEF9 (米白纸张)
前景色: #333333 (炭笔黑)
主色: #555555 (铅笔灰)
强调: #E74C3C (红色标记笔)
蓝色: #3498DB (蓝色圆珠笔)
黄色: #F1C40F (荧光笔)
*/
.card-inner {
background-color: #fffef9;
/* 纸张网格背景 */
background-image:
linear-gradient(#e0e0e0 1px, transparent 1px),
linear-gradient(90deg, #e0e0e0 1px, transparent 1px);
background-size: 30px 30px;
}
.card-content {
color: #333333;
font-size: 42px;
line-height: 1.8;
}
/* 标题样式 */
.card-content h1 {
font-size: 72px;
font-weight: 700;
color: #333333;
margin-bottom: 40px;
line-height: 1.3;
text-decoration: underline;
text-decoration-style: wavy;
text-decoration-color: #e74c3c;
text-underline-offset: 10px;
}
.card-content h2 {
font-size: 56px;
font-weight: 600;
color: #555555;
margin: 50px 0 25px 0;
line-height: 1.4;
border-bottom: 3px dashed #999999;
padding-bottom: 10px;
}
.card-content h3 {
font-size: 48px;
font-weight: 600;
color: #666666;
margin: 40px 0 20px 0;
}
/* 段落 */
.card-content p {
margin-bottom: 35px;
}
/* 粗体 - 加粗圈标 */
.card-content strong,
.card-content b {
font-weight: 700;
color: #333333;
background-color: #f1c40f;
padding: 0.1em 0.3em;
border-radius: 4px;
}
/* 斜体 */
.card-content em,
.card-content i {
font-style: italic;
color: #3498db;
}
/* 链接 */
.card-content a {
color: #3498db;
text-decoration: underline;
text-decoration-style: dashed;
}
/* 列表 */
.card-content ul,
.card-content ol {
margin: 30px 0;
padding-left: 60px;
}
.card-content li {
margin-bottom: 20px;
line-height: 1.6;
}
.card-content li::marker {
color: #e74c3c;
}
/* 引用块 - 便签纸风格 */
.card-content blockquote {
margin: 35px 0;
padding: 30px 40px;
background-color: #fff9c4;
color: #333333;
border: none;
box-shadow: 3px 3px 0 #ddd;
transform: rotate(-0.5deg);
}
.card-content blockquote p {
margin: 0;
font-style: italic;
}
/* 行内代码 */
.card-content code {
font-family: 'Courier New', Courier, monospace;
font-size: 38px;
background-color: #f0f0f0;
color: #555555;
padding: 6px 16px;
border: 2px dashed #999999;
border-radius: 4px;
}
/* 代码块 */
.card-content pre {
margin: 35px 0;
padding: 40px;
background-color: #f5f5f5;
color: #333333;
border: 2px solid #333333;
overflow-x: visible;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
}
.card-content pre code {
background-color: transparent;
color: inherit;
padding: 0;
border: none;
font-size: 36px;
line-height: 1.5;
}
/* 分割线 */
.card-content hr {
margin: 50px 0;
border: none;
height: 3px;
background: repeating-linear-gradient(
90deg,
#333333,
#333333 5px,
transparent 5px,
transparent 10px
);
}
/* 图片 */
.card-content img {
display: block;
max-width: 100%;
height: auto;
margin: 35px auto;
border: 3px solid #333333;
box-shadow: 5px 5px 0 #ddd;
transform: rotate(0.5deg);
}
/* Tags 标签样式 */
.tags-container {
margin-top: 50px;
padding-top: 30px;
border-top: 2px dashed #999999;
}
.tag {
display: inline-block;
background-color: #fff;
color: #333333;
padding: 12px 28px;
border: 2px solid #333333;
font-size: 34px;
margin: 10px 15px 10px 0;
font-weight: 600;
transform: rotate(-1deg);
}
/* ============================================
Terminal - 终端/命令行风格
黑客美学,极简科技感
适配小红书卡片渲染
============================================ */
/* 配色定义
背景色: #0D1117 (深黑)
前景色: #C9D1D9 (淡灰白)
主绿: #39D353 (终端绿)
黄色: #F0E68C (警告黄)
青色: #58A6FF (链接蓝)
紫色: #A371F7 (高亮紫)
*/
.card-inner {
background-color: #0d1117;
}
.card-content {
color: #c9d1d9;
font-family: 'SF Mono', 'Monaco', 'Consolas', 'Liberation Mono', monospace;
font-size: 40px;
line-height: 1.6;
}
/* 标题样式 */
.card-content h1 {
font-size: 68px;
font-weight: 700;
color: #39d353;
margin-bottom: 40px;
line-height: 1.3;
}
.card-content h1::before {
content: '# ';
color: #58a6ff;
}
.card-content h2 {
font-size: 54px;
font-weight: 600;
color: #58a6ff;
margin: 50px 0 25px 0;
line-height: 1.4;
}
.card-content h2::before {
content: '## ';
color: #a371f7;
}
.card-content h3 {
font-size: 46px;
font-weight: 600;
color: #a371f7;
margin: 40px 0 20px 0;
}
.card-content h3::before {
content: '### ';
color: #39d353;
}
/* 段落 */
.card-content p {
margin-bottom: 35px;
}
/* 粗体 */
.card-content strong,
.card-content b {
font-weight: 700;
color: #39d353;
}
/* 斜体 */
.card-content em,
.card-content i {
font-style: italic;
color: #f0e68c;
}
/* 链接 */
.card-content a {
color: #58a6ff;
text-decoration: underline;
text-underline-offset: 4px;
}
/* 列表 */
.card-content ul,
.card-content ol {
margin: 30px 0;
padding-left: 60px;
}
.card-content li {
margin-bottom: 20px;
line-height: 1.6;
}
.card-content li::marker {
color: #39d353;
}
/* 引用块 */
.card-content blockquote {
margin: 35px 0;
padding: 30px 40px;
background-color: #161b22;
color: #8b949e;
border-left: 4px solid #39d353;
}
.card-content blockquote::before {
content: '> ';
color: #39d353;
}
.card-content blockquote p {
margin: 0;
}
/* 行内代码 */
.card-content code {
font-family: inherit;
font-size: 38px;
background-color: #21262d;
color: #f0e68c;
padding: 6px 16px;
border-radius: 6px;
}
/* 代码块 */
.card-content pre {
margin: 35px 0;
padding: 40px;
background-color: #161b22;
color: #c9d1d9;
border: 1px solid #30363d;
border-radius: 8px;
overflow-x: visible;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
}
.card-content pre code {
background-color: transparent;
color: inherit;
padding: 0;
font-size: 36px;
line-height: 1.5;
}
/* 分割线 */
.card-content hr {
margin: 50px 0;
border: none;
height: 2px;
background-color: #30363d;
}
/* 图片 */
.card-content img {
display: block;
max-width: 100%;
height: auto;
margin: 35px auto;
border: 1px solid #30363d;
border-radius: 8px;
}
/* Tags 标签样式 */
.tags-container {
margin-top: 50px;
padding-top: 30px;
border-top: 1px solid #30363d;
}
.tag {
display: inline-block;
background-color: #21262d;
color: #39d353;
padding: 12px 28px;
border-radius: 30px;
font-size: 32px;
margin: 10px 15px 10px 0;
font-weight: 500;
border: 1px solid #39d353;
}
神器一:Notion 📝
全能型笔记工具,支持数据库、看板、日历等多种视图。
一个工具替代十个 App,笔记、任务、项目管理全搞定!
核心功能:
- 📊 灵活的数据库视图
- 🔗 双向链接
- 🎨 丰富的模板库
- 👥 团队协作
神器二:Raycast ⚡
Mac 上的效率启动器,比 Spotlight 强大 100 倍!
# 快捷命令示例
raycast://extensions/raycast/clipboard/clipboard-history必装插件推荐:
- 剪贴板历史
- 窗口管理
- 快捷短语
- API 调试工具
总结 🎯
效率提升不在于工具多少,而在于是否真正用起来。
选择 2-3 个适合自己的工具,持续使用,形成习惯,你就能:
✅ 节省 50% 的时间 ✅ 减少 80% 的焦虑 ✅ 提升 100% 的专注力
神器一:Notion 📝
全能型笔记工具,支持数据库、看板、日历等多种视图。
一个工具替代十个 App,笔记、任务、项目管理全搞定!
核心功能:
- 📊 灵活的数据库视图
- 🔗 双向链接
- 🎨 丰富的模板库
- 👥 团队协作
---
神器二:Raycast ⚡
Mac 上的效率启动器,比 Spotlight 强大 100 倍!
# 快捷命令示例
raycast://extensions/raycast/clipboard/clipboard-history必装插件推荐:
- 剪贴板历史
- 窗口管理
- 快捷短语
- API 调试工具
---
神器三:Arc 浏览器 🌈
全新理念的浏览器体验:
- 侧边栏标签管理
- 空间分组功能
- 内置笔记和画板
- 极简无干扰模式
---
神器四:Warp 终端 🖥️
基于 Rust 的现代化终端:
# 支持 AI 智能补全
def example():
print("Hello Warp!")- ⚡ 极速性能
- 🤖 AI 智能提示
- 📋 自动补全
- 🎯 分组工作区
---
神器五:Fig 自动补全 🔮
终端命令自动补全神器:
- 数百种 CLI 工具支持
- 可视化参数提示
- 团队协作分享
# 小红书 Cookie 配置
# 将此文件复制为 .env 并填入真实的 Cookie
#
# 获取方式:
# 1. 在浏览器中登录小红书 (https://www.xiaohongshu.com)
# 2. 打开开发者工具 (F12)
# 3. 在 Network 标签中,访问任意页面
# 4. 查看请求头中的 Cookie,复制完整的 cookie 字符串
XHS_COOKIE=your_cookie_string_here
# 注意事项:
# - Cookie 有有效期,过期后需要重新获取
# - 不要分享你的 Cookie,它包含登录凭证
{
"name": "md2redbook",
"version": "2.0.0",
"description": "小红书笔记素材创作工具 - 支持多种排版样式和智能分页",
"main": "scripts/render_xhs.js",
"scripts": {
"render": "node scripts/render_xhs.js",
"install-browsers": "npx playwright install chromium"
},
"keywords": [
"xiaohongshu",
"markdown",
"image-generation",
"social-media"
],
"author": "",
"license": "MIT",
"dependencies": {
"marked": "^11.0.0",
"yaml": "^2.3.0",
"playwright": "^1.40.0"
}
}
📕 Auto-Redbook-Skills(已重构版)
自动撰写小红书笔记、生成多主题卡片、可选自动发布的 Skills
当前版本对渲染脚本和样式系统做了一次完整重构,感谢 Cursor 的辅助开发 🙌
---
✨ 本次重构亮点
- 🎨 8 套主题皮肤:默认简约灰 + Playful Geometric / Neo-Brutalism / Botanical / Professional / Retro / Terminal / Sketch
- 📐 4 种分页模式:
separator:按---分隔手动分页auto-fit:固定尺寸,自动整体缩放内容,避免溢出/大面积留白auto-split:根据渲染后高度自动拆分为多张卡片dynamic:根据内容动态调整图片高度- 🧱 统一卡片结构:外层浅灰背景(
card-container)+ 内层主题背景(card-inner)+ 纯排版层(card-content) - 🧠 封面与正文一体化:封面背景、标题渐变和正文卡片背景都按主题自动匹配
---
🖼 主题效果示例
所有示例均为 1080×1440px,小红书推荐 3:4 比例
更多示例去 demo 中查看
| !Playful Geometric | !Retro |
| !Sketch | !Terminal |
Auto-fit 模式示例(自动缩放)
!Auto Fit
---
🚀 使用方式总览
1. 克隆项目
git clone https://github.com/comeonzhj/Auto-Redbook-Skills.git
cd Auto-Redbook-Skills可以将本项目放到支持 Skills 的客户端目录,例如:
- Claude:
~/.claude/skills/ - Alma:
~/.config/Alma/skills/ - TRAE:
/your-path/.trae/skills/
2. 安装依赖
Python:
pip install -r requirements.txt
playwright install chromiumNode.js:
npm install
npx playwright install chromium---
🎨 渲染图片(Python)
核心脚本:scripts/render_xhs.py
# 最简单用法(默认主题 + 手动分页)
python scripts/render_xhs.py demos/content.md
# 使用自动分页(推荐:内容长短难控)
python scripts/render_xhs.py demos/content.md -m auto-split
# 使用固定尺寸自动缩放(auto-fit)
python scripts/render_xhs.py demos/content_auto_fit.md -m auto-fit
# 切换主题(例如 Playful Geometric)
python scripts/render_xhs.py demos/content.md -t playful-geometric -m auto-split
# 自定义尺寸和像素比
python scripts/render_xhs.py demos/content.md -t retro -m dynamic --width 1080 --height 1440 --max-height 2160 --dpr 2主要参数:
| 参数 | 简写 | 说明 |
|---|---|---|
--theme | -t | 主题:default、playful-geometric、neo-brutalism、botanical、professional、retro、terminal、sketch |
--mode | -m | 分页模式:separator / auto-fit / auto-split / dynamic |
--width | -w | 图片宽度(默认 1080) |
--height | 图片高度(默认 1440,dynamic 为最小高度) | |
--max-height | dynamic 模式最大高度(默认 2160) | |
--dpr | 设备像素比,控制清晰度(默认 2) |
生成结果会包含:封面cover.png+ 正文卡片card_1.png、card_2.png...
---
🎨 渲染图片(Node.js)
脚本:scripts/render_xhs.js,参数与 Python 基本一致:
# 默认主题 + 手动分页
node scripts/render_xhs.js demos/content.md
# 指定主题 + 自动分页
node scripts/render_xhs.js demos/content.md -t terminal -m auto-split---
📤 发布到小红书
1. 配置 Cookie
cp env.example.txt .env编辑 .env:
XHS_COOKIE=your_cookie_string_here获取方式:浏览器登录小红书 → F12 → Network → 任意请求的 Cookie 头,复制整串。
2. 手动发布(可选)
python scripts/publish_xhs.py \
--title "笔记标题" \
--desc "笔记描述内容" \
--images cover.png card_1.png card_2.png可选参数:
| 参数 | 说明 |
|---|---|
--private | 设为私密笔记 |
--post-time "2024-01-01 12:00:00" | 定时发布 |
--api-mode | 通过 xhs-api 服务发布 |
--dry-run | 仅验证,不实际发布 |
---
📁 项目结构(重构后)
Auto-Redbook-Skills/
├── SKILL.md # 技能描述(Agent 使用说明)
├── README.md # 项目文档(你现在看到的)
├── requirements.txt # Python 依赖
├── package.json # Node.js 依赖
├── env.example.txt # Cookie 配置示例
├── assets/
│ ├── cover.html # 封面 HTML 模板
│ ├── card.html # 正文卡片 HTML 模板
│ ├── styles.css # 共用容器样式(cover-inner / card-inner 等)
│ └── example.md # 示例 Markdown
├── assets/themes/ # 主题样式(只控制排版 & 内层背景)
│ ├── default.css
│ ├── playful-geometric.css
│ ├── neo-brutalism.css
│ ├── botanical.css
│ ├── professional.css
│ ├── retro.css
│ ├── terminal.css
│ └── sketch.css
├── demos/ # 各主题示例渲染结果
│ ├── content.md
│ ├── content_auto_fit.md
│ ├── auto-fit/
│ ├── playful-geometric/
│ ├── retro/
│ ├── Sketch/
│ └── terminal/
└── scripts/
├── render_xhs.py # Python 渲染脚本(支持主题 + 分页模式)
├── render_xhs.js # Node.js 渲染脚本
└── publish_xhs.py # 小红书发布脚本---
⚠️ 注意事项
1. Cookie 安全:不要把 .env 提交到 Git 或共享出去。 2. Cookie 有效期:过期后发布失败是正常现象,重新抓一次 Cookie 即可。 3. 发布频率:避免短时间内高频发布,以免触发平台风控。 4. 图片尺寸:默认 1080×1440px,符合小红书推荐比例。
---
🙏 致谢
- Playwright - 浏览器自动化渲染
- Marked - Markdown 解析
- xhs - 小红书 API 客户端
- Cursor - 本次重构过程中提供了极大帮助 ❤️
---
📄 License
MIT License © 2026
# 小红书笔记创作技能依赖
# Markdown 处理
markdown>=3.4.0
PyYAML>=6.0
# 浏览器自动化(渲染图片)
playwright>=1.40.0
# 小红书发布
xhs>=0.4.0
# 环境变量管理
python-dotenv>=1.0.0
# HTTP 请求(API 模式)
requests>=2.28.0
#!/usr/bin/env python3
"""
小红书笔记发布脚本 - 浏览器自动化版本
使用 Playwright 模拟人工操作,支持真正可点击的话题标签
使用方法:
python publish_xhs_browser.py --title "标题" --desc "描述" --images cover.png card_1.png --topics AI工具 程序员
环境变量:
XHS_COOKIE: 小红书 Cookie 字符串
依赖安装:
pip install playwright python-dotenv
playwright install chromium
"""
import argparse
import asyncio
import os
import sys
import time
from pathlib import Path
from typing import List, Optional
try:
from playwright.async_api import async_playwright, Page, Browser
from dotenv import load_dotenv
except ImportError as e:
print(f"缺少依赖: {e}")
print("请运行: pip install playwright python-dotenv")
print("然后运行: playwright install chromium")
sys.exit(1)
def load_cookie() -> str:
"""从 .env 文件加载 Cookie"""
env_paths = [
Path.cwd() / '.env',
Path(__file__).parent.parent / '.env',
Path(__file__).parent.parent.parent / '.env',
]
for env_path in env_paths:
if env_path.exists():
load_dotenv(env_path)
break
cookie = os.getenv('XHS_COOKIE')
if not cookie:
print("❌ 错误: 未找到 XHS_COOKIE 环境变量")
print("请创建 .env 文件,添加: XHS_COOKIE=your_cookie_string_here")
sys.exit(1)
return cookie
def parse_cookie_to_list(cookie_string: str) -> List[dict]:
"""将 Cookie 字符串解析为 Playwright 格式"""
cookies = []
for item in cookie_string.split(';'):
item = item.strip()
if '=' in item:
key, value = item.split('=', 1)
cookies.append({
"name": key.strip(),
"value": value.strip(),
"domain": ".xiaohongshu.com",
"path": "/"
})
return cookies
def validate_images(image_paths: List[str]) -> List[str]:
"""验证图片文件是否存在"""
valid_images = []
for path in image_paths:
if os.path.exists(path):
valid_images.append(os.path.abspath(path))
else:
print(f"⚠️ 警告: 图片不存在 - {path}")
if not valid_images:
print("❌ 错误: 没有有效的图片文件")
sys.exit(1)
return valid_images
class XHSBrowserPublisher:
"""小红书浏览器自动化发布器"""
def __init__(self, cookie: str, headless: bool = False):
self.cookie = cookie
self.headless = headless
self.browser: Optional[Browser] = None
self.page: Optional[Page] = None
async def init(self):
"""初始化浏览器"""
print("🌐 启动浏览器...")
playwright = await async_playwright().start()
self.browser = await playwright.chromium.launch(
headless=self.headless,
args=['--disable-blink-features=AutomationControlled']
)
# 创建上下文并注入 Cookie
context = await self.browser.new_context(
viewport={'width': 1280, 'height': 800},
user_agent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
)
# 注入 Cookie
cookies = parse_cookie_to_list(self.cookie)
await context.add_cookies(cookies)
self.page = await context.new_page()
print("✅ 浏览器已启动")
async def close(self):
"""关闭浏览器"""
if self.browser:
await self.browser.close()
print("🔒 浏览器已关闭")
async def goto_creator(self):
"""进入创作者后台"""
print("📍 进入创作者后台...")
await self.page.goto('https://creator.xiaohongshu.com/publish/publish', wait_until='networkidle')
await asyncio.sleep(2)
# 检查是否需要登录
if 'login' in self.page.url.lower():
print("❌ 错误: Cookie 已过期,请重新获取")
await self.close()
sys.exit(1)
print("✅ 已进入创作者后台")
async def upload_images(self, image_paths: List[str]):
"""上传图片"""
print(f"📷 上传 {len(image_paths)} 张图片...")
# 等待上传区域出现
try:
# 查找文件上传 input
upload_input = await self.page.wait_for_selector('input[type="file"]', timeout=10000)
# 上传所有图片
await upload_input.set_input_files(image_paths)
# 等待上传完成
print("⏳ 等待图片上传...")
await asyncio.sleep(3)
# 等待所有图片显示
for i in range(len(image_paths)):
await asyncio.sleep(1)
print(f"✅ 已上传 {len(image_paths)} 张图片")
except Exception as e:
print(f"❌ 图片上传失败: {e}")
raise
async def fill_title(self, title: str):
"""填写标题"""
print(f"📝 填写标题: {title[:20]}...")
try:
# 查找标题输入框
title_input = await self.page.wait_for_selector('input[placeholder*="标题"]', timeout=5000)
if not title_input:
title_input = await self.page.wait_for_selector('.title-input input', timeout=5000)
await title_input.fill(title[:20]) # 标题最多20字
print("✅ 标题已填写")
except Exception as e:
print(f"⚠️ 标题填写失败,尝试其他方式: {e}")
# 尝试其他选择器
try:
await self.page.fill('[class*="title"] input', title[:20])
print("✅ 标题已填写(备用方式)")
except:
print("❌ 无法找到标题输入框")
async def fill_desc(self, desc: str):
"""填写正文描述"""
print(f"📝 填写正文...")
try:
# 查找正文编辑区域
desc_editor = await self.page.wait_for_selector('[contenteditable="true"]', timeout=5000)
if not desc_editor:
desc_editor = await self.page.wait_for_selector('.ql-editor', timeout=5000)
await desc_editor.click()
await self.page.keyboard.type(desc, delay=10)
print("✅ 正文已填写")
except Exception as e:
print(f"⚠️ 正文填写失败: {e}")
async def add_topics(self, topics: List[str]):
"""添加话题标签(可点击版本)"""
if not topics:
return
print(f"🏷️ 添加话题标签: {', '.join(topics)}")
for topic in topics:
try:
# 方法1: 在正文中输入 # 触发话题选择
desc_editor = await self.page.query_selector('[contenteditable="true"]')
if desc_editor:
await desc_editor.click()
# 移动到末尾
await self.page.keyboard.press('End')
await self.page.keyboard.type(' ')
await self.page.keyboard.type(f'#{topic}')
await asyncio.sleep(1)
# 等待话题下拉列表出现
try:
topic_item = await self.page.wait_for_selector(
f'[class*="topic"] >> text="{topic}"',
timeout=3000
)
if topic_item:
await topic_item.click()
print(f" ✅ 已添加话题: #{topic}")
await asyncio.sleep(0.5)
continue
except:
pass
# 方法2: 尝试点击第一个话题建议
try:
first_topic = await self.page.wait_for_selector(
'[class*="topic-item"], [class*="suggest-item"]',
timeout=2000
)
if first_topic:
await first_topic.click()
print(f" ✅ 已添加话题: #{topic}(自动匹配)")
await asyncio.sleep(0.5)
continue
except:
pass
# 如果都失败,按回车确认
await self.page.keyboard.press('Escape')
print(f" ⚠️ 话题 #{topic} 可能未关联(将作为纯文本)")
except Exception as e:
print(f" ❌ 添加话题 #{topic} 失败: {e}")
print("✅ 话题标签处理完成")
async def publish(self):
"""点击发布按钮"""
print("🚀 准备发布...")
try:
# 查找发布按钮
publish_btn = await self.page.wait_for_selector(
'button:has-text("发布"), [class*="publish-btn"], button:has-text("发布笔记")',
timeout=5000
)
if publish_btn:
await asyncio.sleep(1)
await publish_btn.click()
print("⏳ 正在发布...")
# 等待发布完成
await asyncio.sleep(5)
# 检查是否发布成功
current_url = self.page.url
if 'success' in current_url or 'publish' not in current_url:
print("✅ 发布成功!")
return True
else:
print("⚠️ 发布状态未知,请手动检查")
return True
else:
print("❌ 未找到发布按钮")
return False
except Exception as e:
print(f"❌ 发布失败: {e}")
return False
async def main_async(args):
"""异步主函数"""
# 加载 Cookie
cookie = load_cookie()
# 验证图片
valid_images = validate_images(args.images)
print("\n" + "="*50)
print("🍰 小红书浏览器自动化发布")
print("="*50)
print(f" 📌 标题: {args.title}")
print(f" 📝 描述: {args.desc[:50]}..." if len(args.desc) > 50 else f" 📝 描述: {args.desc}")
print(f" 🖼️ 图片: {len(valid_images)} 张")
if args.topics:
print(f" 🏷️ 话题: {', '.join(args.topics)}")
print("="*50 + "\n")
# 创建发布器
publisher = XHSBrowserPublisher(cookie, headless=args.headless)
try:
# 初始化
await publisher.init()
# 进入创作者后台
await publisher.goto_creator()
# 上传图片
await publisher.upload_images(valid_images)
# 填写标题
await publisher.fill_title(args.title)
# 填写正文
await publisher.fill_desc(args.desc)
# 添加话题标签
if args.topics:
await publisher.add_topics(args.topics)
# 如果不是 dry-run,则发布
if not args.dry_run:
success = await publisher.publish()
if success:
print("\n✨ 笔记发布完成!")
else:
print("\n🔍 Dry-run 模式,不会实际发布")
print("按 Enter 键关闭浏览器...")
input()
except Exception as e:
print(f"\n❌ 发布过程出错: {e}")
raise
finally:
await publisher.close()
def main():
parser = argparse.ArgumentParser(
description='小红书笔记发布(浏览器自动化版本,支持可点击话题标签)',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
示例:
# 基本用法
python publish_xhs_browser.py -t "我的标题" -d "正文内容" -i cover.png card_1.png
# 带话题标签
python publish_xhs_browser.py -t "我的标题" -d "正文内容" -i cover.png --topics AI工具 程序员
# 显示浏览器窗口(调试用)
python publish_xhs_browser.py -t "我的标题" -d "正文内容" -i cover.png --no-headless
# 仅测试,不实际发布
python publish_xhs_browser.py -t "我的标题" -d "正文内容" -i cover.png --dry-run
'''
)
parser.add_argument('--title', '-t', required=True, help='笔记标题(不超过20字)')
parser.add_argument('--desc', '-d', default='', help='笔记描述/正文内容')
parser.add_argument('--images', '-i', nargs='+', required=True, help='图片文件路径')
parser.add_argument('--topics', nargs='+', default=None, help='话题标签(可点击版本)')
parser.add_argument('--headless', action='store_true', default=True, help='无头模式运行(默认)')
parser.add_argument('--no-headless', dest='headless', action='store_false', help='显示浏览器窗口')
parser.add_argument('--dry-run', action='store_true', help='仅测试,不实际发布')
args = parser.parse_args()
# 运行异步主函数
asyncio.run(main_async(args))
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
小红书笔记发布脚本 - 增强版
支持直接发布(本地签名)和通过 API 服务发布两种方式
使用方法:
# 直接发布(使用本地签名)
python publish_xhs.py --title "标题" --desc "描述" --images cover.png card_1.png
# 通过 API 服务发布
python publish_xhs.py --title "标题" --desc "描述" --images cover.png card_1.png --api-mode
环境变量:
在同目录或项目根目录下创建 .env 文件,配置:
# 必需:小红书 Cookie
XHS_COOKIE=your_cookie_string_here
# 可选:API 服务地址(使用 --api-mode 时需要)
XHS_API_URL=http://localhost:5005
依赖安装:
pip install xhs python-dotenv requests
"""
import argparse
import os
import sys
import json
import re
from pathlib import Path
from typing import List, Optional, Dict, Any
try:
from dotenv import load_dotenv
import requests
except ImportError as e:
print(f"缺少依赖: {e}")
print("请运行: pip install python-dotenv requests")
sys.exit(1)
def load_cookie() -> str:
"""从 .env 文件加载 Cookie"""
# 尝试从多个位置加载 .env
env_paths = [
Path.cwd() / '.env',
Path(__file__).parent.parent / '.env',
Path(__file__).parent.parent.parent / '.env',
]
for env_path in env_paths:
if env_path.exists():
load_dotenv(env_path)
break
cookie = os.getenv('XHS_COOKIE')
if not cookie:
print("❌ 错误: 未找到 XHS_COOKIE 环境变量")
print("请创建 .env 文件,添加以下内容:")
print("XHS_COOKIE=your_cookie_string_here")
print("\nCookie 获取方式:")
print("1. 在浏览器中登录小红书(https://www.xiaohongshu.com)")
print("2. 打开开发者工具(F12)")
print("3. 在 Network 标签中查看任意请求的 Cookie 头")
print("4. 复制完整的 cookie 字符串")
sys.exit(1)
return cookie
def parse_cookie(cookie_string: str) -> Dict[str, str]:
"""解析 Cookie 字符串为字典"""
cookies = {}
for item in cookie_string.split(';'):
item = item.strip()
if '=' in item:
key, value = item.split('=', 1)
cookies[key.strip()] = value.strip()
return cookies
def validate_cookie(cookie_string: str) -> bool:
"""验证 Cookie 是否包含必要的字段"""
cookies = parse_cookie(cookie_string)
# 检查必需的 cookie 字段
required_fields = ['a1', 'web_session']
missing = [f for f in required_fields if f not in cookies]
if missing:
print(f"⚠️ Cookie 可能不完整,缺少字段: {', '.join(missing)}")
print("这可能导致签名失败,请确保 Cookie 包含 a1 和 web_session 字段")
return False
return True
def get_api_url() -> str:
"""获取 API 服务地址"""
return os.getenv('XHS_API_URL', 'http://localhost:5005')
def validate_images(image_paths: List[str]) -> List[str]:
"""验证图片文件是否存在"""
valid_images = []
for path in image_paths:
if os.path.exists(path):
valid_images.append(os.path.abspath(path))
else:
print(f"⚠️ 警告: 图片不存在 - {path}")
if not valid_images:
print("❌ 错误: 没有有效的图片文件")
sys.exit(1)
return valid_images
class LocalPublisher:
"""本地发布模式:直接使用 xhs 库"""
def __init__(self, cookie: str):
self.cookie = cookie
self.client = None
def init_client(self):
"""初始化 xhs 客户端"""
try:
from xhs import XhsClient
from xhs.help import sign as local_sign
except ImportError:
print("❌ 错误: 缺少 xhs 库")
print("请运行: pip install xhs")
sys.exit(1)
# 解析 a1 值
cookies = parse_cookie(self.cookie)
a1 = cookies.get('a1', '')
def sign_func(uri, data=None, **kwargs):
# 使用 cookie 中的 a1 值,兼容不同版本的 xhs 库
a1_val = kwargs.get('a1', '') or a1
try:
return local_sign(uri, data, a1=a1_val)
except TypeError:
# 旧版本可能不需要 a1 参数
return local_sign(uri, data)
self.client = XhsClient(cookie=self.cookie, sign=sign_func)
def get_user_info(self) -> Optional[Dict[str, Any]]:
"""获取当前登录用户信息"""
try:
info = self.client.get_self_info()
print(f"👤 当前用户: {info.get('nickname', '未知')}")
return info
except Exception as e:
print(f"⚠️ 无法获取用户信息: {e}")
return None
def search_topics(self, keywords: List[str]) -> List[Dict[str, Any]]:
"""搜索话题,获取话题ID"""
topics = []
for keyword in keywords:
try:
result = self.client.get_suggest_topic(keyword)
if result and len(result) > 0:
# 取第一个匹配的话题,直接使用原始返回格式
topic = result[0]
# xhs 库需要的格式: {"id": "xxx", "name": "xxx", "type": "topic", "link": "xxx"}
topics.append(topic)
print(f" ✅ 找到话题: #{topic.get('name')}")
else:
print(f" ⚠️ 未找到话题: {keyword}")
except Exception as e:
print(f" ⚠️ 搜索话题失败 [{keyword}]: {e}")
return topics
def publish(self, title: str, desc: str, images: List[str],
is_private: bool = False, post_time: str = None,
topics: List[Dict[str, Any]] = None) -> Dict[str, Any]:
"""发布图文笔记"""
print(f"\n🚀 准备发布笔记(本地模式)...")
print(f" 📌 标题: {title}")
print(f" 📝 描述: {desc[:50]}..." if len(desc) > 50 else f" 📝 描述: {desc}")
print(f" 🖼️ 图片数量: {len(images)}")
if topics:
print(f" 🏷️ 标签数量: {len(topics)}")
try:
result = self.client.create_image_note(
title=title,
desc=desc,
files=images,
is_private=is_private,
post_time=post_time,
topics=topics
)
print("\n✨ 笔记发布成功!")
if isinstance(result, dict):
note_id = result.get('note_id') or result.get('id')
if note_id:
print(f" 📎 笔记ID: {note_id}")
print(f" 🔗 链接: https://www.xiaohongshu.com/explore/{note_id}")
return result
except Exception as e:
error_msg = str(e)
print(f"\n❌ 发布失败: {error_msg}")
# 提供具体的错误排查建议
if 'sign' in error_msg.lower() or 'signature' in error_msg.lower():
print("\n💡 签名错误排查建议:")
print("1. 确保 Cookie 包含有效的 a1 和 web_session 字段")
print("2. Cookie 可能已过期,请重新获取")
print("3. 尝试使用 --api-mode 通过 API 服务发布")
elif 'cookie' in error_msg.lower():
print("\n💡 Cookie 错误排查建议:")
print("1. 确保 Cookie 格式正确")
print("2. Cookie 可能已过期,请重新获取")
print("3. 确保 Cookie 来自已登录的小红书网页版")
raise
class ApiPublisher:
"""API 发布模式:通过 xhs-api 服务发布"""
def __init__(self, cookie: str, api_url: str = None):
self.cookie = cookie
self.api_url = api_url or get_api_url()
self.session_id = 'md2redbook_session'
def init_client(self):
"""初始化 API 客户端"""
print(f"📡 连接 API 服务: {self.api_url}")
# 健康检查
try:
resp = requests.get(f"{self.api_url}/health", timeout=5)
if resp.status_code != 200:
raise Exception("API 服务不可用")
except requests.exceptions.RequestException as e:
print(f"❌ 无法连接到 API 服务: {e}")
print(f"\n💡 请确保 xhs-api 服务已启动:")
print(f" cd xhs-api && python app_full.py")
sys.exit(1)
# 初始化 session
try:
resp = requests.post(
f"{self.api_url}/init",
json={
"session_id": self.session_id,
"cookie": self.cookie
},
timeout=30
)
result = resp.json()
if resp.status_code == 200 and result.get('status') == 'success':
print(f"✅ API 初始化成功")
user_info = result.get('user_info', {})
if user_info:
print(f"👤 当前用户: {user_info.get('nickname', '未知')}")
elif result.get('status') == 'warning':
print(f"⚠️ {result.get('message')}")
else:
raise Exception(result.get('error', '初始化失败'))
except Exception as e:
print(f"❌ API 初始化失败: {e}")
sys.exit(1)
def get_user_info(self) -> Optional[Dict[str, Any]]:
"""获取当前登录用户信息"""
try:
resp = requests.post(
f"{self.api_url}/user/info",
json={"session_id": self.session_id},
timeout=10
)
if resp.status_code == 200:
result = resp.json()
if result.get('status') == 'success':
info = result.get('user_info', {})
print(f"👤 当前用户: {info.get('nickname', '未知')}")
return info
return None
except Exception as e:
print(f"⚠️ 无法获取用户信息: {e}")
return None
def publish(self, title: str, desc: str, images: List[str],
is_private: bool = False, post_time: str = None,
topics: List[Dict[str, Any]] = None) -> Dict[str, Any]:
"""发布图文笔记"""
print(f"\n🚀 准备发布笔记(API 模式)...")
print(f" 📌 标题: {title}")
print(f" 📝 描述: {desc[:50]}..." if len(desc) > 50 else f" 📝 描述: {desc}")
print(f" 🖼️ 图片数量: {len(images)}")
if topics:
print(f" 🏷️ 标签数量: {len(topics)}")
try:
payload = {
"session_id": self.session_id,
"title": title,
"desc": desc,
"files": images,
"is_private": is_private
}
if post_time:
payload["post_time"] = post_time
if topics:
payload["topics"] = topics
resp = requests.post(
f"{self.api_url}/publish/image",
json=payload,
timeout=120
)
result = resp.json()
if resp.status_code == 200 and result.get('status') == 'success':
print("\n✨ 笔记发布成功!")
publish_result = result.get('result', {})
if isinstance(publish_result, dict):
note_id = publish_result.get('note_id') or publish_result.get('id')
if note_id:
print(f" 📎 笔记ID: {note_id}")
print(f" 🔗 链接: https://www.xiaohongshu.com/explore/{note_id}")
return publish_result
else:
raise Exception(result.get('error', '发布失败'))
except Exception as e:
error_msg = str(e)
print(f"\n❌ 发布失败: {error_msg}")
raise
def main():
parser = argparse.ArgumentParser(
description='将图片发布为小红书笔记',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
示例:
# 基本用法
python publish_xhs.py -t "我的标题" -d "正文内容" -i cover.png card_1.png card_2.png
# 使用 API 模式
python publish_xhs.py -t "我的标题" -d "正文内容" -i *.png --api-mode
# 设为私密笔记
python publish_xhs.py -t "我的标题" -d "正文内容" -i *.png --private
# 定时发布
python publish_xhs.py -t "我的标题" -d "正文内容" -i *.png --post-time "2024-12-01 10:00:00"
'''
)
parser.add_argument(
'--title', '-t',
required=True,
help='笔记标题(不超过20字)'
)
parser.add_argument(
'--desc', '-d',
default='',
help='笔记描述/正文内容'
)
parser.add_argument(
'--images', '-i',
nargs='+',
required=True,
help='图片文件路径(可以多个)'
)
parser.add_argument(
'--private',
action='store_true',
help='是否设为私密笔记'
)
parser.add_argument(
'--post-time',
default=None,
help='定时发布时间(格式:2024-01-01 12:00:00)'
)
parser.add_argument(
'--api-mode',
action='store_true',
help='使用 API 模式发布(需要 xhs-api 服务运行)'
)
parser.add_argument(
'--api-url',
default=None,
help='API 服务地址(默认: http://localhost:5005)'
)
parser.add_argument(
'--dry-run',
action='store_true',
help='仅验证,不实际发布'
)
parser.add_argument(
'--topics',
nargs='+',
default=None,
help='话题标签名称(可点击的标签),例如:--topics AI工具 效率提升 程序员'
)
args = parser.parse_args()
# 验证标题长度
if len(args.title) > 20:
print(f"⚠️ 警告: 标题超过20字,将被截断")
args.title = args.title[:20]
# 加载 Cookie
cookie = load_cookie()
# 验证 Cookie 格式
validate_cookie(cookie)
# 验证图片
valid_images = validate_images(args.images)
if args.dry_run:
print("\n🔍 验证模式 - 不会实际发布")
print(f" 📌 标题: {args.title}")
print(f" 📝 描述: {args.desc}")
print(f" 🖼️ 图片: {valid_images}")
print(f" 🔒 私密: {args.private}")
print(f" ⏰ 定时: {args.post_time or '立即发布'}")
print(f" 📡 模式: {'API' if args.api_mode else '本地'}")
print("\n✅ 验证通过,可以发布")
return
# 选择发布方式
if args.api_mode:
publisher = ApiPublisher(cookie, args.api_url)
else:
publisher = LocalPublisher(cookie)
# 初始化客户端
publisher.init_client()
# 处理话题标签
topics = None
desc = args.desc
if args.topics:
print(f"\n🔍 搜索话题标签...")
# 需要先搜索话题获取 topic_id
if hasattr(publisher, 'search_topics'):
topics = publisher.search_topics(args.topics)
if topics:
# 把标签名加到文案末尾,让标签在文案中显示
topic_names = [f"#{t.get('name')}" for t in topics]
topic_text = " ".join(topic_names)
if desc:
desc = f"{desc}\n\n{topic_text}"
else:
desc = topic_text
print(f" 📝 标签已添加到文案: {topic_text}")
else:
print(" ⚠️ 没有找到任何匹配的话题,将不带标签发布")
topics = None
else:
print(" ⚠️ API 模式暂不支持话题搜索")
# 发布笔记
try:
publisher.publish(
title=args.title,
desc=desc,
images=valid_images,
is_private=args.private,
post_time=args.post_time,
topics=topics
)
except Exception as e:
sys.exit(1)
if __name__ == '__main__':
main()
#!/usr/bin/env node
/**
* 小红书卡片渲染脚本 V2 - Node.js 智能分页版
* 将 Markdown 文件渲染为小红书风格的图片卡片
*
* 新特性:
* 1. 智能分页:自动检测内容高度,超出时自动拆分到多张卡片
* 2. 多种样式:支持多种预设样式主题
*
* 使用方法:
* node render_xhs_v2.js <markdown_file> [options]
*
* 依赖安装:
* npm install marked js-yaml playwright
* npx playwright install chromium
*/
const fs = require('fs');
const path = require('path');
const { chromium } = require('playwright');
const { marked } = require('marked');
const yaml = require('js-yaml');
// 获取脚本所在目录
const SCRIPT_DIR = path.dirname(__dirname);
const ASSETS_DIR = path.join(SCRIPT_DIR, 'assets');
// 卡片尺寸配置 (3:4 比例)
const CARD_WIDTH = 1080;
const CARD_HEIGHT = 1440;
// 内容区域安全高度
const SAFE_HEIGHT = CARD_HEIGHT - 120 - 100 - 80 - 40; // ~1100px
// 样式配置
const STYLES = {
purple: {
name: "紫韵",
cover_bg: "linear-gradient(180deg, #3450E4 0%, #D266DA 100%)",
card_bg: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
accent_color: "#6366f1",
},
xiaohongshu: {
name: "小红书红",
cover_bg: "linear-gradient(180deg, #FF2442 0%, #FF6B81 100%)",
card_bg: "linear-gradient(135deg, #FF2442 0%, #FF6B81 100%)",
accent_color: "#FF2442",
},
mint: {
name: "清新薄荷",
cover_bg: "linear-gradient(180deg, #43e97b 0%, #38f9d7 100%)",
card_bg: "linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)",
accent_color: "#43e97b",
},
sunset: {
name: "日落橙",
cover_bg: "linear-gradient(180deg, #fa709a 0%, #fee140 100%)",
card_bg: "linear-gradient(135deg, #fa709a 0%, #fee140 100%)",
accent_color: "#fa709a",
},
ocean: {
name: "深海蓝",
cover_bg: "linear-gradient(180deg, #4facfe 0%, #00f2fe 100%)",
card_bg: "linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)",
accent_color: "#4facfe",
},
elegant: {
name: "优雅白",
cover_bg: "linear-gradient(180deg, #f5f5f5 0%, #e0e0e0 100%)",
card_bg: "linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%)",
accent_color: "#333333",
},
dark: {
name: "暗黑模式",
cover_bg: "linear-gradient(180deg, #1a1a2e 0%, #16213e 100%)",
card_bg: "linear-gradient(135deg, #1a1a2e 0%, #16213e 100%)",
accent_color: "#e94560",
},
};
/**
* 解析 Markdown 文件,提取 YAML 头部和正文内容
*/
function parseMarkdownFile(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
const yamlPattern = /^---\s*\n([\s\S]*?)\n---\s*\n/;
const yamlMatch = content.match(yamlPattern);
let metadata = {};
let body = content;
if (yamlMatch) {
try {
metadata = yaml.load(yamlMatch[1]) || {};
} catch (e) {
metadata = {};
}
body = content.slice(yamlMatch[0].length);
}
return { metadata, body: body.trim() };
}
/**
* 按照 --- 分隔符拆分正文为多张卡片内容
*/
function splitContentBySeparator(body) {
const parts = body.split(/\n---+\n/);
return parts.filter(part => part.trim()).map(part => part.trim());
}
/**
* 预估内容高度
*/
function estimateContentHeight(content) {
const lines = content.split('\n');
let totalHeight = 0;
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) {
totalHeight += 20;
continue;
}
if (trimmed.startsWith('# ')) {
totalHeight += 130;
} else if (trimmed.startsWith('## ')) {
totalHeight += 110;
} else if (trimmed.startsWith('### ')) {
totalHeight += 90;
} else if (trimmed.startsWith('```')) {
totalHeight += 80;
} else if (trimmed.match(/^[-*+]\s/)) {
totalHeight += 85;
} else if (trimmed.startsWith('>')) {
totalHeight += 100;
} else if (trimmed.startsWith('![')) {
totalHeight += 300;
} else {
const charCount = trimmed.length;
const linesNeeded = Math.max(1, charCount / 28);
totalHeight += Math.floor(linesNeeded * 42 * 1.7) + 35;
}
}
return totalHeight;
}
/**
* 智能拆分内容
*/
function smartSplitContent(content, maxHeight = SAFE_HEIGHT) {
const blocks = [];
let currentBlock = [];
const lines = content.split('\n');
for (const line of lines) {
if (line.trim().startsWith('#') && currentBlock.length > 0) {
blocks.push(currentBlock.join('\n'));
currentBlock = [line];
} else if (line.trim() === '---') {
if (currentBlock.length > 0) {
blocks.push(currentBlock.join('\n'));
currentBlock = [];
}
} else {
currentBlock.push(line);
}
}
if (currentBlock.length > 0) {
blocks.push(currentBlock.join('\n'));
}
if (blocks.length <= 1) {
const paragraphs = content.split('\n\n').filter(b => b.trim());
blocks.length = 0;
blocks.push(...paragraphs);
}
const cards = [];
let currentCard = [];
let currentHeight = 0;
for (const block of blocks) {
const blockHeight = estimateContentHeight(block);
if (blockHeight > maxHeight) {
if (currentCard.length > 0) {
cards.push(currentCard.join('\n\n'));
currentCard = [];
currentHeight = 0;
}
const blockLines = block.split('\n');
let subBlock = [];
let subHeight = 0;
for (const line of blockLines) {
const lineHeight = estimateContentHeight(line);
if (subHeight + lineHeight > maxHeight && subBlock.length > 0) {
cards.push(subBlock.join('\n'));
subBlock = [line];
subHeight = lineHeight;
} else {
subBlock.push(line);
subHeight += lineHeight;
}
}
if (subBlock.length > 0) {
cards.push(subBlock.join('\n'));
}
} else if (currentHeight + blockHeight > maxHeight && currentCard.length > 0) {
cards.push(currentCard.join('\n\n'));
currentCard = [block];
currentHeight = blockHeight;
} else {
currentCard.push(block);
currentHeight += blockHeight;
}
}
if (currentCard.length > 0) {
cards.push(currentCard.join('\n\n'));
}
return cards.length > 0 ? cards : [content];
}
/**
* 将 Markdown 转换为 HTML
*/
function convertMarkdownToHtml(mdContent, style = STYLES.purple) {
const tagsPattern = /((?:#[\w\u4e00-\u9fa5]+\s*)+)$/m;
const tagsMatch = mdContent.match(tagsPattern);
let tagsHtml = "";
if (tagsMatch) {
const tagsStr = tagsMatch[1];
mdContent = mdContent.slice(0, tagsMatch.index).trim();
const tags = tagsStr.match(/#([\w\u4e00-\u9fa5]+)/g);
if (tags) {
const accent = style.accent_color;
tagsHtml = '<div class="tags-container">';
for (const tag of tags) {
tagsHtml += `<span class="tag" style="background: ${accent};">${tag}</span>`;
}
tagsHtml += '</div>';
}
}
const html = marked.parse(mdContent, { breaks: true, gfm: true });
return html + tagsHtml;
}
/**
* 生成封面 HTML
*/
function generateCoverHtml(metadata, styleKey = 'purple') {
const style = STYLES[styleKey] || STYLES.purple;
const emoji = metadata.emoji || '📝';
let title = metadata.title || '标题';
let subtitle = metadata.subtitle || '';
if (title.length > 15) title = title.slice(0, 15);
if (subtitle.length > 15) subtitle = subtitle.slice(0, 15);
const isDark = styleKey === 'dark';
const textColor = isDark ? '#ffffff' : '#000000';
const titleGradient = isDark
? 'linear-gradient(180deg, #ffffff 0%, #cccccc 100%)'
: 'linear-gradient(180deg, #2E67B1 0%, #4C4C4C 100%)';
const innerBg = isDark ? '#1a1a2e' : '#F3F3F3';
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1080, height=1440">
<title>小红书封面</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Noto Sans SC', 'Source Han Sans CN', 'PingFang SC', 'Microsoft YaHei', sans-serif;
width: 1080px; height: 1440px; overflow: hidden;
}
.cover-container {
width: 1080px; height: 1440px;
background: ${style.cover_bg};
position: relative; overflow: hidden;
}
.cover-inner {
position: absolute; width: 950px; height: 1310px;
left: 65px; top: 65px;
background: ${innerBg};
border-radius: 25px;
display: flex; flex-direction: column;
padding: 80px 85px;
}
.cover-emoji { font-size: 180px; line-height: 1.2; margin-bottom: 50px; }
.cover-title {
font-weight: 900; font-size: 130px; line-height: 1.4;
background: ${titleGradient};
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
flex: 1;
display: flex; align-items: flex-start;
word-break: break-all;
}
.cover-subtitle {
font-weight: 350; font-size: 72px; line-height: 1.4;
color: ${textColor};
margin-top: auto;
}
</style>
</head>
<body>
<div class="cover-container">
<div class="cover-inner">
<div class="cover-emoji">${emoji}</div>
<div class="cover-title">${title}</div>
<div class="cover-subtitle">${subtitle}</div>
</div>
</div>
</body>
</html>`;
}
/**
* 生成正文卡片 HTML
*/
function generateCardHtml(content, pageNumber = 1, totalPages = 1, styleKey = 'purple') {
const style = STYLES[styleKey] || STYLES.purple;
const htmlContent = convertMarkdownToHtml(content, style);
const pageText = totalPages > 1 ? `${pageNumber}/${totalPages}` : '';
const isDark = styleKey === 'dark';
const cardBg = isDark ? 'rgba(30, 30, 46, 0.95)' : 'rgba(255, 255, 255, 0.95)';
const textColor = isDark ? '#e0e0e0' : '#475569';
const headingColor = isDark ? '#ffffff' : '#1e293b';
const h2Color = isDark ? '#e0e0e0' : '#334155';
const h3Color = isDark ? '#c0c0c0' : '#475569';
const codeBg = isDark ? '#252540' : '#f1f5f9';
const preBg = isDark ? '#0f0f23' : '#1e293b';
const blockquoteBg = isDark ? '#252540' : '#f1f5f9';
const blockquoteColor = isDark ? '#a0a0a0' : '#64748b';
const hrBg = isDark ? '#333355' : '#e2e8f0';
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=1080">
<title>小红书卡片</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Noto Sans SC', 'Source Han Sans CN', 'PingFang SC', 'Microsoft YaHei', sans-serif;
width: 1080px; min-height: 1440px; overflow: hidden; background: transparent;
}
.card-container {
width: 1080px; min-height: 1440px;
background: ${style.card_bg};
position: relative; padding: 50px; overflow: hidden;
}
.card-inner {
background: ${cardBg};
border-radius: 20px;
padding: 60px;
min-height: calc(1440px - 100px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(10px);
}
.card-content {
color: ${textColor};
font-size: 42px;
line-height: 1.7;
}
.card-content h1 {
font-size: 72px; font-weight: 700; color: ${headingColor};
margin-bottom: 40px; line-height: 1.3;
}
.card-content h2 {
font-size: 56px; font-weight: 600; color: ${h2Color};
margin: 50px 0 25px 0; line-height: 1.4;
}
.card-content h3 {
font-size: 48px; font-weight: 600; color: ${h3Color};
margin: 40px 0 20px 0;
}
.card-content p { margin-bottom: 35px; }
.card-content strong { font-weight: 700; color: ${headingColor}; }
.card-content em { font-style: italic; color: ${style.accent_color}; }
.card-content a {
color: ${style.accent_color}; text-decoration: none;
border-bottom: 2px solid ${style.accent_color};
}
.card-content ul, .card-content ol {
margin: 30px 0; padding-left: 60px;
}
.card-content li { margin-bottom: 20px; line-height: 1.6; }
.card-content blockquote {
border-left: 8px solid ${style.accent_color};
padding-left: 40px;
background: ${blockquoteBg};
padding-top: 25px; padding-bottom: 25px; padding-right: 30px;
margin: 35px 0;
color: ${blockquoteColor};
font-style: italic;
border-radius: 0 12px 12px 0;
}
.card-content blockquote p { margin: 0; }
.card-content code {
background: ${codeBg};
padding: 6px 16px; border-radius: 8px;
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
font-size: 38px;
color: ${style.accent_color};
}
.card-content pre {
background: ${preBg};
color: ${isDark ? '#e0e0e0' : '#e2e8f0'};
padding: 40px; border-radius: 16px;
margin: 35px 0;
overflow-x: visible;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
white-space: pre-wrap;
font-size: 36px; line-height: 1.5;
}
.card-content pre code {
background: transparent; color: inherit; padding: 0; font-size: inherit;
}
.card-content img {
max-width: 100%; height: auto; border-radius: 16px;
margin: 35px auto; display: block;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.card-content hr {
border: none; height: 2px;
background: ${hrBg};
margin: 50px 0;
}
.tags-container {
margin-top: 50px; padding-top: 30px;
border-top: 2px solid ${hrBg};
}
.tag {
display: inline-block;
background: ${style.accent_color};
color: white;
padding: 12px 28px; border-radius: 30px;
font-size: 34px;
margin: 10px 15px 10px 0;
font-weight: 500;
}
.page-number {
position: absolute;
bottom: 80px; right: 80px;
font-size: 36px;
color: rgba(255, 255, 255, 0.8);
font-weight: 500;
}
</style>
</head>
<body>
<div class="card-container">
<div class="card-inner">
<div class="card-content">
${htmlContent}
</div>
</div>
<div class="page-number">${pageText}</div>
</div>
</body>
</html>`;
}
/**
* 测量内容高度
*/
async function measureContentHeight(page, htmlContent) {
await page.setContent(htmlContent, { waitUntil: 'networkidle' });
await page.waitForTimeout(300);
return await page.evaluate(() => {
const inner = document.querySelector('.card-inner');
if (inner) return inner.scrollHeight;
const container = document.querySelector('.card-container');
return container ? container.scrollHeight : document.body.scrollHeight;
});
}
/**
* 处理和渲染卡片
*/
async function processAndRenderCards(cardContents, outputDir, styleKey) {
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: CARD_WIDTH, height: CARD_HEIGHT } });
const allCards = [];
try {
for (const content of cardContents) {
const estimatedHeight = estimateContentHeight(content);
let splitContents;
if (estimatedHeight > SAFE_HEIGHT) {
splitContents = smartSplitContent(content, SAFE_HEIGHT);
} else {
splitContents = [content];
}
for (const splitContent of splitContents) {
const tempHtml = generateCardHtml(splitContent, 1, 1, styleKey);
const actualHeight = await measureContentHeight(page, tempHtml);
if (actualHeight > CARD_HEIGHT - 100) {
const lines = splitContent.split('\n');
const subContents = [];
let subLines = [];
for (const line of lines) {
const testLines = [...subLines, line];
const testHtml = generateCardHtml(testLines.join('\n'), 1, 1, styleKey);
const testHeight = await measureContentHeight(page, testHtml);
if (testHeight > CARD_HEIGHT - 100 && subLines.length > 0) {
subContents.push(subLines.join('\n'));
subLines = [line];
} else {
subLines = testLines;
}
}
if (subLines.length > 0) {
subContents.push(subLines.join('\n'));
}
allCards.push(...subContents);
} else {
allCards.push(splitContent);
}
}
}
} finally {
await browser.close();
}
return allCards;
}
/**
* 渲染 HTML 到图片
*/
async function renderHtmlToImage(page, htmlContent, outputPath) {
await page.setContent(htmlContent, { waitUntil: 'networkidle' });
await page.waitForTimeout(300);
await page.screenshot({
path: outputPath,
clip: { x: 0, y: 0, width: CARD_WIDTH, height: CARD_HEIGHT },
type: 'png'
});
console.log(` ✅ 已生成: ${outputPath}`);
}
/**
* 主渲染函数
*/
async function renderMarkdownToCards(mdFile, outputDir, styleKey = 'purple') {
console.log(`\n🎨 开始渲染: ${mdFile}`);
console.log(`🎨 使用样式: ${STYLES[styleKey].name}`);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
const data = parseMarkdownFile(mdFile);
const { metadata, body } = data;
const cardContents = splitContentBySeparator(body);
console.log(` 📄 检测到 ${cardContents.length} 个内容块`);
console.log(' 🔍 分析内容高度并智能分页...');
const processedCards = await processAndRenderCards(cardContents, outputDir, styleKey);
const totalCards = processedCards.length;
console.log(` 📄 将生成 ${totalCards} 张卡片`);
if (metadata.emoji || metadata.title) {
console.log(' 📷 生成封面...');
const coverHtml = generateCoverHtml(metadata, styleKey);
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: CARD_WIDTH, height: CARD_HEIGHT } });
try {
await renderHtmlToImage(page, coverHtml, path.join(outputDir, 'cover.png'));
} finally {
await browser.close();
}
}
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: CARD_WIDTH, height: CARD_HEIGHT } });
try {
for (let i = 0; i < processedCards.length; i++) {
const pageNum = i + 1;
console.log(` 📷 生成卡片 ${pageNum}/${totalCards}...`);
const cardHtml = generateCardHtml(processedCards[i], pageNum, totalCards, styleKey);
const cardPath = path.join(outputDir, `card_${pageNum}.png`);
await renderHtmlToImage(page, cardHtml, cardPath);
}
} finally {
await browser.close();
}
console.log(`\n✨ 渲染完成!共生成 ${totalCards} 张卡片,保存到: ${outputDir}`);
return totalCards;
}
/**
* 列出所有样式
*/
function listStyles() {
console.log('\n📋 可用样式列表:');
console.log('-'.repeat(40));
for (const [key, style] of Object.entries(STYLES)) {
console.log(` ${key.padEnd(12)} - ${style.name}`);
}
console.log('-'.repeat(40));
}
/**
* 解析命令行参数
*/
function parseArgs() {
const args = process.argv.slice(2);
if (args.length === 0 || args.includes('--help')) {
console.log(`
使用方法: node render_xhs_v2.js <markdown_file> [options]
选项:
-o, --output-dir <dir> 输出目录(默认为当前工作目录)
-s, --style <style> 样式主题(默认: purple)
--list-styles 列出所有可用样式
--help 显示帮助信息
可用样式:
purple, xiaohongshu, mint, sunset, ocean, elegant, dark
示例:
node render_xhs_v2.js note.md
node render_xhs_v2.js note.md -o ./output --style xiaohongshu
`);
process.exit(0);
}
if (args.includes('--list-styles')) {
listStyles();
process.exit(0);
}
let markdownFile = null;
let outputDir = process.cwd();
let style = 'purple';
for (let i = 0; i < args.length; i++) {
if (args[i] === '--output-dir' || args[i] === '-o') {
outputDir = args[i + 1];
i++;
} else if (args[i] === '--style' || args[i] === '-s') {
if (STYLES[args[i + 1]]) {
style = args[i + 1];
} else {
console.error(`❌ 无效样式: ${args[i + 1]}`);
console.log('可用样式:', Object.keys(STYLES).join(', '));
process.exit(1);
}
i++;
} else if (!args[i].startsWith('-')) {
markdownFile = args[i];
}
}
if (!markdownFile) {
console.error('❌ 错误: 请指定 Markdown 文件');
process.exit(1);
}
if (!fs.existsSync(markdownFile)) {
console.error(`❌ 错误: 文件不存在 - ${markdownFile}`);
process.exit(1);
}
return { markdownFile, outputDir, style };
}
// 主函数
async function main() {
const { markdownFile, outputDir, style } = parseArgs();
await renderMarkdownToCards(markdownFile, outputDir, style);
}
main().catch(error => {
console.error('❌ 渲染失败:', error.message);
process.exit(1);
});
小红书笔记样式预览
本文档展示所有可用的样式主题,方便用户选择合适的设计风格。
样式列表
1. purple(紫韵)- 默认
- 封面背景:蓝紫色渐变
- 卡片背景:紫蓝渐变
- 强调色:#6366f1
- 适用场景:通用、科技感、创意设计
python scripts/render_xhs_v2.py note.md --style purple2. xiaohongshu(小红书红)
- 封面背景:小红书品牌红渐变
- 卡片背景:粉红渐变
- 强调色:#FF2442
- 适用场景:小红书原生风格、时尚、生活方式
python scripts/render_xhs_v2.py note.md --style xiaohongshu3. mint(清新薄荷)
- 封面背景:绿色渐变
- 卡片背景:薄荷绿渐变
- 强调色:#43e97b
- 适用场景:健康、环保、自然、春季主题
python scripts/render_xhs_v2.py note.md --style mint4. sunset(日落橙)
- 封面背景:粉橙渐变
- 卡片背景:日落渐变
- 强调色:#fa709a
- 适用场景:温暖、浪漫、傍晚、秋季主题
python scripts/render_xhs_v2.py note.md --style sunset5. ocean(深海蓝)
- 封面背景:天蓝渐变
- 卡片背景:海洋蓝渐变
- 强调色:#4facfe
- 适用场景:清新、水、夏季主题、专业商务
python scripts/render_xhs_v2.py note.md --style ocean6. elegant(优雅白)
- 封面背景:灰白渐变
- 卡片背景:浅灰渐变
- 强调色:#333333
- 适用场景:极简、商务、正式、高级
python scripts/render_xhs_v2.py note.md --style elegant7. dark(暗黑模式)
- 封面背景:深蓝黑色渐变
- 卡片背景:深色渐变
- 强调色:#e94560
- 适用场景:夜间阅读、科技、编程、游戏
python scripts/render_xhs_v2.py note.md --style dark样式选择建议
| 内容类型 | 推荐样式 |
|---|---|
| 科技/编程 | dark, purple |
| 时尚/美妆 | xiaohongshu, sunset |
| 健康/自然 | mint, ocean |
| 商务/职场 | ocean, elegant |
| 生活/情感 | sunset, xiaohongshu |
| 创意/设计 | purple, dark |
查看所有样式
运行以下命令列出所有可用样式:
# Python 版本
python scripts/render_xhs_v2.py --list-styles
# Node.js 版本
node scripts/render_xhs_v2.js --list-styles