【问题标题】:How to read JSON from socket in python? (Incremental parsing of JSON)如何从python中的套接字读取JSON? (JSON的增量解析)
【发布时间】:2020-10-03 17:28:19
【问题描述】:

我打开了一个套接字,我想从中读取一些 json 数据。问题是标准库中的json 模块只能从字符串解析(load 只读取整个文件并在内部调用loads)它甚至看起来在模块内部一直都取决于参数细绳。

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

所以我的问题是:是否有(简单而优雅的)解决方法?是否有另一个可以增量解析数据的 json 库?值得自己写吗?

编辑:它是 XBMC jsonrpc api。没有邮件信封,我无法控制格式。每条消息可以在一行或多行。 我可以编写一些只需要某种形式的 getc 函数并使用s.recv(1) 提供它的简单解析器,但这不是一个非常 Pythonic 的解决方案,我有点懒惰这样做:-)

【问题讨论】:

  • 这个套接字流是否包含信封?大多数套接字协议可以让您了解顺流而下的内容的大小。您是否正在尝试连接到众所周知的 json 套接字协议?您是否可以控制套接字协议?最简单的方法是知道每条消息的大小(如 HTTP 有 Content-Length 标头)。否则,您必须在数据进入时对其进行解析以了解数据何时开始和何时结束,而标准库无法帮助您。

标签: python json sockets


【解决方案1】:

编辑:鉴于您没有定义协议,这没有用,但在其他情况下可能有用。


假设它是一个流 (TCP) 套接字,您需要实现自己的消息框架机制(或使用现有的更高级别的协议)。一种直接的方法是将每条消息定义为一个 32 位整数长度字段,后跟那么多字节的数据。

发送方:取JSON包的长度,用struct模块打包成4个字节,在socket上发送,然后发送JSON包。

Receiver:反复从socket中读取,直到你有至少4个字节的数据,使用struct.unpack解包长度。从套接字读取,直到您至少拥有那么多数据,这就是您的 JSON 数据包;剩下的就是下一条消息的长度。

如果在某个时候您想要通过同一个套接字发送由 JSON 以外的其他内容组成的消息,您可能希望在长度和数据负载之间发送一个消息类型代码;恭喜,你发明了另一种协议。

另一种更标准的方法是 DJB 的Netstrings 协议;它与上面提出的系统非常相似,但使用文本编码长度而不是二进制;它直接受到Twisted等框架的支持。

【讨论】:

    【解决方案2】:

    如果您从 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。它可能少得多高效。)

      编辑:因为我发现事实上(cythonized 版本)我的方法比 ijson 更有效,所以我将它打包为一个独立的库 - 另请参阅此处以获取一些粗略的基准:http://pietrobattiston.it/jsaone

      【讨论】:

        【解决方案4】:

        你可以控制 json 吗?尝试将每个对象写成一行。然后以described here 的身份在套接字上执行 readline 调用。

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

        【讨论】:

          【解决方案5】:

          浏览 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()
          

          【讨论】:

          • 这可以工作,但我仍然必须解析字符串(至少打开和关闭引号和转义引号),因为这样的字符串:"\"{"
          • 我尝试了一些 JSONRPC 库。它们中的大多数都太复杂了,不能在原始 TCP 连接上运行,或者只能作为服务器运行。我尝试过的唯一 JSON 解析器是标准库中的那个。
          • 我想我仍然不清楚你的问题的参数。您的应用程序是客户端还是服务器角色?您是否希望将原始 TCP 连接不仅仅用于 JSON RPC?
          • 我的应用是客户端。服务器响应要么响应我的方法调用,要么发送没有响应的通知。在套接字上发送的所有消息都是 JSON 对象或数组。
          【解决方案6】:

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

          【讨论】:

            【解决方案7】:
            res = str(s.recv(4096), 'utf-8') # Getting a response as string
            res_lines = res.splitlines() # Split the string to an array
            last_line = res_lines[-1] # Normally, the last one is the json data
            pair = json.loads(last_line)
            

            https://github.com/A1vinSmith/arbitrary-python/blob/master/sockets/loopHost.py

            【讨论】:

            • 这不起作用,因为:1) json 对象可以超过 4kB 2) 对象可以分布在多行
            • 是的,这更像是一个示例。不是一个详细的解决方案。可以使用其他库或在此处添加正则表达式。
            • 如果不限于套接字。 requests 使用 response.json() 会让事情变得更容易。 github.com/A1vinSmith/arbitrary-python/blob/master/requests/…
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-02-20
            • 1970-01-01
            • 2016-07-12
            • 2012-10-22
            • 2021-06-30
            • 1970-01-01
            相关资源
            最近更新 更多