仅当在单独的文件中给出转换命令时,才能将 HTML 转换为 PDF

问题描述

我想用我编写的一个小 Python 程序生成发票。我希望它首先将发票输出为 HTML 文件,然后将其转换为 PDF。目前我的代码正在根据需要创建 HTML 文档,但它输出的是一个空白的 PDF 文件

import pdfkit 
import time 

name = input('Enter business name: ')
date = input('Enter the date: ')
invoice = input('Enter invoice number: ')
total = 1000 # Fixed number for Now just for testing purposes

f = open('invoice.html','w')
message = f"""<html>
<head>
<link rel='stylesheet' href='style.css')>
</head>
<body>
<h1>INVOICE<span id="invoice-number"> {invoice}</h1>
<h3>{date} </h3> 
<h3>{name} </h3>
<h3>Total: ${total}</h3>
</body>
</html>"""

f.write(message)
time.sleep(2)

options = {
    'enable-local-file-access': None
}
pdfkit.from_file('invoice.html','out.pdf',options = options)

当我运行上面的代码时,它输出的 HTML 文档看起来不错,但它创建的 PDF 是空白的。我根本没有收到错误消息。终端中的输出是:

Loading pages (1/6)
Counting pages (2/6)                                               
Resolving links (4/6)                                                       
Loading headers and footers (5/6)                                           
Printing pages (6/6)
Done  

但是,我在同一目录中有另一个名为 test.py文件。该文件包含导入语句,与第一个代码完全相同的最后四行,没有别的:

import pdfkit 

options = {
    'enable-local-file-access': None
}
pdfkit.from_file('invoice.html',options = options)

如果我在上一个文件之后运行这个文件,它会正确输出 PDF,看起来就像 HTML 版本。为什么此代码在单独的文件中运行时有效,但包含在原始文件中时不起作用?我怎样才能让它在同一个文件中运行,这样我就不必执行两个命令来获取发票?

解决方法

你永远不会关闭你正在写入的文件,或者明确地刷新它。由于它是一个小文本片段,并且默认情况下启用了缓冲,因此在您的进程终止之前它不会被写入磁盘。您不会看到错误,因为文件在打开时会立即创建。问题不是您认为的时间问题。

这个问题有多种解决方案。最简单和最正确的方法是在完成写入后立即关闭文件。这就是你在 python 中惯用的打开文件的方式:

name = input('Enter business name: ')
date = input('Enter the date: ')
total = 1000 # Fixed number for now just for testing purposes

message = ...
options = {
    'enable-local-file-access': None
}

with open('invoice.html','w') as f:
    f.write(message)

pdfkit.from_file('invoice.html','out.pdf',options = options)

请注意,您不需要等待任何事情:退出 with 块将关闭文件并在进程中刷新它即使发生错误。您应该养成在完成工作后立即关闭文件的习惯:大多数操作系统支持每个进程的文件句柄数量有限。这意味着在 pdfkit.from_file 块之后调用 with 可以保证它有一个完全刷新的文件可以使用。

这里有一些其他的方法,它们不是惯用的,在实践中不推荐使用。我提供它们只是为了让您了解不同步骤的工作原理:

  1. 在尝试读取文件之前刷新文件。而不是 sleep,调用

    f.flush()
    

    这将使文件对象保持打开状态并且不一定正确处理错误,但它会确保在您尝试阅读之前写出它们的 HTML 内容。

  2. 关闭缓冲。缓冲意味着直到您写入 4kb 左右的数据(无论您的磁盘块大小如何),在您刷新它之前,不会将任何数据写入磁盘。您可以禁用此行为,以便通过像这样调用 open 来立即写出字节

    open('invoice.html','w',buffering=0)