测试提交
This commit is contained in:
145
match_all.py
Normal file
145
match_all.py
Normal file
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
# 遍历 jsons/ 目录下所有 JSON 文件,按 URL 来源匹配 Excel 暗码:
|
||||
# - url 含 www.21cnjy.com → 在 "21世纪资料名" 列精确匹配 filename[0]
|
||||
# - url 含 www.zxxk.com → 在 "学科网资料名" 列精确匹配 filename[0]
|
||||
# 学科网资料名有重复时,从 item 的 url 中提取数字 ID 与各暗码候选值比对消歧
|
||||
# - 匹配成功写入 xkw_code,未匹配写入 "0"
|
||||
# - 未匹配数据汇总写入 问题数据/未匹配填充0.json
|
||||
|
||||
import json # 用于读写 JSON 文件
|
||||
import re # 用于正则表达式提取 URL 中的数字 ID
|
||||
import glob # 用于批量匹配目录下的文件路径
|
||||
import openpyxl # 用于读取 Excel (.xlsx) 文件
|
||||
|
||||
EXCEL_PATH = "结果.xlsx" # Excel 数据文件路径
|
||||
JSON_DIR = "jsons" # JSON 文件所在目录
|
||||
UNMATCHED_OUT = "问题数据/未匹配填充0.json" # 未匹配数据输出路径
|
||||
|
||||
|
||||
def clean(v):
|
||||
# 将单元格值转为字符串并去除首尾空格
|
||||
if not v:
|
||||
return "" # 空值直接返回空字符串
|
||||
s = str(v).strip() # 转字符串并去首尾空白
|
||||
if s.startswith("'"):
|
||||
s = s[1:] # 去掉 Excel 强制文本时产生的前导单引号(如以 + 开头的单元格)
|
||||
return s
|
||||
|
||||
|
||||
def load_excel_indexes(path):
|
||||
# 读取 Excel 文件,构建两个查找字典
|
||||
# 返回:
|
||||
# map_21: {21世纪资料名 → 暗码}
|
||||
# map_xkw: {学科网资料名 → [暗码, ...]} (同名可能对应多个暗码,全部保留)
|
||||
wb = openpyxl.load_workbook(path) # 打开 Excel 文件
|
||||
ws = wb.active # 取活动工作表
|
||||
headers = [cell.value for cell in ws[1]] # 读取第一行作为列名
|
||||
col = {h: i for i, h in enumerate(headers)} # 构建列名到列索引的映射
|
||||
|
||||
idx_21 = col["21世纪资料名"] # "21世纪资料名" 列的索引
|
||||
idx_xkw = col["学科网资料名"] # "学科网资料名" 列的索引
|
||||
idx_code = col["暗码"] # "暗码" 列的索引
|
||||
|
||||
map_21 = {} # 21世纪资料名 → 暗码(唯一映射,重复则后者覆盖前者)
|
||||
map_xkw = {} # 学科网资料名 → [暗码列表](同名保留全部暗码)
|
||||
for row in ws.iter_rows(min_row=2, values_only=True): # 从第2行开始逐行读取
|
||||
code = clean(row[idx_code]) # 读取并清洗暗码
|
||||
name_21 = clean(row[idx_21]) # 读取并清洗 21世纪资料名
|
||||
name_xkw = clean(row[idx_xkw]) # 读取并清洗学科网资料名
|
||||
if name_21 and code:
|
||||
map_21[name_21] = code # 写入 21世纪索引
|
||||
if name_xkw and code:
|
||||
map_xkw.setdefault(name_xkw, []).append(code) # 追加到学科网索引列表
|
||||
|
||||
return map_21, map_xkw # 返回两个索引字典
|
||||
|
||||
|
||||
def extract_url_id(url):
|
||||
# 从学科网 URL 中提取纯数字资源 ID
|
||||
# 例如 https://www.zxxk.com/soft/38079968.html → '38079968'
|
||||
m = re.search(r'/(\d+)(?:\.\w+)?(?:\?|$)', url) # 匹配路径最后一段的纯数字
|
||||
return m.group(1) if m else None # 有匹配则返回数字字符串,否则返回 None
|
||||
|
||||
|
||||
def resolve_xkw_code(fn0, url, map_xkw):
|
||||
# 学科网资料名匹配逻辑:
|
||||
# - 无匹配 → 返回 None
|
||||
# - 只有一个唯一暗码 → 直接返回
|
||||
# - 多个不同暗码(同名不同资源)→ 用 URL ID 消歧,找到匹配则返回,否则返回 None
|
||||
codes = map_xkw.get(fn0) # 查找该文件名对应的暗码列表
|
||||
if not codes:
|
||||
return None # 在学科网索引中找不到该文件名
|
||||
unique_codes = list(dict.fromkeys(codes)) # 去重并保持原顺序
|
||||
if len(unique_codes) == 1:
|
||||
return unique_codes[0] # 唯一暗码,直接返回
|
||||
url_id = extract_url_id(url) # 从 URL 中提取数字 ID
|
||||
if url_id and url_id in codes:
|
||||
return url_id # URL ID 与某个暗码一致,用该值
|
||||
return None # 无法消歧,返回 None
|
||||
|
||||
|
||||
def process_file(json_path, map_21, map_xkw):
|
||||
# 处理单个 JSON 文件:为每个 item 写入 xkw_code
|
||||
with open(json_path, encoding="utf-8") as f:
|
||||
data = json.load(f) # 读取 JSON 数据
|
||||
|
||||
matched = 0 # 成功匹配的计数
|
||||
unmatched_items = [] # 未匹配的 item 列表,用于汇总输出
|
||||
|
||||
for item in data.get("items", []): # 遍历每个资源 item
|
||||
fns = item.get("filename", []) # 读取 filename 列表
|
||||
if not fns:
|
||||
continue # filename 为空则跳过该 item
|
||||
fn0 = fns[0].strip() # 只取第一个 filename 并去空格
|
||||
url = item.get("url", "") # 读取该 item 的 URL
|
||||
|
||||
code = None # 初始化暗码为空
|
||||
if "www.21cnjy.com" in url:
|
||||
code = map_21.get(fn0) # 在 21世纪索引中精确查找
|
||||
elif "www.zxxk.com" in url:
|
||||
code = resolve_xkw_code(fn0, url, map_xkw) # 在学科网索引中查找(含消歧)
|
||||
|
||||
if code:
|
||||
item["xkw_code"] = code # 匹配成功,写入暗码
|
||||
matched += 1 # 匹配计数加一
|
||||
else:
|
||||
item["xkw_code"] = "0" # 未匹配,填充 "0"
|
||||
unmatched_items.append({
|
||||
"source_file": json_path, # 来源 JSON 文件路径
|
||||
"filename": fn0, # 未匹配的文件名
|
||||
"url": url, # 对应的 URL
|
||||
})
|
||||
|
||||
with open(json_path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2) # 将修改后的数据写回 JSON 文件
|
||||
|
||||
return matched, unmatched_items # 返回匹配数和未匹配列表
|
||||
|
||||
|
||||
def main():
|
||||
print("加载 Excel 索引...")
|
||||
map_21, map_xkw = load_excel_indexes(EXCEL_PATH) # 构建 Excel 查找索引
|
||||
print(f"Excel 索引:21世纪 {len(map_21)} 条,学科网 {len(map_xkw)} 个不重复名称\n")
|
||||
|
||||
files = sorted(glob.glob(f"{JSON_DIR}/*.json")) # 获取所有 JSON 文件路径并排序
|
||||
total_files = len(files) # JSON 文件总数
|
||||
total_matched = 0 # 全局匹配计数
|
||||
all_unmatched = [] # 全局未匹配列表
|
||||
|
||||
for i, path in enumerate(files, 1): # 逐个处理每个 JSON 文件
|
||||
matched, unmatched_items = process_file(path, map_21, map_xkw) # 处理单文件
|
||||
total_matched += matched # 累加匹配数
|
||||
all_unmatched.extend(unmatched_items) # 合并未匹配列表
|
||||
print(f"[{i}/{total_files}] {path} 匹配:{matched} 未匹配:{len(unmatched_items)}")
|
||||
|
||||
with open(UNMATCHED_OUT, "w", encoding="utf-8") as f:
|
||||
json.dump(all_unmatched, f, ensure_ascii=False, indent=2) # 写出所有未匹配数据
|
||||
|
||||
total_unmatched = len(all_unmatched) # 全局未匹配总数
|
||||
print(f"\n完成:处理 {total_files} 个文件,写入 xkw_code {total_matched + total_unmatched} 条"
|
||||
f"(匹配:{total_matched},填0:{total_unmatched})")
|
||||
print(f"未匹配数据已保存至 {UNMATCHED_OUT}") # 提示未匹配数据保存路径
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main() # 脚本直接运行时执行 main 函数
|
||||
Reference in New Issue
Block a user