+
54
-

python有没有共享打印机的库?

python有没有可以讲自己电脑打印机共享到互联网提供有偿打印服务的库?

网友回复

+
27
-

可以通过http的方式对外提供本地的打印服务,比如局域网中,通过python暴露一个网页,打开网页上传文档,点击打印,python调用win32print或pycups(linux)实现文档打印。

由于Windows自带的打印功能对PDF、Word等复杂文档支持不佳。最稳妥的方法是使用一个支持命令行打印的程序。SumatraPDF 是一个极佳的选择,它轻量、免费、启动快,且有强大的命令行接口。

下载并安装 SumatraPDF: https://www.sumatrapdfreader.org/free-pdf-reader

记下它的安装路径,例如 C:\Program Files\SumatraPDF\SumatraPDF.exe。我们将在代码中使用这个路径。

python后端代码

# app.py
import os
import sqlite3
import random
import string
import threading
import time
import subprocess
from flask import Flask, request, jsonify, render_template
from werkzeug.utils import secure_filename

# --- 配置 ---
# 警告:在生产环境中,请使用更安全的密钥
APP_SECRET_KEY = 'your-super-secret-key' 
UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'docx', 'xlsx'}
DATABASE = 'printer.db'

# --- 关键:配置你的打印机和打印程序 ---
# 1. 在Windows "打印机和扫描仪" 设置中找到你的打印机确切名称
PRINTER_NAME = "Microsoft Print to PDF" # <-- 替换成你的真实打印机名称, 如 "HP LaserJet Pro M102w"
# 2. (推荐) SumatraPDF 的路径,用于打印PDF等复杂文件
SUMATRA_PDF_PATH = "C:\\Program Files\\SumatraPDF\\SumatraPDF.exe" # <-- 替换成你的SumatraPDF.exe路径

# --- Flask应用初始化 ---
app = Flask(__name__, template_folder='.')
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['SECRET_KEY'] = APP_SECRET_KEY

# --- 数据库辅助函数 ---
def get_db():
    db = sqlite3.connect(DATABASE)
    db.row_factory = sqlite3.Row
    return db

def init_db():
    """初始化数据库,创建表结构"""
    if os.path.exists(DATABASE):
        return
    print("Creating database...")
    db = get_db()
    with db:
        db.execute('''
            CREATE TABLE print_jobs (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                original_filename TEXT NOT NULL,
                stored_filepath TEXT NOT NULL,
                pickup_code TEXT UNIQUE NOT NULL,
                status TEXT NOT NULL, -- 'pending', 'printing', 'printed', 'completed', 'error'
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        ''')
    print("Database created.")

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def generate_pickup_code(length=6):
    """生成一个唯一的、纯数字的提取码"""
    while True:
        code = ''.join(random.choices(string.digits, k=length))
        db = get_db()
        exists = db.execute('SELECT id FROM print_jobs WHERE pickup_code = ?', (code,)).fetchone()
        db.close()
        if not exists:
            return code

def print_document_task(job_id, filepath, printer_name):
    """在后台线程中执行的打印任务"""
    db = get_db()
    try:
        print(f"Job {job_id}: Starting to print file {filepath} on printer {printer_name}")
        db.execute('UPDATE print_jobs SET status = ? WHERE id = ?', ('printing', job_id))
        db.commit()

        # 使用 SumatraPDF 命令行进行打印 (推荐,支持多种格式)
        # -print-to "printer_name": 指定打印机
        # -silent: 静默模式,不显示任何UI
        # -exit-on-print: 打印后自动退出
        if os.path.exists(SUMATRA_PDF_PATH):
            command = [
                SUMATRA_PDF_PATH,
                '-print-to', printer_name,
                '-silent',
                '-exit-on-print',
                filepath
            ]
            subprocess.run(command, check=True, timeout=120) # 120秒超时
        else:
            # 备用方案: 使用Windows shell命令 (仅对某些文件类型有效)
            # import win32api
            # win32api.ShellExecute(0, "print", filepath, f'"{printer_name}"', ".", 0)
            # 这行代码更简单,但兼容性较差,推荐使用SumatraPDF
            raise FileNotFoundError(f"SumatraPDF not found at {SUMATRA_PDF_PATH}. Printing failed.")

        time.sleep(5) # 留出一点时间让文件进入打印机队列

        print(f"Job {job_id}: Print command sent successfully.")
        db.execute('UPDATE print_jobs SET status = ? WHERE id = ?', ('printed', job_id))
        db.commit()

    except Exception as e:
        print(f"Error printing job {job_id}: {e}")
        db.execute('UPDATE print_jobs SET status = ? WHERE id = ?', ('error', job_id))
        db.commit()
    finally:
        db.close()
        # 打印完成后,无论成功与否,都删除临时文件
        try:
            os.remove(filepath)
            print(f"Job {job_id}: Temporary file {filepath} deleted.")
        except OSError as e:
            print(f"Error deleting file {filepath}: {e}")

