|
@@ -2,6 +2,7 @@
|
|
|
@author: luojunhui
|
|
@author: luojunhui
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
|
|
+import json
|
|
|
import re
|
|
import re
|
|
|
import oss2
|
|
import oss2
|
|
|
import random
|
|
import random
|
|
@@ -13,7 +14,7 @@ from scipy.stats import t
|
|
|
from odps import ODPS
|
|
from odps import ODPS
|
|
|
|
|
|
|
|
from datetime import datetime, timezone, date, timedelta
|
|
from datetime import datetime, timezone, date, timedelta
|
|
|
-from typing import List
|
|
|
|
|
|
|
+from typing import Dict, List, Optional
|
|
|
|
|
|
|
|
from requests import RequestException
|
|
from requests import RequestException
|
|
|
from urllib.parse import urlparse, parse_qs
|
|
from urllib.parse import urlparse, parse_qs
|
|
@@ -24,6 +25,80 @@ from tenacity import (
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def safe_json_parse(text: str) -> Optional[Dict | List]:
|
|
|
|
|
+ """多层降级解析 JSON:直接解析 → 提取代码块 → 提取 JSON 对象/数组
|
|
|
|
|
+
|
|
|
|
|
+ 模型有时返回 ```json ... ``` 包裹的文本,或文本中夹杂 markdown 前缀/后缀。
|
|
|
|
|
+ 先尝试直接解析(最常见路径),失败后逐层降级提取。
|
|
|
|
|
+ """
|
|
|
|
|
+ if not text:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ # 降级 1:直接解析
|
|
|
|
|
+ try:
|
|
|
|
|
+ return json.loads(text)
|
|
|
|
|
+ except (json.JSONDecodeError, TypeError):
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+ clean = text.strip()
|
|
|
|
|
+
|
|
|
|
|
+ # 降级 2:提取最外层 json 代码块 ```json ... ```
|
|
|
|
|
+ # 优先匹配带语言标注的,再退到任意 code fence
|
|
|
|
|
+ m = re.search(r"```json\s*(.*?)\s*```", clean, re.DOTALL)
|
|
|
|
|
+ if m:
|
|
|
|
|
+ try:
|
|
|
|
|
+ return json.loads(m.group(1))
|
|
|
|
|
+ except (json.JSONDecodeError, TypeError):
|
|
|
|
|
+ pass
|
|
|
|
|
+ else:
|
|
|
|
|
+ m = re.search(r"```\s*(.*?)\s*```", clean, re.DOTALL)
|
|
|
|
|
+ if m:
|
|
|
|
|
+ try:
|
|
|
|
|
+ return json.loads(m.group(1))
|
|
|
|
|
+ except (json.JSONDecodeError, TypeError):
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+ # 降级 3:在文本中查找第一个完整 JSON 对象 { ... } 或数组 [ ... ]
|
|
|
|
|
+ # 逐字符扫描,维护字符串状态机,正确处理内嵌括号和转义引号
|
|
|
|
|
+ for bracket_pair in [("{}", "{", "}"), ("[]", "[", "]")]:
|
|
|
|
|
+ opener, closer = bracket_pair[1], bracket_pair[2]
|
|
|
|
|
+ start = clean.find(opener)
|
|
|
|
|
+ if start == -1:
|
|
|
|
|
+ continue
|
|
|
|
|
+ depth = 0
|
|
|
|
|
+ in_string = False
|
|
|
|
|
+ escape_next = False
|
|
|
|
|
+ for i in range(start, len(clean)):
|
|
|
|
|
+ ch = clean[i]
|
|
|
|
|
+ if escape_next:
|
|
|
|
|
+ escape_next = False
|
|
|
|
|
+ continue
|
|
|
|
|
+ if ch == "\\":
|
|
|
|
|
+ escape_next = True
|
|
|
|
|
+ continue
|
|
|
|
|
+ if ch == '"' and not escape_next:
|
|
|
|
|
+ in_string = not in_string
|
|
|
|
|
+ continue
|
|
|
|
|
+ if in_string:
|
|
|
|
|
+ continue
|
|
|
|
|
+ if ch == opener:
|
|
|
|
|
+ depth += 1
|
|
|
|
|
+ elif ch == closer:
|
|
|
|
|
+ depth -= 1
|
|
|
|
|
+ if depth == 0:
|
|
|
|
|
+ try:
|
|
|
|
|
+ return json.loads(clean[start : i + 1])
|
|
|
|
|
+ except (json.JSONDecodeError, TypeError):
|
|
|
|
|
+ return None
|
|
|
|
|
+ # 数组或对象未闭合时也尝试下
|
|
|
|
|
+ try:
|
|
|
|
|
+ return json.loads(clean[start:])
|
|
|
|
|
+ except (json.JSONDecodeError, TypeError):
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def str_to_md5(strings):
|
|
def str_to_md5(strings):
|
|
|
"""
|
|
"""
|
|
|
字符串转化为 md5 值
|
|
字符串转化为 md5 值
|
|
@@ -132,7 +207,7 @@ def show_desc_to_sta(show_desc: str):
|
|
|
show_v = show_v.replace(",", ".")
|
|
show_v = show_v.replace(",", ".")
|
|
|
|
|
|
|
|
# 提取 数字 + 单位
|
|
# 提取 数字 + 单位
|
|
|
- match = re.search(r"(\d+(?:\.\d+)?)([a-z\u4e00-\u9fa5]*)", show_v)
|
|
|
|
|
|
|
+ match = re.search(r"(\d+(?:\.\d+)?)([a-z一-龥]*)", show_v)
|
|
|
if not match:
|
|
if not match:
|
|
|
return 0
|
|
return 0
|
|
|
|
|
|
|
@@ -202,8 +277,8 @@ def show_desc_to_sta(show_desc: str):
|
|
|
|
|
|
|
|
sta = {}
|
|
sta = {}
|
|
|
|
|
|
|
|
- # 按“组”切分(兼容各种奇怪空格)
|
|
|
|
|
- groups = re.split(r"[\u2004\u2005]+", show_desc)
|
|
|
|
|
|
|
+ # 按"组"切分(兼容各种奇怪空格)
|
|
|
|
|
+ groups = re.split(r"[ ]+", show_desc)
|
|
|
|
|
|
|
|
for group in groups:
|
|
for group in groups:
|
|
|
group = group.strip()
|
|
group = group.strip()
|
|
@@ -211,7 +286,7 @@ def show_desc_to_sta(show_desc: str):
|
|
|
continue
|
|
continue
|
|
|
|
|
|
|
|
# 按 key-value 分隔符拆
|
|
# 按 key-value 分隔符拆
|
|
|
- parts = group.split("\u2006")
|
|
|
|
|
|
|
+ parts = group.split(" ")
|
|
|
if len(parts) != 2:
|
|
if len(parts) != 2:
|
|
|
continue
|
|
continue
|
|
|
|
|
|