719 lines
28 KiB
Python
Executable File
719 lines
28 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
小红书多图发布器
|
||
支持文字分页、多图片轮播、不同背景风格
|
||
支持Markdown格式渲染(标题、加粗等)
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import logging
|
||
import tempfile
|
||
import subprocess
|
||
import re
|
||
from PIL import Image, ImageDraw, ImageFont
|
||
from typing import List, Tuple, Optional, Dict, Any
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
class XiaohongshuMultiImagePublisher:
|
||
"""小红书多图发布器"""
|
||
|
||
def __init__(self):
|
||
"""初始化发布器"""
|
||
self.temp_dir = "/tmp/xiaohongshu_multi_images"
|
||
os.makedirs(self.temp_dir, exist_ok=True)
|
||
|
||
# 字体设置
|
||
self.font_path = "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc"
|
||
self.font_path_bold = "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc" # 粗体字体
|
||
self.title_font_size = 42 # 主标题(文章标题)
|
||
self.heading1_font_size = 38 # ## 标题
|
||
self.heading2_font_size = 34 # ### 标题
|
||
self.content_font_size = 32 # 正文
|
||
self.line_spacing = 1.5 # 1.5倍行距
|
||
|
||
# 图片尺寸(小红书推荐)
|
||
self.image_width = 1080
|
||
self.image_height = 1440
|
||
|
||
# 边距
|
||
self.margin = 80
|
||
self.top_margin = 120
|
||
self.bottom_margin = 100
|
||
|
||
def split_text_to_pages(self, text: str, max_chars_per_page: int = 800) -> List[str]:
|
||
"""
|
||
将文本分割成多页,保持段落完整性
|
||
|
||
Args:
|
||
text: 原始文本
|
||
max_chars_per_page: 每页最大字符数
|
||
|
||
Returns:
|
||
分页后的文本列表
|
||
"""
|
||
pages = []
|
||
|
||
# 按段落分割
|
||
paragraphs = text.split('\n\n')
|
||
current_page = ""
|
||
current_chars = 0
|
||
|
||
for para in paragraphs:
|
||
para = para.strip()
|
||
if not para:
|
||
continue
|
||
|
||
para_with_newline = para + '\n\n'
|
||
para_length = len(para_with_newline)
|
||
|
||
# 如果段落本身超过一页,需要进一步分割
|
||
if para_length > max_chars_per_page:
|
||
# 先保存当前页
|
||
if current_page:
|
||
pages.append(current_page.strip())
|
||
current_page = ""
|
||
current_chars = 0
|
||
|
||
# 分割长段落
|
||
lines = para.split('\n')
|
||
current_line = ""
|
||
|
||
for line in lines:
|
||
line = line.strip()
|
||
if not line:
|
||
continue
|
||
|
||
line_with_newline = line + '\n'
|
||
line_length = len(line_with_newline)
|
||
|
||
if current_chars + line_length > max_chars_per_page:
|
||
if current_line:
|
||
pages.append(current_line.strip())
|
||
current_line = line
|
||
current_chars = len(line_with_newline)
|
||
else:
|
||
pages.append(line)
|
||
current_chars = 0
|
||
else:
|
||
if current_line:
|
||
current_line += '\n' + line
|
||
current_chars += line_length
|
||
else:
|
||
current_line = line
|
||
current_chars = line_length
|
||
|
||
# 保存最后一部分
|
||
if current_line:
|
||
pages.append(current_line.strip())
|
||
current_page = ""
|
||
current_chars = 0
|
||
|
||
# 正常段落
|
||
elif current_chars + para_length > max_chars_per_page:
|
||
# 当前页已满,开始新页
|
||
pages.append(current_page.strip())
|
||
current_page = para_with_newline
|
||
current_chars = para_length
|
||
else:
|
||
# 添加到当前页
|
||
current_page += para_with_newline
|
||
current_chars += para_length
|
||
|
||
# 保存最后一页
|
||
if current_page.strip():
|
||
pages.append(current_page.strip())
|
||
|
||
logger.info(f"文本分割完成: {len(pages)} 页")
|
||
for i, page in enumerate(pages):
|
||
logger.debug(f"第 {i+1} 页: {len(page)} 字符")
|
||
|
||
return pages
|
||
|
||
def parse_markdown_line(self, line: str) -> Dict[str, Any]:
|
||
"""
|
||
解析单行Markdown,提取格式信息
|
||
|
||
Args:
|
||
line: 原始文本行
|
||
|
||
Returns:
|
||
包含格式信息的字典:
|
||
{
|
||
'text': 纯文本内容(不含Markdown标记),
|
||
'type': 'heading1'/'heading2'/'bold'/'normal',
|
||
'level': 标题级别(1, 2等,仅标题类型)
|
||
}
|
||
"""
|
||
line = line.strip()
|
||
if not line:
|
||
return {'text': '', 'type': 'normal'}
|
||
|
||
# 匹配 ## 标题 (H2)
|
||
h2_match = re.match(r'^##\s+(.+)$', line)
|
||
if h2_match:
|
||
return {
|
||
'text': h2_match.group(1).strip(),
|
||
'type': 'heading1',
|
||
'level': 2
|
||
}
|
||
|
||
# 匹配 ### 标题 (H3)
|
||
h3_match = re.match(r'^###\s+(.+)$', line)
|
||
if h3_match:
|
||
return {
|
||
'text': h3_match.group(1).strip(),
|
||
'type': 'heading2',
|
||
'level': 3
|
||
}
|
||
|
||
# 匹配 **加粗** (支持多段)
|
||
if '**' in line:
|
||
# 移除 ** 标记,标记为加粗
|
||
text = re.sub(r'\*\*(.+?)\*\*', r'\1', line)
|
||
return {
|
||
'text': text,
|
||
'type': 'bold'
|
||
}
|
||
|
||
# 普通文本
|
||
return {
|
||
'text': line,
|
||
'type': 'normal'
|
||
}
|
||
|
||
def get_font_for_style(self, style_type: str, base_style: str = "tech") -> Tuple[ImageFont.FreeTypeFont, Tuple[int, int, int]]:
|
||
"""
|
||
根据文本样式获取对应的字体和颜色
|
||
|
||
Args:
|
||
style_type: 文本类型 ('heading1', 'heading2', 'bold', 'normal')
|
||
base_style: 基础风格 ('tech', 'warm', 'modern')
|
||
|
||
Returns:
|
||
(字体, 颜色) 元组
|
||
"""
|
||
# 根据类型选择字体大小
|
||
if style_type == 'heading1': # ## 标题
|
||
font_size = self.heading1_font_size
|
||
elif style_type == 'heading2': # ### 标题
|
||
font_size = self.heading2_font_size
|
||
else: # bold, normal
|
||
font_size = self.content_font_size
|
||
|
||
# 加载字体
|
||
try:
|
||
font = ImageFont.truetype(self.font_path, font_size)
|
||
except:
|
||
font = ImageFont.load_default()
|
||
|
||
# 根据风格选择颜色
|
||
if base_style == "tech":
|
||
if style_type in ['heading1', 'heading2']:
|
||
color = (255, 220, 100) # 金黄色标题(科技风格强调)
|
||
elif style_type == 'bold':
|
||
color = (150, 200, 255) # 亮蓝色加粗
|
||
else:
|
||
color = (220, 220, 255) # 浅蓝白色正文
|
||
else: # warm, modern
|
||
if style_type in ['heading1', 'heading2']:
|
||
color = (180, 80, 40) # 深棕色标题
|
||
elif style_type == 'bold':
|
||
color = (40, 80, 150) # 深蓝色加粗
|
||
else:
|
||
color = (80, 80, 80) # 深灰色正文
|
||
|
||
return font, color
|
||
|
||
def create_background(self, style: str = "tech") -> Image.Image:
|
||
"""
|
||
创建背景图片
|
||
|
||
Args:
|
||
style: 背景风格
|
||
- tech: 科技风格(蓝色/黑色)
|
||
- warm: 温暖风格(米黄色)
|
||
- modern: 现代风格
|
||
|
||
Returns:
|
||
PIL Image对象
|
||
"""
|
||
if style == "tech":
|
||
# 科技风格:蓝色渐变
|
||
from PIL import ImageFilter
|
||
|
||
# 创建渐变背景
|
||
bg = Image.new('RGB', (self.image_width, self.image_height), color=(10, 20, 40))
|
||
|
||
# 添加科技感元素
|
||
draw = ImageDraw.Draw(bg)
|
||
|
||
# 添加网格线
|
||
for i in range(0, self.image_width, 50):
|
||
draw.line([(i, 0), (i, self.image_height)], fill=(30, 60, 120, 30), width=1)
|
||
|
||
for i in range(0, self.image_height, 50):
|
||
draw.line([(0, i), (self.image_width, i)], fill=(30, 60, 120, 30), width=1)
|
||
|
||
# 添加光点
|
||
import random
|
||
for _ in range(50):
|
||
x = random.randint(0, self.image_width)
|
||
y = random.randint(0, self.image_height)
|
||
radius = random.randint(1, 3)
|
||
draw.ellipse([x-radius, y-radius, x+radius, y+radius],
|
||
fill=(100, 150, 255, 150))
|
||
|
||
# 模糊效果
|
||
bg = bg.filter(ImageFilter.GaussianBlur(radius=1))
|
||
|
||
elif style == "warm":
|
||
# 温暖风格:米黄色背景
|
||
bg = Image.new('RGB', (self.image_width, self.image_height), color=(245, 245, 220))
|
||
|
||
# 添加纹理
|
||
draw = ImageDraw.Draw(bg)
|
||
|
||
# 添加纸张纹理效果
|
||
import random
|
||
for _ in range(2000):
|
||
x = random.randint(0, self.image_width)
|
||
y = random.randint(0, self.image_height)
|
||
brightness = random.randint(230, 245)
|
||
draw.point((x, y), fill=(brightness, brightness, 210))
|
||
|
||
else: # modern 或其他
|
||
# 现代风格:简洁背景
|
||
bg = Image.new('RGB', (self.image_width, self.image_height), color=(240, 240, 245))
|
||
|
||
# 添加简单装饰
|
||
draw = ImageDraw.Draw(bg)
|
||
|
||
# 顶部和底部的装饰线
|
||
draw.line([(0, 50), (self.image_width, 50)], fill=(200, 200, 220), width=2)
|
||
draw.line([(0, self.image_height-50), (self.image_width, self.image_height-50)],
|
||
fill=(200, 200, 220), width=2)
|
||
|
||
return bg
|
||
|
||
def create_text_image(self, title: str, content: str, page_num: int = 1,
|
||
total_pages: int = 1, style: str = "tech") -> str:
|
||
"""
|
||
创建文字图片
|
||
|
||
Args:
|
||
title: 标题
|
||
content: 内容文本
|
||
page_num: 当前页码
|
||
total_pages: 总页数
|
||
style: 背景风格
|
||
|
||
Returns:
|
||
图片文件路径
|
||
"""
|
||
try:
|
||
# 创建背景
|
||
bg = self.create_background(style)
|
||
draw = ImageDraw.Draw(bg)
|
||
|
||
# 加载字体
|
||
try:
|
||
title_font = ImageFont.truetype(self.font_path, self.title_font_size)
|
||
content_font = ImageFont.truetype(self.font_path, self.content_font_size)
|
||
except:
|
||
# 回退到默认字体
|
||
title_font = ImageFont.load_default()
|
||
content_font = ImageFont.load_default()
|
||
logger.warning("使用默认字体,中文字体可能无法正常显示")
|
||
|
||
# 计算可用区域
|
||
usable_width = self.image_width - 2 * self.margin
|
||
usable_height = self.image_height - self.top_margin - self.bottom_margin
|
||
|
||
# 绘制标题
|
||
title_color = (255, 255, 255) if style == "tech" else (50, 50, 50)
|
||
title_y = self.top_margin
|
||
|
||
# 标题可能需要换行
|
||
title_lines = []
|
||
current_line = ""
|
||
|
||
for char in title:
|
||
test_line = current_line + char
|
||
bbox = draw.textbbox((0, 0), test_line, font=title_font)
|
||
text_width = bbox[2] - bbox[0]
|
||
|
||
if text_width > usable_width:
|
||
title_lines.append(current_line)
|
||
current_line = char
|
||
else:
|
||
current_line = test_line
|
||
|
||
if current_line:
|
||
title_lines.append(current_line)
|
||
|
||
# 绘制标题行
|
||
for i, line in enumerate(title_lines):
|
||
bbox = draw.textbbox((0, 0), line, font=title_font)
|
||
text_width = bbox[2] - bbox[0]
|
||
text_height = bbox[3] - bbox[1]
|
||
|
||
x = self.margin + (usable_width - text_width) // 2
|
||
y = title_y + i * (text_height + 10)
|
||
|
||
# 文字阴影(科技风格)
|
||
if style == "tech":
|
||
draw.text((x+2, y+2), line, fill=(0, 0, 0, 150), font=title_font)
|
||
|
||
draw.text((x, y), line, fill=title_color, font=title_font)
|
||
|
||
# 更新Y坐标
|
||
title_height = len(title_lines) * (text_height + 10)
|
||
content_y = title_y + title_height + 40
|
||
|
||
# 绘制内容(支持Markdown格式)
|
||
current_y = content_y
|
||
|
||
# 按行分割内容
|
||
lines = content.split('\n')
|
||
|
||
for line in lines:
|
||
if not line.strip():
|
||
current_y += int(self.content_font_size * self.line_spacing * 0.5)
|
||
continue
|
||
|
||
# 解析Markdown格式
|
||
parsed = self.parse_markdown_line(line)
|
||
text = parsed['text']
|
||
line_type = parsed['type']
|
||
|
||
if not text:
|
||
continue
|
||
|
||
# 根据类型获取字体和颜色
|
||
font, text_color = self.get_font_for_style(line_type, style)
|
||
|
||
# 标题类型添加额外间距
|
||
if line_type in ['heading1', 'heading2']:
|
||
current_y += int(self.content_font_size * 0.5) # 标题前加间距
|
||
|
||
# 检查是否需要换行
|
||
bbox = draw.textbbox((0, 0), text, font=font)
|
||
text_width = bbox[2] - bbox[0]
|
||
|
||
if text_width > usable_width:
|
||
# 需要自动换行
|
||
words = []
|
||
current_word = ""
|
||
|
||
for char in text:
|
||
if char == ' ' or char == ' ' or char == ',' or char == '。':
|
||
if current_word:
|
||
words.append(current_word)
|
||
current_word = ""
|
||
words.append(char)
|
||
else:
|
||
current_word += char
|
||
|
||
if current_word:
|
||
words.append(current_word)
|
||
|
||
# 构建行
|
||
current_line = ""
|
||
for word in words:
|
||
test_line = current_line + word
|
||
bbox = draw.textbbox((0, 0), test_line, font=font)
|
||
test_width = bbox[2] - bbox[0]
|
||
|
||
if test_width > usable_width:
|
||
# 绘制当前行
|
||
bbox = draw.textbbox((0, 0), current_line, font=font)
|
||
text_height = bbox[3] - bbox[1]
|
||
|
||
if current_y + text_height > self.image_height - self.bottom_margin:
|
||
break # 超出页面范围
|
||
|
||
# 绘制文字(加粗类型添加下划线效果增强)
|
||
if line_type == 'bold' and style == "tech":
|
||
draw.text((self.margin, current_y + 2), current_line,
|
||
fill=(0, 0, 0, 100), font=font) # 轻微阴影
|
||
|
||
draw.text((self.margin, current_y), current_line,
|
||
fill=text_color, font=font)
|
||
current_y += int(text_height * self.line_spacing)
|
||
current_line = word
|
||
else:
|
||
current_line = test_line
|
||
|
||
# 绘制最后一行
|
||
if current_line and current_y <= self.image_height - self.bottom_margin:
|
||
bbox = draw.textbbox((0, 0), current_line, font=font)
|
||
text_height = bbox[3] - bbox[1]
|
||
|
||
if line_type == 'bold' and style == "tech":
|
||
draw.text((self.margin, current_y + 2), current_line,
|
||
fill=(0, 0, 0, 100), font=font)
|
||
|
||
draw.text((self.margin, current_y), current_line,
|
||
fill=text_color, font=font)
|
||
current_y += int(text_height * self.line_spacing)
|
||
|
||
else:
|
||
# 单行文本
|
||
bbox = draw.textbbox((0, 0), text, font=font)
|
||
text_height = bbox[3] - bbox[1]
|
||
|
||
if current_y + text_height > self.image_height - self.bottom_margin:
|
||
break # 超出页面范围
|
||
|
||
# 标题居中,其他左对齐
|
||
if line_type in ['heading1', 'heading2']:
|
||
x = self.margin + (usable_width - text_width) // 2
|
||
else:
|
||
x = self.margin
|
||
|
||
# 绘制文字(加粗类型添加轻微阴影增强效果)
|
||
if line_type == 'bold' and style == "tech":
|
||
draw.text((x + 1, current_y + 1), text,
|
||
fill=(0, 0, 0, 100), font=font)
|
||
|
||
draw.text((x, current_y), text, fill=text_color, font=font)
|
||
current_y += int(text_height * self.line_spacing)
|
||
|
||
# 标题类型添加额外间距
|
||
if line_type in ['heading1', 'heading2']:
|
||
current_y += int(self.content_font_size * 0.3) # 标题后加间距
|
||
|
||
# 添加页码
|
||
page_text = f"{page_num}/{total_pages}"
|
||
bbox = draw.textbbox((0, 0), page_text, font=content_font)
|
||
text_width = bbox[2] - bbox[0]
|
||
text_height = bbox[3] - bbox[1]
|
||
|
||
page_x = self.image_width - self.margin - text_width
|
||
page_y = self.image_height - self.bottom_margin + 20
|
||
|
||
draw.text((page_x, page_y), page_text, fill=(150, 150, 150), font=content_font)
|
||
|
||
# 保存图片
|
||
timestamp = os.times().elapsed
|
||
image_path = os.path.join(self.temp_dir, f"xhs_page_{page_num}_{int(timestamp)}.png")
|
||
bg.save(image_path, 'PNG', quality=95)
|
||
|
||
logger.info(f"创建图片第 {page_num}/{total_pages} 页: {image_path}")
|
||
return image_path
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建图片失败: {e}")
|
||
return None
|
||
|
||
def publish_to_xiaohongshu(self, title: str, content: str, tags: List[str] = None,
|
||
style: str = "tech") -> bool:
|
||
"""
|
||
发布到小红书(多图版本)
|
||
|
||
Args:
|
||
title: 标题
|
||
content: 内容(将自动分页)
|
||
tags: 标签列表
|
||
style: 背景风格
|
||
|
||
Returns:
|
||
是否成功
|
||
"""
|
||
try:
|
||
# 1. 文本分页
|
||
logger.info(f"开始处理文章: {title}")
|
||
pages = self.split_text_to_pages(content)
|
||
|
||
if not pages:
|
||
logger.error("文本分页失败")
|
||
return False
|
||
|
||
# 2. 创建多张图片
|
||
image_paths = []
|
||
for i, page_content in enumerate(pages):
|
||
page_num = i + 1
|
||
image_path = self.create_text_image(
|
||
title=title if i == 0 else f"{title} (续{page_num})",
|
||
content=page_content,
|
||
page_num=page_num,
|
||
total_pages=len(pages),
|
||
style=style
|
||
)
|
||
|
||
if image_path and os.path.exists(image_path):
|
||
image_paths.append(image_path)
|
||
logger.info(f"图片创建成功: {image_path}")
|
||
else:
|
||
logger.warning(f"第 {page_num} 页图片创建失败")
|
||
|
||
if not image_paths:
|
||
logger.error("没有成功创建任何图片")
|
||
return False
|
||
|
||
# 3. 构建小红书内容(去掉原文链接)
|
||
xhs_content = f"【{title}】\n\n"
|
||
|
||
# 添加原文摘要(前200字符),过滤掉原文链接
|
||
content_preview = content[:200] + "..." if len(content) > 200 else content
|
||
|
||
# 过滤掉"原文链接:"相关内容
|
||
import re
|
||
# 移除"原文链接:"及其后面的URL
|
||
content_preview = re.sub(r'原文链接:.*?(?=\n|$)', '', content_preview)
|
||
# 移除URL链接
|
||
content_preview = re.sub(r'https?://\S+', '', content_preview)
|
||
# 清理多余的空行
|
||
content_preview = re.sub(r'\n\s*\n', '\n\n', content_preview).strip()
|
||
|
||
xhs_content += f"{content_preview}\n\n"
|
||
|
||
# 添加标签
|
||
if tags:
|
||
xhs_content += " ".join([f"#{tag}" for tag in tags])
|
||
|
||
# 4. 发布到小红书
|
||
logger.info(f"准备发布到小红书: {len(image_paths)} 张图片")
|
||
|
||
# 构建xhs命令(使用完整路径避免PATH问题)
|
||
xhs_cmd = os.environ.get('XHS_CLI_PATH', '/home/ubuntu/.local/bin/xhs')
|
||
cmd = [xhs_cmd, 'post', '--title', title, '--body', xhs_content]
|
||
|
||
# 添加多张图片参数
|
||
for img_path in image_paths:
|
||
cmd.extend(['--images', img_path])
|
||
|
||
# 添加标签(如果有)
|
||
if tags:
|
||
tags_str = ','.join(tags[:5])
|
||
cmd.extend(['--topic', tags_str])
|
||
|
||
logger.info(f"执行命令: {' '.join(cmd[:10])}...")
|
||
|
||
# 执行发布命令
|
||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
||
|
||
if result.returncode == 0:
|
||
logger.info("✅ 小红书发布成功!")
|
||
|
||
# 尝试提取笔记ID
|
||
import re
|
||
output = result.stdout or result.stderr
|
||
note_id_match = re.search(r'id:\s*([a-f0-9]+)', output, re.IGNORECASE)
|
||
|
||
if note_id_match:
|
||
note_id = note_id_match.group(1)
|
||
logger.info(f"📝 笔记ID: {note_id}")
|
||
else:
|
||
logger.info(f"📄 输出: {output[:200]}...")
|
||
|
||
# 清理临时图片
|
||
for img_path in image_paths:
|
||
try:
|
||
os.remove(img_path)
|
||
logger.debug(f"清理临时图片: {img_path}")
|
||
except:
|
||
pass
|
||
|
||
return True
|
||
else:
|
||
logger.error(f"❌ 小红书发布失败: {result.stderr}")
|
||
return False
|
||
|
||
except Exception as e:
|
||
logger.error(f"发布过程中出现错误: {e}")
|
||
return False
|
||
|
||
def test_multi_image(self):
|
||
"""测试多图功能"""
|
||
test_title = "测试多图发布功能"
|
||
test_content = """这是一篇测试文章,用于验证小红书多图发布功能。
|
||
|
||
随着人工智能技术的快速发展,越来越多的企业开始将AI应用于实际业务中。从自然语言处理到计算机视觉,从机器学习到深度学习,AI正在改变我们的工作和生活方式。
|
||
|
||
在技术领域,最近有几个重要的发展趋势值得关注:
|
||
|
||
1. **大语言模型的普及**:像GPT-4、Claude这样的模型正在被广泛应用于内容创作、代码编写、客户服务等领域。
|
||
|
||
2. **边缘AI的兴起**:随着设备计算能力的提升,越来越多的AI应用可以在本地设备上运行,减少了对云端的依赖。
|
||
|
||
3. **多模态AI的发展**:能够同时处理文本、图像、音频等多种输入形式的AI系统正在成为新的研究热点。
|
||
|
||
4. **AI伦理和安全性**:随着AI应用的普及,如何确保AI系统的安全性、公平性和透明度成为了重要议题。
|
||
|
||
对于开发者来说,掌握AI技术已经成为了必备技能。无论是前端开发、后端开发还是数据分析,AI都能提供强大的辅助工具。
|
||
|
||
未来,我们可以期待更多创新的AI应用出现,它们将进一步提升生产效率,改善用户体验,推动社会进步。
|
||
|
||
当然,技术的发展也带来了一些挑战,比如数据隐私、算法偏见、就业影响等问题都需要我们认真思考和解决。
|
||
|
||
总的来说,AI技术正在以前所未有的速度发展,它既带来了机遇也带来了挑战。我们需要以开放的心态拥抱变化,同时也要保持警惕,确保技术的发展能够造福全人类。"""
|
||
|
||
test_tags = ["技术测试", "多图发布", "AI技术", "小红书", "自动化"]
|
||
|
||
print("测试小红书多图发布功能...")
|
||
print(f"标题: {test_title}")
|
||
print(f"内容长度: {len(test_content)} 字符")
|
||
print(f"标签: {', '.join(test_tags)}")
|
||
print("")
|
||
|
||
# 测试文本分页
|
||
print("1. 测试文本分页...")
|
||
pages = self.split_text_to_pages(test_content, max_chars_per_page=500)
|
||
print(f" 分页结果: {len(pages)} 页")
|
||
|
||
# 测试图片生成
|
||
print("\n2. 测试图片生成...")
|
||
for i, page in enumerate(pages[:2]): # 只测试前2页
|
||
print(f" 生成第 {i+1} 页图片...")
|
||
img_path = self.create_text_image(
|
||
title=test_title if i == 0 else f"{test_title} (续{i+1})",
|
||
content=page,
|
||
page_num=i+1,
|
||
total_pages=len(pages),
|
||
style="tech"
|
||
)
|
||
|
||
if img_path:
|
||
print(f" ✅ 图片生成成功: {img_path}")
|
||
|
||
# 显示图片信息
|
||
try:
|
||
with Image.open(img_path) as img:
|
||
print(f" 尺寸: {img.size}, 格式: {img.format}")
|
||
except:
|
||
pass
|
||
else:
|
||
print(f" ❌ 图片生成失败")
|
||
|
||
print("\n3. 测试发布功能(模拟)...")
|
||
print(" ⚠️ 实际发布需要xhs-cli和有效的Cookie")
|
||
print(" 可以调用 publish_to_xiaohongshu() 方法进行实际发布")
|
||
|
||
return True
|
||
|
||
def main():
|
||
"""主函数"""
|
||
import sys
|
||
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||
)
|
||
|
||
publisher = XiaohongshuMultiImagePublisher()
|
||
|
||
if len(sys.argv) > 1 and sys.argv[1] == "test":
|
||
publisher.test_multi_image()
|
||
else:
|
||
print("使用方法:")
|
||
print(" python3 xiaohongshu_multi_image.py test # 测试功能")
|
||
print("")
|
||
print("编程接口:")
|
||
print(" publisher = XiaohongshuMultiImagePublisher()")
|
||
print(" success = publisher.publish_to_xiaohongshu(title, content, tags, style)")
|
||
|
||
if __name__ == "__main__":
|
||
main() |