classification_process.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. import pandas as pd
  2. import os
  3. # 指定 Excel 文件路径
  4. # file_path = '/Users/jinxinglin/Desktop/ICT 行业分类.xlsx'
  5. file_path = os.path.dirname(__file__) + '/ICT行业分类_v11.xlsx'
  6. # 1. 读取 Excel 表格
  7. dfs = pd.read_excel(file_path, sheet_name=['标的物关键词表', '标的物规则表','业主关键词表', '业主规则表'])
  8. # 处理各个表的数据
  9. df_p_keyword = dfs['标的物关键词表']
  10. # print(f"标的物关键词表数量: {len(df_p_keyword)}")
  11. df_p_rule = dfs['标的物规则表']
  12. # print(f"标的物规则表数量: {len(df_p_rule)}")
  13. df_e_keyword= dfs['业主关键词表']
  14. # print(f"业主关键词表数量: {len(df_e_keyword)}")
  15. df_e_rule = dfs['业主规则表']
  16. # print(f"业主规则表数量: {len(df_e_rule)}")
  17. def process_rules(entity, df_e_rule=df_e_rule, df_e_keyword=df_e_keyword):
  18. # 预处理规则表
  19. df_e_rule = df_e_rule.fillna('')
  20. df_e_rule = df_e_rule[df_e_rule['status'] == 1]
  21. df_e_rule = df_e_rule.sort_values(by='priority', ascending=True)
  22. # 预处理关键词表:按rule_id和keyword_type分组
  23. keyword_groups = df_e_keyword.groupby(['rule_id', 'keyword_type'])['keyword'].agg(list).reset_index()
  24. keyword_dict = {}
  25. for _, row in keyword_groups.iterrows():
  26. key = (row['rule_id'], row['keyword_type'])
  27. keyword_dict.setdefault(key, []).extend(row['keyword'])
  28. # 使用字典存储结果标签
  29. result_tags = {}
  30. # 使用集合存储已匹配的标签
  31. matched_tags = set()
  32. # 维护一个清理后的实体字符串
  33. entity_clean = entity
  34. # 动态维护过滤标签集合
  35. active_filter_category_1 = set()
  36. active_filter_category_2 = set()
  37. # 存储所有匹配的规则信息
  38. rule_matches = []
  39. # 第一遍:收集所有匹配的规则
  40. for _, row in df_e_rule.iterrows():
  41. rule_id = row['id']
  42. category_1 = row['category_1']
  43. category_2 = row['category_2']
  44. filter_category_1 = row['filter_category_1']
  45. filter_category_2 = row['filter_category_2']
  46. contain_conditions = row['contain_conditions']
  47. priority = row['priority']
  48. # 获取当前规则的所有关键词类型
  49. exact_words = keyword_dict.get((rule_id, 'exact'), [])
  50. special_endings = keyword_dict.get((rule_id, 'special_ending'), [])
  51. exclusion_words = keyword_dict.get((rule_id, 'exclusion'), [])
  52. shielding_words = keyword_dict.get((rule_id, 'shielding'), [])
  53. assist_words = keyword_dict.get((rule_id, 'assist'), [])
  54. tag_key = f"{category_1}-{category_2}"
  55. # 检查当前规则是否被已激活的过滤标签排除
  56. skip_rule = False
  57. # 一级标签过滤
  58. if filter_category_1:
  59. filter_list = filter_category_1.split(',')
  60. if any(fc in active_filter_category_1 for fc in filter_list):
  61. skip_rule = True
  62. # 二级标签过滤
  63. if not skip_rule and filter_category_2:
  64. filter_list = filter_category_2.split(',')
  65. if any(f"{category_1}-{fc}" in active_filter_category_2 for fc in filter_list):
  66. skip_rule = True
  67. if skip_rule:
  68. continue
  69. # 1. 精准定位词匹配
  70. matched_keywords = []
  71. for kw in exact_words:
  72. if kw in entity:
  73. matched_keywords.append(kw)
  74. # 2. 特殊结尾词匹配
  75. if not matched_keywords:
  76. for kw in special_endings:
  77. if entity.endswith(kw):
  78. matched_keywords.append(kw)
  79. # 3. 辅助定位词处理流程(需要检查contain_conditions)
  80. if not matched_keywords and assist_words:
  81. # 检查contain_conditions条件
  82. conditions_met = True
  83. if contain_conditions:
  84. # 拆分条件词
  85. conditions = [c.strip() for c in contain_conditions.split(',') if c.strip()]
  86. # 检查实体中是否包含任意一个条件词
  87. conditions_met = any(cond in entity for cond in conditions)
  88. # 如果条件满足,才进行辅助词匹配
  89. if conditions_met:
  90. # 过滤词检查
  91. if exclusion_words and any(kw in entity for kw in exclusion_words):
  92. continue
  93. # 屏蔽词处理
  94. if shielding_words:
  95. for kw in shielding_words:
  96. entity_clean = entity_clean.replace(kw, "")
  97. # 辅助定位词匹配
  98. for kw in assist_words:
  99. if kw in entity_clean:
  100. matched_keywords.append(kw)
  101. # 如果有匹配的关键词
  102. if matched_keywords:
  103. # 记录匹配的规则信息
  104. rule_matches.append({
  105. 'rule_id': rule_id,
  106. 'tag_key': tag_key,
  107. 'priority': priority,
  108. 'keywords': matched_keywords,
  109. 'filter_category_1': filter_category_1,
  110. 'filter_category_2': filter_category_2
  111. })
  112. matched_tags.add(tag_key)
  113. # 激活过滤标签
  114. if filter_category_1:
  115. active_filter_category_1.update(filter_category_1.split(','))
  116. if filter_category_2:
  117. for fc in filter_category_2.split(','):
  118. active_filter_category_2.add(f"{category_1}-{fc}")
  119. # 第二遍:根据优先级和排除规则处理匹配结果
  120. # 按优先级排序(高优先级先处理)
  121. rule_matches.sort(key=lambda x: x['priority'])
  122. for match in rule_matches:
  123. # tag_key = match['tag_key']
  124. tag_key = match['tag_key'] + "-" + str(match['rule_id'])
  125. keywords = match['keywords']
  126. filter_category_1 = match['filter_category_1']
  127. filter_category_2 = match['filter_category_2']
  128. # 检查是否被排除
  129. skip = False
  130. # 检查一级排除
  131. if filter_category_1:
  132. filter_list = filter_category_1.split(',')
  133. for fc in filter_list:
  134. if any(tag.startswith(fc + '-') for tag in matched_tags):
  135. skip = True
  136. break
  137. # 检查二级排除
  138. if not skip and filter_category_2:
  139. filter_list = filter_category_2.split(',')
  140. for fc in filter_list:
  141. exclude_tag = f"{tag_key.split('-')[0]}-{fc}"
  142. if exclude_tag in matched_tags:
  143. skip = True
  144. break
  145. # 如果未被排除,添加标签
  146. if not skip:
  147. # 添加当前标签
  148. if tag_key not in result_tags:
  149. result_tags[tag_key] = []
  150. result_tags[tag_key].extend(keywords)
  151. matched_tags.add(tag_key)
  152. # 检查并排除冲突标签
  153. if filter_category_1:
  154. filter_list = filter_category_1.split(',')
  155. for fc in filter_list:
  156. # 排除所有匹配的一级标签
  157. tags_to_remove = [tag for tag in matched_tags if tag.startswith(fc + '-')]
  158. for tag in tags_to_remove:
  159. if tag != tag_key and tag in result_tags: # 不排除自身
  160. del result_tags[tag]
  161. matched_tags.remove(tag)
  162. if filter_category_2:
  163. filter_list = filter_category_2.split(',')
  164. for fc in filter_list:
  165. exclude_tag = f"{tag_key.split('-')[0]}-{fc}"
  166. if exclude_tag != tag_key and exclude_tag in result_tags:
  167. del result_tags[exclude_tag]
  168. matched_tags.remove(exclude_tag)
  169. return result_tags
  170. # print(process_rules("公安厅", df_e_rule, df_e_keyword))
  171. ################ 标的物 ##########################
  172. # 新的方法,包含评分规则
  173. import re
  174. import pandas as pd
  175. from itertools import product
  176. def build_group_map(df_keyword):
  177. """
  178. 构建组名到关键词集合的映射字典
  179. 参数:
  180. df_keyword: 关键词表的DataFrame
  181. 返回:
  182. 字典,key为组名,value为该组的关键词集合
  183. """
  184. df_keyword = df_keyword[df_keyword['status'] == 1]
  185. group_map = {}
  186. for group_name, group_df in df_keyword.groupby('group'):
  187. keywords = {str(kw).lower() for kw in group_df['keyword']}
  188. group_map[group_name] = keywords
  189. return group_map
  190. def prepare_rules(df_rule):
  191. """
  192. 预处理规则表
  193. 参数:
  194. df_rule: 规则表的DataFrame
  195. 返回:
  196. 排序后的有效规则DataFrame
  197. """
  198. active_rules = df_rule[df_rule['status'] == 1].copy()
  199. return active_rules.sort_values('priority', ascending=True)
  200. def parse_rule(rule_str, group_map):
  201. """
  202. 解析规则字符串
  203. 参数:
  204. rule_str: 规则字符串
  205. group_map: 组名到关键词集合的映射字典
  206. 返回:
  207. tuple: (组合模式列表, 排除组集合)
  208. """
  209. tokens = re.split(r'([+-])', rule_str)
  210. tokens = [t.strip() for t in tokens if t.strip()]
  211. combine_tokens = []
  212. exclude_groups = set()
  213. if tokens:
  214. combine_tokens.append(tokens[0])
  215. i = 1
  216. while i < len(tokens):
  217. operator = tokens[i]
  218. value = tokens[i+1]
  219. if operator == '+':
  220. combine_tokens.append(value)
  221. elif operator == '-':
  222. if value in group_map:
  223. exclude_groups.add(value)
  224. i += 2
  225. return combine_tokens, exclude_groups
  226. def generate_combinations(combine_tokens, group_map):
  227. """
  228. 生成所有可能的组合字符串
  229. 参数:
  230. combine_tokens: 组合模式列表
  231. group_map: 组名到关键词集合的映射字典
  232. 返回:
  233. 所有可能的组合字符串集合
  234. """
  235. expanded_tokens = []
  236. for token in combine_tokens:
  237. if token in group_map:
  238. expanded_tokens.append(group_map[token])
  239. else:
  240. expanded_tokens.append({token})
  241. combinations = set()
  242. for combo in product(*expanded_tokens):
  243. # 使用"+"连接多个关键词
  244. combinations.add('+'.join(combo))
  245. return combinations
  246. def contains_exclusion(text, exclude_groups, group_map):
  247. """
  248. 检查文本中是否包含任何排除组的关键词
  249. 参数:
  250. text: 待检查的文本
  251. exclude_groups: 排除组集合
  252. group_map: 组名到关键词集合的映射字典
  253. 返回:
  254. bool: 如果包含任何排除关键词则返回True,否则返回False
  255. """
  256. for group_name in exclude_groups:
  257. if any(keyword in text for keyword in group_map[group_name]):
  258. return True
  259. return False
  260. def find_matched_keywords(text, combinations):
  261. """
  262. 在文本中查找匹配的组合关键词
  263. 参数:
  264. text: 待匹配的文本(字符串)
  265. combinations: 所有可能的组合字符串集合(元素格式为"关键词"或"关键词1+关键词2")
  266. 返回:
  267. 匹配的组合字符串集合
  268. """
  269. matched = set()
  270. for combo in combinations:
  271. # 分割组合关键词(支持单个或多个关键词)
  272. keywords = combo.split('+')
  273. # 检查所有关键词是否都存在于文本中
  274. if all(keyword in text for keyword in keywords):
  275. matched.add(combo)
  276. return matched
  277. def extract_core_text(all_text):
  278. """
  279. 从文本中提取包含特定关键词之后45个汉字以内的核心内容
  280. 参数:
  281. all_text: 完整的文本内容
  282. 返回:
  283. 核心文本内容
  284. """
  285. # 定义需要匹配的关键词列表
  286. # keywords = [
  287. # '项目名称', '工程名称', '采购名称', '标段名称', '项目的名称', '设备名称', '申购主题',
  288. # '申购单主题', '标的', '商品名称', '二级目录', '招标内容', '项目内容', '商品清单',
  289. # '标的名称', '采购内容', '集成要求', '概况介绍', '品目分类', '招标范围', '采购范围',
  290. # '项目采购分', '采购合同', '招标合同'
  291. # ]
  292. keywords = [
  293. '项目名称','工程名称','采购名称','标段名称','项目的名称','设备名称','申购主题',
  294. '申购单主题','标的','商品名称','二级目录','招标内容','项目内容','商品清单',
  295. '标的名称','采购内容','集成要求','概况介绍','品目分类','招标范围','采购范围',
  296. '项目采购分','采购合同','招标合同','物料描述','物资名称','物料名称',
  297. '资质要求', '具备', '资格要求', '项目概况', '服务品目', '准入条件', '交易项目',
  298. '标项名称','规格描述','采购标的','采购需求','需求描述','项目简介','规格描述',
  299. '采购明细','采购条目','服务描述','服务名称'
  300. ]
  301. # 创建正则表达式模式,匹配任意一个关键词
  302. pattern = r'(' + '|'.join(re.escape(kw) for kw in keywords) + r')'
  303. # 查找所有匹配位置
  304. matches = list(re.finditer(pattern, all_text))
  305. if not matches:
  306. return "" # 没有找到关键词
  307. # 取第一个匹配位置
  308. first_match = matches[0]
  309. start_pos = first_match.end() # 关键词结束位置
  310. # 提取关键词之后的内容
  311. after_text = all_text[start_pos:]
  312. # 提取最多45个汉字
  313. chinese_chars = []
  314. count = 0
  315. for char in after_text:
  316. # 判断是否为汉字 (Unicode范围)
  317. if '\u4e00' <= char <= '\u9fff':
  318. count += 1
  319. if count > 45:
  320. break
  321. chinese_chars.append(char)
  322. # 将字符列表组合成字符串
  323. core_text = ''.join(chinese_chars).strip()
  324. return core_text
  325. def normalize_whitespace(text):
  326. """
  327. 将字符串中的多个空格、换行符、制表符等替换为单个空格
  328. 参数:
  329. text: 输入的文本字符串
  330. 返回:
  331. 处理后的文本字符串
  332. """
  333. # 使用正则表达式替换所有连续空白字符为单个空格
  334. normalized_text = re.sub(r'\s+', ' ', text)
  335. return normalized_text.strip()
  336. def calculate_keyword_position(text, keyword):
  337. """
  338. 计算关键词在文本中的最后出现位置
  339. 参数:
  340. text: 目标文本
  341. keyword: 要查找的关键词
  342. 返回:
  343. 关键词最后出现的位置索引(从0开始),如果未找到返回-1
  344. """
  345. last_index = -1
  346. start = 0
  347. while True:
  348. index = text.find(keyword, start)
  349. if index == -1:
  350. break
  351. last_index = index
  352. start = index + 1
  353. return last_index
  354. def calculate_rule_score(text, matched_keywords):
  355. """
  356. 计算规则的得分
  357. 参数:
  358. text: 目标文本
  359. matched_keywords: 匹配的关键词集合
  360. 返回:
  361. tuple: (匹配关键词数量, 最高位置分数)
  362. """
  363. keyword_count = len(matched_keywords)
  364. max_position = -1
  365. # 计算所有匹配关键词中的最高位置分数
  366. for combo in matched_keywords:
  367. # 对组合中的每个关键词计算位置
  368. keywords = combo.split('+')
  369. for keyword in keywords:
  370. pos = calculate_keyword_position(text, keyword)
  371. if pos > max_position:
  372. max_position = pos
  373. return keyword_count, max_position
  374. def classify_text(title, all_text, product_text, df_p_keyword=df_p_keyword, df_p_rule=df_p_rule):
  375. """
  376. 分类主函数 - 遍历所有规则并返回得分最高的规则
  377. 参数:
  378. title: 公告标题
  379. all_text: 公告正文
  380. product: 产品词
  381. df_p_keyword: 关键词表的DataFrame
  382. df_p_rule: 规则表的DataFrame
  383. 返回:
  384. tuple: (category_1, category_2, category_3, matched_keywords, rule_id)
  385. category_1: 一级分类
  386. category_2: 二级分类
  387. category_3: 三级分类
  388. matched_keywords: 匹配的关键词列表
  389. rule_id: 匹配的规则ID
  390. """
  391. # 预处理文本:去除多余空白
  392. title = normalize_whitespace(title)
  393. all_text = normalize_whitespace(all_text)
  394. product_text = normalize_whitespace(product_text)
  395. # 构建组映射
  396. group_map = build_group_map(df_p_keyword)
  397. # 准备规则
  398. rules = prepare_rules(df_p_rule)
  399. # 统一转换为小写
  400. title_lower = title.lower()
  401. all_text_lower = all_text.lower()
  402. product_text_lower = product_text.lower()
  403. # 提取核心文本并转换为小写
  404. core_text = extract_core_text(all_text)
  405. core_text_lower = core_text.lower() if core_text else ""
  406. core_text_lower = core_text_lower + product_text_lower
  407. # 存储所有匹配的规则及其得分
  408. candidate_rules = []
  409. # 遍历所有规则
  410. for _, rule in rules.iterrows():
  411. rule_id = rule['id'] # 获取规则ID
  412. target = rule['target']
  413. rule_str = rule['rule']
  414. category_2_str = rule['category_2']
  415. # 选择目标文本
  416. text = ''
  417. if target == 'title':
  418. text = title_lower
  419. elif target == 'all_text':
  420. text = all_text_lower
  421. elif target == 'core_text':
  422. text = core_text_lower
  423. elif target == 'product':
  424. text = product_text_lower
  425. else:
  426. # 默认使用全部文本
  427. text = f"{title_lower} {all_text_lower} {core_text_lower} {product_text_lower}"
  428. # 解析规则
  429. combine_tokens, exclude_groups = parse_rule(rule_str, group_map)
  430. # 生成组合
  431. combinations = generate_combinations(combine_tokens, group_map)
  432. # 查找匹配的关键词
  433. matched_keywords = find_matched_keywords(text, combinations)
  434. # 如果没有匹配则继续下一条规则
  435. if not matched_keywords:
  436. continue
  437. # 检查排除条件
  438. if contains_exclusion(text, exclude_groups, group_map):
  439. continue
  440. # 如果是标题规则且匹配成功,立即返回(标题排除规则)
  441. if category_2_str == '标题排除':
  442. return (
  443. rule['category_1'] if 'category_1' in rule else rule.get('categroy_1', '其他'),
  444. rule.get('category_2', ''),
  445. rule.get('category_3', ''),
  446. list(matched_keywords),
  447. rule_id
  448. )
  449. # 计算规则得分(匹配关键词数量和最高位置)
  450. keyword_count, max_position = calculate_rule_score(text, matched_keywords)
  451. # 存储候选规则信息
  452. candidate_rules.append({
  453. 'rule': rule,
  454. 'matched_keywords': matched_keywords,
  455. 'keyword_count': keyword_count,
  456. 'max_position': max_position,
  457. 'rule_id': rule_id
  458. })
  459. # 如果没有匹配的规则
  460. if not candidate_rules:
  461. return ("其他", "", "", [], "")
  462. # 选择得分最高的规则
  463. # 1. 按匹配关键词数量降序排序
  464. # 2. 如果数量相同,按最高位置降序排序(位置越靠后分数越高)
  465. candidate_rules.sort(key=lambda x: (x['keyword_count'], x['max_position']), reverse=True)
  466. best_rule = candidate_rules[0]
  467. return (
  468. best_rule['rule']['category_1'] if 'category_1' in best_rule['rule'] else best_rule['rule'].get('categroy_1', '其他'),
  469. best_rule['rule'].get('category_2', ''),
  470. best_rule['rule'].get('category_3', ''),
  471. list(best_rule['matched_keywords']),
  472. best_rule['rule_id']
  473. )
  474. # # 示例1:测试组合规则
  475. # # 读取测试数据
  476. # test_df = pd.read_excel('/Users/jinxinglin/Desktop/工作簿5.xlsx')
  477. # print(f"成功读取测试数据,共 {len(test_df)} 条记录")
  478. #
  479. # # 3. 准备结果存储
  480. # results = []
  481. #
  482. #
  483. # # 4. 处理每条记录
  484. # for idx, row in test_df.iterrows():
  485. # docid = row['docid']
  486. # doctitle = row['doctitle'] if pd.notna(row['doctitle']) else ""
  487. # all_text = row['doctextcon'] if pd.notna(row['doctextcon']) else ""
  488. # all_text = re.sub(r'\s+', ' ', all_text)
  489. # product_text = row['product'] if pd.notna(row['product']) else ""
  490. # tenderee = row['tenderee'] if pd.notna(row['tenderee']) else ""
  491. # doctitle = doctitle.replace(tenderee, '')
  492. # all_text = all_text.replace(tenderee, '')
  493. # # 分类处理
  494. # category_1, category_2, category_3, matched_keywords, rule_id = classify_text(
  495. # doctitle, all_text, product_text, df_p_keyword, df_p_rule
  496. # )
  497. #
  498. # # 将匹配的关键词列表转换为字符串(用逗号分隔)
  499. # matched_keywords_str = ", ".join(matched_keywords) if matched_keywords else ""
  500. #
  501. # # 存储结果
  502. # results.append({
  503. # 'docid': docid,
  504. # 'doctitle': doctitle,
  505. # 'product_text': product_text,
  506. # 'category_1': category_1,
  507. # 'category_2': category_2,
  508. # 'category_3': category_3,
  509. # 'matched_keywords': matched_keywords_str,
  510. # 'rule_id': rule_id
  511. # })
  512. #
  513. # # 每处理100条打印一次进度
  514. # if (idx + 1) % 100 == 0:
  515. # print(f"已处理 {idx + 1} 条记录")
  516. #
  517. # # 5. 创建结果DataFrame
  518. # result_df = pd.DataFrame(results)
  519. #
  520. # # 6. 保存结果
  521. # result_df.to_excel('/Users/jinxinglin/Desktop/分类结果.xlsx', index=False)
  522. # print(f"处理记录数: {len(result_df)}")