duqing2026 commited on
Commit
1b5b420
·
0 Parent(s):

Initial commit

Browse files
Files changed (6) hide show
  1. .gitignore +4 -0
  2. Dockerfile +22 -0
  3. README.md +66 -0
  4. app.py +80 -0
  5. requirements.txt +6 -0
  6. templates/index.html +229 -0
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .DS_Store
Dockerfile ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ # Install system dependencies and fonts for Chinese support
4
+ RUN apt-get update && apt-get install -y \
5
+ fonts-noto-cjk \
6
+ gcc \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ WORKDIR /app
10
+
11
+ COPY requirements.txt .
12
+ RUN pip install --no-cache-dir -r requirements.txt
13
+
14
+ COPY . .
15
+
16
+ # Create a non-root user for Hugging Face Spaces
17
+ RUN useradd -m -u 1000 user
18
+ USER user
19
+ ENV HOME=/home/user \
20
+ PATH=/home/user/.local/bin:$PATH
21
+
22
+ CMD ["python", "app.py"]
README.md ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Word Cloud Generator
3
+ emoji: ☁️
4
+ colorFrom: indigo
5
+ colorTo: purple
6
+ sdk: docker
7
+ app_port: 7860
8
+ ---
9
+
10
+ # 在线词云生成器 (Word Cloud Generator)
11
+
12
+ 这是一个基于 Flask 和 WordCloud 的在线词云生成工具,专为内容创作者和数据分析师设计。
13
+
14
+ ## ✨ 主要功能
15
+
16
+ - **中文支持**:内置 Jieba 分词和中文字体,完美支持中文文本。
17
+ - **高度定制**:可调整图片尺寸、背景颜色、最大词数。
18
+ - **配色方案**:提供多种专业的配色方案(Viridis, Plasma, Pastel 等)。
19
+ - **实时预览**:快速生成并预览结果。
20
+ - **一键下载**:生成的高清 PNG 图片可直接下载。
21
+ - **隐私安全**:所有处理在服务器内存中完成,不保存用户文本。
22
+
23
+ ## 🛠️ 技术栈
24
+
25
+ - **Backend**: Python, Flask, WordCloud, Jieba, NumPy, Pillow, Matplotlib
26
+ - **Frontend**: Vue 3, Tailwind CSS (CDN)
27
+ - **Deployment**: Docker
28
+
29
+ ## 🚀 快速开始 (本地运行)
30
+
31
+ 1. 克隆仓库:
32
+ ```bash
33
+ git clone https://github.com/yourusername/word-cloud-generator.git
34
+ cd word-cloud-generator
35
+ ```
36
+
37
+ 2. 安装依赖:
38
+ ```bash
39
+ pip install -r requirements.txt
40
+ ```
41
+ *注意:本地运行时请确保系统中有中文字体,或修改 app.py 中的 FONT_PATH。*
42
+
43
+ 3. 运行应用:
44
+ ```bash
45
+ python app.py
46
+ ```
47
+
48
+ 4. 打开浏览器访问 `http://localhost:7860`
49
+
50
+ ## 🐳 Docker 部署
51
+
52
+ ```bash
53
+ docker build -t word-cloud-generator .
54
+ docker run -p 7860:7860 word-cloud-generator
55
+ ```
56
+
57
+ ## 📝 赚钱与增长思路
58
+
59
+ 此项目可以作为:
60
+ 1. **引流工具 (Lead Magnet)**:SEO 优化工具箱的一部分,吸引内容创作者。
61
+ 2. **数据服务演示**:展示数据可视化能力,吸引企业客户咨询定制报表。
62
+ 3. **内容辅助**:帮助公众号/博客作者快速制作封面图。
63
+
64
+ ## 📄 License
65
+
66
+ MIT
app.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from flask import Flask, render_template, request, jsonify
3
+ from wordcloud import WordCloud
4
+ import jieba
5
+ import io
6
+ import base64
7
+
8
+ app = Flask(__name__)
9
+
10
+ # Font path logic
11
+ # Priority: Docker/Linux path -> Mac path -> Default
12
+ FONT_PATH = "arial.ttf"
13
+ possible_fonts = [
14
+ "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
15
+ "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc",
16
+ "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
17
+ "/System/Library/Fonts/PingFang.ttc",
18
+ "/System/Library/Fonts/STHeiti Light.ttc",
19
+ "/System/Library/Fonts/Supplemental/Arial Unicode.ttf"
20
+ ]
21
+
22
+ for p in possible_fonts:
23
+ if os.path.exists(p):
24
+ FONT_PATH = p
25
+ break
26
+
27
+ print(f"Using font: {FONT_PATH}")
28
+
29
+ @app.route('/')
30
+ def index():
31
+ return render_template('index.html')
32
+
33
+ @app.route('/api/generate', methods=['POST'])
34
+ def generate():
35
+ try:
36
+ data = request.json
37
+ text = data.get('text', '')
38
+ if not text:
39
+ return jsonify({'error': '请提供文本内容'}), 400
40
+
41
+ # Options
42
+ width = int(data.get('width', 800))
43
+ height = int(data.get('height', 600))
44
+ bg_color = data.get('bg_color', '#ffffff')
45
+ max_words = int(data.get('max_words', 200))
46
+ colormap = data.get('colormap', 'viridis')
47
+
48
+ # Chinese segmentation
49
+ # Use jieba to cut the text
50
+ seg_list = jieba.cut(text)
51
+ # Filter out short words (length < 2) and whitespace
52
+ clean_text = " ".join([w for w in seg_list if len(w.strip()) > 1])
53
+
54
+ if not clean_text:
55
+ return jsonify({'error': '文本内容有效词汇太少'}), 400
56
+
57
+ wc = WordCloud(
58
+ font_path=FONT_PATH,
59
+ width=width,
60
+ height=height,
61
+ background_color=bg_color,
62
+ max_words=max_words,
63
+ colormap=colormap,
64
+ collocations=False, # Avoid repeating phrases
65
+ margin=2
66
+ ).generate(clean_text)
67
+
68
+ img = wc.to_image()
69
+ img_io = io.BytesIO()
70
+ img.save(img_io, 'PNG')
71
+ img_io.seek(0)
72
+ img_b64 = base64.b64encode(img_io.getvalue()).decode()
73
+
74
+ return jsonify({'image': f'data:image/png;base64,{img_b64}'})
75
+ except Exception as e:
76
+ print(f"Error: {e}")
77
+ return jsonify({'error': str(e)}), 500
78
+
79
+ if __name__ == '__main__':
80
+ app.run(host='0.0.0.0', port=7860)
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Flask
2
+ wordcloud
3
+ jieba
4
+ Pillow
5
+ numpy
6
+ matplotlib
templates/index.html ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>在线词云生成器 - Word Cloud Generator</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
9
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
10
+ <style>
11
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
12
+ body { font-family: 'Noto Sans SC', sans-serif; }
13
+ .gradient-bg { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
14
+ </style>
15
+ </head>
16
+ <body class="bg-gray-50 min-h-screen">
17
+ <div id="app" class="min-h-screen flex flex-col">
18
+ <!-- Header -->
19
+ <header class="gradient-bg text-white shadow-lg">
20
+ <div class="container mx-auto px-6 py-4 flex justify-between items-center">
21
+ <div class="flex items-center space-x-3">
22
+ <i class="fas fa-cloud text-2xl"></i>
23
+ <h1 class="text-2xl font-bold">在线词云生成器</h1>
24
+ </div>
25
+ <div class="text-sm opacity-80 hidden md:block">
26
+ 帮助内容创作者快速提炼关键词,制作精美可视化图片
27
+ </div>
28
+ </div>
29
+ </header>
30
+
31
+ <!-- Main Content -->
32
+ <main class="flex-grow container mx-auto px-4 py-8">
33
+ <div class="flex flex-col lg:flex-row gap-8">
34
+ <!-- Settings Panel -->
35
+ <div class="w-full lg:w-1/3 space-y-6">
36
+ <div class="bg-white rounded-xl shadow-md p-6">
37
+ <h2 class="text-lg font-semibold text-gray-800 mb-4 flex items-center">
38
+ <i class="fas fa-pen-fancy mr-2 text-indigo-500"></i> 输入文本
39
+ </h2>
40
+ <textarea
41
+ v-model="text"
42
+ class="w-full h-40 p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 resize-none transition text-sm"
43
+ placeholder="请在此粘贴文章内容、报告文本或关键词列表..."
44
+ ></textarea>
45
+ <p class="text-xs text-gray-500 mt-2 text-right">{{ text.length }} 字符</p>
46
+ </div>
47
+
48
+ <div class="bg-white rounded-xl shadow-md p-6">
49
+ <h2 class="text-lg font-semibold text-gray-800 mb-4 flex items-center">
50
+ <i class="fas fa-sliders-h mr-2 text-indigo-500"></i> 参数设置
51
+ </h2>
52
+
53
+ <div class="grid grid-cols-2 gap-4 mb-4">
54
+ <div>
55
+ <label class="block text-sm font-medium text-gray-700 mb-1">宽度 (px)</label>
56
+ <input type="number" v-model.number="width" class="w-full p-2 border rounded-md text-sm">
57
+ </div>
58
+ <div>
59
+ <label class="block text-sm font-medium text-gray-700 mb-1">高度 (px)</label>
60
+ <input type="number" v-model.number="height" class="w-full p-2 border rounded-md text-sm">
61
+ </div>
62
+ </div>
63
+
64
+ <div class="mb-4">
65
+ <label class="block text-sm font-medium text-gray-700 mb-1">最大词数: {{ maxWords }}</label>
66
+ <input type="range" v-model.number="maxWords" min="50" max="500" step="10" class="w-full accent-indigo-500">
67
+ </div>
68
+
69
+ <div class="grid grid-cols-2 gap-4 mb-4">
70
+ <div>
71
+ <label class="block text-sm font-medium text-gray-700 mb-1">背景颜色</label>
72
+ <div class="flex items-center border rounded-md p-1">
73
+ <input type="color" v-model="bgColor" class="h-8 w-8 cursor-pointer border-none bg-transparent">
74
+ <span class="ml-2 text-xs text-gray-500">{{ bgColor }}</span>
75
+ </div>
76
+ </div>
77
+ <div>
78
+ <label class="block text-sm font-medium text-gray-700 mb-1">配色方案</label>
79
+ <select v-model="colormap" class="w-full p-2 border rounded-md bg-white text-sm">
80
+ <option value="viridis">Viridis (默认)</option>
81
+ <option value="plasma">Plasma</option>
82
+ <option value="inferno">Inferno</option>
83
+ <option value="magma">Magma</option>
84
+ <option value="cividis">Cividis</option>
85
+ <option value="Pastel1">Pastel 1 (柔和)</option>
86
+ <option value="Pastel2">Pastel 2</option>
87
+ <option value="Set2">Set 2</option>
88
+ <option value="tab10">Tab 10</option>
89
+ <option value="Reds">Reds (红色系)</option>
90
+ <option value="Blues">Blues (蓝色系)</option>
91
+ </select>
92
+ </div>
93
+ </div>
94
+
95
+ <button
96
+ @click="generateCloud"
97
+ :disabled="isLoading || !text"
98
+ class="w-full py-3 bg-indigo-600 hover:bg-indigo-700 text-white font-bold rounded-lg shadow transition transform hover:scale-[1.02] disabled:opacity-50 disabled:cursor-not-allowed flex justify-center items-center"
99
+ >
100
+ <i v-if="isLoading" class="fas fa-spinner fa-spin mr-2"></i>
101
+ <span v-if="isLoading">生成中...</span>
102
+ <span v-else>生成词云</span>
103
+ </button>
104
+ </div>
105
+ </div>
106
+
107
+ <!-- Preview Panel -->
108
+ <div class="w-full lg:w-2/3">
109
+ <div class="bg-white rounded-xl shadow-md p-6 h-full flex flex-col">
110
+ <div class="flex justify-between items-center mb-4">
111
+ <h2 class="text-lg font-semibold text-gray-800 flex items-center">
112
+ <i class="fas fa-image mr-2 text-indigo-500"></i> 预览结果
113
+ </h2>
114
+ <button
115
+ v-if="imageUrl"
116
+ @click="downloadImage"
117
+ class="px-4 py-2 bg-green-500 hover:bg-green-600 text-white text-sm rounded-md transition flex items-center shadow hover:shadow-md"
118
+ >
119
+ <i class="fas fa-download mr-2"></i> 下载图片
120
+ </button>
121
+ </div>
122
+
123
+ <div class="flex-grow flex items-center justify-center bg-gray-100 rounded-lg border-2 border-dashed border-gray-300 overflow-hidden relative min-h-[400px]">
124
+ <div v-if="!imageUrl && !isLoading" class="text-center text-gray-400">
125
+ <i class="fas fa-cloud text-6xl mb-4 opacity-50"></i>
126
+ <p>在左侧输入文本并点击生成</p>
127
+ </div>
128
+ <div v-if="isLoading" class="text-center text-indigo-500">
129
+ <i class="fas fa-circle-notch fa-spin text-4xl mb-2"></i>
130
+ <p>正在进行中文分词与绘图...</p>
131
+ </div>
132
+ <img
133
+ v-if="imageUrl"
134
+ :src="imageUrl"
135
+ alt="Word Cloud"
136
+ class="max-w-full max-h-full object-contain shadow-lg"
137
+ >
138
+ </div>
139
+ </div>
140
+ </div>
141
+ </div>
142
+ </main>
143
+
144
+ <!-- Footer -->
145
+ <footer class="bg-gray-800 text-gray-400 py-6 mt-8">
146
+ <div class="container mx-auto px-4 text-center">
147
+ <p>&copy; 2024 在线词云生成器 (Word Cloud Generator). All rights reserved.</p>
148
+ <p class="text-sm mt-2 opacity-75">Designed for Content Creators & Data Enthusiasts</p>
149
+ </div>
150
+ </footer>
151
+ </div>
152
+
153
+ <script>
154
+ const { createApp, ref } = Vue
155
+
156
+ createApp({
157
+ setup() {
158
+ const text = ref('词云(Word Cloud)是对文本中出现频率较高的“关键词”给予视觉化的展现,词云过滤掉大量的低频低质的文本信息,使得浏览者只要一眼扫过文本就可领略文本的主旨。这里可以输入任意的中文或英文文本,系统会自动进行分词和统计。通过调整参数,您可以定制词云的大小、颜色和风格。此工具非常适合用于制作PPT插图、文章封面、数据分析报告等。')
159
+ const width = ref(1000)
160
+ const height = ref(600)
161
+ const bgColor = ref('#ffffff')
162
+ const maxWords = ref(200)
163
+ const colormap = ref('viridis')
164
+ const imageUrl = ref('')
165
+ const isLoading = ref(false)
166
+
167
+ const generateCloud = async () => {
168
+ if (!text.value) return
169
+
170
+ isLoading.value = true
171
+ imageUrl.value = '' // Clear previous
172
+
173
+ try {
174
+ const response = await fetch('/api/generate', {
175
+ method: 'POST',
176
+ headers: {
177
+ 'Content-Type': 'application/json'
178
+ },
179
+ body: JSON.stringify({
180
+ text: text.value,
181
+ width: width.value,
182
+ height: height.value,
183
+ bg_color: bgColor.value,
184
+ max_words: maxWords.value,
185
+ colormap: colormap.value
186
+ })
187
+ })
188
+
189
+ const data = await response.json()
190
+
191
+ if (response.ok) {
192
+ imageUrl.value = data.image
193
+ } else {
194
+ alert('生成失败: ' + (data.error || '未知错误'))
195
+ }
196
+ } catch (error) {
197
+ alert('请求错误: ' + error.message)
198
+ } finally {
199
+ isLoading.value = false
200
+ }
201
+ }
202
+
203
+ const downloadImage = () => {
204
+ if (!imageUrl.value) return
205
+ const link = document.createElement('a')
206
+ link.href = imageUrl.value
207
+ link.download = `wordcloud_${new Date().getTime()}.png`
208
+ document.body.appendChild(link)
209
+ link.click()
210
+ document.body.removeChild(link)
211
+ }
212
+
213
+ return {
214
+ text,
215
+ width,
216
+ height,
217
+ bgColor,
218
+ maxWords,
219
+ colormap,
220
+ imageUrl,
221
+ isLoading,
222
+ generateCloud,
223
+ downloadImage
224
+ }
225
+ }
226
+ }).mount('#app')
227
+ </script>
228
+ </body>
229
+ </html>