PDF转CSV应用前后端交互报错:405与JSON解析错误求助
点击提交文件时触发报错:An error occurred: SyntaxError: Unexpected end of JSON input,浏览器控制台显示Failed to load resource: the server responded with a status of 405 (Method Not Allowed)。前端(HTML/JS)实际请求5502端口,但后端配置运行在5000端口,导致前后端跨域运行。
后端Python代码
from flask_cors import CORS import camelot import pandas as pd import os app = Flask(__name__) app.run(host='127.0.0.1', port=5000) CORS(app, resources={r"/convert": {"origins": "*"}}) @app.route('/') def index(): return render_template('index.html') @app.route('/convert', methods=['POST']) def convert(): print(request.form) start_page = int(request.form['start_page']) end_page = int(request.form['end_page']) filename = request.form['filename'] output_directory = request.form['output_directory'] uploaded_file = request.files['pdf_file'] # Get the uploaded PDF file pdf_file_path = os.path.join('uploads', uploaded_file.filename) uploaded_file.save(pdf_file_path) pages_range = f'{start_page}-{end_page}' tables = camelot.read_pdf(pdf_file_path, pages=pages_range, flavor='stream') dataframes = [table.df for table in tables] print("Dataframes successfully created.") combined_dataframe_vertical = pd.concat(dataframes, ignore_index=True) rows_to_remove = [] merged_rows = [] for index, row in combined_dataframe_vertical.iterrows(): if all(pd.isnull(val) or val == '' for val in row[1:]): merged_rows[-1] = merged_rows[-1] + row rows_to_remove.append(index) else: merged_rows.append(row) combined_dataframe_vertical.drop(rows_to_remove, inplace=True) combined_dataframe_vertical = pd.DataFrame(merged_rows, columns=combined_dataframe_vertical.columns) combined_dataframe_vertical.reset_index(drop=True, inplace=True) csv_file_path = os.path.join(output_directory, f'{filename}.csv') combined_dataframe_vertical.to_csv(csv_file_path, index=False, header=False) response_data = {'message': f"Tables extracted and CSV exported to: {csv_file_path}"} return jsonify(response_data) # Return JSON response if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)
前端HTML文件
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script defer src="/static/pdftocsv.js"></script> <title>PDF to CSV Converter</title> </head> <body> <h1>PDF to CSV Converter</h1> <div id="drag-area" style="border: 2px dashed #ccc; padding: 20px; text-align: center;"> <p>Drag a PDF file here to convert</p> <input type="file" id="file-input" name="pdf_file" style="display: none;"> </div> <button id="customize-button" style="display: none;">Convert to CSV</button> </body> </html>
前端JS文件
const dragArea = document.getElementById('drag-area'); const fileInput = document.getElementById('file-input'); const customizeButton = document.getElementById('customize-button'); let pdfFilename; // Declare the variable outside of the event handlers dragArea.addEventListener('dragover', (e) => { e.preventDefault(); dragArea.style.border = '2px dashed #000'; }); dragArea.addEventListener('dragleave', () => { dragArea.style.border = '2px dashed #ccc'; }); dragArea.addEventListener('drop', (e) => { e.preventDefault(); const file = e.dataTransfer.files[0]; fileInput.files = e.dataTransfer.files; pdfFilename = file.name; // Set the PDF filename customizeButton.style.display = 'block'; customizeButton.addEventListener('click', () => { // Prompt for start page const startPage = prompt('Enter Start Page:', '1'); // Prompt for end page const endPage = prompt('Enter End Page:', '1'); // Automatically set the output directory to the downloads folder const output_directory = 'downloads'; const defaultCsvFilename = pdfFilename.replace('.pdf', '.csv'); const customFilename = prompt('Enter File Name:', defaultCsvFilename); // Send the data to the server for processing fetch(window.location.origin + '/convert', { method: 'POST', body: new URLSearchParams({ start_page: startPage, end_page: endPage, output_directory: output_directory, filename: customFilename, // Only send the custom filename }), headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }) .then(response => response.json()) .then(data => { alert(data.message); }) .catch(error => { alert('An error occurred: ' + error); }); }); }); fileInput.addEventListener('change', () => { const file = fileInput.files[0]; pdfFilename = file.name; // Set the PDF filename customizeButton.style.display = 'block'; });
错误原因及修复方案
1. 跨域请求地址错误
前端用window.location.origin + '/convert'请求的是当前页面所在的5502端口,而非后端的5000端口,导致请求发送到错误的服务器,返回405不允许方法。
修复:
修改JS中的fetch地址为后端实际地址:
fetch('http://127.0.0.1:5000/convert', { // ... 其他参数 })
2. Flask初始化顺序错误
后端代码中app.run(host='127.0.0.1', port=5000)在CORS配置和路由定义之前执行,导致:
- 提前启动了一个未配置CORS和路由的Flask实例
- 后续的CORS和路由配置不会生效
- 最后
if __name__ == '__main__'又重复启动了一次服务
修复:
删除提前执行的app.run,确保CORS配置在路由之前:
from flask import Flask, request, render_template, jsonify # 补充缺失的导入 from flask_cors import CORS import camelot import pandas as pd import os app = Flask(__name__) CORS(app, resources={r"/convert": {"origins": "*"}}) # CORS配置放在路由前 @app.route('/') def index(): return render_template('index.html') @app.route('/convert', methods=['POST']) def convert(): # ... 原有逻辑 return jsonify(response_data) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True) # 只保留这一处启动代码
3. 未导入Flask核心模块
后端代码缺少request、render_template、jsonify的导入,运行时会抛出NameError,导致接口无法正常响应。
修复:
在开头补充导入:
from flask import Flask, request, render_template, jsonify
4. 前端未发送PDF文件
前端用URLSearchParams只提交了文本参数,没有包含用户上传的PDF文件,后端request.files['pdf_file']会获取不到文件,抛出错误。同时application/x-www-form-urlencoded格式无法传输文件。
修复:
改用FormData来封装文件和所有参数,不需要手动设置Content-Type:
customizeButton.addEventListener('click', () => { const startPage = prompt('Enter Start Page:', '1'); const endPage = prompt('Enter End Page:', '1'); const output_directory = 'downloads'; const defaultCsvFilename = pdfFilename.replace('.pdf', '.csv'); const customFilename = prompt('Enter File Name:', defaultCsvFilename); // 创建FormData对象 const formData = new FormData(); formData.append('pdf_file', fileInput.files[0]); // 添加PDF文件 formData.append('start_page', startPage); formData.append('end_page', endPage); formData.append('output_directory', output_directory); formData.append('filename', customFilename); fetch('http://127.0.0.1:5000/convert', { method: 'POST', body: formData // 直接传入FormData }) .then(response => response.json()) .then(data => { alert(data.message); }) .catch(error => { alert('An error occurred: ' + error); }); });
5. 后端缺少目录存在性检查
后端保存文件到uploads目录,但如果该目录不存在,会抛出FileNotFoundError。
修复:
在保存文件前创建目录(如果不存在):
pdf_file_path = os.path.join('uploads', uploaded_file.filename) os.makedirs('uploads', exist_ok=True) # 添加这一行 uploaded_file.save(pdf_file_path)
同时确保downloads目录也存在:
csv_file_path = os.path.join(output_directory, f'{filename}.csv') os.makedirs(output_directory, exist_ok=True) # 添加这一行 combined_dataframe_vertical.to_csv(csv_file_path, index=False, header=False)
内容的提问来源于stack exchange,提问作者CatLover2291

