幽灵JS:导出PDF到标准输出

19

有没有一种方式能够触发PhantomJS中的PDF导出功能而无需指定带有.pdf扩展名的输出文件?我们想使用 stdout 来输出PDF。

4个回答

20
你可以直接输出到标准输出(stdout),不需要使用临时文件。 page.render('/dev/stdout', { format: 'pdf' }); 查看这里以了解添加此功能的历史记录:here
如果您想从stdin获取HTML并将PDF输出到stdout,请see here

1
运行良好,但如果您正在Node中读取stdout,例如在此处描述的https://www.npmjs.org/package/phantomjs,您需要设置二进制文件的execFile选项,并可能按照这里https://dev59.com/W2025IYBdhLWcg3wLizo#6170723所述增加缓冲区大小。 - poof
这是来自PhantomJS维基的文档。但是对于我来说,该方法不尊重 page.viewportSize 设置。 - Rick Mohr
2
由于某些原因,它在Mac OS X上可以工作,但在Linux上无法工作(PhantomJS版本1.9.8)。 - guidoman
即使添加选项:{encoding:'binary',maxBuffer:5000 * 1024},在node中这也无法到达stdout - gabrielAnzaldo
1
对我来说,在Windows 7和phantomjs-prebuilt 2.1.13中都不起作用。 - gabrielAnzaldo

19

非常抱歉回答太长了;我感觉我将来要用这个方法好几十次,所以我会写出“一种解决所有问题的答案”。我先谈一下文件、文件描述符、(命名)管道和输出重定向,然后回答你的问题。


考虑这个简单的C99程序:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{

  if (argc < 2) {
    printf("Usage: %s file_name\n", argv[0]);
    return 1;
  }

  FILE* file = fopen(argv[1], "w");
  if (!file) {
    printf("No such file: %s\n", argv[1]);
    return 2;
  }

  fprintf(file, "some text...");

  fclose(file); 

  return 0;
}

非常简单。它需要一个参数(文件名),并将一些文本打印到其中。没有比这更简单的了。


使用clang write_to_file.c -o write_to_file.ogcc write_to_file.c -o write_to_file.o编译它。

现在,运行./write_to_file.o some_file(将文本写入some_file)。然后运行cat some_file。结果如预期的那样是some text...

现在让我们变得更加花哨。在终端中输入(./write_to_file.o /dev/stdout) > some_file。我们要求程序写入其标准输出(而不是普通文件),然后我们将该stdout重定向到some_file(使用> some_file)。我们可以使用以下任何一个来实现这一点:

  • (./write_to_file.o /dev/stdout) > some_file,这意味着“使用stdout

  • (./write_to_file.o /dev/stderr) 2> some_file,这意味着“使用stderr,并使用2>重定向它”

  • (./write_to_file.o /dev/fd/2) 2> some_file,与上面相同;stderr是Unix进程默认分配的第三个文件描述符(在stdinstdout之后)

  • (./write_to_file.o /dev/fd/5) 5> some_file,这意味着“使用您的第六个文件描述符,并将其重定向到some_file

如果不清楚,我们正在使用Unix管道而不是实际文件(毕竟在Unix中一切都是文件)。我们可以通过此管道执行各种花哨的操作:将其写入文件,或将其写入命名管道并在不同进程之间共享。


现在,让我们创建一个命名管道:

mkfifo my_pipe

如果您现在键入ls -l,您将看到:

total 32
prw-r--r--  1 pooriaazimi  staff     0 Jul 15 09:12 my_pipe
-rw-r--r--  1 pooriaazimi  staff   336 Jul 15 08:29 write_to_file.c
-rwxr-xr-x  1 pooriaazimi  staff  8832 Jul 15 08:34 write_to_file.o

注意第二行开头的p,这意味着my_pipe是一个(命名)管道。

现在,让我们明确我们想要用我们的管道做什么:

gzip -c < my_pipe > out.gz &

这意味着将我放入my_pipe中的gzip并将结果写入out.gz。末尾的&表示要求shell在后台运行此命令。你将得到类似于[1] 10449的内容,然后控制权回到终端。

然后,只需将我们C程序的输出重定向到此管道即可:

(./write_to_file.o /dev/fd/5) 5> my_pipe

或者

./write_to_file.o my_pipe

您将获得

[1]+  Done                    gzip -c < my_pipe > out.gz

这意味着 gzip 命令已经完成。

现在,再运行另一个命令:ls -l

