Соответствие JSONlines из списков в новый список JSON
Я пытаюсь сопоставить списки продуктов в формате строк JSON с продуктами в другом файле, также в формате JSON. Иногда это называется связью записей, разрешением сущностей, сверкой эталонов или просто сопоставлением.
Цель состоит в том, чтобы сопоставить списки продуктов сторонних розничных продавцов, например, "Цифровая зеркальная камера Nikon D90 12,3MP (только для корпуса)", с набором известных продуктов, например, "Nikon D90".
подробности
Объекты данных
Товар
{
"product_name": String // A unique id for the product
"manufacturer": String
"family": String // optional grouping of products
"model": String
"announced-date": String // ISO-8601 formatted date string, e.g. 2011-04-28T19:00:00.000-05:00
}
список
{
"title": String // description of product for sale
"manufacturer": String // who manufactures the product for sale
"currency": String // currency code, e.g. USD, CAD, GBP, etc.
"price": String // price, e.g. 19.99, 100.00
}
Результат
{
"product_name": String
"listings": Array[Listing]
}
Данные содержат два файла: products.txt - содержит около 700 списков продуктов.txt - содержит около 20 000 списков продуктов
Текущий код (с использованием Python):
import jsonlines
import json
import re
import logging, sys
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
with jsonlines.open('products.jsonl') as products:
for prod in products:
jdump = json.dumps(prod)
jload = json.loads(jdump)
regpat = re.compile("^\s+|\s*-| |_\s*|\s+$")
prodmatch = [x for x in regpat.split(jload["product_name"].lower()) if x]
manumatch = [x for x in regpat.split(jload["manufacturer"].lower()) if x]
modelmatch = [x for x in regpat.split(jload["model"].lower()) if x]
wordmatch = prodmatch + manumatch + modelmatch
#print (wordmatch)
#logging.debug('product first output')
with jsonlines.open('listings.jsonl') as listings:
for entry in listings:
jdump2 = json.dumps(entry)
jload2 = json.loads(jdump2)
wordmatch2 = [x for x in regpat.split(jload2["title"].lower()) if x]
#print (wordmatch2)
#logging.debug('listing first output')
contained = [x for x in wordmatch2 if x in wordmatch]
if contained:
print(contained)
#logging.debug('contained first match')
Приведенный выше код разбивает слова в имени_продукта, модели и производителе в файле продуктов и пытается сопоставить строки из файла списков, но я чувствую, что это слишком медленно и должен быть лучший способ сделать это. Любая помощь приветствуется
1 ответ
Во-первых, я не уверен, что происходит с dumps(), за которой следует load (). Если вы сможете найти способ избежать сериализации и десериализации всего на каждой итерации, это будет большой победой, поскольку кажется, что он полностью лишен кода, который вы разместили здесь.
Во-вторых, это листинги: поскольку они никогда не меняются, почему бы не проанализировать их один раз перед циклом в какую-то структуру данных (возможно, дикт, отображающий содержимое wordmap2 в листинг, из которого она была получена) и повторно использовать эту структуру при анализе продуктов.json?
Далее: если есть способ перенастроить это, чтобы использовать multiprocessing
Я настоятельно рекомендую вам сделать это. Здесь вы полностью связаны с процессором, и вы можете легко заставить его работать параллельно на всех ваших ядрах.
Наконец, я сделал снимок с некоторыми модными регулярными выражениями. Цель здесь состоит в том, чтобы ввести в регулярное выражение как можно больше логики, полагая, что re
реализован на C и, следовательно, будет более производительным, чем выполнение всей этой строковой работы на Python.
import json
import re
PRODUCTS = """
[
{
"product_name": "Puppersoft Doggulator 5000",
"manufacturer": "Puppersoft",
"family": "Doggulator",
"model": "5000",
"announced-date": "ymd"
},
{
"product_name": "Puppersoft Doggulator 5001",
"manufacturer": "Puppersoft",
"family": "Doggulator",
"model": "5001",
"announced-date": "ymd"
},
{
"product_name": "Puppersoft Doggulator 5002",
"manufacturer": "Puppersoft",
"family": "Doggulator",
"model": "5002",
"announced-date": "ymd"
}
]
"""
LISTINGS = """
[
{
"title": "Doggulator 5002",
"manufacturer": "Puppersoft",
"currency": "Pupper Bux",
"price": "420"
},
{
"title": "Doggulator 5005",
"manufacturer": "Puppersoft",
"currency": "Pupper Bux",
"price": "420"
},
{
"title": "Woofer",
"manufacturer": "Shibasoft",
"currency": "Pupper Bux",
"price": "420"
}
]
"""
SPLITTER_REGEX = re.compile("^\s+|\s*-| |_\s*|\s+$")
product_re_map = {}
product_re_parts = []
# get our matching keywords from products.json
for idx, product in enumerate(json.loads(PRODUCTS)):
matching_parts = [x for x in SPLITTER_REGEX.split(product["product_name"]) if x]
matching_parts += [x for x in SPLITTER_REGEX.split(product["manufacturer"]) if x]
matching_parts += [x for x in SPLITTER_REGEX.split(product["model"]) if x]
# store the product object for outputting later if we get a match
group_name = 'i{idx}'.format(idx=idx)
product_re_map[group_name] = product
# create a giganto-regex that matches anything from a given product.
# the group name is a reference back to the matching product.
# I use set() here to deduplicate repeated words in matching_parts.
product_re_parts.append("(?P<{group_name}>{words})".format(group_name=group_name, words="|".join(set(matching_parts))))
# Do the case-insensitive matching in C code
product_re = re.compile("|".join(product_re_parts), re.I)
for listing in json.loads(LISTINGS):
# we match against split words in the regex created above so we need to
# split our source input in the same way
matching_listings = []
for word in SPLITTER_REGEX.split(listing['title']):
if word:
product_match = product_re.match(word)
if product_match:
for k in product_match.groupdict():
matching_listing = product_re_map[k]
if matching_listing not in matching_listings:
matching_listings.append(matching_listing)
print listing['title'], matching_listings