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

13 投票
7 回答
22729 浏览
提问于 2025-04-17 01:43

我打开了一个套接字,想从中读取一些json数据。问题是,标准库里的json模块只能从字符串中解析数据(load方法只能读取整个文件,然后在里面调用loads)。看起来这个模块的所有功能都依赖于参数是字符串。

这在使用套接字时是个大问题,因为你无法一次性把所有数据都读成字符串,而且你也不知道在解析之前需要读取多少字节。

所以我想问:有没有什么简单优雅的解决办法?有没有其他的json库可以逐步解析数据?自己写一个这样的库值得吗?

补充一下:这是XBMC的jsonrpc API。消息没有封装格式,我对格式没有控制。每条消息可能在一行上,也可能分成几行。我可以写一个简单的解析器,只需要某种形式的getc函数,然后用s.recv(1)来喂它,但这样做似乎不太符合Python的风格,而且我有点懒得去做这个:-)

7 个回答

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
7

编辑:考虑到你没有定义协议,这个内容可能不太有用,但在其他情况下可能会有帮助。


假设这是一个流式(TCP)套接字,你需要自己实现消息的封装机制(或者使用一个已经存在的更高级的协议)。一种简单的方法是把每条消息的长度定义为一个32位的整数,然后再跟着那么多字节的数据。

发送方:先计算出JSON数据包的长度,把它用struct模块打包成4个字节,然后通过套接字发送这个长度,再发送JSON数据包。

接收方:不断从套接字读取数据,直到至少有4个字节,使用struct.unpack来解包这个长度。然后继续从套接字读取数据,直到你有了至少这么多字节的数据,这就是你的JSON数据包;剩下的部分就是下一个消息的长度。

如果你将来想通过同一个套接字发送除了JSON以外的其他类型的消息,你可能需要在长度和数据之间发送一个消息类型代码;恭喜你,这样你就发明了另一个协议。

还有一种稍微标准一点的方法是DJB的Netstrings协议;它和上面提到的系统非常相似,但使用的是文本编码的长度,而不是二进制;这个协议被像Twisted这样的框架直接支持。

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

撰写回答