登录后台

页面导航

最近TG图床关闭,我怕已经存档的图片丢失,便想依靠手中的url批量下载图片。没成想,没有类似的网站;只好自己做一个了。

效果展示

在GPT4的部分帮助下,我得到了如图的Flask代码:

from flask import Flask, request, send_file, jsonify
import requests
import os
import zipfile
from io import BytesIO

app = Flask(__name__)

@app.route('/download-images', methods=['POST'])
def download_images():
    data = request.get_json()
    urls = data.get('urls', [])

    if not urls:
        return jsonify({"error": "No URLs provided"}), 400

    zip_buffer = BytesIO()
    with zipfile.ZipFile(zip_buffer, 'w') as zip_file:
        for index, url in enumerate(urls):
            try:
                response = requests.get(url)
                response.raise_for_status()

                # 将图片写入 ZIP 文件
                filename = f'image_{index + 1}.{url.split(".")[-1]}'
                zip_file.writestr(filename, response.content)
            except requests.RequestException as e:
                return jsonify({"error": f"Failed to download image from {url}. Error: {e}"}), 500

    zip_buffer.seek(0)
    return send_file(zip_buffer, mimetype='application/zip', as_attachment=True, download_name='images.zip')

if __name__ == '__main__':
    app.run(debug=True)

和这样的前端代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图片批量下载</title>
    <style>
        body {
            margin: 0;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            background: linear-gradient(to bottom right, #0072FF, #00C6FF, #E0F7FA, #FFFFFF);
            font-family: Arial, sans-serif;
        }

        .download-panel {
            background-color: white;
            padding: 30px;
            border-radius: 15px;
            box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1);
            max-width: 600px;
            text-align: center;
        }

        .download-panel h1 {
            font-size: 24px;
            color: #0072FF;
            margin-bottom: 20px;
        }

        .download-panel textarea {
            width: 100%;
            height: 150px;
            margin-bottom: 20px;
            padding: 10px;
            border-radius: 10px;
            border: 1px solid #ccc;
            font-size: 16px;
            resize: none;
        }

        .download-panel button {
            background-color: #0072FF;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            font-size: 16px;
            cursor: pointer;
        }

        .download-panel button:hover {
            background-color: #005BBB;
        }

        .footer {
            position: absolute;
            bottom: 10px;
            width: 100%;
            text-align: center;
            font-size: 12px;
            color: #888;
        }
    </style>
</head>
<body>
    <div class="download-panel">
        <h1>图片URL批量下载</h1>
        <textarea id="image-urls" placeholder="请输入图片链接,每行一个"></textarea>
        <button onclick="downloadImages()">下载图片</button>
        <h4>图片链接每行一个,将会被打包为ZIP文件.<br>如果图片过大/数量过多,需等待一会.<br>如图片不能被服务器访问(服务器位置为香港),则无法被下载.<br>开源,保证不收集任何信息,包括您输入的URL/图片文件。</h4>
    </div>
    
    <div class="footer">
        &copy; 2024 DuckXu.Com. All rights reserved. 联系我:duckxu@duckxu.com
    </div>

    <script>
        function downloadImages() {
            const urls = document.getElementById('image-urls').value.trim().split('\n');
            if (urls.length === 0) {
                alert('请输入至少一个图片链接.');
                return;
            }

            fetch('https://misaka.665656.xyz/download-images', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ urls })
            })
            .then(response => response.blob())
            .then(blob => {
                const link = document.createElement('a');
                link.href = window.URL.createObjectURL(blob);
                link.download = 'images.zip';
                link.click();
            })
            .catch(error => {
                console.error('下载失败:', error);
            });
        }
    </script>
</body>
</html>

到这一步还是比较简单的,我在服务器上更新软件库,通过

pip install flask requests

安装了Flask,在媒体站里放了py文件。紧接着,第一个问题就来了:无法访问端口。
检查了一下,是宝塔拦截的,把宝塔防火墙关了就行了。我没关,但是单独放行了5000端口。

2024-09-15T16:38:18.png

部署域名picdown.duckxu.com,保持后端运行。我使用了systemd。
先创建一个服务文件

sudo nano /etc/systemd/system/flask_app.service

再写入以下内容

[Unit]
Description=Flask App

[Service]
ExecStart=/usr/bin/python /path/to/your/app.py
WorkingDirectory=/path/to/your/app
Restart=always
User=yourusername

[Install]
WantedBy=multi-user.target

注意,ExecStart=/usr/bin/python /path/to/your/app.py这里面的/usr/bin/python 一定要保留,只修改后面,否则无法找到路径。
重新加载systemd,启动服务。

sudo systemctl daemon-reload
sudo systemctl start flask_app.service
sudo systemctl enable flask_app.service

可以通过

sudo systemctl status flask_app.service

接着,我遇到了这个问题:

picdown.duckxu.com/:1 Access to fetch at 'http://...:5000/' from origin 'http://picdown.duckxu.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

查了一下,原来是HTTPS加密的地址不能去请求HTTP未加密的地址,是“CORS”。
那安装一下吧。运行以下命令:

pip3 install flask-cors

再在Flask代码里添加CORS的内容:

from flask_cors import CORS  # 导入 CORS

app = Flask(__name__)
CORS(app)  # 启用 CORS

也可以限制请求来源网址,不过我这是公共API,就没有做。

CORS(app, resources={r"/*": {"origins": "http://picdown.duckxu.com"}})

接着,我发现IP地址无法申请SSL证书。那只好开一个子域名绑定到服务器5000端口上了。

server {
    listen 80 443;
    server_name example.com www.example.com;

    location / {
        proxy_pass http://localhost:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

你可以修改或只保留这个nginx配置的一部分,简单来说就是让你的域名监听5000端口。但是,域名只监听5000端口,后缀得加上。比如说我之前的IP上开放的是...:5000/download-images,那我请求的域名就得是https://example.com/download-images。很简单,在第一个html的JS里更改fetch即可,比如说我的:

fetch('https://misaka.665656.xyz/download-images', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ urls })
            })

再次运行,成功请求。

2024-09-15T16:00:51.png

已有 5 条评论