jljiu commited on
Commit
110d062
·
verified ·
1 Parent(s): 1ea98d8

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +35 -0
  2. index.html +195 -0
  3. main.ts +77 -0
  4. readme.md +120 -0
Dockerfile ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用官方的Ubuntu作为基础镜像
2
+ FROM ubuntu:latest
3
+
4
+ # 更新软件包列表并安装所需软件
5
+ RUN apt -y update && \
6
+ apt -y install curl unzip xubuntu-desktop
7
+
8
+ # 安装 Deno
9
+ RUN curl -fsSL https://deno.land/x/install/install.sh | sh
10
+
11
+ # 设置环境变量,将Deno可执行文件路径添加到PATH中
12
+ ENV DENO_INSTALL="/root/.deno"
13
+ ENV PATH="$DENO_INSTALL/bin:$PATH"
14
+
15
+ # 创建项目目录并进入
16
+ WORKDIR /app
17
+
18
+ # 复制本地的ts文件到容器中
19
+ COPY main.ts ./
20
+ COPY index.html ./
21
+
22
+ # 手动下载和安装最新版本的 Chrome
23
+ RUN apt-get update && apt-get install -y wget unzip jq curl && \
24
+ CHROME_VERSION=$(curl -s https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json | jq -r '.channels.Stable.version') && \
25
+ wget -q "https://storage.googleapis.com/chrome-for-testing-public/${CHROME_VERSION}/linux64/chrome-linux64.zip" -O /tmp/chrome.zip && \
26
+ mkdir -p /app/chrome && \
27
+ unzip /tmp/chrome.zip -d /app/chrome && \
28
+ rm /tmp/chrome.zip && \
29
+ chmod +x /app/chrome/chrome-linux64/chrome
30
+
31
+ # 暴露7860端口
32
+ EXPOSE 7860
33
+
34
+ # 运行Deno应用
35
+ CMD ["deno", "run", "-A", "main.ts"]
index.html ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>URL Parser & Screenshot Tool</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
16
+ line-height: 1.6;
17
+ padding: 2rem;
18
+ max-width: 1200px;
19
+ margin: 0 auto;
20
+ background-color: #f5f5f5;
21
+ }
22
+
23
+ .container {
24
+ background-color: white;
25
+ padding: 2rem;
26
+ border-radius: 8px;
27
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
28
+ }
29
+
30
+ h1 {
31
+ color: #333;
32
+ margin-bottom: 1.5rem;
33
+ text-align: center;
34
+ }
35
+
36
+ .input-group {
37
+ margin-bottom: 2rem;
38
+ }
39
+
40
+ #inputBox {
41
+ width: 100%;
42
+ padding: 0.8rem;
43
+ font-size: 1rem;
44
+ border: 2px solid #ddd;
45
+ border-radius: 4px;
46
+ transition: border-color 0.3s;
47
+ }
48
+
49
+ #inputBox:focus {
50
+ outline: none;
51
+ border-color: #007bff;
52
+ }
53
+
54
+ #urls {
55
+ display: flex;
56
+ flex-direction: column;
57
+ gap: 0.8rem;
58
+ }
59
+
60
+ #urls a {
61
+ color: #007bff;
62
+ text-decoration: none;
63
+ padding: 0.5rem;
64
+ background-color: #f8f9fa;
65
+ border-radius: 4px;
66
+ transition: background-color 0.3s;
67
+ }
68
+
69
+ #urls a:hover {
70
+ background-color: #e9ecef;
71
+ text-decoration: underline;
72
+ }
73
+
74
+ .screenshot-btn {
75
+ display: inline-block;
76
+ padding: 0.4rem 0.8rem;
77
+ margin-left: 0.5rem;
78
+ background-color: #28a745;
79
+ color: white;
80
+ border: none;
81
+ border-radius: 4px;
82
+ cursor: pointer;
83
+ font-size: 0.9rem;
84
+ transition: background-color 0.3s;
85
+ }
86
+
87
+ .screenshot-btn:hover {
88
+ background-color: #218838;
89
+ }
90
+
91
+ .preview-container {
92
+ margin-top: 1rem;
93
+ border: 1px solid #ddd;
94
+ border-radius: 4px;
95
+ overflow: hidden;
96
+ }
97
+
98
+ .preview-image {
99
+ max-width: 100%;
100
+ height: auto;
101
+ display: block;
102
+ }
103
+
104
+ .loading {
105
+ text-align: center;
106
+ padding: 2rem;
107
+ color: #666;
108
+ }
109
+ </style>
110
+ </head>
111
+ <body>
112
+ <div class="container">
113
+ <h1>URL 解析 & 截图工具</h1>
114
+ <div class="input-group">
115
+ <input type="text"
116
+ id="inputBox"
117
+ placeholder="粘贴包含URL的文本..."
118
+ onblur="parseUrls()"
119
+ onchange="parseUrls()">
120
+ </div>
121
+ <div id="urls"></div>
122
+ <div id="preview-container" class="preview-container" style="display: none;">
123
+ <div id="loading" class="loading">加载中...</div>
124
+ <img id="preview-image" class="preview-image" style="display: none;">
125
+ </div>
126
+ </div>
127
+
128
+ <script>
129
+ async function takeScreenshot(url) {
130
+ const previewContainer = document.getElementById('preview-container');
131
+ const loading = document.getElementById('loading');
132
+ const previewImage = document.getElementById('preview-image');
133
+
134
+ previewContainer.style.display = 'block';
135
+ loading.style.display = 'block';
136
+ previewImage.style.display = 'none';
137
+
138
+ try {
139
+ const response = await fetch(`/screenshot?url=${encodeURIComponent(url)}`);
140
+ if (response.ok) {
141
+ const blob = await response.blob();
142
+ const imageUrl = URL.createObjectURL(blob);
143
+ previewImage.src = imageUrl;
144
+ previewImage.style.display = 'block';
145
+ } else {
146
+ loading.textContent = '截图失败,请重试';
147
+ }
148
+ } catch (error) {
149
+ loading.textContent = '截图失败,请重试';
150
+ } finally {
151
+ loading.style.display = 'none';
152
+ }
153
+ }
154
+
155
+ function parseUrls() {
156
+ const inputBox = document.getElementById('inputBox');
157
+ const pastedText = inputBox.value;
158
+ const urlPattern = /(\b(?:https?:\/\/)?(?:[\da-z\.-]+)\.(?:[a-z\.]{2,6})(?:[\/\w\.-]*)*\/?(?:\?[^\s]*)?)/gi;
159
+ const urls = pastedText.match(urlPattern);
160
+
161
+ if (urls) {
162
+ const urlsContainer = document.getElementById('urls');
163
+ urlsContainer.innerHTML = '';
164
+
165
+ urls.forEach(url => {
166
+ let fullUrl = url;
167
+ if (!/^https?:\/\//i.test(url)) {
168
+ fullUrl = 'https://' + url;
169
+ }
170
+
171
+ const urlContainer = document.createElement('div');
172
+ urlContainer.style.display = 'flex';
173
+ urlContainer.style.alignItems = 'center';
174
+
175
+ const linkElement = document.createElement('a');
176
+ linkElement.href = fullUrl;
177
+ linkElement.textContent = fullUrl;
178
+ linkElement.target = '_blank';
179
+
180
+ const screenshotBtn = document.createElement('button');
181
+ screenshotBtn.textContent = '截图';
182
+ screenshotBtn.className = 'screenshot-btn';
183
+ screenshotBtn.onclick = () => {
184
+ takeScreenshot(fullUrl);
185
+ };
186
+
187
+ urlContainer.appendChild(linkElement);
188
+ urlContainer.appendChild(screenshotBtn);
189
+ urlsContainer.appendChild(urlContainer);
190
+ });
191
+ }
192
+ }
193
+ </script>
194
+ </body>
195
+ </html>
main.ts ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Puppeteer } from "https://deno.land/x/[email protected]/mod.ts";
2
+
3
+ let browser: any;
4
+
5
+ const initBrowser = async () => {
6
+ const puppeteer = new Puppeteer({
7
+ browserPath: '/app/chrome/chrome-linux64/chrome',
8
+ });
9
+
10
+ browser = await puppeteer.launch({
11
+ headless: true,
12
+ args: [
13
+ "--no-sandbox",
14
+ "--disable-setuid-sandbox"
15
+ ]
16
+ });
17
+ };
18
+
19
+ initBrowser();
20
+
21
+ Deno.serve({ port: 7860 }, async (req: Request) => {
22
+ const url = new URL(req.url);
23
+ const pathname = url.pathname;
24
+ const searchParams = url.searchParams;
25
+
26
+ if (pathname === "/screenshot" && req.method === "GET") {
27
+ const targetUrl = searchParams.get("url");
28
+ if (!targetUrl) {
29
+ return new Response("Missing url parameter", { status: 400 });
30
+ }
31
+
32
+ let page;
33
+ try {
34
+ if (!browser) {
35
+ await initBrowser();
36
+ }
37
+
38
+ page = await browser.newPage();
39
+ await page.setViewport({ width: 1920, height: 1080 });
40
+
41
+ await page.goto(targetUrl, {
42
+ waitUntil: 'networkidle0',
43
+ timeout: 30000
44
+ });
45
+
46
+ const screenshotBuffer = await page.screenshot();
47
+
48
+ if (page) {
49
+ await page.close();
50
+ }
51
+
52
+ return new Response(screenshotBuffer, {
53
+ headers: { "Content-Type": "image/png" },
54
+ status: 200,
55
+ });
56
+ } catch (error) {
57
+ console.error("Error taking screenshot:", error);
58
+ if (page) {
59
+ try {
60
+ await page.close();
61
+ } catch (e) {
62
+ // 忽略关闭页面时的错误
63
+ }
64
+ }
65
+ return new Response("Error taking screenshot", { status: 500 });
66
+ }
67
+ } else if (pathname === "/") {
68
+ const htmlFilePath = "/app/index.html";
69
+ const htmlContent = await Deno.readTextFile(htmlFilePath);
70
+ return new Response(htmlContent, {
71
+ headers: { "Content-Type": "text/html" },
72
+ status: 200,
73
+ });
74
+ } else {
75
+ return new Response("Not Found", { status: 404 });
76
+ }
77
+ });
readme.md ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Web Screenshot Service
2
+
3
+ 一个基于 Deno 的网页截图服务。
4
+
5
+ ## 功能特点
6
+
7
+ - 🖥️ 网页 URL 解析与展示
8
+ - 📸 高质量网页截图功能
9
+ - 🚀 基于 Docker 快速部署
10
+ - 💻 支持 1920x1080 高清分辨率
11
+
12
+ ## 快速开始
13
+
14
+ 1. 构建 Docker 镜像:
15
+ bash
16
+ docker build -t web-screenshot .
17
+
18
+ 2. 运行容器:
19
+ docker run -p 7860:7860 web-screenshot
20
+
21
+ 3. 访问服务:
22
+ - 网页界面: `http://localhost:7860`
23
+ - 截图API: `http://localhost:7860/screenshot?url=https://example.com`
24
+
25
+ ### 本地开发
26
+
27
+ 1. 安装依赖:
28
+ - Deno: https://deno.land/#installation
29
+ - Node.js: https://nodejs.org/
30
+ - Puppeteer: `npm install puppeteer`
31
+
32
+ 2. 运行服务:
33
+ - bash:deno/README.md
34
+ deno run -A main.ts
35
+
36
+ ## API 说明
37
+
38
+ ### 获取网页截图
39
+
40
+ ```http
41
+ GET /screenshot?url=<target_url>
42
+ ```
43
+
44
+ 参数说明:
45
+ - `url`: 需要截图的目标网页地址(必填)
46
+
47
+ 返回格式:
48
+ - Content-Type: image/png
49
+ - 分辨率: 1920x1080
50
+
51
+ 示例:
52
+
53
+ ```bash
54
+ curl "http://localhost:7860/screenshot?url=https://example.com" > screenshot.png
55
+ ```
56
+
57
+ ### 主页 URL 解析
58
+
59
+ ```http
60
+ GET /
61
+ ```
62
+
63
+ 返回网页解析工具界面,支持:
64
+ - URL 文本解析
65
+ - 自动协议补全
66
+ - 一键截图功能
67
+ - 新窗口打开链接
68
+
69
+ ## 技术架构
70
+
71
+ - **运行时**: Deno
72
+ - **开发语言**: TypeScript
73
+ - **截图引擎**: Puppeteer
74
+ - **容器化**: Docker
75
+ - **基础镜像**: Ubuntu
76
+
77
+ ## 目录结构
78
+
79
+ ```
80
+ .
81
+ ├── Dockerfile # Docker 构建文件
82
+ ├── README.md # 项目说明文档
83
+ ├── main.ts # 主程序入口
84
+ └── index.html # 前端界面
85
+ ```
86
+
87
+ ## 环境要求
88
+
89
+ - Deno 1.x
90
+ - Node.js 14+
91
+ - Docker (可选)
92
+ - 内存: 建议 2GB 以上
93
+ - 磁盘空间: 至少 1GB 可用空间
94
+
95
+ ## 常见问题
96
+
97
+ 1. **Q: 截图服务返回 500 错误**
98
+ - A: 检查目标网站是否可访问
99
+ - A: 确认容器内存限制是否充足
100
+
101
+ 2. **Q: Docker 构建失败**
102
+ - A: 确保网络连接正常
103
+ - A: 尝试清理 Docker 缓存后重新构建
104
+
105
+ ## 开发计划
106
+
107
+ - [ ] 支持自定义截图尺寸
108
+ - [ ] 添加截图延时选项
109
+ - [ ] 支持 PDF 导出
110
+ - [ ] 添加批量截图功能
111
+ - [ ] 优化图片压缩选项
112
+
113
+ ## 贡献指南
114
+
115
+ 欢迎提交 Issue 和 Pull Request。在提交 PR 前请确保:
116
+
117
+ 1. 代码遵循项目编码规范
118
+ 2. 添加必要的测试用例
119
+ 3. 更新相关文档
120
+