marketing-pipeline-share-ai-content-automation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMarketing Pipeline Share - AI Content Automation
营销流水线分享 - AI内容自动化
Skill by ara.so — Marketing Skills collection.
This project is an end-to-end AI-powered content automation system that handles research (web scraping), content generation (Claude/OpenAI), and video rendering (Remotion). It automates content creation from keyword input to publishable video output.
由 ara.so 提供的技能 — 营销技能合集。
本项目是一个端到端的AI驱动内容自动化系统,可处理调研(网页抓取)、内容生成(Claude/OpenAI)和视频渲染(Remotion)。它能实现从关键词输入到可发布视频输出的全流程内容创作自动化。
What It Does
功能介绍
- Auto-Research: Scrapes TechCrunch, a16z, Twitter/X, LinkedIn for recent news (24h window)
- AI Content Generation: Creates diverse formats (toplist, POV, case study, how-to) using Claude 3 or OpenAI
- Multi-language Support: Generates content in both English and Vietnamese
- Video Rendering: Automatically creates infographics and short-form videos using Remotion
- Multi-platform Export: Optimizes videos for Reels, TikTok, Shorts
- 自动调研:抓取TechCrunch、a16z、Twitter/X、LinkedIn上的近期新闻(24小时窗口)
- AI内容生成:使用Claude 3或OpenAI创建多种格式内容(排行榜、观点文、案例研究、教程)
- 多语言支持:生成英文和越南语内容
- 视频渲染:使用Remotion自动创建信息图和短视频
- 多平台导出:针对Reels、TikTok、Shorts优化视频
Installation
安装
bash
undefinedbash
undefinedClone the repository
Clone the repository
git clone https://github.com/pennydinh/marketing-pineline-share.git
cd marketing-pineline-share
git clone https://github.com/pennydinh/marketing-pineline-share.git
cd marketing-pineline-share
Install dependencies
Install dependencies
npm install
npm install
or
or
yarn install
yarn install
or
or
pnpm install
undefinedpnpm install
undefinedEnvironment Configuration
环境配置
Create a file in the root directory:
.env.localenv
undefined在根目录创建 文件:
.env.localenv
undefinedAI Provider Keys
AI Provider Keys
ANTHROPIC_API_KEY=your_claude_api_key
OPENAI_API_KEY=your_openai_api_key
ANTHROPIC_API_KEY=your_claude_api_key
OPENAI_API_KEY=your_openai_api_key
Scraping/Research APIs (RapidAPI)
Scraping/Research APIs (RapidAPI)
RAPIDAPI_KEY=your_rapidapi_key
RAPIDAPI_KEY=your_rapidapi_key
Optional: Custom API endpoints
Optional: Custom API endpoints
NEXT_PUBLIC_API_URL=http://localhost:3000
NEXT_PUBLIC_API_URL=http://localhost:3000
Remotion Configuration
Remotion Configuration
REMOTION_AWS_ACCESS_KEY_ID=your_aws_key
REMOTION_AWS_SECRET_ACCESS_KEY=your_aws_secret
undefinedREMOTION_AWS_ACCESS_KEY_ID=your_aws_key
REMOTION_AWS_SECRET_ACCESS_KEY=your_aws_secret
undefinedProject Structure
项目结构
marketing-pineline-share/
├── src/
│ ├── app/ # Next.js app directory
│ ├── components/ # React components
│ ├── lib/
│ │ ├── ai/ # AI provider integrations
│ │ ├── scraper/ # Web scraping modules
│ │ └── video/ # Remotion video rendering
│ ├── types/ # TypeScript definitions
│ └── utils/ # Utility functions
├── remotion/ # Remotion video templates
└── public/ # Static assetsmarketing-pineline-share/
├── src/
│ ├── app/ # Next.js应用目录
│ ├── components/ # React组件
│ ├── lib/
│ │ ├── ai/ # AI提供商集成模块
│ │ ├── scraper/ # 网页抓取模块
│ │ └── video/ # Remotion视频渲染模块
│ ├── types/ # TypeScript类型定义
│ └── utils/ # 工具函数
├── remotion/ # Remotion视频模板
└── public/ # 静态资源Core Usage Patterns
核心使用模式
1. Content Research & Scraping
1. 内容调研与抓取
typescript
import { scrapeNews } from '@/lib/scraper/news-scraper';
import { analyzeTrends } from '@/lib/scraper/trend-analyzer';
// Scrape recent news from multiple sources
async function researchTopic(keyword: string) {
const sources = ['techcrunch', 'a16z', 'twitter', 'linkedin'];
const newsData = await scrapeNews({
keyword,
sources,
timeframe: '24h',
limit: 50
});
// Analyze and extract insights
const insights = await analyzeTrends(newsData);
return {
articles: newsData,
insights,
keywords: insights.topKeywords,
stats: insights.statistics
};
}typescript
import { scrapeNews } from '@/lib/scraper/news-scraper';
import { analyzeTrends } from '@/lib/scraper/trend-analyzer';
// 从多个来源抓取近期新闻
async function researchTopic(keyword: string) {
const sources = ['techcrunch', 'a16z', 'twitter', 'linkedin'];
const newsData = await scrapeNews({
keyword,
sources,
timeframe: '24h',
limit: 50
});
// 分析并提取洞察
const insights = await analyzeTrends(newsData);
return {
articles: newsData,
insights,
keywords: insights.topKeywords,
stats: insights.statistics
};
}2. AI Content Generation with Claude
2. 基于Claude的AI内容生成
typescript
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
async function generateContent(
topic: string,
format: 'toplist' | 'pov' | 'case-study' | 'how-to',
language: 'en' | 'vi',
researchData: any
) {
const systemPrompt = `You are an expert content creator.
Create a ${format} article about ${topic} in ${language}.
Use the following research data: ${JSON.stringify(researchData)}`;
const message = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 4096,
temperature: 0.7,
system: systemPrompt,
messages: [
{
role: 'user',
content: `Create an engaging ${format} article about ${topic}.
Include data-backed insights and recent trends.`
}
]
});
return message.content[0].text;
}typescript
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
async function generateContent(
topic: string,
format: 'toplist' | 'pov' | 'case-study' | 'how-to',
language: 'en' | 'vi',
researchData: any
) {
const systemPrompt = `You are an expert content creator.
Create a ${format} article about ${topic} in ${language}.
Use the following research data: ${JSON.stringify(researchData)}`;
const message = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 4096,
temperature: 0.7,
system: systemPrompt,
messages: [
{
role: 'user',
content: `Create an engaging ${format} article about ${topic}.
Include data-backed insights and recent trends.`
}
]
});
return message.content[0].text;
}3. Alternative: OpenAI Integration
3. 替代方案:OpenAI集成
typescript
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
async function generateWithOpenAI(
topic: string,
context: string
) {
const completion = await openai.chat.completions.create({
model: 'gpt-4-turbo-preview',
messages: [
{
role: 'system',
content: 'You are a marketing content expert.'
},
{
role: 'user',
content: `Create content about ${topic}. Context: ${context}`
}
],
temperature: 0.8,
max_tokens: 3000
});
return completion.choices[0].message.content;
}typescript
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
async function generateWithOpenAI(
topic: string,
context: string
) {
const completion = await openai.chat.completions.create({
model: 'gpt-4-turbo-preview',
messages: [
{
role: 'system',
content: 'You are a marketing content expert.'
},
{
role: 'user',
content: `Create content about ${topic}. Context: ${context}`
}
],
temperature: 0.8,
max_tokens: 3000
});
return completion.choices[0].message.content;
}4. Video Generation with Remotion
4. 基于Remotion的视频生成
typescript
import { bundle } from '@remotion/bundler';
import { renderMedia, selectComposition } from '@remotion/renderer';
import { webpackOverride } from './remotion/webpack-override';
async function renderContentVideo(
content: string,
title: string,
format: 'reels' | 'tiktok' | 'shorts'
) {
// Video dimensions based on format
const dimensions = {
reels: { width: 1080, height: 1920 },
tiktok: { width: 1080, height: 1920 },
shorts: { width: 1080, height: 1920 }
};
const bundleLocation = await bundle({
entryPoint: './remotion/index.ts',
webpackOverride,
});
const composition = await selectComposition({
serveUrl: bundleLocation,
id: 'ContentVideo',
inputProps: {
title,
content,
...dimensions[format]
},
});
await renderMedia({
composition,
serveUrl: bundleLocation,
codec: 'h264',
outputLocation: `out/${title}-${format}.mp4`,
inputProps: {
title,
content,
},
});
return `out/${title}-${format}.mp4`;
}typescript
import { bundle } from '@remotion/bundler';
import { renderMedia, selectComposition } from '@remotion/renderer';
import { webpackOverride } from './remotion/webpack-override';
async function renderContentVideo(
content: string,
title: string,
format: 'reels' | 'tiktok' | 'shorts'
) {
// 根据格式设置视频尺寸
const dimensions = {
reels: { width: 1080, height: 1920 },
tiktok: { width: 1080, height: 1920 },
shorts: { width: 1080, height: 1920 }
};
const bundleLocation = await bundle({
entryPoint: './remotion/index.ts',
webpackOverride,
});
const composition = await selectComposition({
serveUrl: bundleLocation,
id: 'ContentVideo',
inputProps: {
title,
content,
...dimensions[format]
},
});
await renderMedia({
composition,
serveUrl: bundleLocation,
codec: 'h264',
outputLocation: `out/${title}-${format}.mp4`,
inputProps: {
title,
content,
},
});
return `out/${title}-${format}.mp4`;
}5. Complete Pipeline Example
5. 完整流水线示例
typescript
import { scrapeNews } from '@/lib/scraper/news-scraper';
import { generateContent } from '@/lib/ai/claude-generator';
import { renderContentVideo } from '@/lib/video/remotion-renderer';
async function runContentPipeline(
keyword: string,
config: {
format: 'toplist' | 'pov' | 'case-study' | 'how-to';
languages: ('en' | 'vi')[];
videoFormats: ('reels' | 'tiktok' | 'shorts')[];
}
) {
// Step 1: Research
console.log('🔍 Researching topic...');
const research = await scrapeNews({
keyword,
sources: ['techcrunch', 'a16z'],
timeframe: '24h'
});
// Step 2: Generate content for each language
const contents = await Promise.all(
config.languages.map(async (lang) => {
console.log(`✍️ Generating ${lang} content...`);
const content = await generateContent(
keyword,
config.format,
lang,
research
);
return { lang, content };
})
);
// Step 3: Render videos
const videos = await Promise.all(
contents.flatMap(({ lang, content }) =>
config.videoFormats.map(async (format) => {
console.log(`🎬 Rendering ${format} video (${lang})...`);
const videoPath = await renderContentVideo(
content,
`${keyword}-${lang}`,
format
);
return { lang, format, videoPath };
})
)
);
return {
research,
contents,
videos
};
}
// Usage
const result = await runContentPipeline('AI Marketing Trends', {
format: 'toplist',
languages: ['en', 'vi'],
videoFormats: ['reels', 'tiktok']
});typescript
import { scrapeNews } from '@/lib/scraper/news-scraper';
import { generateContent } from '@/lib/ai/claude-generator';
import { renderContentVideo } from '@/lib/video/remotion-renderer';
async function runContentPipeline(
keyword: string,
config: {
format: 'toplist' | 'pov' | 'case-study' | 'how-to';
languages: ('en' | 'vi')[];
videoFormats: ('reels' | 'tiktok' | 'shorts')[];
}
) {
// 步骤1:调研
console.log('🔍 正在调研主题...');
const research = await scrapeNews({
keyword,
sources: ['techcrunch', 'a16z'],
timeframe: '24h'
});
// 步骤2:为每种语言生成内容
const contents = await Promise.all(
config.languages.map(async (lang) => {
console.log(`✍️ 正在生成${lang}语言内容...`);
const content = await generateContent(
keyword,
config.format,
lang,
research
);
return { lang, content };
})
);
// 步骤3:渲染视频
const videos = await Promise.all(
contents.flatMap(({ lang, content }) =>
config.videoFormats.map(async (format) => {
console.log(`🎬 正在渲染${format}格式视频(${lang}语言)...`);
const videoPath = await renderContentVideo(
content,
`${keyword}-${lang}`,
format
);
return { lang, format, videoPath };
})
)
);
return {
research,
contents,
videos
};
}
// 使用示例
const result = await runContentPipeline('AI Marketing Trends', {
format: 'toplist',
languages: ['en', 'vi'],
videoFormats: ['reels', 'tiktok']
});API Routes (Next.js)
API路由(Next.js)
Research Endpoint
调研接口
typescript
// app/api/research/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { scrapeNews } from '@/lib/scraper/news-scraper';
export async function POST(request: NextRequest) {
const { keyword, sources, timeframe } = await request.json();
try {
const data = await scrapeNews({ keyword, sources, timeframe });
return NextResponse.json({ success: true, data });
} catch (error) {
return NextResponse.json(
{ success: false, error: error.message },
{ status: 500 }
);
}
}typescript
// app/api/research/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { scrapeNews } from '@/lib/scraper/news-scraper';
export async function POST(request: NextRequest) {
const { keyword, sources, timeframe } = await request.json();
try {
const data = await scrapeNews({ keyword, sources, timeframe });
return NextResponse.json({ success: true, data });
} catch (error) {
return NextResponse.json(
{ success: false, error: error.message },
{ status: 500 }
);
}
}Content Generation Endpoint
内容生成接口
typescript
// app/api/generate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateContent } from '@/lib/ai/claude-generator';
export async function POST(request: NextRequest) {
const { topic, format, language, research } = await request.json();
try {
const content = await generateContent(topic, format, language, research);
return NextResponse.json({ success: true, content });
} catch (error) {
return NextResponse.json(
{ success: false, error: error.message },
{ status: 500 }
);
}
}typescript
// app/api/generate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { generateContent } from '@/lib/ai/claude-generator';
export async function POST(request: NextRequest) {
const { topic, format, language, research } = await request.json();
try {
const content = await generateContent(topic, format, language, research);
return NextResponse.json({ success: true, content });
} catch (error) {
return NextResponse.json(
{ success: false, error: error.message },
{ status: 500 }
);
}
}Development Commands
开发命令
bash
undefinedbash
undefinedStart development server
启动开发服务器
npm run dev
npm run dev
Build for production
构建生产版本
npm run build
npm run build
Start production server
启动生产服务器
npm start
npm start
Render Remotion video (development)
渲染Remotion视频(开发模式)
npm run remotion:dev
npm run remotion:dev
Render Remotion video (production)
渲染Remotion视频(生产模式)
npm run remotion:render -- --props='{"title":"My Video"}'
npm run remotion:render -- --props='{"title":"My Video"}'
Type checking
类型检查
npm run type-check
npm run type-check
Linting
代码检查
npm run lint
undefinednpm run lint
undefinedRemotion Video Templates
Remotion视频模板
Create a custom video template:
typescript
// remotion/compositions/ContentVideo.tsx
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from 'remotion';
export const ContentVideo: React.FC<{
title: string;
content: string;
}> = ({ title, content }) => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
const opacity = Math.min(1, frame / 30);
return (
<AbsoluteFill
style={{
backgroundColor: '#000',
justifyContent: 'center',
alignItems: 'center',
}}
>
<div style={{ opacity }}>
<h1 style={{ color: 'white', fontSize: 80 }}>{title}</h1>
<p style={{ color: 'white', fontSize: 40, maxWidth: width * 0.8 }}>
{content}
</p>
</div>
</AbsoluteFill>
);
};Register the composition:
typescript
// remotion/index.ts
import { registerRoot } from 'remotion';
import { ContentVideo } from './compositions/ContentVideo';
registerRoot(() => {
return (
<>
<Composition
id="ContentVideo"
component={ContentVideo}
durationInFrames={300}
fps={30}
width={1080}
height={1920}
defaultProps={{
title: 'Default Title',
content: 'Default content',
}}
/>
</>
);
});创建自定义视频模板:
typescript
// remotion/compositions/ContentVideo.tsx
import { AbsoluteFill, useCurrentFrame, useVideoConfig } from 'remotion';
export const ContentVideo: React.FC<{
title: string;
content: string;
}> = ({ title, content }) => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
const opacity = Math.min(1, frame / 30);
return (
<AbsoluteFill
style={{
backgroundColor: '#000',
justifyContent: 'center',
alignItems: 'center',
}}
>
<div style={{ opacity }}>
<h1 style={{ color: 'white', fontSize: 80 }}>{title}</h1>
<p style={{ color: 'white', fontSize: 40, maxWidth: width * 0.8 }}>
{content}
</p>
</div>
</AbsoluteFill>
);
};注册合成组件:
typescript
// remotion/index.ts
import { registerRoot } from 'remotion';
import { ContentVideo } from './compositions/ContentVideo';
registerRoot(() => {
return (
<>
<Composition
id="ContentVideo"
component={ContentVideo}
durationInFrames={300}
fps={30}
width={1080}
height={1920}
defaultProps={{
title: 'Default Title',
content: 'Default content',
}}
/>
</>
);
});Common Patterns
通用模式
Rate Limiting & Error Handling
速率限制与错误处理
typescript
import { RateLimiter } from '@/lib/utils/rate-limiter';
const limiter = new RateLimiter({
maxRequests: 50,
perMinutes: 1
});
async function safeAPICall<T>(
fn: () => Promise<T>,
retries = 3
): Promise<T> {
await limiter.waitForSlot();
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
throw new Error('Max retries exceeded');
}typescript
import { RateLimiter } from '@/lib/utils/rate-limiter';
const limiter = new RateLimiter({
maxRequests: 50,
perMinutes: 1
});
async function safeAPICall<T>(
fn: () => Promise<T>,
retries = 3
): Promise<T> {
await limiter.waitForSlot();
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
throw new Error('Max retries exceeded');
}Content Quality Validation
内容质量验证
typescript
interface ContentQuality {
wordCount: number;
readabilityScore: number;
hasData: boolean;
hasHeadings: boolean;
}
function validateContent(content: string): ContentQuality {
const wordCount = content.split(/\s+/).length;
const hasData = /\d+%|\$\d+|statistics|data shows/i.test(content);
const hasHeadings = /#+ /m.test(content);
return {
wordCount,
readabilityScore: calculateReadability(content),
hasData,
hasHeadings,
};
}typescript
interface ContentQuality {
wordCount: number;
readabilityScore: number;
hasData: boolean;
hasHeadings: boolean;
}
function validateContent(content: string): ContentQuality {
const wordCount = content.split(/\s+/).length;
const hasData = /\d+%|\$\d+|statistics|data shows/i.test(content);
const hasHeadings = /#+ /m.test(content);
return {
wordCount,
readabilityScore: calculateReadability(content),
hasData,
hasHeadings,
};
}Troubleshooting
故障排除
API Rate Limits
API速率限制
If you encounter rate limit errors:
typescript
// Implement exponential backoff
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
async function retryWithBackoff<T>(
fn: () => Promise<T>,
maxRetries = 5
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (error.status === 429 && i < maxRetries - 1) {
const waitTime = Math.pow(2, i) * 1000;
console.log(`Rate limited. Waiting ${waitTime}ms...`);
await delay(waitTime);
} else {
throw error;
}
}
}
throw new Error('Max retries exceeded');
}如果遇到速率限制错误:
typescript
// 实现指数退避
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
async function retryWithBackoff<T>(
fn: () => Promise<T>,
maxRetries = 5
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (error.status === 429 && i < maxRetries - 1) {
const waitTime = Math.pow(2, i) * 1000;
console.log(`速率受限,等待${waitTime}ms...`);
await delay(waitTime);
} else {
throw error;
}
}
}
throw new Error('Max retries exceeded');
}Scraping Failures
抓取失败
Check for blocked requests or invalid selectors:
typescript
// Add user agent and retry logic
import axios from 'axios';
const scrapeWithHeaders = async (url: string) => {
return axios.get(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; ContentBot/1.0)',
'Accept': 'text/html,application/xhtml+xml',
},
timeout: 10000,
});
};检查请求是否被拦截或选择器是否无效:
typescript
// 添加用户代理和重试逻辑
import axios from 'axios';
const scrapeWithHeaders = async (url: string) => {
return axios.get(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; ContentBot/1.0)',
'Accept': 'text/html,application/xhtml+xml',
},
timeout: 10000,
});
};Video Rendering Memory Issues
视频渲染内存问题
For large video renders:
typescript
// Reduce quality or split into chunks
await renderMedia({
composition,
serveUrl: bundleLocation,
codec: 'h264',
quality: 80, // Reduce from default 100
concurrency: 1, // Limit concurrent frames
outputLocation: outputPath,
});针对大型视频渲染:
typescript
// 降低质量或拆分片段
await renderMedia({
composition,
serveUrl: bundleLocation,
codec: 'h264',
quality: 80, // 从默认100降低
concurrency: 1, // 限制并发帧数
outputLocation: outputPath,
});TypeScript Configuration
TypeScript配置
Ensure proper types for AI responses:
typescript
// types/content.ts
export interface GeneratedContent {
title: string;
body: string;
metadata: {
format: string;
language: string;
wordCount: number;
};
sources: string[];
}
// Use with AI generation
const content = await generateContent(...) as GeneratedContent;确保AI响应有正确的类型:
typescript
// types/content.ts
export interface GeneratedContent {
title: string;
body: string;
metadata: {
format: string;
language: string;
wordCount: number;
};
sources: string[];
}
// 在AI生成中使用
const content = await generateContent(...) as GeneratedContent;