# --- API 路由 ---

@app.route('/')
def index():
    """主页,渲染HTML"""
    return render_template('index.html')

@app.route('/api/upload', methods=['POST'])
def upload_file():
    """处理文件上传和创建打印任务"""
    if 'file' not in request.files:
        return jsonify({'error': 'No file part'}), 400
    file = request.files['file']
    if file.filename == '':
        return jsonify({'error': 'No selected file'}), 400
    
    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        # 使用时间戳确保文件名唯一,避免覆盖
        unique_filename = f"{int(time.time())}_{filename}"
        filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)
        file.save(filepath)

        pickup_code = generate_pickup_code()

        db = get_db()
        cursor = db.cursor()
        cursor.execute(
            'INSERT INTO print_jobs (original_filename, stored_filepath, pickup_code, status) VALUES (?, ?, ?, ?)',
            (filename, filepath, pickup_code, 'pending')
        )
        job_id = cursor.lastrowid
        db.commit()
        db.close()

        # 创建并启动一个新线程来处理打印,防止阻塞Web请求
        print_thread = threading.Thread(target=print_document_task, args=(job_id, filepath, PRINTER_NAME))
        print_thread.start()

        return jsonify({'message': 'File uploaded successfully', 'pickup_code': pickup_code})
    
    return jsonify({'error': 'File type not allowed'}), 400

@app.route('/api/pickup', methods=['POST'])
def pickup_file():
    """核销提取码"""
    data = request.get_json()
    code = data.get('code')
    if not code:
        return jsonify({'error': 'Pickup code is required'}), 400

    db = get_db()
    job = db.execute('SELECT id, status, original_filename FROM print_jobs WHERE pickup_code = ?', (code,)).fetchone()

    if not job:
        db.close()
        return jsonify({'error': 'Invalid pickup code'}), 404

    if job['status'] == 'printed':
        db.execute('UPDATE print_jobs SET status = ? WHERE id = ?', ('completed', job['id']))
        db.commit()
        db.close()
        return jsonify({'success': True, 'message': f"Verification successful for file: {job['original_filename']}"})
    elif job['status'] in ['pending', 'printing']:
        db.close()
        return jsonify({'error': 'Document is still printing. Please wait.'}), 425 # Too Early
    elif job['status'] == 'completed':
        db.close()
        return jsonify({'error': 'This code has already been used.'}), 410 # Gone
    else: # error
        db.close()
        return jsonify({'error': 'An error occurred while printing this document.'}), 500

# --- 主程序入口 ---
if __name__ == '__main__':
    # 确保上传目录存在
    if not os.path.exists(UPLOAD_FOLDER):
        os.makedirs(UPLOAD_FOLDER)
    
    # 初始化数据库
    init_db()
    
    # 启动Flask应用
    # host='0.0.0.0' 使其在局域网内可访问
    app.run(host='0.0.0.0', port=5000, debug=True)

前端h5

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>自助共享打印</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <style>
        :root {
            --bg-color: #f0f2f5;
            --card-bg: #ffffff;
            --text-color: #333;
            --primary-color: #007bff;
            --primary-hover: #0056b3;
            --border-color: #e0e0e0;
            --success-color: #28a745;
            --error-color: #dc3545;
            --gray-text: #6c757d;
        }

        /* 暗黑模式 */
        @media (prefers-color-scheme: dark) {
            :root {
                --bg-color: #1a1a1a;
                --card-bg: #2c2c2c;
                --text-color: #e0e0e0;
                --primary-color: #3b82f6;
                --primary-hover: #2563eb;
                --border-color: #444;
            }
       ...

点击查看剩余70%

我知道答案,我要回答