如何解决 json.loads 的 Invalid \escape 错误?
结论
报错的原因是页面提供的 JSON 不符合语法规范。出错数据包含:
Jede\*r
Mitarbeitern\*innen
在 JSON 字符串中,反斜杠 \ 用于转义,但 \* 不是合法的 JSON 转义序列,因此 json.loads() 会抛出:
JSONDecodeError: Invalid \escape
设置 strict=False 也无法解析 \*。这个参数主要用于放宽对字符串内控制字符的检查,并不允许任意反斜杠转义。
JSON 中允许的转义形式
JSON 字符串支持以下常规转义:
\" \\ \/ \b \f \n \r \t \uXXXX
如果原数据需要保留反斜杠,应写成:
"Jede\\*r"
解析后,Python 字符串中才会包含:
Jede\*r
如果反斜杠只是排版时产生的多余字符,正确内容应是:
"Jede*r"
示例中的 &uuml;、<br/> 等 HTML 实体不会触发 Invalid \escape。问题出在 \*。
优先方案:修正数据源
如果能够控制生成 JSON 的页面,应使用正规的 JSON 序列化器输出数据,不要手工拼接字符串。例如:
import json
data = {
"description": r"Jede\*r bei STORMING"
}
json_text = json.dumps(data, ensure_ascii=False)
print(json_text)
输出中的反斜杠会得到正确编码:
{"description": "Jede\\*r bei STORMING"}
这种方式最可靠,因为问题会在数据生成时得到修复,爬虫不必猜测原始文本的含义。
无法修改页面时:针对已知错误进行清理
对于这个特定页面,如果能够确定 \* 中的反斜杠没有实际含义,可以在解析前将其删除:
import json
raw_json = company_jdata.get_text()
raw_json = raw_json.replace(r"\*", "*")
company_js = json.loads(raw_json, strict=False)
如果解析后的文本需要保留反斜杠,则应将输入中的单个反斜杠改成 JSON 要求的两个反斜杠:
raw_json = company_jdata.get_text()
raw_json = raw_json.replace(r"\*", r"\\*")
company_js = json.loads(raw_json, strict=False)
这两种处理方式的语义不同:
# 删除多余的反斜杠
r"Jede\*r" -> "Jede*r"
# 保留反斜杠字符
r"Jede\*r" -> r"Jede\*r"
具体采用哪一种,应根据字段的实际含义决定,不能盲目替换。
处理多种未知转义
如果第三方页面可能包含多种非法转义,可以只转义那些不属于 JSON 合法转义的反斜杠:
import json
import re
INVALID_JSON_BACKSLASH = re.compile(r'\\(?!["\\/bfnrtu])')
def repair_invalid_escapes(raw_json: str) -> str:
return INVALID_JSON_BACKSLASH.sub(r"\\\\", raw_json)
raw_json = company_jdata.get_text()
try:
company_js = json.loads(raw_json, strict=False)
except json.JSONDecodeError as exc:
if exc.msg.startswith("Invalid \\escape"):
repaired_json = repair_invalid_escapes(raw_json)
company_js = json.loads(repaired_json, strict=False)
else:
raise
这种方法会把未知的 \*、\q 等内容解释为普通反斜杠加字符。不过,它并不是完整的 JSON 修复器。格式错误的 \u 转义、缺少引号或多余逗号仍然会导致解析失败。因此,它只适合在无法控制第三方数据时作为兼容措施。
定位具体错误内容
JSONDecodeError 会提供错误位置。打印该位置附近的原始文本,可以快速找到问题字符:
import json
raw_json = company_jdata.get_text()
try:
company_js = json.loads(raw_json, strict=False)
except json.JSONDecodeError as exc:
start = max(0, exc.pos - 80)
end = min(len(raw_json), exc.pos + 80)
print(f"JSON error: {exc}")
print(repr(raw_json[start:end]))
这里应使用 repr(),因为它可以清楚地显示反斜杠、换行符和其他不可见字符。
在循环中跳过损坏页面
一个无效页面不应导致整个抓取任务终止。可以记录对应的 URL 和错误上下文,然后继续处理其他页面:
import json
import urllib.request
from urllib.request import Request
from bs4 import BeautifulSoup
for count_site, site in enumerate(sitelis, start=1):
try:
request = Request(
site,
headers={"User-Agent": "Mozilla/5.0"}
)
with urllib.request.urlopen(request, context=ctx) as response:
company_data = response.read()
soup = BeautifulSoup(company_data, "lxml")
script = soup.select_one('script[type="application/ld+json"]')
if script is None:
print(f"[{count_site}] No JSON-LD found: {site}")
continue
raw_json = script.get_text()
raw_json = raw_json.replace(r"\*", "*")
company_js = json.loads(raw_json, strict=False)
print(json.dumps(company_js, indent=4, ensure_ascii=False))
except json.JSONDecodeError as exc:
start = max(0, exc.pos - 80)
end = min(len(raw_json), exc.pos + 80)
print(f"[{count_site}] Invalid JSON: {site}")
print(f"Reason: {exc}")
print(f"Context: {raw_json[start:end]!r}")
except KeyboardInterrupt:
print("Program interrupted by user.")
break
注意事项
不要进行下面这种全局替换:
raw_json = raw_json.replace("\\", "")
这样会破坏原本合法的 \\、\"、\n 和 \uXXXX,还可能改变字段内容。
示例中的 description 似乎还包含字符串内的原始换行。严格的 JSON 要求将这些换行编码为 \n。strict=False 可能暂时容忍部分控制字符,但无法把整体不规范的数据变成有效 JSON。
更稳妥的处理方式是先记录原始错误上下文,只修复已经确认的异常模式,并尽可能让数据提供方正确生成 JSON-LD。
备注:内容仅供参考。
