如何在Python中从套接字读取JSON?(逐步解析JSON)

13

我已经打开了一个套接字,并希望从中读取一些JSON数据。问题在于标准库中的 json 模块只能解析字符串(load 只读取整个文件并在内部调用 loads)。甚至看起来,在模块的所有层次结构中,它都依赖于参数为字符串。

这是套接字的真正问题,因为你永远无法将它全部读取到字符串中,并且在实际解析之前也不知道要读取多少字节。

所以我的问题是:有没有(简单而优雅的)解决方法?有没有其他可以逐步解析数据的 JSON 库?值得自己编写吗?

编辑:这是XBMC jsonrpc api 。没有消息包装,我无法控制格式。每条消息可能在单行或多行上。我可以编写一些简单的解析器,只需要使用某种形式的 getc 函数并使用 s.recv(1) 进行喂送,但这不是非常Pythonic 的解决方案,而且我有点懒得那样做 :-)


5
这个套接字流是否包含信封?大多数套接字协议都会给出从流中传输的内容大小的一些指示。您是否试图连接到一个众所周知的 JSON 套接字协议?您是否控制套接字协议?最简单的方法是知道每条消息的大小(就像 HTTP 具有 Content-Length 标头一样)。否则,您必须在数据到达时解析它,才能知道何时开始和结束,并且标准库无法帮助您。 - six8
7个回答

7

编辑:鉴于你没有定义协议,这并没有什么用处,但在其他情况下可能会有用。


假设这是一个流(TCP)套接字,则需要实现自己的消息帧机制(或使用已经实现的更高级别的协议)。一种简单的方法是将每个消息定义为32位整数长度字段,后跟该长度的字节数组。

发送方:获取JSON数据包的长度,使用struct模块将其打包为4个字节,将其发送到套接字上,然后发送JSON数据包。

接收方:从套接字中重复读取,直到您至少拥有4个字节的数据,请使用struct.unpack解包长度。从套接字中读取,直到拥有至少如此多的数据为止,这就是您的JSON数据包;剩下的任何内容都是下一个消息的长度。

如果您最终要通过同一套接字发送不仅限于JSON的其他消息,则可能需要在长度和数据有效负载之间发送消息类型代码;恭喜您,您发明了另一种协议。

另一种稍微更标准的方法是DJB的 Netstrings 协议;它非常类似于上面提出的系统,但长度是以文本编码而不是二进制编码;它由诸如 Twisted 之类的框架直接支持。


5

如果你从一个HTTP流中获取JSON,使用Content-Length头来获取JSON数据的长度。比如:

import httplib
import json

h = httplib.HTTPConnection('graph.facebook.com')
h.request('GET', '/19292868552')
response = h.getresponse()
content_length = int(response.getheader('Content-Length','0'))

# Read data until we've read Content-Length bytes or the socket is closed
data = ''
while len(data) < content_length or content_length == 0:
    s = response.read(content_length - len(data))
    if not s:
        break
    data += s

# We now have the full data -- decode it
j = json.loads(data)
print j

3
您需要的是ijson,一款增量式的JSON解析器。 它可以在此处找到:https://pypi.python.org/pypi/ijson/。使用方法应该很简单(从该页面复制):
import ijson.backends.python as ijson

for item in ijson.items(file_obj):
    # ...

(对于那些更喜欢自包含的东西的人 - 意思是它仅依赖于标准库:我昨天写了一个小的json包装器 - 只是因为我不知道ijson。它可能要低效得多。) 编辑:由于我发现实际上(cython化版本的)我的方法比ijson更高效,因此我将其打包为独立库 - 这里还有一些粗略的基准测试:http://pietrobattiston.it/jsaone

2

您是否可以控制JSON?尝试将每个对象写成单行。然后按照此处描述的方式,在套接字上进行readline调用。

infile = sock.makefile()

while True:
    line = infile.readline()
    if not line: break
    # ...
    result = json.loads(line)

0

浏览 XBMC JSON RPC 文档后,我认为您需要一个现有的 JSON-RPC 库 - 您可以查看以下内容: http://www.freenet.org.nz/dojo/pyjson/

如果由于某种原因它不适合您,那么在我看来每个请求和响应都包含在一个 JSON 对象中(而不是可能是字符串、数组或数字的松散的 JSON 原语),因此您要找的信封就是定义 JSON 对象的 '{ ... }'。

因此,我建议尝试类似以下的假代码:

while not dead:
    read from the socket and append it to a string buffer
    set a depth counter to zero
    walk each character in the string buffer:
        if you encounter a '{':
            increment depth
        if you encounter a '}':
            decrement depth
            if depth is zero:
                remove what you have read so far from the buffer
                pass that to json.loads()

2
这个可以行得通,但是由于像这样的字符串:"\"{",我仍需要解析字符串(至少是开头和结尾引号以及转义引号)。 - cube
我尝试了几个JSONRPC库。大多数都太复杂,不能在原始TCP连接上运行,或者只能作为服务器运行。我尝试过的唯一JSON解析器是标准库中的一个。 - cube
我想我仍然不清楚您的问题参数。您的应用程序是在客户端还是服务器角色中?您是否希望仅使用原始TCP连接进行JSON RPC以外的其他操作? - Russell Borogove
我的应用程序是一个客户端,服务器要么响应我的方法调用,要么发送没有响应的通知。所有在套接字上发送的消息都是 JSON 对象或数组。 - cube

0

在这种情况下,您可能会发现JSON-RPC很有用。它是一种远程过程调用协议,应该允许您调用XBMC JSON-RPC公开的方法。您可以在Trac上找到规范。


0

这不起作用的原因是:1)JSON对象可能超过4kB 2)该对象可能分布在多行上。 - cube
是的,这更像是一个示例。不是冗长的解决方案。可以使用其他库或在此处添加正则表达式。 - Alvin Smith
如果不限制于套接字,使用requests并使用response.json()会使事情变得更加容易。https://github.com/A1vinSmith/arbitrary-python/blob/master/requests/loopHost.py - Alvin Smith

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