You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flask服务器中图片转PDF无法本地保存问题求助

问题诊断与修复方案

核心问题:PDF生成逻辑未执行

你的代码里,图片下载和PDF生成的核心逻辑被包裹在if __name__ == "__main__":判断中。本地直接运行脚本时该条件成立,代码正常执行;但部署到Flask服务器(如Gunicorn、uWSGI)时,__name__的值不是__main__,导致这部分代码完全跳过,自然不会生成PDF文件。

其他潜在问题

  • 全局变量冲突:使用全局变量FileName存储PDF文件名,多用户并发访问时会被覆盖,导致返回错误文件或找不到文件。
  • 路径不明确:依赖相对路径和os.chdir,服务器环境中工作目录可能变化,导致文件路径错误。
  • 异步执行兼容性:在Flask路由中直接调用asyncio.run,在WSGI服务器环境下可能引发事件循环冲突。
  • 权限问题:服务器运行Flask的用户可能没有PDFs、temp文件夹的创建/写入权限。

修复后的代码示例

from cmath import e
from os import link
from pickle import GET
from webbrowser import get
from flask import Flask, render_template, request, jsonify , redirect, url_for
from flask import send_file
import json
import requests
from libgen_api import LibgenSearch
from kissmanga import get_search_results, get_manga_details, get_manga_episode, get_manga_chapter
import aiohttp
import aiofiles
import asyncio
import os
from fake_headers import Headers
from fpdf import FPDF
from PIL import Image
import glob
import uuid


app = Flask(__name__)

# 定义固定的文件夹绝对路径
TEMP_FOLDER = os.path.join(app.root_path, 'temp')
PDF_FOLDER = os.path.join(app.root_path, 'PDFs')

# 提前初始化文件夹,避免请求时重复判断
os.makedirs(TEMP_FOLDER, exist_ok=True)
os.makedirs(PDF_FOLDER, exist_ok=True)


@app.route("/")
def home():
    return render_template("index.html")

@app.route("/api/default")
def titleSearch():
    try:
        title = request.args.get('query')
        s = LibgenSearch()
        results = s.search_title(title)
        item_to_download = results[0]
        download_links = s.resolve_download_links(item_to_download)
        return render_template(
            "results.html", results=results, download_links=download_links, title=title,
        )
    except IndexError:
        return render_template("error.html")
            
