如何使用Python将网页转换为PDF

138

我正在寻找用Python将网页打印为本地文件PDF的解决方案。其中一个好的解决方案是使用Qt,在这里找到:https://bharatikunal.wordpress.com/2010/01/

一开始它没有起作用,因为我在安装PyQt4时出了问题,它给出了错误消息,如 'ImportError: No module named PyQt4.QtCore' 和 'ImportError: No module named PyQt4.QtCore'。

这是因为PyQt4没有正确安装。我曾经把库位于C:\Python27\Lib,但这不适用于PyQt4。

事实上,只需要从http://www.riverbankcomputing.com/software/pyqt/download(注意您使用的正确Python版本),并将其安装到C:\Python27(我的情况)即可。

现在脚本运行得很好,所以我想分享它。有关使用Qprinter的更多选项,请参阅http://qt-project.org/doc/qt-4.8/qprinter.html#Orientation-enum


1
请注意,如果您正在自我回答问题,那么您可以同时发布问答,并且通常的质量规则仍适用于两个部分。 - jonrsharpe
10个回答

188

您还可以使用pdfkit

用法

import pdfkit
pdfkit.from_url('http://google.com', 'out.pdf')

安装

MacOS: brew install Caskroom/cask/wkhtmltopdf

Debian/Ubuntu: apt-get install wkhtmltopdf

Windows: choco install wkhtmltopdf

请参考官方文档以获取在MacOS/Ubuntu/其他操作系统上的安装指南:https://github.com/JazzCore/python-pdfkit/wiki/Installing-wkhtmltopdf


