Как читать JSON из сокета в Python? (Инкрементальный разбор JSON) - PullRequest
10 голосов
/ 07 сентября 2011

У меня открыт сокет, и я хочу прочитать из него некоторые данные json. Проблема в том, что модуль json из стандартной библиотеки может анализировать только строки (load читает только весь файл и вызывает loads внутри). Даже выглядит так, что все внутри модуля все зависит от параметра. строка.

Это реальная проблема с сокетами, так как вы никогда не можете прочитать все это до строки и не знаете, сколько байтов нужно прочитать, прежде чем вы фактически проанализируете это.

Итак, мои вопросы: есть (простой и элегантный) обходной путь? Есть ли другая библиотека json, которая может анализировать данные постепенно? Стоит ли писать самому?

Редактировать: это XBMC jsonrpc API. Конвертов сообщений нет, и я не контролирую формат. Каждое сообщение может быть в одной строке или в нескольких строках. Я мог бы написать какой-нибудь простой парсер, которому нужна только функция getc в какой-то форме, и передать ее, используя s.recv(1), но это не очень питонное решение, и я немного ленив, чтобы сделать это:

Ответы [ 6 ]

4 голосов
/ 07 сентября 2011

Редактировать: учитывая, что вы не определяете протокол, это не полезно, но может быть полезно в других контекстах.


Предполагая, что это потоковый (TCP) сокет, вам необходимо реализовать собственный механизм создания сообщений (или использовать существующий протокол более высокого уровня, который это делает). Один простой способ - определить каждое сообщение как 32-битное поле целочисленной длины, за которым следует такое количество байтов данных.

Отправитель: взять длину пакета JSON, упаковать его в 4 байта с помощью модуля struct, отправить его в сокет, а затем отправить пакет JSON.

Приемник: Повторное чтение из сокета до тех пор, пока у вас не будет как минимум 4 байта данных, используйте struct.unpack для распаковки длины. Читайте из сокета, пока у вас не будет хотя бы столько данных, и это ваш JSON-пакет; все, что осталось - это длина следующего сообщения.

Если в какой-то момент вы захотите отправлять сообщения, состоящие из чего-то другого, кроме JSON, через один и тот же сокет, вы можете отправить код типа сообщения между длиной и полезной нагрузкой данных; поздравляю, вы изобрели еще один протокол.

Другой, немного более стандартный метод - протокол DJB Netstrings ; это очень похоже на систему, предложенную выше, но с длинами в кодированном виде вместо двоичной; он напрямую поддерживается фреймворками, такими как Twisted .

3 голосов
/ 25 ноября 2013

То, что вы хотите (ed), это ijson, инкрементный анализатор json.Это доступно здесь: https://pypi.python.org/pypi/ijson/.Использование должно быть простым как (копирование с этой страницы):

import ijson.backends.python as ijson

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

(для тех, кто предпочитает что-то автономное - в том смысле, что оно опирается только на стандартную библиотеку: я написал вчера небольшую оболочкувокруг json - но только потому, что я не знал об ijson. Вероятно, он гораздо менее эффективен.)

EDIT : так как я узнал, что на самом деле (цитонизированная версия) мойподход был намного более эффективным, чем ijson, я упаковал его как независимую библиотеку - см. здесь также некоторые приблизительные тесты: http://pietrobattiston.it/jsaone

3 голосов
/ 07 сентября 2011

Если вы получаете JSON из потока HTTP, используйте заголовок 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
2 голосов
/ 07 сентября 2011

У вас есть контроль над JSON?Попробуйте написать каждый объект как одну строку.Затем выполните вызов readline для сокета как , описанный здесь .

infile = sock.makefile()

while True:
    line = infile.readline()
    if not line: break
    # ...
    result = json.loads(line)
0 голосов
/ 17 сентября 2011

Вы можете найти JSON-RPC полезным для этой ситуации.Это протокол удаленного вызова процедур, который должен позволять вам вызывать методы, предоставляемые XSON-RPC XBMC.Вы можете найти спецификацию на Trac .

0 голосов
/ 08 сентября 2011

Сканируя документы 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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...