a
This commit is contained in:
+719
@@ -0,0 +1,719 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user