99i commited on
Commit
5a2ddbd
·
verified ·
1 Parent(s): 8c0a03b

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +360 -360
main.py CHANGED
@@ -1,361 +1,361 @@
1
- from fastapi import FastAPI, HTTPException, Request
2
- from fastapi.responses import HTMLResponse, FileResponse
3
- from fastapi.staticfiles import StaticFiles
4
- from pydantic import BaseModel
5
- from typing import Optional
6
- import asyncio
7
- import tempfile
8
- import os
9
- import uuid
10
- from drivisionpage import ChromiumPage
11
-
12
- app = FastAPI(title="HTML to Image Converter", version="1.0.0")
13
-
14
- # 挂载静态文件目录
15
- app.mount("/static", StaticFiles(directory="static"), name="static")
16
-
17
- # 创建临时目录用于存储图片
18
- os.makedirs("temp", exist_ok=True)
19
-
20
- class ImageRequest(BaseModel):
21
- url: Optional[str] = None
22
- html_content: Optional[str] = None
23
- selector: Optional[str] = None
24
- full_page: bool = False
25
- width: int = 1200
26
- height: int = 800
27
-
28
- class ImageResponse(BaseModel):
29
- success: bool
30
- image_url: Optional[str] = None
31
- error: Optional[str] = None
32
-
33
- @app.get("/", response_class=HTMLResponse)
34
- async def read_root(request: Request):
35
- return """
36
- <!DOCTYPE html>
37
- <html>
38
- <head>
39
- <title>HTML to Image Converter</title>
40
- <meta charset="UTF-8">
41
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
42
- <style>
43
- body {
44
- font-family: Arial, sans-serif;
45
- max-width: 800px;
46
- margin: 0 auto;
47
- padding: 20px;
48
- background-color: #f5f5f5;
49
- }
50
- .container {
51
- background-color: white;
52
- padding: 30px;
53
- border-radius: 10px;
54
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
55
- }
56
- h1 {
57
- color: #333;
58
- text-align: center;
59
- }
60
- .form-group {
61
- margin-bottom: 20px;
62
- }
63
- label {
64
- display: block;
65
- margin-bottom: 5px;
66
- font-weight: bold;
67
- }
68
- input, textarea, select {
69
- width: 100%;
70
- padding: 10px;
71
- border: 1px solid #ddd;
72
- border-radius: 5px;
73
- box-sizing: border-box;
74
- }
75
- button {
76
- background-color: #4CAF50;
77
- color: white;
78
- padding: 12px 20px;
79
- border: none;
80
- border-radius: 5px;
81
- cursor: pointer;
82
- font-size: 16px;
83
- width: 100%;
84
- }
85
- button:hover {
86
- background-color: #45a049;
87
- }
88
- #result {
89
- margin-top: 20px;
90
- text-align: center;
91
- }
92
- #imagePreview {
93
- max-width: 100%;
94
- border: 1px solid #ddd;
95
- border-radius: 5px;
96
- margin-top: 10px;
97
- }
98
- .tab {
99
- overflow: hidden;
100
- border: 1px solid #ccc;
101
- background-color: #f1f1f1;
102
- }
103
- .tab button {
104
- background-color: inherit;
105
- float: left;
106
- border: none;
107
- outline: none;
108
- cursor: pointer;
109
- padding: 14px 16px;
110
- transition: 0.3s;
111
- font-size: 17px;
112
- }
113
- .tab button:hover {
114
- background-color: #ddd;
115
- }
116
- .tab button.active {
117
- background-color: #ccc;
118
- }
119
- .tabcontent {
120
- display: none;
121
- padding: 6px 12px;
122
- border: 1px solid #ccc;
123
- border-top: none;
124
- }
125
- </style>
126
- </head>
127
- <body>
128
- <div class="container">
129
- <h1>HTML to Image Converter</h1>
130
-
131
- <div class="tab">
132
- <button class="tablinks active" onclick="openTab(event, 'urlTab')">URL地址</button>
133
- <button class="tablinks" onclick="openTab(event, 'htmlTab')">HTML代码</button>
134
- </div>
135
-
136
- <div id="urlTab" class="tabcontent" style="display: block;">
137
- <form id="imageFormUrl">
138
- <div class="form-group">
139
- <label for="url">网页URL:</label>
140
- <input type="url" id="url" name="url" required placeholder="https://example.com">
141
- </div>
142
- <div class="form-group">
143
- <label for="selector">CSS选择器 (可选):</label>
144
- <input type="text" id="selector" name="selector" placeholder="例如: .content, #main">
145
- </div>
146
- <div class="form-group">
147
- <label for="full_page">截图类型:</label>
148
- <select id="full_page" name="full_page">
149
- <option value="false">可见区域</option>
150
- <option value="true">完整页面</option>
151
- </select>
152
- </div>
153
- <div class="form-group">
154
- <label for="width">宽度:</label>
155
- <input type="number" id="width" name="width" value="1200" min="100" max="5000">
156
- </div>
157
- <div class="form-group">
158
- <label for="height">高度:</label>
159
- <input type="number" id="height" name="height" value="800" min="100" max="5000">
160
- </div>
161
- <button type="submit">生成图片</button>
162
- </form>
163
- </div>
164
-
165
- <div id="htmlTab" class="tabcontent">
166
- <form id="imageFormHtml">
167
- <div class="form-group">
168
- <label for="html_content">HTML代码:</label>
169
- <textarea id="html_content" name="html_content" rows="10" placeholder="在这里输入HTML代码..."></textarea>
170
- </div>
171
- <div class="form-group">
172
- <label for="selectorHtml">CSS选择器 (可选):</label>
173
- <input type="text" id="selectorHtml" name="selector" placeholder="例如: .content, #main">
174
- </div>
175
- <div class="form-group">
176
- <label for="full_page_html">截图类型:</label>
177
- <select id="full_page_html" name="full_page">
178
- <option value="false">可见区域</option>
179
- <option value="true">完整页面</option>
180
- </select>
181
- </div>
182
- <div class="form-group">
183
- <label for="width_html">宽度:</label>
184
- <input type="number" id="width_html" name="width" value="1200" min="100" max="5000">
185
- </div>
186
- <div class="form-group">
187
- <label for="height_html">高度:</label>
188
- <input type="number" id="height_html" name="height" value="800" min="100" max="5000">
189
- </div>
190
- <button type="submit">生成图片</button>
191
- </form>
192
- </div>
193
-
194
- <div id="result"></div>
195
- </div>
196
-
197
- <script>
198
- // 切换标签页
199
- function openTab(evt, tabName) {
200
- var i, tabcontent, tablinks;
201
- tabcontent = document.querySelectorAll(".tabcontent");
202
- for (i = 0; i < tabcontent.length; i++) {
203
- tabcontent[i].style.display = "none";
204
- }
205
- tablinks = document.querySelectorAll(".tablinks");
206
- for (i = 0; i < tablinks.length; i++) {
207
- tablinks[i].className = tablinks[i].className.replace(" active", "");
208
- }
209
- document.getElementById(tabName).style.display = "block";
210
- evt.currentTarget.className += " active";
211
- }
212
-
213
- // URL表单提交处理
214
- document.getElementById('imageFormUrl').addEventListener('submit', async function(e) {
215
- e.preventDefault();
216
- const resultDiv = document.getElementById('result');
217
- resultDiv.innerHTML = '<p>正在处理...</p>';
218
-
219
- const formData = new FormData(this);
220
- const data = Object.fromEntries(formData);
221
- data['html_content'] = null; // 清除HTML内容
222
-
223
- try {
224
- const response = await fetch('/convert', {
225
- method: 'POST',
226
- headers: {
227
- 'Content-Type': 'application/json',
228
- },
229
- body: JSON.stringify(data)
230
- });
231
-
232
- const result = await response.json();
233
-
234
- if (result.success) {
235
- resultDiv.innerHTML = `
236
- <h2>转换成功!</h2>
237
- <img id="imagePreview" src="${result.image_url}" alt="生成的图片">
238
- `;
239
- } else {
240
- resultDiv.innerHTML = `<p style="color: red;">错误: ${result.error}</p>`;
241
- }
242
- } catch (error) {
243
- resultDiv.innerHTML = `<p style="color: red;">请求失败: ${error.message}</p>`;
244
- }
245
- });
246
-
247
- // HTML表单提交处理
248
- document.getElementById('imageFormHtml').addEventListener('submit', async function(e) {
249
- e.preventDefault();
250
- const resultDiv = document.getElementById('result');
251
- resultDiv.innerHTML = '<p>正在处理...</p>';
252
-
253
- const formData = new FormData(this);
254
- const data = Object.fromEntries(formData);
255
- data['url'] = null; // 清除URL
256
-
257
- try {
258
- const response = await fetch('/convert', {
259
- method: 'POST',
260
- headers: {
261
- 'Content-Type': 'application/json',
262
- },
263
- body: JSON.stringify(data)
264
- });
265
-
266
- const result = await response.json();
267
-
268
- if (result.success) {
269
- resultDiv.innerHTML = `
270
- <h2>转换成功!</h2>
271
- <img id="imagePreview" src="${result.image_url}" alt="生成的图片">
272
- `;
273
- } else {
274
- resultDiv.innerHTML = `<p style="color: red;">错误: ${result.error}</p>`;
275
- }
276
- } catch (error) {
277
- resultDiv.innerHTML = `<p style="color: red;">请求失败: ${error.message}</p>`;
278
- }
279
- });
280
- </script>
281
- </body>
282
- </html>
283
- """
284
-
285
- @app.post("/convert", response_model=ImageResponse)
286
- async def convert_html_to_image(request: ImageRequest):
287
- try:
288
- # 生成唯一的临时文件名
289
- unique_id = str(uuid.uuid4())
290
- temp_image_path = f"temp/{unique_id}.png"
291
-
292
- # 使用DrissionPage进行截图
293
- page = ChromiumPage()
294
-
295
- try:
296
- # 设置页面视口大小
297
- page.set.window.size(request.width, request.height)
298
-
299
- # 根据是URL还是HTML内容来处理
300
- if request.url:
301
- # 打开URL
302
- page.get(request.url)
303
-
304
- # 等待页面加载
305
- page.wait.loadstart()
306
- page.wait.loadfinish()
307
- elif request.html_content:
308
- # 使用HTML内容
309
- page.get("about:blank")
310
- page.ele("body").html(request.html_content)
311
- # 等待页面渲染完成
312
- page.wait.loadfinish()
313
- else:
314
- raise HTTPException(status_code=400, detail="必须提供URL或HTML内容")
315
-
316
- # 根据参数决定截图方式
317
- if request.selector:
318
- element = page.ele(request.selector)
319
- if element:
320
- element.screenshot(path=temp_image_path)
321
- else:
322
- raise HTTPException(status_code=400, detail="CSS选择器未找到元素")
323
- elif request.full_page:
324
- page.screenshot(path=temp_image_path, full_page=True)
325
- else:
326
- page.screenshot(path=temp_image_path)
327
-
328
- finally:
329
- page.quit()
330
-
331
- return ImageResponse(
332
- success=True,
333
- image_url=f"/{temp_image_path}"
334
- )
335
-
336
- except Exception as e:
337
- return ImageResponse(
338
- success=False,
339
- error=str(e)
340
- )
341
-
342
- @app.get("/{image_path:path}")
343
- async def get_image(image_path: str):
344
- # 安全检查,只允许访问temp目录下的文件
345
- if not image_path.startswith("temp/"):
346
- raise HTTPException(status_code=404, detail="Image not found")
347
-
348
- # 检查文件是否存在
349
- if not os.path.exists(image_path):
350
- raise HTTPException(status_code=404, detail="Image not found")
351
-
352
- # 返回图片文件
353
- return FileResponse(image_path)
354
-
355
- @app.get("/health")
356
- async def health_check():
357
- return {"status": "healthy"}
358
-
359
- if __name__ == "__main__":
360
- import uvicorn
361
  uvicorn.run(app, host="0.0.0.0", port=8000)
 
