提高大型JSON文件的读取性能

2024-04-29 01:03:37 发布

您现在位置:Python中文网/ 问答频道 /正文

我不是IT工程师,而是机械工程师,所以请随时向我询问更多细节

我有大量的魔法收集卡片,并编写了一个程序,通过OpenCV从图片中读取卡片。它处理图片,提取卡片的名称,在JSON文件中搜索并将其附加到我的库中

enter image description here

我尝试优化JSON文件的读取,因为它会尽一切可能匹配图片中检测到的卡名。收集所有数据的Json文件大约为210Mo,可在线访问https://mtgjson.com/downloads/all-files/

在下面的示例中,考虑到已经在变量“keyVal”中提取了卡名,大约需要10秒:

import json
from difflib import SequenceMatcher

def similar(a, b):
    return SequenceMatcher(None, a, b).ratio()

keyVal ="Arc électrique"

json_file = open("AllPrintings.json", "r", encoding="utf-8")
bdd = json.load(json_file)   

for item in bdd["data"]:
    for card in bdd["data"][item]["cards"]:
        for langue in card["foreignData"]:
            if similar(langue["name"],keyVal) > 0.85:
                print(langue["name"],card["name"], card["type"], card["artist"], bdd["data"][item]["name"], card["number"], card["identifiers"]["multiverseId"])
            if similar(card["name"], keyVal) > 0.85:
                print(card["name"], card["type"], card["artist"], bdd["data"][item]["name"],
                      card["number"], card["identifiers"]["multiverseId"])

我的第一个意图是读取Json文件并只记录我需要的数据,但它变成了一个非常巨大的文件

你对如何改进研究时机有什么想法吗

谢谢,请不要犹豫要求澄清


Tags: 文件nameinjsonfordata图片card
2条回答

这是您的程序,使用Python自己的sqlite3https://mtgjson.com/downloads/all-files/方便地提供的SQLite数据库,转换为基于SQL的方法:

import sqlite3
from difflib import SequenceMatcher

def similar(a, b):
    return SequenceMatcher(None, a, b).ratio()

conn = sqlite3.connect(r"C:\Users\Tomalak\Downloads\AllPrintings.sqlite")
conn.create_function("SIMILAR", 2, similar)

def find_similar_cards(key_val):
    return conn.execute("""
        SELECT
            c.number, c.name, c.type, c.artist, c.multiverseId,
            fd.name AS local_name, fd.language
        FROM
            cards AS c
            INNER JOIN foreign_data AS fd ON fd.uuid = c.uuid
        WHERE
            SIMILAR(fd.name, ?) > 0.85
    """, [key_val])

for row in find_similar_cards("Arc électrique"):
    print(row)

你可以看到,当你阅读它的时候,它会立即变得更加明显,所以这已经是一个很大的优点了

SQLite允许导入用户定义的函数,并使它们可用于SQL查询,因此我导入了SequenceMatcher

不幸的是,这也是罪魁祸首。它必须扫描foreign_data中237000条记录中的每一条,并分析每一条name的相似性值。这是一个缓慢的过程,对此我们也无能为力。在我的(较旧的)笔记本电脑上,完成此查询和打印只需10秒多一点

('97', 'Arc Lightning', 'Sorcery', 'Seb McKinnon', '386478', 'Arc électrique', 'French')
('97', 'Arc Lightning', 'Sorcery', 'Seb McKinnon', '394068', 'Arc électrique', 'French')
('174', 'Arc Lightning', 'Sorcery', 'Andrew Goldhawk', '5733', 'Arc électrique', 'French') 

但仍有优化的空间。foreign_data表只包含160000个不同的名称。可以使用那些唯一的名称创建一个helper表,以便更快地进行扫描,然后重新连接到cards表。但无论你做什么,搜索“模糊”值总是需要一些时间

一般来说,改进搜索时间的选项包括

  • 减少工作量(即在记录较少的表上工作,如“仅限不同的名称”)
  • 使用更快的比较机制(即从SequenceMatcher切换到其他设备)
  • 使用预先计算的结果(不适用于所有情况,例如不适用于这种情况)
  • 可能:使用全文索引(SQLite supports that。可能需要一些时间才能理解它,但最终可能是值得的。全文索引非常快速,而且相当“模糊”)

除此之外,下载的SQLite DB根本没有定义索引,这取决于您经常查询的数据类型,这里也有改进的余地

一旦您不搜索计算值,并且适当的索引已经就位,这将非常迅速

根据你的回答,我做了两件事@Tomalak。 我使用sqlitebrowser在sqlite bdd中创建并保存了一个专用表,此代码仅包含我需要的数据:

CREATE table res AS SELECT
    c.number, c.name, c.artist, fd.name AS local_name, fd.language, st.name as local_print
FROM
    cards AS c
    LEFT JOIN foreign_data AS fd ON fd.uuid = c.uuid
    LEFT JOIN sets AS st ON st.code = c.setCode
WHERE
    fd.language IS NULL OR fd.language = "French"

然后,我使用FTS4在python中调用它,并连续执行两个请求,以测量一个请求的虚拟表初始化时间,然后使用相同的virtualtable测量单个请求的时间:

import sqlite3
import time
init = time.time()

keyVal ="Act of Treason"

conn = sqlite3.connect(r"AllPrintings.sqlite")
conn.create_function("SIMILAR", 2, similar)
cur = conn.cursor()

cur.execute('''DROP TABLE IF EXISTS mtgsearch''')
cur.execute('''CREATE VIRTUAL TABLE mtgsearch USING fts4(number, name, artist, namefr, language, local_print)''')
cur.execute('''INSERT INTO mtgsearch(number, name, artist, namefr, language, local_print) SELECT c.number AS number, c.name AS name, c.artist AS artist, c.local_name AS namefr, c.language AS language, c.local_print AS local_print FROM res AS c''')
conn.commit()

stock = cur.execute('''SELECT * FROM mtgsearch WHERE name= ?''',[keyVal])


for row in stock:
    print(row[0], row[1], row[2], row[3], row[4], row[5])


print(time.time() - init)
init = time.time()

keyVal= "Air Elemental"
stock = cur.execute('''SELECT * FROM mtgsearch WHERE name= ?''',[keyVal])

for row in stock:
    print(row[0], row[1], row[2], row[3], row[4], row[5])

print(time.time() - init)

与之前相比,结果令人兴奋==>;第一个结果是0.4秒,第二个结果是0.015秒

如果我使用Sequencematcher,第一个请求将在1.7秒内发出,第二个请求将在1.3秒内发出。所以下一个目标是找到一种改进更快相似性算法的方法。有什么想法吗

无论如何谢谢你的帮助,我学到了很多关于SQLite的知识。我对写第一篇文章一无所知

相关问题 更多 >