我有一个 REST API(RavenDB 的查询流),它返回大量 JSON 格式的数据。一次性加载到内存并解析的量太大了:
问题在于,它不是“每行一个文档”,这会让事情变得非常简单,而是在名为“结果”的字段中返回一个字符串,其中包含我们的文档,如下所示:
{"Results":[
{"Name":"Hello World"}
]}
我真正想做的是使用 python 的请求库来传输响应,如下所示:
r = requests.get('.../streams/query/Raven/DocumentsByEntityName?query=', stream=True)
for chunk in r.iter_content(chunk_size=512, decode_unicode=False):
print chunk
但我想生成单独的 JSON 文档,这样就不必解析整个响应。一次生成一个 JSON 文档的最有效方法是什么?
json.load()
有一个可选的
object_pairs_hook
参数,您可以使用它。这个想法是捕获每个内部
dict
,从回调函数返回一个空字典(或者可能是
None
),以避免在内存中建立巨大的数据结构。请记住,这不是性能优化:在我的测试中(使用
import simplejson as json
),我发现虽然可以节省内存,但使用钩子检查每个元素实际上会使解析速度慢几倍。不过,如果你记不清了,那也比什么都没有好。
just内部 JSON 文档,每行一个(请参阅:JSON Lines)。
这让我能够将输出流式传输到文本文件,稍后我可以逐行解码该文本文件,而无需解码内存中的整个项目。欢迎任何建议或优化!
def yield_stream(url1 = '/streams/query/Raven/DocumentsByEntityName?query=', query1=''):
r = requests.get(conf.db + url1 + query1, auth=conf.db_auth, stream=True)
i = 0
is_doc = False
is_str = False
doc1 = []
for chunk in r.iter_content(chunk_size=1024, decode_unicode=True):
for char in chunk:
if is_doc:
doc1.append(char)
if doc1[-2:-1] != ['\\'] and doc1[-1:] == ['"']:
is_str = not is_str
if char == '{' and not is_str:
i += 1
if i == 2:
doc1.append(char)
is_doc = True
if char == '}' and not is_str:
i -= 1
if i == 1:
yield ''.join(doc1)
doc1 = []
is_doc = False
我能够以一种内存友好的格式在 Python 3 中运行,每个块仅读取 25 MB 的数据,并提取它找到的所有文档。谢谢@Aaron 提供的最佳解决方案。
def yield_json(file):
i = 1
is_doc = False
is_str = False
doc1 = []
with bz2.open(file, 'r') as bzfh:
for chunk in iter(lambda: bzfh.read(READ_BUFFER).decode('utf8'), b''):
for char in chunk:
if is_doc:
doc1.append(char)
if doc1[-2:-1] != ['\\'] and doc1[-1:] == ['"']:
is_str = not is_str
if char == '{' and not is_str:
i += 1
if i == 2:
doc1.append(char)
is_doc = True
if char == '}' and not is_str:
i -= 1
if i == 1:
yield json.loads(''.join(doc1))
doc1 = []
is_doc = Falseenter code here