1
+ from fastapi import FastAPI, HTTPException, Request
2
+ from fastapi.responses import HTMLResponse, FileResponse
3
+ from fastapi.staticfiles import StaticFiles
4
+ from pydantic import BaseModel
5
+ from typing import Optional
6
+ import asyncio
7
+ import tempfile
8
+ import os
9
+ import uuid
10
+ from drissionpage import ChromiumPage
11
+
12
+ app = FastAPI(title="HTML to Image Converter", version="1.0.0")
13
+
14
+ # 挂载静态文件目录
15
+ app.mount("/static", StaticFiles(directory="static"), name="static")
16
+
17
+ # 创建临时目录用于存储图片
18
+ os.makedirs("temp", exist_ok=True)
19
+
20
+ class ImageRequest(BaseModel):
21
+ url: Optional[str] = None
22
+ html_content: Optional[str] = None
23
+ selector: Optional[str] = None
24
+ full_page: bool = False
25
+ width: int = 1200
26
+ height: int = 800
27
+
28
+ class ImageResponse(BaseModel):
29
+ success: bool
30
+ image_url: Optional[str] = None
31
+ error: Optional[str] = None
32
+
33
+ @app.get("/", response_class=HTMLResponse)
34
+ async def read_root(request: Request):
35
+ return """
36
+ <!DOCTYPE html>
37
+ <html>
38
+ <head>
39
+ <title>HTML to Image Converter</title>
40
+ <meta charset="UTF-8">
41
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
42
+ <style>
43
+ body {
44
+ font-family: Arial, sans-serif;
45
+ max-width: 800px;
46
+ margin: 0 auto;
47
+ padding: 20px;
48
+ background-color: #f5f5f5;
49
+ }
50
+ .container {
51
+ background-color: white;
52
+ padding: 30px;
53
+ border-radius: 10px;
54
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
55
+ }
56
+ h1 {
57
+ color: #333;
58
+ text-align: center;
59
+ }
60
+ .form-group {
61
+ margin-bottom: 20px;
62
+ }
63
+ label {
64
+ display: block;
65
+ margin-bottom: 5px;
66
+ font-weight: bold;
67
+ }
68
+ input, textarea, select {
69
+ width: 100%;
70
+ padding: 10px;
71
+ border: 1px solid #ddd;
72
+ border-radius: 5px;
73
+ box-sizing: border-box;
74
+ }
75
+ button {
76
+ background-color: #4CAF50;
77
+ color: white;
78
+ padding: 12px 20px;
79
+ border: none;
80
+ border-radius: 5px;
81
+ cursor: pointer;
82
+ font-size: 16px;
83
+ width: 100%;
84
+ }
85
+ button:hover {
86
+ background-color: #45a049;
87
+ }
88
+ #result {
89
+ margin-top: 20px;
90
+ text-align: center;
91
+ }
92
+ #imagePreview {
93
+ max-width: 100%;
94
+ border: 1px solid #ddd;
95
+ border-radius: 5px;
96
+ margin-top: 10px;
97
+ }
98
+ .tab {
99
+ overflow: hidden;
100
+ border: 1px solid #ccc;
101
+ background-color: #f1f1f1;
102
+ }
103
+ .tab button {
104
+ background-color: inherit;
105
+ float: left;
106
+ border: none;
107
+ outline: none;
108
+ cursor: pointer;
109
+ padding: 14px 16px;
110
+ transition: 0.3s;
111
+ font-size: 17px;
112
+ }
113
+ .tab button:hover {
114
+ background-color: #ddd;
115
+ }
116
+ .tab button.active {
117
+ background-color: #ccc;
118
+ }
119
+ .tabcontent {
120
+ display: none;
121
+ padding: 6px 12px;
122
+ border: 1px solid #ccc;
123
+ border-top: none;
124
+ }
125
+ </style>
126
+ </head>
127
+ <body>
128
+ <div class="container">
129
+ <h1>HTML to Image Converter</h1>
130
+
131
+ <div class="tab">
132
+ <button class="tablinks active" onclick="openTab(event, 'urlTab')">URL地址</button>
133
+ <button class="tablinks" onclick="openTab(event, 'htmlTab')">HTML代码</button>
134
+ </div>
135
+
136
+ <div id="urlTab" class="tabcontent" style="display: block;">
137
+ <form id="imageFormUrl">
138
+ <div class="form-group">
139
+ <label for="url">网页URL:</label>
140
+ <input type="url" id="url" name="url" required placeholder="https://example.com">
141
+ </div>
142
+ <div class="form-group">
143
+ <label for="selector">CSS选择器 (可选):</label>
144
+ <input type="text" id="selector" name="selector" placeholder="例如: .content, #main">
145
+ </div>
146
+ <div class="form-group">
147
+ <label for="full_page">截图类型:</label>
148
+ <select id="full_page" name="full_page">
149
+ <option value="false">可见区域</option>
150
+ <option value="true">完整页面</option>
151
+ </select>
152
+ </div>
153
+ <div class="form-group">
154
+ <label for="width">宽度:</label>
155
+ <input type="number" id="width" name="width" value="1200" min="100" max="5000">
156
+ </div>
157
+ <div class="form-group">
158
+ <label for="height">高度:</label>
159
+ <input type="number" id="height" name="height" value="800" min="100" max="5000">
160
+ </div>
161
+ <button type="submit">生成图片</button>
162
+ </form>
163
+ </div>
164
+
165
+ <div id="htmlTab" class="tabcontent">
166
+ <form id="imageFormHtml">
167
+ <div class="form-group">
168
+ <label for="html_content">HTML代码:</label>
169
+ <textarea id="html_content" name="html_content" rows="10" placeholder="在这里输入HTML代码..."></textarea>
170
+ </div>
171
+ <div class="form-group">
172
+ <label for="selectorHtml">CSS选择器 (可选):</label>
173
+ <input type="text" id="selectorHtml" name="selector" placeholder="例如: .content, #main">
174
+ </div>
175
+ <div class="form-group">
176
+ <label for="full_page_html">截图类型:</label>
177
+ <select id="full_page_html" name="full_page">
178
+ <option value="false">可见区域</option>
179
+ <option value="true">完整页面</option>
180
+ </select>
181
+ </div>
182
+ <div class="form-group">
183
+ <label for="width_html">宽度:</label>
184
+ <input type="number" id="width_html" name="width" value="1200" min="100" max="5000">
185
+ </div>
186
+ <div class="form-group">
187
+ <label for="height_html">高度:</label>
188
+ <input type="number" id="height_html" name="height" value="800" min="100" max="5000">
189
+ </div>
190
+ <button type="submit">生成图片</button>
191
+ </form>
192
+ </div>
193
+
194
+ <div id="result"></div>
195
+ </div>
196
+
197
+ <script>
198
+ // 切换标签页
199
+ function openTab(evt, tabName) {
200
+ var i, tabcontent, tablinks;
201
+ tabcontent = document.querySelectorAll(".tabcontent");
202
+ for (i = 0; i < tabcontent.length; i++) {
203
+ tabcontent[i].style.display = "none";
204
+ }
205
+ tablinks = document.querySelectorAll(".tablinks");
206
+ for (i = 0; i < tablinks.length; i++) {
207
+ tablinks[i].className = tablinks[i].className.replace(" active", "");
208
+ }
209
+ document.getElementById(tabName).style.display = "block";
210
+ evt.currentTarget.className += " active";
211
+ }
212
+
213
+ // URL表单提交处理
214
+ document.getElementById('imageFormUrl').addEventListener('submit', async function(e) {
215
+ e.preventDefault();
216
+ const resultDiv = document.getElementById('result');
217
+ resultDiv.innerHTML = '<p>正在处理...</p>';
218
+
219
+ const formData = new FormData(this);
220
+ const data = Object.fromEntries(formData);
221
+ data['html_content'] = null; // 清除HTML内容
222
+
223
+ try {
224
+ const response = await fetch('/convert', {
225
+ method: 'POST',
226
+ headers: {
227
+ 'Content-Type': 'application/json',
228
+ },
229
+ body: JSON.stringify(data)
230
+ });
231
+
232
+ const result = await response.json();
233
+
234
+ if (result.success) {
235
+ resultDiv.innerHTML = `
236
+ <h2>转换成功!</h2>
237
+ <img id="imagePreview" src="${result.image_url}" alt="生成的图片">
238
+ `;
239
+ } else {
240
+ resultDiv.innerHTML = `<p style="color: red;">错误: ${result.error}</p>`;
241
+ }
242
+ } catch (error) {
243
+ resultDiv.innerHTML = `<p style="color: red;">请求失败: ${error.message}</p>`;
244
+ }
245
+ });
246
+
247
+ // HTML表单提交处理
248
+ document.getElementById('imageFormHtml').addEventListener('submit', async function(e) {
249
+ e.preventDefault();
250
+ const resultDiv = document.getElementById('result');
251
+ resultDiv.innerHTML = '<p>正在处理...</p>';
252
+
253
+ const formData = new FormData(this);
254
+ const data = Object.fromEntries(formData);
255
+ data['url'] = null; // 清除URL
256
+
257
+ try {
258
+ const response = await fetch('/convert', {
259
+ method: 'POST',
260
+ headers: {
261
+ 'Content-Type': 'application/json',
262
+ },
263
+ body: JSON.stringify(data)
264
+ });
265
+
266
+ const result = await response.json();
267
+
268
+ if (result.success) {
269
+ resultDiv.innerHTML = `
270
+ <h2>转换成功!</h2>
271
+ <img id="imagePreview" src="${result.image_url}" alt="生成的图片">
272
+ `;
273
+ } else {
274
+ resultDiv.innerHTML = `<p style="color: red;">错误: ${result.error}</p>`;
275
+ }
276
+ } catch (error) {
277
+ resultDiv.innerHTML = `<p style="color: red;">请求失败: ${error.message}</p>`;
278
+ }
279
+ });
280
+ </script>
281
+ </body>
282
+ </html>
283
+ """
284
+
285
+ @app.post("/convert", response_model=ImageResponse)
286
+ async def convert_html_to_image(request: ImageRequest):
287
+ try:
288
+ # 生成唯一的临时文件名
289
+ unique_id = str(uuid.uuid4())
290
+ temp_image_path = f"temp/{unique_id}.png"
291
+
292
+ # 使用DrissionPage进行截图
293
+ page = ChromiumPage()
294
+
295
+ try:
296
+ # 设置页面视口大小
297
+ page.set.window.size(request.width, request.height)
298
+
299
+ # 根据是URL还是HTML内容来处理
300
+ if request.url:
301
+ # 打开URL
302
+ page.get(request.url)
303
+
304
+ # 等待页面加载
305
+ page.wait.loadstart()
306
+ page.wait.loadfinish()
307
+ elif request.html_content:
308
+ # 使用HTML内容
309
+ page.get("about:blank")
310
+ page.ele("body").html(request.html_content)
311
+ # 等待页面渲染完成
312
+ page.wait.loadfinish()
313
+ else:
314
+ raise HTTPException(status_code=400, detail="必须提供URL或HTML内容")
315
+
316
+ # 根据参数决定截图方式
317
+ if request.selector:
318
+ element = page.ele(request.selector)
319
+ if element:
320
+ element.screenshot(path=temp_image_path)
321
+ else:
322
+ raise HTTPException(status_code=400, detail="CSS选择器未找到元素")
323
+ elif request.full_page:
324
+ page.screenshot(path=temp_image_path, full_page=True)
325
+ else:
326
+ page.screenshot(path=temp_image_path)
327
+
328
+ finally:
329
+ page.quit()
330
+
331
+ return ImageResponse(
332
+ success=True,
333
+ image_url=f"/{temp_image_path}"
334
+ )
335
+
336
+ except Exception as e:
337
+ return ImageResponse(
338
+ success=False,
339
+ error=str(e)
340
+ )
341
+
342
+ @app.get("/{image_path:path}")
343
+ async def get_image(image_path: str):
344
+ # 安全检查,只允许访问temp目录下的文件
345
+ if not image_path.startswith("temp/"):
346
+ raise HTTPException(status_code=404, detail="Image not found")
347
+
348
+ # 检查文件是否存在
349
+ if not os.path.exists(image_path):
350
+ raise HTTPException(status_code=404, detail="Image not found")
351
+
352
+ # 返回图片文件
353
+ return FileResponse(image_path)
354
+
355
+ @app.get("/health")
356
+ async def health_check():
357
+ return {"status": "healthy"}
358
+
359
+ if __name__ == "__main__":
360
+ import uvicorn
361
  uvicorn.run(app, host="0.0.0.0", port=8000)