赛题信息
名称:基于星火大模型的群聊对话分角色要素提取挑战赛
链接: 2024 iFLYTEK A.I.开发者大赛-讯飞开放平台
赛题解析
平台说明
参赛选手需基于讯飞星火大模型Spark Max完成任务,可使用大模型微调。报名参赛选手将获得API和微调训练资源福利,详情请登录讯飞开放平台和大模型训练平台查看。
数据说明
赛题提供了184条真实场景的群聊对话数据,包括训练数据129条,测试数据55条。字段提取的难易程度分为1、2、3三种,具体得分规则如下:
序号 | 字段名称 | 是否单值 | 是否可为空 | 难度分数 | 答案是否唯一 |
1 | 基本信息-姓名 | 是 | 是 | 1 | 是 |
2 | 基本信息-手机号码 | 是 | 是 | 1 | 是 |
3 | 基本信息-邮箱 | 是 | 是 | 1 | 是 |
4 | 基本信息-地区 | 是 | 是 | 1 | 是 |
5 | 基本信息-详细地址 | 是 | 是 | 1 | 是 |
6 | 基本信息-性别 | 是 | 是 | 1 | 是 |
7 | 基本信息-年龄 | 是 | 是 | 1 | 是 |
8 | 基本信息-生日 | 是 | 是 | 1 | 是 |
9 | 咨询类型 | 否 | 是 | 2 | 是 |
10 | 意向产品 | 否 | 是 | 3 | 是 |
11 | 购买异议点 | 否 | 是 | 3 | 是 |
12 | 客户预算-预算是否充足 | 是 | 是 | 2 | 是 |
13 | 客户预算-总体预算金额 | 是 | 是 | 2 | 是 |
14 | 客户预算-预算明细 | 是 | 是 | 3 | 否 |
15 | 竞品信息 | 是 | 是 | 2 | 是 |
16 | 客户是否有意向 | 是 | 是 | 1 | 是 |
17 | 客户是否有卡点 | 是 | 是 | 1 | 是 |
18 | 客户购买阶段 | 是 | 是 | 2 | 是 |
19 | 下一步跟进计划-参与人 | 否 | 是 | 2 | 是 |
20 | 下一步跟进计划-时间点 | 是 | 是 | 2 | 是 |
21 | 下一步跟进计划-具体事项 | 是 | 是 | 3 | 否 |
备注
- 可为空的字段,当判定无相应信息、无法做出判断等情况,统一取值为空字符串。
- 对于非单值字段,请使用列表(list)来表示。
赛题数据集
训练数据集
train. json,格式如下:
[
{
"chat_text": "王勇7:哈喽吴娜8,企微那边关于外部联系人上回说的沟通对接有什么反馈嘛\n王勇7:价格有没有调整\n吴娜8:目前应该是没有 具体的方案还没出来 我估计对方是突然不需要了就不搞了吧\n王勇7:[图片]\n王勇7:会话存档又有新动作了\n吴娜8:企微好友这块还没明确\n吴娜8:[图片]\n吴娜8:不知道到底啥情况hhh\n王勇7:他们腾讯内部针对代理商售卖折扣做了调整,可能只是渠道这一类\n王勇7:现在他们有很多腾讯代理商也是企微服务商,做底走单子\n王勇7:[图片]\n王勇7:看样子这条路也堵住了\n王勇7:太难了\n吴娜8:真不容易hhh\n王勇7:哎对了吴娜8,咨询你个事\n王勇7:今天辉煌有限公司这个公司咨询我们工具\n吴娜8:也是元保的 子公司\n王勇7:我记得这个公司是之前元保对接下过会话存档,他是和元保啥关系\n王勇7:哦哦\n王勇7:原来如此\n王勇7:okok\n王勇7:懂了\n吴娜8:就是一个子公司hhh\n王勇7:付款还是元保付嘛\n王勇7:还是他们自己也可以走对接\n吴娜8:都是同一批人吧 最终还是采购部采购 \n吴娜8:是不是换人了\n王勇7:对 换人了\n王勇7:那我找元保的采购老师问问吧\n吴娜8:嗯嗯 采购部新招了好几个人 \n吴娜8:其实都是一个部门的 分工有调整\n吴娜8:或者业务先咨询 后面转采购也是有可能的\n王勇7:get 还得是你呀\n王勇7:最近我们公司有一批腾讯龙年的企鹅公仔\n王勇7:给你寄一个吧\n王勇7:2024年的新款\n吴娜8:之前元保采购就两个人 我和领导 \n吴娜8:我走了之后变成了 六七个人 哈哈哈哈哈\n王勇7:[图片]\n王勇7:[图片]\n王勇7:这是一条引用/回复消息:\n\"吴娜8:\n我走了之后变成了 六七个人 哈哈哈哈哈\"\n------\n这说明你一个人顶六七个人,牛蛙牛蛙\n吴娜8:一把辛酸泪hhh 不用给我寄啦哈哈~别客气\n王勇7:[表情]\n王勇7:行吧行吧\n吴娜8:[表情]\n王勇7:后面企微有新动静都帮你关注,第一时间跟你说~\n吴娜8:好嘞~\n王勇7:哦了",
"infos": [
{
"基本信息-姓名": "吴娜8",
"基本信息-手机号码": "",
"基本信息-邮箱": "",
"基本信息-地区": "",
"基本信息-详细地址": "",
"基本信息-性别": "",
"基本信息-年龄": "",
"基本信息-生日": "",
"咨询类型": [
"答疑"
],
"意向产品": [],
"购买异议点": [
"客户内部问题"
],
"客户预算-预算是否充足": "",
"客户预算-总体预算金额": "",
"客户预算-预算明细": "",
"竞品信息": "",
"客户是否有意向": "无意向",
"客户是否有卡点": "无卡点",
"客户购买阶段": "项目搁置",
"下一步跟进计划-参与人": [],
"下一步跟进计划-时间点": "",
"下一步跟进计划-具体事项": ""
}
]
},
...
]
需要关注的字段解释如下:
- chat_text:群聊消息记录
- infos:提取的字段信息
测试数据集
[
{
"chat_text": "王勇4:钱老师,客户那边确定了时间了嘛?\n钱洋9:确定了呀 我发咱们群里了\n王勇4:是不是还有其他群啊?我在的这个群,没收到信息呢\n王勇4:[图片]\n王勇4:现在确定的具体时间是怎样的呢,我好内部协调下时间安排,谢啦\n钱洋9:哦哦sorry[捂脸]下周一(12.4号)下午两点交流哈\n钱洋9:时间没问题吧\n王勇4:收到,我去内部协调时间哈\n钱洋9:多谢\n王勇4:[握手]\n王勇4:钱老师,下周一我们售前是线上支持,届时需要您帮忙创建一个线上会议链接哈。我要他们通过个微登录进去。\n钱洋9:啊能尽量现场支持一下吗\n王勇4:我去沟通下哈,请问客户侧此次参加的是哪个部门呢\n钱洋9:sorry刚在客户,多谢哈,线上支持这个效果可能达不到预期,因为这块确实咱们巨石蓝海的同事更专业, 而且客户这边层级还是蛮高的是客户CTO和营销系统总监\n王勇4:好滴\n王勇4:我内部协调下哈\n钱洋9:感谢\n王勇4:已经确定好了哈,售前会去现场\n钱洋9:感谢感谢 可以拉进群里到时候好碰头[抱拳]\n王勇4:嗯嗯,好滴\n钱洋9:王勇4 麻烦问下吉祥汽车你们有在接触吗\n钱洋9:上次听他们IT说也有scrm需求不知道你们是不是已经对接了\n王勇4:吉利是我们合作的\n王勇4:但是吉祥没有呢\n钱洋9:OK\n王勇4:早上好啊,极石客户那边上次提到和业务部门进行需求确定,有给答复嘛?\n王勇4:风华有限公司,上次咱们聊到的吉祥汽车,我们内部暂时没有人在对接的哈\n钱洋9:好的 客户还没反馈哈 估计要下周了\n王勇4:好滴\n钱洋9:[会话记录消息]\n钱洋9:洛轲那边年前需求就能确定 跟王勇4老师同步下[呲牙]\n王勇4:好嘞\n王勇4:那就等他们的最新消息了\n钱洋9:嗯啊",
"infos": []
},
...
]
提交示例
[
{
"infos": [],
"index": 1
},
{
"infos": [],
"index": 2
},
{
"infos": [],
"index": 3
},
{
"infos": [],
"index": 4
},
{
"infos": [],
"index": 5
},
{
"infos": [],
"index": 6
},
{
"infos": [],
"index": 7
},
{
"infos": [],
"index": 8
},
{
"infos": [],
"index": 9
},
{
"infos": [],
"index": 10
},
{
"infos": [],
"index": 11
},
{
"infos": [],
"index": 12
},
{
"infos": [],
"index": 13
},
{
"infos": [],
"index": 14
},
{
"infos": [],
"index": 15
},
{
"infos": [],
"index": 16
},
{
"infos": [],
"index": 17
},
{
"infos": [],
"index": 18
},
{
"infos": [],
"index": 19
},
{
"infos": [],
"index": 20
},
{
"infos": [],
"index": 21
},
{
"infos": [],
"index": 22
},
{
"infos": [],
"index": 23
},
{
"infos": [],
"index": 24
},
{
"infos": [],
"index": 25
},
{
"infos": [],
"index": 26
},
{
"infos": [],
"index": 27
},
{
"infos": [],
"index": 28
},
{
"infos": [],
"index": 29
},
{
"infos": [],
"index": 30
},
{
"infos": [],
"index": 31
},
{
"infos": [],
"index": 32
},
{
"infos": [],
"index": 33
},
{
"infos": [],
"index": 34
},
{
"infos": [],
"index": 35
},
{
"infos": [],
"index": 36
},
{
"infos": [],
"index": 37
},
{
"infos": [],
"index": 38
},
{
"infos": [],
"index": 39
},
{
"infos": [],
"index": 40
},
{
"infos": [],
"index": 41
},
{
"infos": [],
"index": 42
},
{
"infos": [],
"index": 43
},
{
"infos": [],
"index": 44
},
{
"infos": [],
"index": 45
},
{
"infos": [],
"index": 46
},
{
"infos": [],
"index": 47
},
{
"infos": [],
"index": 48
},
{
"infos": [],
"index": 49
},
{
"infos": [],
"index": 50
},
{
"infos": [],
"index": 51
},
{
"infos": [],
"index": 52
},
{
"infos": [],
"index": 53
},
{
"infos": [],
"index": 54
},
{
"infos": [],
"index": 55
}
]
Baseline 详解
baseline 基于讯飞星火大模型 V 3.5 模型对群聊对话数据进行字段的抽取
链接:基于星火大模型的群聊对话分角色要素提取挑战-baseline - 飞桨AI Studio星河社区
环境配置
!pip install --upgrade -q spark_ai_python tqdm
数据处理
首先,简单读取一下数据集和测试集的数据本身,了解具体需要处理什么
import json
def read_json(json_file_path):
"""读取json文件"""
with open(json_file_path, 'r') as f:
data = json.load(f)
return data
def write_json(json_file_path, data):
"""写入json文件"""
with open(json_file_path, 'w') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
# 读取数据
train_data = read_json("dataset/train.json")
test_data = read_json("dataset/test_data.json")
# 查看对话数据
print(train_data[100]['chat_text'])
可以看到下面的对话数据很像我们的聊天截图,这样的聊天数据其实在日常生活中很常见。那么这个任务也很有必要,就是从这些杂乱的记录里抽取出我们需要的数据。
![[Pasted image 20240706200606.png]]
prompt 工程
prompt 编写规则可以进行这样的理解:
任务目标——抽取数据定义——抽取内容引入——抽取规则强调
以这样的 prompt 模版完成抽取任务:
# prompt 设计
PROMPT_EXTRACT = """
你将获得一段群聊对话记录。你的任务是根据给定的表单格式从对话记录中提取结构化信息。在提取信息时,请确保它与类型信息完全匹配,不要添加任何没有出现在下面模式中的属性。
表单格式如下:
info: Array<Dict(
"基本信息-姓名": string | "", // 客户的姓名。
"基本信息-手机号码": string | "", // 客户的手机号码。
"基本信息-邮箱": string | "", // 客户的电子邮箱地址。
"基本信息-地区": string | "", // 客户所在的地区或城市。
"基本信息-详细地址": string | "", // 客户的详细地址。
"基本信息-性别": string | "", // 客户的性别。
"基本信息-年龄": string | "", // 客户的年龄。
"基本信息-生日": string | "", // 客户的生日。
"咨询类型": string[] | [], // 客户的咨询类型,如询价、答疑等。
"意向产品": string[] | [], // 客户感兴趣的产品。
"购买异议点": string[] | [], // 客户在购买过程中提出的异议或问题。
"客户预算-预算是否充足": string | "", // 客户的预算是否充足。示例:充足, 不充足
"客户预算-总体预算金额": string | "", // 客户的总体预算金额。
"客户预算-预算明细": string | "", // 客户预算的具体明细。
"竞品信息": string | "", // 竞争对手的信息。
"客户是否有意向": string | "", // 客户是否有购买意向。示例:有意向, 无意向
"客户是否有卡点": string | "", // 客户在购买过程中是否遇到阻碍或卡点。示例:有卡点, 无卡点
"客户购买阶段": string | "", // 客户当前的购买阶段,如合同中、方案交流等。
"下一步跟进计划-参与人": string[] | [], // 下一步跟进计划中涉及的人员(客服人员)。
"下一步跟进计划-时间点": string | "", // 下一步跟进的时间点。
"下一步跟进计划-具体事项": string | "" // 下一步需要进行的具体事项。
)>
请分析以下群聊对话记录,并根据上述格式提取信息:
**对话记录:**
```
{content}
```
请将提取的信息以JSON格式输出。
不要添加任何澄清信息。
输出必须遵循上面的模式。
不要添加任何没有出现在模式中的附加字段。
不要随意删除字段。
**输出:**
```
[{{
"基本信息-姓名": "姓名",
"基本信息-手机号码": "手机号码",
"基本信息-邮箱": "邮箱",
"基本信息-地区": "地区",
"基本信息-详细地址": "详细地址",
"基本信息-性别": "性别",
"基本信息-年龄": "年龄",
"基本信息-生日": "生日",
"咨询类型": ["咨询类型"],
"意向产品": ["意向产品"],
"购买异议点": ["购买异议点"],
"客户预算-预算是否充足": "充足或不充足",
"客户预算-总体预算金额": "总体预算金额",
"客户预算-预算明细": "预算明细",
"竞品信息": "竞品信息",
"客户是否有意向": "有意向或无意向",
"客户是否有卡点": "有卡点或无卡点",
"客户购买阶段": "购买阶段",
"下一步跟进计划-参与人": ["跟进计划参与人"],
"下一步跟进计划-时间点": "跟进计划时间点",
"下一步跟进计划-具体事项": "跟进计划具体事项"
}}, ...]
```
"""
数据抽取
使用prompt进行调试发现以下几个问题:
- 大模型总是不能直接输出python直接可读取的json格式,如:
[
{
"基本信息-姓名": "张三",
"基本信息-手机号码": "12345678901",
"基本信息-邮箱": "zhangsan@example.com",
"基本信息-地区": "北京市",
"基本信息-详细地址": "朝阳区某街道",
"基本信息-性别": "男",
"基本信息-年龄": "30",
"基本信息-生日": "1990-01-01",
"咨询类型": ["询价"],
"意向产品": ["产品A"],
"购买异议点": ["价格高"],
"客户预算-预算是否充足": "充足",
"客户预算-总体预算金额": "10000",
"客户预算-预算明细": "详细预算内容",
"竞品信息": "竞争对手B",
"客户是否有意向": "有意向",
"客户是否有卡点": "无卡点",
"客户购买阶段": "合同中",
"下一步跟进计划-参与人": ["客服A"],
"下一步跟进计划-时间点": "2024-07-01",
"下一步跟进计划-具体事项": "沟通具体事项"
}
]
故使用函数 convert_all_json_in_text_to_dict
对 json 数据进行提取
def convert_all_json_in_text_to_dict(text):
"""提取LLM输出文本中的json字符串"""
dicts, stack = [], []
for i in range(len(text)):
if text[i] == '{':
stack.append(i)
elif text[i] == '}':
begin = stack.pop()
if not stack:
dicts.append(json.loads(text[begin:i+1]))
return dicts
- 大模型偶尔会出现缺少字段的情况,故使用
check_and_complete_json_format
函数对大模型抽取的结果进行字段格式的检查以及缺少的字段进行补全。
import json
class JsonFormatError(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
def check_and_complete_json_format(data):
required_keys = {
"基本信息-姓名": str,
"基本信息-手机号码": str,
"基本信息-邮箱": str,
"基本信息-地区": str,
"基本信息-详细地址": str,
"基本信息-性别": str,
"基本信息-年龄": str,
"基本信息-生日": str,
"咨询类型": list,
"意向产品": list,
"购买异议点": list,
"客户预算-预算是否充足": str,
"客户预算-总体预算金额": str,
"客户预算-预算明细": str,
"竞品信息": str,
"客户是否有意向": str,
"客户是否有卡点": str,
"客户购买阶段": str,
"下一步跟进计划-参与人": list,
"下一步跟进计划-时间点": str,
"下一步跟进计划-具体事项": str
}
if not isinstance(data, list):
raise JsonFormatError("Data is not a list")
for item in data:
if not isinstance(item, dict):
raise JsonFormatError("Item is not a dictionary")
for key, value_type in required_keys.items():
if key not in item:
item[key] = [] if value_type == list else ""
if not isinstance(item[key], value_type):
raise JsonFormatError(f"Key '{key}' is not of type {value_type.__name__}")
if value_type == list and not all(isinstance(i, str) for i in item[key]):
raise JsonFormatError(f"Key '{key}' does not contain all strings in the list")
return data
完整代码
from sparkai.llm.llm import ChatSparkLLM, ChunkPrintHandler
from sparkai.core.messages import ChatMessage
import json
from tqdm import tqdm
#星火认知大模型Spark3.5 Max的URL值,其他版本大模型URL值请前往文档(https://www.xfyun.cn/doc/spark/Web.html)查看
SPARKAI_URL = 'wss://spark-api.xf-yun.com/v3.5/chat'
#星火认知大模型调用秘钥信息,请前往讯飞开放平台控制台(https://console.xfyun.cn/services/bm35)查看
SPARKAI_APP_ID = ''
SPARKAI_API_SECRET = ''
SPARKAI_API_KEY = ''
#星火认知大模型Spark3.5 Max的domain值,其他版本大模型domain值请前往文档(https://www.xfyun.cn/doc/spark/Web.html)查看
SPARKAI_DOMAIN = 'generalv3.5'
# prompt 设计
PROMPT_EXTRACT = """
你将获得一段群聊对话记录。你的任务是根据给定的表单格式从对话记录中提取结构化信息。在提取信息时,请确保它与类型信息完全匹配,不要添加任何没有出现在下面模式中的属性。
表单格式如下:
info: Array<Dict(
"基本信息-姓名": string | "", // 客户的姓名。
"基本信息-手机号码": string | "", // 客户的手机号码。
"基本信息-邮箱": string | "", // 客户的电子邮箱地址。
"基本信息-地区": string | "", // 客户所在的地区或城市。
"基本信息-详细地址": string | "", // 客户的详细地址。
"基本信息-性别": string | "", // 客户的性别。
"基本信息-年龄": string | "", // 客户的年龄。
"基本信息-生日": string | "", // 客户的生日。
"咨询类型": string[] | [], // 客户的咨询类型,如询价、答疑等。
"意向产品": string[] | [], // 客户感兴趣的产品。
"购买异议点": string[] | [], // 客户在购买过程中提出的异议或问题。
"客户预算-预算是否充足": string | "", // 客户的预算是否充足。示例:充足, 不充足
"客户预算-总体预算金额": string | "", // 客户的总体预算金额。
"客户预算-预算明细": string | "", // 客户预算的具体明细。
"竞品信息": string | "", // 竞争对手的信息。
"客户是否有意向": string | "", // 客户是否有购买意向。示例:有意向, 无意向
"客户是否有卡点": string | "", // 客户在购买过程中是否遇到阻碍或卡点。示例:有卡点, 无卡点
"客户购买阶段": string | "", // 客户当前的购买阶段,如合同中、方案交流等。
"下一步跟进计划-参与人": string[] | [], // 下一步跟进计划中涉及的人员(客服人员)。
"下一步跟进计划-时间点": string | "", // 下一步跟进的时间点。
"下一步跟进计划-具体事项": string | "" // 下一步需要进行的具体事项。
)>
请分析以下群聊对话记录,并根据上述格式提取信息:
**对话记录:**
```
{content}
```
请将提取的信息以JSON格式输出。
不要添加任何澄清信息。
输出必须遵循上面的模式。
不要添加任何没有出现在模式中的附加字段。
不要随意删除字段。
**输出:**
```
[{{
"基本信息-姓名": "姓名",
"基本信息-手机号码": "手机号码",
"基本信息-邮箱": "邮箱",
"基本信息-地区": "地区",
"基本信息-详细地址": "详细地址",
"基本信息-性别": "性别",
"基本信息-年龄": "年龄",
"基本信息-生日": "生日",
"咨询类型": ["咨询类型"],
"意向产品": ["意向产品"],
"购买异议点": ["购买异议点"],
"客户预算-预算是否充足": "充足或不充足",
"客户预算-总体预算金额": "总体预算金额",
"客户预算-预算明细": "预算明细",
"竞品信息": "竞品信息",
"客户是否有意向": "有意向或无意向",
"客户是否有卡点": "有卡点或无卡点",
"客户购买阶段": "购买阶段",
"下一步跟进计划-参与人": ["跟进计划参与人"],
"下一步跟进计划-时间点": "跟进计划时间点",
"下一步跟进计划-具体事项": "跟进计划具体事项"
}}, ...]
```
"""
def read_json(json_file_path):
"""读取json文件"""
with open(json_file_path, 'r') as f:
data = json.load(f)
return data
def write_json(json_file_path, data):
"""写入json文件"""
with open(json_file_path, 'w') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
def get_completions(text):
messages = [ChatMessage(
role="user",
content=text
)]
spark = ChatSparkLLM(
spark_api_url=SPARKAI_URL,
spark_app_id=SPARKAI_APP_ID,
spark_api_key=SPARKAI_API_KEY,
spark_api_secret=SPARKAI_API_SECRET,
spark_llm_domain=SPARKAI_DOMAIN,
streaming=False,
)
handler = ChunkPrintHandler()
a = spark.generate([messages], callbacks=[handler])
return a.generations[0][0].text
def convert_all_json_in_text_to_dict(text):
"""提取LLM输出文本中的json字符串"""
dicts, stack = [], []
for i in range(len(text)):
if text[i] == '{':
stack.append(i)
elif text[i] == '}':
begin = stack.pop()
if not stack:
dicts.append(json.loads(text[begin:i+1]))
return dicts
class JsonFormatError(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
def check_and_complete_json_format(data):
required_keys = {
"基本信息-姓名": str,
"基本信息-手机号码": str,
"基本信息-邮箱": str,
"基本信息-地区": str,
"基本信息-详细地址": str,
"基本信息-性别": str,
"基本信息-年龄": str,
"基本信息-生日": str,
"咨询类型": list,
"意向产品": list,
"购买异议点": list,
"客户预算-预算是否充足": str,
"客户预算-总体预算金额": str,
"客户预算-预算明细": str,
"竞品信息": str,
"客户是否有意向": str,
"客户是否有卡点": str,
"客户购买阶段": str,
"下一步跟进计划-参与人": list,
"下一步跟进计划-时间点": str,
"下一步跟进计划-具体事项": str
}
if not isinstance(data, list):
raise JsonFormatError("Data is not a list")
for item in data:
if not isinstance(item, dict):
raise JsonFormatError("Item is not a dictionary")
for key, value_type in required_keys.items():
if key not in item:
item[key] = [] if value_type == list else ""
if not isinstance(item[key], value_type):
raise JsonFormatError(f"Key '{key}' is not of type {value_type.__name__}")
if value_type == list and not all(isinstance(i, str) for i in item[key]):
raise JsonFormatError(f"Key '{key}' does not contain all strings in the list")
if __name__ == "__main__":
retry_count = 5 # 重试次数
result = []
error_data = []
# 读取数据
train_data = read_json("dataset/train.json")
test_data = read_json("dataset/test_data.json")
for index, data in tqdm(enumerate(test_data)):
index += 1
is_success = False
for i in range(retry_count):
try:
res = get_completions(PROMPT_EXTRACT.format(content=data["chat_text"]))
infos = convert_all_json_in_text_to_dict(res)
infos = check_and_complete_json_format(infos)
result.append({
"infos": infos,
"index": index
})
is_success = True
break
except Exception as e:
print("index:", index, ", error:", e)
continue
if not is_success:
data["index"] = index
error_data.append(data)
write_json("output.json", result)