total 40
prw-r--r--  1 pooriaazimi  staff     0 Jul 15 09:14 my_pipe
-rw-r--r--  1 pooriaazimi  staff    32 Jul 15 09:14 out.gz
-rw-r--r--  1 pooriaazimi  staff   336 Jul 15 08:29 write_to_file.c
-rwxr-xr-x  1 pooriaazimi  staff  8832 Jul 15 08:34 write_to_file.o

我们已成功使用gzip压缩了文本!

执行gzip -d out.gz解压此gzip压缩文件。它将被删除,然后将创建一个新文件(out)。 cat out将显示:

some text...

这正是我们所期望的。

别忘了使用 rm my_pipe 命令删除管道!


现在回到 PhantomJS。

这是一个简单的 PhantomJS 脚本(render.coffee,使用 CoffeeScript 编写),它接受两个参数:一个 URL 和一个文件名。它加载该 URL,渲染并将其写入给定的文件名:

system = require 'system'

renderUrlToFile = (url, file, callback) ->
  page = require('webpage').create()
  page.viewportSize = { width: 1024, height : 800 }
  page.settings.userAgent = 'Phantom.js bot'

  page.open url, (status) ->
    if status isnt 'success'
      console.log "Unable to render '#{url}'"
    else
      page.render file

    delete page
    callback url, file


url         = system.args[1]
file_name   = system.args[2]

console.log "Will render to #{file_name}"
renderUrlToFile "http://#{url}", file_name, (url, file) ->
  console.log "Rendered '#{url}' to '#{file}'"
  phantom.exit()

现在在终端中输入phantomjs render.coffee news.ycombinator.com hn.png,将Hacker News首页呈现为hn.png文件。它按预期工作。 phantomjs render.coffee news.ycombinator.com hn.pdf也是如此。

让我们用我们的C程序重复之前所做的操作:

(phantomjs render.coffee news.ycombinator.com /dev/fd/5) 5> hn.pdf

为什么不起作用呢? 因为正如PhantomJS 手册所述:

render(fileName)

将网页呈现为图像缓冲区并将其保存在指定的文件中。

目前输出格式基于文件扩展名自动设置。支持的格式为PNG,JPEG和PDF。

它失败了,仅仅因为/dev/fd/2/dev/stdout都不以.PNG等结尾。

但是不要担心,命名管道可以帮助你!

创建另一个命名管道,但这次使用扩展名.pdf:

mkfifo my_pipe.pdf

现在,让它简单地将其输入使用 cat 命令输出到 hn.pdf 文件中:

cat < my_pipe.pdf > hn.pdf &

然后运行:

phantomjs render.coffee news.ycombinator.com my_pipe.pdf 

看哪,美丽的hn.pdf出现了!

显然你想要做更复杂的事情而不只是简单的输出cat结果,但我相信现在你应该知道该怎么做了 :)


TL;DR:

  1. 创建一个命名管道,使用“.pdf”文件扩展名(这样它就会欺骗PhantomJS以为它是一个PDF文件):

    mkfifo my_pipe.pdf
    
  2. 您可以根据需要执行以下操作:

    cat < my_pipe.pdf > hn.pdf
    

    将其简单地使用cat命令写入到hn.pdf文件中。

  3. 在PhantomJS中,输出渲染结果到此文件或管道。

  4. 稍后,应该移除该管道:

  5. rm my_pipe.pdf
    

14

正如 Niko 所指出的那样,您可以使用renderBase64()将网页呈现为图像缓冲区,并将结果作为base64编码字符串返回。
但是目前这仅适用于PNG、JPEG和GIF。

要从 phantomjs 脚本向 stdout 写入内容,只需使用文件系统 API。

我在图像方面使用类似于此的东西:

var base64image = page.renderBase64('PNG');
var fs = require("fs");
fs.write("/dev/stdout", base64image, "w");

我不知道renderBase64()的PDF格式是否会在未来版本的phantomjs中出现,但作为一种解决方法,以下内容可能适用于您:

page.render(output);
var fs = require("fs");
var pdf = fs.read(output);
fs.write("/dev/stdout", pdf, "w");
fs.remove(output);

其中 output 是 PDF 文件的路径。


1
为了让这个方法正常工作,我不得不将 fs.read 行更改为 var pdf = fs.open(output, 'rb').read(); -- 以二进制形式读取文件非常重要(否则将 stdout 重定向到文件会导致 PDF 文件不正确)。然而,后来我甚至能够在没有任何临时文件的情况下使其正常工作 - 参见 https://dev59.com/P2gu5IYBdhLWcg3wUlnt#17282463 - philfreo

2

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