const fs = require('fs'); const path = require('path'); // Reddit 数据分布模式(基于真实 Reddit 统计) const SUBREDDIT_CONFIG = { 'AmITheAsshole': { weight: 0.30, // 30% 数据 scoreRange: [100, 25000], commentRange: [50, 5000], upvoteRatioRange: [0.65, 0.98], conflictTypes: ['family_conflict', 'relationship_crisis', 'workplace_drama', 'inheritance_dispute', 'wedding_drama'], dramaKeywords: ['AITA', 'boyfriend', 'girlfriend', 'husband', 'wife', 'mother', 'father', 'sister', 'brother', 'inlaws'] }, 'relationship_advice': { weight: 0.25, scoreRange: [50, 15000], commentRange: [30, 3000], upvoteRatioRange: [0.70, 0.95], conflictTypes: ['relationship_crisis', 'breakup_drama', 'trust_issues', 'communication_breakdown'], dramaKeywords: ['cheating', 'trust', 'breakup', 'divorce', 'relationship', 'partner', 'communicate'] }, 'funny': { weight: 0.20, scoreRange: [500, 50000], commentRange: [10, 800], upvoteRatioRange: [0.80, 0.99], conflictTypes: ['absurd_situation', 'social_awkwardness', 'unexpected_twist'], dramaKeywords: ['hilarious', 'lol', 'funny', 'unexpected', 'awkward'] }, 'jokes': { weight: 0.15, scoreRange: [200, 30000], commentRange: [5, 500], upvoteRatioRange: [0.75, 0.98], conflictTypes: ['wordplay', 'absurd_situation', 'dark_humor'], dramaKeywords: ['joke', 'punchline', 'setup', 'humor', 'laugh'] }, 'StandUpComedy': { weight: 0.10, scoreRange: [100, 10000], commentRange: [10, 600], upvoteRatioRange: [0.72, 0.96], conflictTypes: ['observational_humor', 'social_commentary', 'self_deprecation'], dramaKeywords: ['comedy', 'standup', 'joke', 'routine', 'audience', 'stage'] } }; // 生成随机整数 function randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } // 生成长尾分布的随机数(更真实) function randomLongTail(min, max, exponent = 2) { const u = Math.random(); const scaled = Math.pow(u, exponent); return Math.floor(min + scaled * (max - min)); } // 生成随机日期(最近30天) function randomDate() { const daysAgo = randomInt(0, 30); const date = new Date(); date.setDate(date.getDate() - daysAgo); return date.toISOString(); } // 合成标题模板 const TITLE_TEMPLATES = { 'AmITheAsshole': [ 'AITA for {action} when {context}?', 'AITA for refusing to {action}?', 'My {relation} wants me to {action}, AITA for saying no?', 'AITA for being upset about {situation}?', 'I {action} and now my {relation} is mad at me, AITA?' ], 'relationship_advice': [ 'My {relation} {action} and I don\'t know what to do', 'Should I {action} after {situation}?', 'I found out my {relation} {action}, how do I proceed?', 'My {relation} and I keep fighting about {issue}', 'Is it normal for {relation} to {action}?' ], 'funny': [ 'When you {situation}...', 'My {relation} tried to {action} and failed spectacularly', 'This {thing} just made my day', '{situation} happened today', 'Found this gem: {description}' ], 'jokes': [ 'Why did the {noun} {verb}?', 'What do you call a {description}?', 'My {relation} told me this one: {setup}', 'I heard {person} say: {setup}', 'Classic: {setup}' ], 'StandUpComedy': [ 'On {topic}: "{quote}"', 'My take on {topic}...', 'Why is {topic} so {adjective}?', 'The thing about {topic} nobody talks about', 'Crowd work gone wrong: {situation}' ] }; const ACTIONS = ['ignoring my calls', 'going through my phone', 'skipping the wedding', 'telling the truth', 'setting boundaries', 'moving out', 'quitting my job', 'confronting them']; const CONTEXTS = ['my sister asked me to', 'it made me uncomfortable', 'they were being unreasonable', 'I had other plans', 'it was an emergency']; const RELATIONS = ['boyfriend', 'girlfriend', 'husband', 'wife', 'mother', 'father', 'sister', 'brother', 'boss', 'coworker', 'friend', 'roommate']; const SITUATIONS = ['realized I was wrong', 'spilled coffee everywhere', 'forgot my keys', 'got caught in the rain', 'tried to cook', 'attempted DIY']; const ISSUES = ['money', 'time management', 'household chores', 'family events', 'work-life balance', 'trust', 'communication']; const NOUNS = ['chicken', 'programmer', 'doctor', 'teacher', 'penguin', 'lawyer', 'actor', 'chef']; const VERBS = ['cross the road', 'get fired', 'get promoted', 'fall down', 'start laughing', 'leave early']; const TOPICS = ['dating apps', 'airplane food', 'in-laws', 'grocery shopping', 'social media', 'adulting', 'millennials']; const ADJECTIVES = ['weird', 'funny', 'relatable', 'confusing', 'frustrating', 'hilarious']; function generateTitle(subreddit) { const templates = TITLE_TEMPLATES[subreddit]; const template = templates[randomInt(0, templates.length - 1)]; return template .replace(/{action}/g, ACTIONS[randomInt(0, ACTIONS.length - 1)]) .replace(/{context}/g, CONTEXTS[randomInt(0, CONTEXTS.length - 1)]) .replace(/{relation}/g, RELATIONS[randomInt(0, RELATIONS.length - 1)]) .replace(/{situation}/g, SITUATIONS[randomInt(0, SITUATIONS.length - 1)]) .replace(/{issue}/g, ISSUES[randomInt(0, ISSUES.length - 1)]) .replace(/{noun}/g, NOUNS[randomInt(0, NOUNS.length - 1)]) .replace(/{verb}/g, VERBS[randomInt(0, VERBS.length - 1)]) .replace(/{topic}/g, TOPICS[randomInt(0, TOPICS.length - 1)]) .replace(/{adjective}/g, ADJECTIVES[randomInt(0, ADJECTIVES.length - 1)]) .replace(/{thing}/g, ['meme', 'post', 'comment', 'video', 'story'][randomInt(0, 4)]) .replace(/{description}/g, ['funny story', 'weird encounter', 'unexpected outcome'][randomInt(0, 2)]) .replace(/{setup}/g, ['the setup...', 'you know what I mean', 'classic punchline'][randomInt(0, 2)]) .replace(/{quote}/g, ['it gets better', 'trust the process', 'life is weird'][randomInt(0, 2)]) .replace(/{person}/g, ['my dad', 'a comedian', 'my friend', 'some guy at work'][randomInt(0, 3)]); } // 生成正文内容 function generateSelftext(subreddit, conflictType) { const lengths = randomInt(100, 2000); const paragraphs = Math.ceil(lengths / 300); const dramaElements = { protagonist: RELATIONS[randomInt(0, RELATIONS.length - 1)], antagonist: RELATIONS[randomInt(0, RELATIONS.length - 1)], conflict_type: conflictType, key_quotes: [], dramatic_moments: [] }; // 生成关键台词 const quoteCount = randomInt(1, 4); for (let i = 0; i < quoteCount; i++) { dramaElements.key_quotes.push({ text: `"${['You never listen!', 'That\'s not fair!', 'I can\'t believe you!', 'Why would you do that?'][randomInt(0, 3)]}"`, speaker: dramaElements.protagonist, context: 'confrontation' }); } // 生成戏剧时刻 const momentCount = randomInt(1, 3); for (let i = 0; i < momentCount; i++) { dramaElements.dramatic_moments.push({ type: ['reveal', 'confrontation', 'twist', 'climax'][randomInt(0, 3)], description: 'A pivotal moment in the narrative', intensity: randomInt(1, 10) }); } const contentTypes = { 'AmITheAsshole': `I (M${randomInt(18, 45)}) have been dealing with a situation involving my ${dramaElements.antagonist}. ` + `It all started when ${['I made a decision', 'something happened', 'we had a disagreement'][randomInt(0, 2)]}. ` + `The issue is that ${['they\'re upset with me', 'I feel guilty', 'we can\'t agree'][randomInt(0, 2)]}. ` + `My ${dramaElements.antagonist} thinks I should ${['apologize', 'change my behavior', 'do things differently'][randomInt(0, 2)]}, ` + `but I feel like ${['I\'m in the right', 'they\'re overreacting', 'we need to compromise'][randomInt(0, 2)]}. ` + `${'Edit: Thanks for all the comments!' }`, 'relationship_advice': `I've been with my ${dramaElements.antagonist} for ${randomInt(1, 10)} years. ` + `Recently, ${['we\'ve been fighting a lot', 'I discovered something', 'things have changed'][randomInt(0, 2)]}. ` + `I\'m not sure if ${['I\'m overreacting', 'this is a red flag', 'we can work through this'][randomInt(0, 2)]}. ` + `Should I ${['talk to them', 'end things', 'seek counseling', 'give it time'][randomInt(0, 3)]}?`, 'funny': `So this happened today... ${SITUATIONS[randomInt(0, SITUATIONS.length - 1)]} and it was absolutely hilarious. ` + `${['Everyone was staring', 'I wanted to disappear', 'Couldn\'t stop laughing'][randomInt(0, 2)]}!`, 'jokes': `Why did the ${NOUNS[randomInt(0, NOUNS.length - 1)]} ${VERBS[randomInt(0, VERBS.length - 1)]}?\n\n` + `Because ${['it wanted to', 'it had to', 'someone told it to', 'that\'s what they do'][randomInt(0, 3)]}!`, 'StandUpComedy': `Working on new material about ${TOPICS[randomInt(0, TOPICS.length - 1)]}. ` + `The crowd seemed to love it when I said: "${['You ever notice...', 'Why is it that...', 'Isn\'t it weird how...'][randomInt(0, 2)]}" ` + `Getting good feedback so far!` }; return { text: contentTypes[subreddit], drama_elements: dramaElements, paragraphs: paragraphs }; } // 计算戏剧性评分 function calculateDramaticScore(post, conflictType, subreddit) { // 基于多维度计算戏剧性 // 1. 互动量得分 (0-4分) const engagementScore = Math.min(post.num_comments / 1500, 4) + Math.min(post.score / 8000, 3); // 2. 争议度得分 (0-3分) - upvote_ratio 越低越具争议 const controversyScore = (1 - post.upvote_ratio) * 15; // 3. 冲突类型权重 (1-3分) const conflictWeights = { 'family_conflict': 2.5, 'relationship_crisis': 2.8, 'workplace_drama': 2.0, 'breakup_drama': 2.6, 'trust_issues': 2.4, 'inheritance_dispute': 2.3, 'wedding_drama': 2.2, 'communication_breakdown': 1.8, 'absurd_situation': 1.5, 'social_awkwardness': 1.0, 'unexpected_twist': 1.4, 'wordplay': 0.8, 'dark_humor': 1.2, 'observational_humor': 0.9, 'social_commentary': 1.3, 'self_deprecation': 0.8 }; const typeScore = conflictWeights[conflictType] || 1.0; // 4. Subreddit 基础权重调整 const subredditBias = { 'AmITheAsshole': 1.2, 'relationship_advice': 1.15, 'funny': 0.8, 'jokes': 0.6, 'StandUpComedy': 0.7 }; const bias = subredditBias[subreddit] || 1.0; const totalScore = (engagementScore + controversyScore + typeScore) * bias; // 添加一些随机波动使分布更自然 const noise = (Math.random() - 0.5) * 1.5; const finalScore = Math.max(1.0, Math.min(10.0, totalScore + noise)); return Math.round(finalScore * 10) / 10; } // 生成单条帖子 function generatePost(id, subreddit) { const config = SUBREDDIT_CONFIG[subreddit]; const conflictType = config.conflictTypes[randomInt(0, config.conflictTypes.length - 1)]; const score = randomLongTail(config.scoreRange[0], config.scoreRange[1]); const numComments = randomLongTail(config.commentRange[0], config.commentRange[1]); const upvoteRatio = parseFloat((Math.random() * (config.upvoteRatioRange[1] - config.upvoteRatioRange[0]) + config.upvoteRatioRange[0]).toFixed(2)); const content = generateSelftext(subreddit, conflictType); const dramaticScore = calculateDramaticScore({score, num_comments: numComments, upvote_ratio: upvoteRatio}, conflictType, subreddit); return { id: `t3_${Buffer.from(String(id)).toString('base64').substring(0, 6).toLowerCase()}`, subreddit: subreddit, title: generateTitle(subreddit), author: `user_${randomInt(1000, 99999)}`, selftext: content.text, score: score, upvotes: Math.floor(score * upvoteRatio), num_comments: numComments, upvote_ratio: upvoteRatio, created_utc: randomDate(), permalink: `/r/${subreddit}/comments/${Buffer.from(String(id)).toString('base64').substring(0, 6).toLowerCase()}/${subreddit.toLowerCase()}_post/`, drama_elements: content.drama_elements, dramatic_score: dramaticScore, dramatic_level: dramaticScore >= 7.0 ? 'high' : dramaticScore >= 5.0 ? 'medium' : 'low', is_drama_candidate: dramaticScore >= 6.0 }; } // 主生成函数 function generateDataset() { const totalPosts = 280; const posts = []; let id = 1000; for (const [subreddit, config] of Object.entries(SUBREDDIT_CONFIG)) { const count = Math.floor(totalPosts * config.weight); for (let i = 0; i < count; i++) { posts.push(generatePost(id++, subreddit)); } } // 打乱顺序 for (let i = posts.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [posts[i], posts[j]] = [posts[j], posts[i]]; } return posts; } // 生成并保存 const dataset = generateDataset(); const outputPath = path.join(__dirname, 'synthetic_reddit_data.json'); fs.writeFileSync(outputPath, JSON.stringify(dataset, null, 2)); console.log(`Generated ${dataset.length} synthetic Reddit posts`); console.log(`Saved to: ${outputPath}`); // 统计信息 const stats = { total: dataset.length, bySubreddit: {}, byConflictType: {}, dramaticLevel: { high: 0, medium: 0, low: 0 }, dramaCandidates: 0, avgScore: 0, avgComments: 0, avgUpvoteRatio: 0 }; dataset.forEach(post => { stats.bySubreddit[post.subreddit] = (stats.bySubreddit[post.subreddit] || 0) + 1; stats.byConflictType[post.drama_elements.conflict_type] = (stats.byConflictType[post.drama_elements.conflict_type] || 0) + 1; stats.dramaticLevel[post.dramatic_level]++; if (post.is_drama_candidate) stats.dramaCandidates++; stats.avgScore += post.score; stats.avgComments += post.num_comments; stats.avgUpvoteRatio += post.upvote_ratio; }); stats.avgScore = Math.round(stats.avgScore / dataset.length); stats.avgComments = Math.round(stats.avgComments / dataset.length); stats.avgUpvoteRatio = (stats.avgUpvoteRatio / dataset.length).toFixed(2); console.log('\n=== Statistics ==='); console.log(JSON.stringify(stats, null, 2));