7
太棒了!比起折腾 reportlab 或使用打印驱动程序转换简单多了。非常感谢。 - Dowlers
4
PDFKit需要运行中的X Server(或“虚拟”X Server)。 :( 请参见此处:https://github.com/JazzCore/python-pdfkit/wiki/Using-wkhtmltopdf-without-X-server - Tim Ludwinski
1
似乎Windows不支持pdfkit。这是真的吗? - Kane Chew
2
完美!甚至可以下载嵌入的图片,不必使用它!您需要执行“apt-get install wkhtmltopdf”。 - Tinmarino
28
pdfkit 依赖于非 Python 软件包 wkhtmltopdf,而 wkhtmltopdf 又需要运行 X 服务器。因此,尽管在某些环境下很好用,但这不是适用于 Python 的通用解决方案。 - Rasmus Kaj
显示剩余13条评论

71

WeasyPrint

pip install weasyprint  # No longer supports Python 2.x.

python
>>> import weasyprint
>>> pdf = weasyprint.HTML('http://www.google.com').write_pdf()
>>> len(pdf)
92059
>>> open('google.pdf', 'wb').write(pdf)

8
我可以提供文件路径而不是URL吗? - Piyush S. Wanare
25
我认为我会更喜欢这个项目,因为它的依赖关系是Python软件包而不是系统软件包。截至2018年1月,它似乎有更频繁的更新和更好的文档。 - stvsmth
14
有太多东西需要安装了。我在安装 libpango 时停下来,转而选择 pdfkit。对于整个系统的 wkhtmltopdf 来说有些棘手,但 weasyprint 也需要安装一些系统级别的软件。 - visoft
3
这不会将HTML文件中的javascripts转换。为此,您需要使用pdfkit - suhailvs
2
WeasyPrint 在处理大型 HTML 页面时速度较慢。在我的情况下,它需要 50 秒,而 pdfkit 只需要 7 秒。 - Artem Bernatskyi
显示剩余5条评论

25
感谢以下帖子,我现在能够在生成的PDF上打印网页链接地址和当前时间,无论有多少页。 使用Python向现有PDF添加文本 https://github.com/disflux/django-mtr/blob/master/pdfgen/doc_overlay.py 分享以下脚本:
import time
from pyPdf import PdfFileWriter, PdfFileReader
import StringIO
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
from xhtml2pdf import pisa
import sys 
from PyQt4.QtCore import *
from PyQt4.QtGui import * 
from PyQt4.QtWebKit import * 

url = 'http://www.yahoo.com'
tem_pdf = "c:\\tem_pdf.pdf"
final_file = "c:\\younameit.pdf"

app = QApplication(sys.argv)
web = QWebView()
#Read the URL given
web.load(QUrl(url))
printer = QPrinter()
#setting format
printer.setPageSize(QPrinter.A4)
printer.setOrientation(QPrinter.Landscape)
printer.setOutputFormat(QPrinter.PdfFormat)
#export file as c:\tem_pdf.pdf
printer.setOutputFileName(tem_pdf)

def convertIt():
    web.print_(printer)
    QApplication.exit()

QObject.connect(web, SIGNAL("loadFinished(bool)"), convertIt)

app.exec_()
sys.exit

# Below is to add on the weblink as text and present date&time on PDF generated

outputPDF = PdfFileWriter()
packet = StringIO.StringIO()
# create a new PDF with Reportlab
can = canvas.Canvas(packet, pagesize=letter)
can.setFont("Helvetica", 9)
# Writting the new line
oknow = time.strftime("%a, %d %b %Y %H:%M")
can.drawString(5, 2, url)
can.drawString(605, 2, oknow)
can.save()

#move to the beginning of the StringIO buffer
packet.seek(0)
new_pdf = PdfFileReader(packet)
# read your existing PDF
existing_pdf = PdfFileReader(file(tem_pdf, "rb"))
pages = existing_pdf.getNumPages()
output = PdfFileWriter()
# add the "watermark" (which is the new pdf) on the existing page
for x in range(0,pages):
    page = existing_pdf.getPage(x)
    page.mergePage(new_pdf.getPage(0))
    output.addPage(page)
# finally, write "output" to a real file
outputStream = file(final_file, "wb")
output.write(outputStream)
outputStream.close()

print final_file, 'is ready.'

@user2426679,您的意思是将在线PDF转换为本地PDF文件吗? - Mark K
谢谢您的回复,抱歉我回复晚了。最终我使用了wkhtmltopdf,因为它能够处理我所要处理的内容。但是我想问如何加载我的本地硬盘上的PDF文件。谢谢! - sam-6174
2
html5lib存在一些问题,而xhtml2pdf使用了它。这个解决方案修复了这个问题:https://github.com/xhtml2pdf/xhtml2pdf/issues/318 - Blairg23
我认为这个不再适用了,“No module named 'pdf'”。如果我没记错的话,PyPdf2已经被弃用,应该使用作者推荐的另一个工具。 - 3pitt
@MikePalmice,这是2.76。 - Mark K
显示剩余3条评论

16
根据这篇回答: 如何使用Python将网页转换为PDF,建议使用pdfkit。您还需要安装wkhtmltopdf。 如果您有本地的.html文件,则需要使用该命令:
pdfkit.from_file('test.html', 'out.pdf')

但是如果您没有将wkhtmltopdf可执行文件添加到系统路径中,这将会导致错误。这就是让我困惑的部分,我想分享一下。

在Windows上,打开您的环境变量并将它们添加到您的系统变量>路径中,如下所示。在我的情况下,安装了wkhtmltopdf之后,这些.exe文件位于以下位置:

C:\Program Files\wkhtmltopdf\bin

environment variables


1
我在Win10上遇到了同样的问题,这个帮了我很多,非常感谢。 - kudo_shinichi

14

这里是一个正常工作的示例:

import sys 
from PyQt4.QtCore import *
from PyQt4.QtGui import * 
from PyQt4.QtWebKit import * 

app = QApplication(sys.argv)
web = QWebView()
web.load(QUrl("http://www.yahoo.com"))
printer = QPrinter()
printer.setPageSize(QPrinter.A4)
printer.setOutputFormat(QPrinter.PdfFormat)
printer.setOutputFileName("fileOK.pdf")

def convertIt():
    web.print_(printer)
    print("Pdf generated")
    QApplication.exit()

QObject.connect(web, SIGNAL("loadFinished(bool)"), convertIt)
sys.exit(app.exec_())

有趣的是,在生成的PDF中,网页链接被生成为文本而不是链接。 - amergin
1
有人知道为什么这会生成空白的PDF吗? - boson

11

这里是使用QT的简单解决方案。我在StackOverFlow的另一个问题的答案中找到了它。我在Windows上测试过了。

from PyQt4.QtGui import QTextDocument, QPrinter, QApplication

import sys
app = QApplication(sys.argv)

doc = QTextDocument()
location = "c://apython//Jim//html//notes.html"
html = open(location).read()
doc.setHtml(html)

printer = QPrinter()
printer.setOutputFileName("foo.pdf")
printer.setOutputFormat(QPrinter.PdfFormat)
printer.setPageSize(QPrinter.A4);
printer.setPageMargins (15,15,15,15,QPrinter.Millimeter);

doc.print_(printer)
print "done!"

9

我尝试使用pdfkit来实现@NorthCat所提供的答案。

它需要先安装wkhtmltopdf。可以从这里下载安装程序。https://wkhtmltopdf.org/downloads.html

安装可执行文件后,可以写一行代码指示wkhtmltopdf的位置,像下面这样。(引用自Can't create pdf using python PDFKIT Error : " No wkhtmltopdf executable found:"

import pdfkit


path_wkthmltopdf = "C:\\Folder\\where\\wkhtmltopdf.exe"
config = pdfkit.configuration(wkhtmltopdf = path_wkthmltopdf)

pdfkit.from_url("http://google.com", "out.pdf", configuration=config)

我在软件中心点击.deb文件安装后,它去哪里了? - mLstudent33

7

我使用PyQt5版本5.15.0,这个解决方案对我有用。

import sys
from PyQt5 import QtWidgets, QtWebEngineWidgets
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QPageLayout, QPageSize
from PyQt5.QtWidgets import QApplication

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    loader = QtWebEngineWidgets.QWebEngineView()
    loader.setZoomFactor(1)
    layout = QPageLayout()
    layout.setPageSize(QPageSize(QPageSize.A4Extra))
    layout.setOrientation(QPageLayout.Portrait)
    loader.load(QUrl('https://dev59.com/-mAg5IYBdhLWcg3wo8P2))
    loader.page().pdfPrintingFinished.connect(lambda *args: QApplication.exit())

    def emit_pdf(finished):
        loader.page().printToPdf("test.pdf", pageLayout=layout)

    loader.loadFinished.connect(emit_pdf)
    sys.exit(app.exec_())

1
我尝试了这个操作,但出现了以下错误: Traceback (most recent call last): File "C:/Users/brentond/Documents/Python/PdfWebsite.py", line 2, in <module> from PyQt5 import QtWidgets, QtWebEngineWidgets ImportError: DLL加载失败:找不到指定的模块。 - Dan
1
你需要先安装PyQt5包:pip install PyQt5 - Y.kh
我已经安装了它...但是据我所知,没有叫做QtwebEngineWidgets的PyQt5方法...至少在我在PyCharm中安装的5.15.2版本中没有。 - Dan
3
你还需要执行 pip install PyQtWebEngine 才能使其正常工作。 - Dániel Kis-Nagy

5
如果你使用selenium和chromium,就不需要自己管理cookies,并且可以通过chromium的打印功能生成pdf页面。 你可以参考这个项目来实现。 https://github.com/maxvst/python-selenium-chrome-html-to-pdf-converter 修改的基础 > https://github.com/maxvst/python-selenium-chrome-html-to-pdf-converter/blob/master/sample/html_to_pdf_converter.py
import sys
import json, base64


def send_devtools(driver, cmd, params={}):
    resource = "/session/%s/chromium/send_command_and_get_result" % driver.session_id
    url = driver.command_executor._url + resource
    body = json.dumps({'cmd': cmd, 'params': params})
    response = driver.command_executor._request('POST', url, body)
    return response.get('value')


def get_pdf_from_html(driver, url, print_options={}, output_file_path="example.pdf"):
    driver.get(url)

    calculated_print_options = {
        'landscape': False,
        'displayHeaderFooter': False,
        'printBackground': True,
        'preferCSSPageSize': True,
    }
    calculated_print_options.update(print_options)
    result = send_devtools(driver, "Page.printToPDF", calculated_print_options)
    data = base64.b64decode(result['data'])
    with open(output_file_path, "wb") as f:
        f.write(data)



# example
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import shutil

# Check for the existence of the chromedriver executable
chromedriver = shutil.which("chromedriver")
assert chromedriver is not None, "chromedriver not on PATH"

url = "https://dev59.com/-mAg5IYBdhLWcg3wo8P2"
webdriver_options = Options()
webdriver_options.add_argument("--no-sandbox")
webdriver_options.add_argument('--headless')
webdriver_options.add_argument('--disable-gpu')

driver = webdriver.Chrome(chromedriver, options=webdriver_options)
get_pdf_from_html(driver, url)
driver.quit()

1
首先,我使用了Weasyprint,但它不支持cookies。即使你可以编写自己的default_url_fetcher来处理cookies,但后来在Ubuntu16上安装时出现了问题。然后我使用了wkhtmltopdf,它支持cookie设置,但在处理某些页面时会引发许多OSERROR错误,例如-15 -11。 - Yuanmeng Xiao
1
感谢分享,肖元梦先生。 - Mark K
1
嗨@YuanmengXiao,我复制了你上面的代码,但是出现了以下错误:Traceback (most recent call last): File "C:/Users/brentond/Documents/Python/PdfWebsite.py", line 39, in <module> driver = webdriver.Chrome(chromedriver, options=webdriver_options) NameError: name 'chromedriver' is not defined - Dan
我随后安装了一个名为chromedriver的模块,并将其导入到上述代码中,现在出现以下错误: Traceback (most recent call last): File "C:/Users/brentond/Documents/Python/PdfWebsite.py", line 33, in <module> import chromedriver File "C:\Program Files\ArcGIS\Pro\bin\Python\envs\arcgispro-py3\lib\site-packages\chromedriver_init_.py", line 16, in <module> raise RuntimeError('This package supports only Linux, MacOSX or Windows platforms') RuntimeError: This package supports only Linux, MacOSX or Windows platforms - Dan
谢谢你,Yuanmeng Xiao。我主要在工作中使用Python,我们不允许下载和安装额外的东西,所以我希望能够在Pycharm中使用Python模块来将网站转换为PDF。 - Dan
显示剩余2条评论

0
根据另一个答案的解释;如果您在本地拥有.html文件,则可以使用以下内容:
pdfkit.from_file('abc.html', 'abc.pdf')

此外,如果您的源HTML文件具有img标签src,则应该是相对路径,并且您必须包含此选项以允许本地文件访问。
pdfkit.from_file('abc.html', 'abc.pdf',options={"enable-local-file-access": ""})

否则您可能会遇到以下错误:

OSError:wkhtmltopdf 报告了一个错误:由于网络错误 ProtocolUnknownError,退出代码为 1

Source: https://github.com/wkhtmltopdf/wkhtmltopdf/issues/2660#issuecomment-663063752

pdfkit错误:由于网络错误Exit with code 1:ProtocolUnknownError


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接