diff --git a/match_all.py b/match_all.py new file mode 100644 index 0000000..e63a20d --- /dev/null +++ b/match_all.py @@ -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 函数