@app.route("/api/manga")
def mangasearch():
    try:
        class bcolors:
            HEADER = '\033[95m'
            OKBLUE = '\033[94m'
            OKCYAN = '\033[96m'
            OKGREEN = '\033[92m'
            WARNING = '\033[93m'
            FAIL = '\033[91m'
            ENDC = '\033[0m'
            BOLD = '\033[1m'
            UNDERLINE = '\033[4m'
        
        InputMangaTitle = request.args.get('manganame')
        InputMangaCh = request.args.get('mangach')

        manga_search = get_search_results(query=InputMangaTitle)
        if not manga_search:
            print(f'{bcolors.FAIL}Query did not yield any results!{bcolors.ENDC}')
            return render_template("error.html")
        
        titleManga = manga_search[0].get('title')
        IdManga = manga_search[0].get('mangaid')
        mangaChNum = InputMangaCh
        pdf_base_name = f"{titleManga} ch.# {mangaChNum}"
        # 用UUID生成唯一文件名,避免冲突和特殊字符问题
        unique_pdf_name = f"{uuid.uuid4().hex}_{pdf_base_name.replace('/', '_').replace('\\', '_')}.pdf"
        pdf_path = os.path.join(PDF_FOLDER, unique_pdf_name)

        manga_chapter = get_manga_chapter(mangaid=IdManga, chapNumber=mangaChNum)

        async def fetch(session, url):
            try:
                url = url.strip('[]')
                async with session.get(url) as resp:
                    fileNameNE = (url.split('/')[-1]).split('.')[0]
                    fileName = f"{fileNameNE}.jpg"
                    fullFileName = os.path.join(TEMP_FOLDER, fileName)
                    if resp.status == 200:
                        async with aiofiles.open(fullFileName, mode='wb') as f:
                            await f.write(await resp.read())
                        print(f'{bcolors.OKGREEN}Done: {bcolors.ENDC}{url}')
                        return fullFileName
                    else:
                        print(f'{bcolors.WARNING}Rejected URL: {url} | Status: {resp.status}{bcolors.ENDC}')
            except Exception as err:
                print(err)
                return None

        async def main(image_urls):
            tasks = []
            headers = Headers(headers=True).generate()
            async with aiohttp.ClientSession(headers=headers) as session:
                for image in image_urls:
                    tasks.append(fetch(session, image))
                data = await asyncio.gather(*tasks)
                return data
            
        def pdfGen(imageList, output_path):
            pdf = FPDF()
            for image in imageList:
                if image is None or not os.path.exists(image):
                    continue
                cover = Image.open(image)
                width, height = cover.size
                # 转换为毫米单位
                width, height = float(width * 0.264583), float(height * 0.264583)
                pdf_size = {'P': {'w': 210, 'h': 297}, 'L': {'w': 297, 'h': 210}}
                orientation = 'P' if width < height else 'L'
                # 限制图片尺寸不超过页面
                width = min(width, pdf_size[orientation]['w'])
                height = min(height, pdf_size[orientation]['h'])

                pdf.add_page(orientation=orientation)
                pdf.image(image, 0, 0, width, height)
            
            pdf.output(output_path, "F")
            print(f'{bcolors.OKCYAN}Generated PDF: {output_path} {bcolors.ENDC}')
        
        # 移除__name__判断,直接执行生成逻辑
        try:
            image_urls = manga_chapter['totalPages'].strip('][').split(', ')
            # 创建独立事件循环,兼容WSGI服务器环境
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            result = loop.run_until_complete(main(image_urls))
            pdfGen(result, pdf_path)
            # 清理临时文件
            files = glob.glob(os.path.join(TEMP_FOLDER, '*'))
            for f in files:
                os.remove(f)
        except Exception as e:
            print(e)
            return render_template("error.html")
        
        # 将唯一文件名传递给模板,用于下载路由
        return render_template("manga.html", pdf_file_name=unique_pdf_name) 
        
    except IndexError:
        return render_template("error.html")

@app.route('/return-files/<pdf_file_name>')
def return_files_tut(pdf_file_name):
    try:
        pdf_path = os.path.join(PDF_FOLDER, pdf_file_name)
        if not os.path.exists(pdf_path):
            return "PDF文件不存在"
        return send_file(pdf_path)
    except Exception as e:
        return str(e)
    
if __name__ == "__main__":
    app.run(debug=False, port=80)

关键修改点

  1. 移除if __name__ == "__main__"判断:确保PDF生成逻辑在路由被调用时正常执行。
  2. 使用绝对路径:通过app.root_path获取应用根目录,拼接出temp和PDFs的绝对路径,避免工作目录变化导致的路径错误。
  3. 替换全局变量:用UUID生成唯一的PDF文件名,通过路由参数传递文件名,避免多用户并发时的变量冲突。
  4. 优化异步执行:创建独立事件循环执行异步任务,兼容WSGI服务器环境。
  5. 文件夹初始化提前:在应用启动时就创建temp和PDFs文件夹,避免每次请求重复判断。
  6. 增加文件存在检查:在下载路由中先检查文件是否存在,返回明确提示。

额外检查项

  • 确保服务器运行Flask的用户对temp和PDFs文件夹有读写权限。
  • 如果使用Linux服务器,可执行chown -R www-data:www-data /path/to/your/app(假设运行用户为www-data)赋予权限。

内容的提问来源于stack exchange,提问作者Samurai6465

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 13:35:40