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)
关键修改点
- 移除
if __name__ == "__main__"判断:确保PDF生成逻辑在路由被调用时正常执行。 - 使用绝对路径:通过
app.root_path获取应用根目录,拼接出temp和PDFs的绝对路径,避免工作目录变化导致的路径错误。 - 替换全局变量:用UUID生成唯一的PDF文件名,通过路由参数传递文件名,避免多用户并发时的变量冲突。
- 优化异步执行:创建独立事件循环执行异步任务,兼容WSGI服务器环境。
- 文件夹初始化提前:在应用启动时就创建
temp和PDFs文件夹,避免每次请求重复判断。 - 增加文件存在检查:在下载路由中先检查文件是否存在,返回明确提示。
额外检查项
- 确保服务器运行Flask的用户对
temp和PDFs文件夹有读写权限。 - 如果使用Linux服务器,可执行
chown -R www-data:www-data /path/to/your/app(假设运行用户为www-data)赋予权限。
内容的提问来源于stack exchange,提问作者Samurai6465
相关产品推荐
相关产品推荐

