如何从Ruby中生成的进程中返回大字符串?

时间:2014-07-17 15:02:05

标签: ruby spawn

从我的Ruby脚本中我产生了一个PhantomJS进程。该进程将返回一个JSON字符串给Ruby,Ruby将解析它。

一切正常,但是当PhantomJS脚本返回一个巨大的JSON(超过10000个条目)时,似乎Ruby不知道如何处理它(它没有得到整个字符串,但它被切断了)。

JSON中的条目看起来像这样(但有些条目可以有大约5个属性):

{"id" : 3, "nSteps" : 5, "class" : "class-name", "width" : 300, "height" : 500, "source" : "this is a big string", "nMove" : 10, "id-name" : "this is a big string", "checked" : true, "visible" : false}

这是我现在的代码:

@pid = Process.spawn("phantomjs",
                       "myparser.js",
                       :out => pipe_cmd_out, :err => pipe_cmd_out
                      )
  Timeout.timeout(400) do
    Process.wait(@pid)
    pipe_cmd_out.close
    output = pipe_cmd_in.read
    return JSON.parse(output)
  end

有什么方法可以通过块读取JSON或以某种方式增加管道的缓冲区限制?

编辑:

为了将数据从PhantomJS发送到Ruby,我在PhantomJS脚本的最后有以下内容:

console.log(JSON.stringify(data));
phantom.exit();

如果我从终端启动PhantomJS脚本,我会正确获取JSON。但是,当我在Ruby中执行此操作时,响应会被切断。

console.log中断时放入的字符串的大小为:132648

编辑:

我想我发现了什么是确切的问题。当我在生成进程时指定:out时,如果返回的JSON很大(132648长度),它将不会让Ruby读取它。所以在做的时候:

reader, writer = IO.pipe
pid = Process.spawn("phantomjs",
                    "script.js",
                    :out => writer
                    )
Timeout.timeout(100) do
  Process.wait(pid)
  writer.close
  output = reader.read
  json_output = JSON.parse(output)
end

它无效。

但是如果我让PhantomJS只是写入它的标准标准输出,它将正确输出JSON。所以,做:

reader, writer = IO.pipe
pid = Process.spawn("phantomjs",
                    "script.js"
                    )
Timeout.timeout(100) do
  Process.wait(pid)
  writer.close
  output = reader.read
  json_output = JSON.parse(output)
end

将正确输出终端中的结果。所以我认为问题在于,对于大JSON来说它不能正确编写,或者Ruby读者不知道如何阅读它。

1 个答案:

答案 0 :(得分:1)

我怀疑问题不是Ruby,而是你在阅读时的方式或时间。

可能是PhantomJS在您阅读之前尚未完成发送输出,导致部分响应。尝试将输出路由到文件以确定其大小(以字节为单位)。这将告诉您PhantomJS 是否正在完成其任务并正确关闭JSON,并会告诉您可以在缓冲区中看到多少字节。


  

...连接Ruby和衍生进程的管道怎么样?我在哪里可以找到缓冲区的限制?

在我的记忆的黑暗角落里挖掘,以找出它是如何工作的......

根据我的记忆,这应该是相当准确的:TCP / IP堆栈将缓冲输入数据,直到其缓冲区已满,然后它将告诉发送方停止。缓冲区清除后,由于脚本已读取缓冲区,因此告知发送方继续发送。因此,即使有多GB GB待处理,也不会一次发送所有数据,除非脚本从缓冲区连续读取以保持清晰。

当脚本执行read时,read不会仅获取缓冲区中的内容,它希望看到EOF,并且在TCP / IP会话认为之前不应该看到它它收到发件人和会话/连接关闭的所有内容。 Ruby I / O子系统和TCP / IP子系统读取从发送方接收的数据块中的数据,并将其存储在变量中。换句话说,您的脚本应暂停并等待所有数据传输完毕,然后继续,因为read是阻止操作。

处理I / O有不同的方法。你正在啜饮数据,这就是我们在步骤中阅读所有内容时所称的数据。这是不可扩展的,因为如果传入的数据大于Ruby的字符串可以存储,那么你就遇到了问题。您可能应该进行增量读取,然后将数据存储在临时文件中,然后使用类似YAJL的内容将其传输到JSON解析器中,但第一步是确定您是否实际获取了完整的JSON字符串。

处理问题的另一种方法是请求较小的数据集,然后在脚本中重新组合它们。就像通过SQL从数据库中请求每条记录一样,这是一个坏主意,因为它不可扩展并且打败了DBM,也许你应该在页面或块中请求你的JSON数据,并且只处理必要的结果。


这可能是......

@pid = Process.spawn("phantomjs",
                       "myparser.js",
                       :out => pipe_cmd_out, :err => pipe_cmd_out
                      )
  Timeout.timeout(400) do
    Process.wait(@pid)
    pipe_cmd_out.close
    output = pipe_cmd_in.read
    return JSON.parse(output)
  end

PhantomJS的STDOUT和STDERR被分配到pipe_cmd_out,但您使用pipe_cmd_out.close关闭该流,然后尝试读取未定义的pipe_cmd_in。这一切似乎都错了。我认为你应该pipe_cmd_in.close然后pipe_cmd_out.read

@pid = Process.spawn(
  "phantomjs",
  "myparser.js",
  :in => pipe_cmd_in,
  :out => pipe_cmd_out, 
  :err => pipe_cmd_out
)
Timeout.timeout(400) do
  pipe_cmd_in.close
  Process.wait(@pid)
  output = pipe_cmd_out.read
  return JSON.parse(output)
end

小心尝试解析STDERR输出。它可能不是JSON,并且在解析器抛出时会导致异常。

我们关闭脚本的输出/命令行应用程序的输入,因为许多从STDIN读取输入的命令行工具将挂起,直到它们的STDIN关闭。这就是pip_cmd_in.close将要做的事情,它会关闭PhantomJS的STDIN,并向它发出信号,告知它应该开始处理。然后,当它输出到STDOUT时,您的脚本应该通过pipe_cmd_out中提供的流来查看。

而且,不是将STDOUT和STDERR的输出加倍到一个变量,我可能会使用:

@pid = Process.spawn(
  "phantomjs",
  "myparser.js",
  :in => pipe_cmd_in,
  :out => pipe_cmd_out, 
  :err => pipe_cmd_err
)
Timeout.timeout(400) do
  pipe_cmd_in.close
  Process.wait(@pid)
  output = pipe_cmd_out.read
  if output.empty?
    pipe_cmd_err
  else
    JSON.parse(output)
  end
end

调用上述代码的代码需要检测返回值是String还是Array或Hash。如果是第一次发生错误。如果它是后两个中的一个,那么它是成功的,你可以迭代它们。