get_label_dic.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. @author: bidikeji
  5. @time: 2024/7/23 14:45
  6. """
  7. from BiddingKG.dl.common.Utils import getUnifyMoney
  8. import math
  9. import re
  10. def chinese_to_arabic(s):
  11. # 中文数字到阿拉伯数字的映射
  12. num_map = {'零': 0, '一': 1, '二': 2, '两': 2, '三': 3, '四': 4,
  13. '五': 5, '六': 6, '七': 7, '八': 8, '九': 9}
  14. # 单位到倍数的映射
  15. unit_map = {'十': 10, '百': 100}
  16. # 初始化结果和当前数值
  17. result = 0
  18. current_num = 0
  19. has_unit = False
  20. if s.startswith('十'):
  21. result = 10
  22. # 遍历字符串
  23. for char in s:
  24. if char in num_map:
  25. # 如果是数字,则进行处理
  26. if has_unit:
  27. # 如果之前已经有单位了,则需要将当前数字乘以前面的单位
  28. result += current_num * unit_map[last_unit]
  29. current_num = num_map[char]
  30. has_unit = False
  31. else:
  32. # 如果之前没有单位,则直接累加
  33. current_num = current_num * 10 + num_map[char]
  34. elif char in unit_map:
  35. # 如果是单位,则标记为已有单位,并保存最后一个单位
  36. last_unit = char
  37. has_unit = True
  38. # 处理字符串末尾的数字(如果没有单位,则直接加上)
  39. if current_num != 0:
  40. if has_unit:
  41. result += current_num * unit_map[last_unit]
  42. else:
  43. result += current_num
  44. return result
  45. def get_all_label(title, content):
  46. def is_direct_procurement():
  47. # 企业直采
  48. if re.search('询比价|询比|竞价|议价|报价', title) or re.search('我要报价|竞价起止时间|报价起止时间', content) or \
  49. (re.search('公司|集团|企业', content) and re.search('招标|中标|投标', content) == None):
  50. return 1
  51. return 0
  52. def is_target_small():
  53. # 专门面向中小企业
  54. if re.search('专门面向中小微?企业', content):
  55. if re.search('(非|不属于|不|是/否))?专门面向(中小微?企业)?|部分面向中小微?企业|专门面向中小企业采购:否', content):
  56. return 0
  57. return 1
  58. elif re.search('仅面向小微企业|专门面向.{,30}中小企业采购|是否专门面向中小微?企业(采购)?:是|本项目为中小型企业预留项目|专门面向中小微?企业', content):
  59. return 1
  60. elif re.search('落实政府采购政策需满足的资格要求.{,30}供应商为中小企业', content):
  61. if re.search('(非|不属于|不|是/否))?专门面向中小微?企业|部分面向中小微?企业',content):
  62. return 0
  63. return 1
  64. return 0
  65. def registered_years():
  66. # 注册年限
  67. ser = None
  68. if re.search('禁止.{,5}注册未满(?P<num>([一二三四五六七八九十]+|\d+))(?P<unit>(年|个?月))', content):
  69. ser = re.search('禁止.{,5}注册未满(?P<num>([一二三四五六七八九十]+|\d+))(?P<unit>(年|个?月))', content)
  70. elif re.search('(成立|注册)时间:?.{,10}(不[低少]于|大于(等于)?|需满)(?P<num>([一二三四五六七八九十]+|\d+))(?P<unit>(年|个?月))', content):
  71. ser = re.search('(成立|注册)时间:?.{,10}(不[低少]于|大于(等于)?|需满)(?P<num>([一二三四五六七八九十]+|\d+))(?P<unit>(年|个?月))',
  72. content)
  73. elif re.search('(成立|注册)时间:?.{,10}(?P<num>([一二三四五六七八九十]+|\d+))(?P<unit>(年|个?月)[或及]?以上)', content):
  74. ser = re.search('(成立|注册)时间:?.{,10}(?P<num>([一二三四五六七八九十]+|\d+))(?P<unit>(年|个?月)[或及]?以上)', content)
  75. elif re.search('(成立|注册)时间:?.{,10}不满(?P<num>([一二三四五六七八九十]+|\d+))(?P<unit>(年|个?月).{,5}请勿报价)', content):
  76. ser = re.search('(成立|注册)时间:?.{,10}不满(?P<num>([一二三四五六七八九十]+|\d+))(?P<unit>(年|个?月).{,5}请勿报价)', content)
  77. if ser:
  78. num = ser.group('num')
  79. unit = ser.group('unit')
  80. if num.isdigit():
  81. num = int(num)
  82. else:
  83. num = chinese_to_arabic(num)
  84. if '月' in unit: # 向上取整为年
  85. num = math.ceil(num/12)
  86. return num
  87. return 0
  88. def registered_capital():
  89. # 注册资本
  90. ser = None
  91. if re.search('注册(资本|资金):?.{,5}(不[低少]于|大于(等于)?|≥)(?P<num>(\d+[\d.]*))(?P<unit>([万亿]?元))', content):
  92. ser = re.search('注册(资本|资金):?.{,5}(不[低少]于|大于(等于)?|≥)(?P<num>(\d+[\d.]*))(?P<unit>([万亿]?元))', content)
  93. elif re.search('注册(资本|资金):?.{,5}(?P<num>(\d+[\d.]*))(?P<unit>([万亿]?元)[或及]?以上)', content):
  94. ser = re.search('注册(资本|资金):?.{,5}(?P<num>(\d+[\d.]*))(?P<unit>([万亿]?元)[或及]?以上)', content)
  95. if ser:
  96. num = ser.group('num')
  97. unit = ser.group('unit')
  98. return float(getUnifyMoney(num + unit))
  99. return 0
  100. def need_qualification():
  101. # 有资质证书要求
  102. if re.search('资质要求.{,150}(行业资质|证书|许可证|认证|经营范围|一级|二级|三级|甲级|乙级|丙级|特级|壹级|贰级|叁级)', content):
  103. return 1
  104. elif re.search('(提供|有|具备).{,50}(资质|认证|证书|许可证)', content):
  105. return 1
  106. elif re.search('资格)?要求:?.{,50}(甲级|丙级|乙级|一级|二级|三级|特级|壹级|贰级|叁级)', content):
  107. return 1
  108. elif re.search('认证体系要求', content):
  109. return 1
  110. elif re.search('经营范围须包含|经营范围需包含|经营范围需有|经营范围须有|经营范围有|经营范围内含|营业执照范围内包括', content):
  111. return 1
  112. return 0
  113. def need_ca():
  114. # 7 是否需要办CA
  115. if re.search('需要.{,20}数字证书|使用.{,20}(签章|数字证书|CA|ca)', content):
  116. return 1
  117. elif re.search('办理.{,20}(数字证书|CA|ca|密钥|签章)', content):
  118. if re.search('无需.{,15}办理', content):
  119. return 0
  120. return 1
  121. elif re.search('(数字证书|CA|ca|密钥).{,5}办理|是否要求供应商使用(CA|ca)数字证书参与:是', content):
  122. return 1
  123. if re.search('(不使用|无需).{,20}(数字证书|CA|ca)|是否要求供应商使用(CA|ca)数字证书参与:不要求', content):
  124. return 0
  125. return 0
  126. def need_performance():
  127. # 有业绩要求
  128. if re.search('业绩证明|业绩要求|行业业绩|相关业绩|同类项目业绩经验|业绩材料', content):
  129. if re.search('业绩.{,5}[/无]', content):
  130. return 0
  131. return 1
  132. elif re.search('类似.{,10}业绩', content):
  133. if re.search('业绩.{,5}如有', content):
  134. return 0
  135. return 1
  136. elif re.search('完成[^。;]{,100}项目', content):
  137. return 1
  138. elif re.search('(提供|有|完成|承接|具备|承担)[^。;]{,100}业绩', content):
  139. return 1
  140. return 0
  141. def mode_of_partipation():
  142. # 参与方式 1线上 2线下 0其他
  143. if re.search('(平台|网站|http|www|官网|网址|网页|网上中介|邮件|邮箱|客户端|采购网|系统|邮寄).{,20}(注册|报名)', content):
  144. return 1
  145. elif re.search('现场报名', content):
  146. if re.search(
  147. '非现场报名|(在线报名|邮件|邮寄|邮箱|线上报名|网络报名).{,10}现场报名|现场报名.{,10}(在线报名|邮件|邮寄|邮箱|线上报名|网络报名)', content) == None:
  148. return 1
  149. return 2
  150. elif re.search('(获取采购文件|文件的获取|文件获取|获取竞价文件|获取招标文件|文件的领取|文件领取|获取投标文件).{,200}'
  151. '(平台|线上|客户端|邮寄|网上获取|网站|网址|http|www|邮箱|寄送|网络获取|采购网|网络领购|系统|邮件|在线报名|网络报名|非现场报名|线上报名)', content):
  152. return 1
  153. elif re.search('(获取采购文件|文件的获取|文件获取|获取竞价文件|获取招标文件|文件的领取|文件领取|获取投标文件).{,200}'
  154. '([\d一二三四五六七八九十]号|接待室|开标室|现场领取|会议室|线下购买|现场获取|X[\d一二三四五六七八九十]|办公楼|现场报名)', content):
  155. return 2
  156. elif re.search('(报价地址|报价信息|报价请点击|报价方法|报价式|报价方式|报价提交|报价地点).{,30}'
  157. '(平台|网站|http|www|官网|网址|网页|网上中介|邮件|邮箱|客户端|采购网|系统|在线报价|线上报价)', content):
  158. return 1
  159. elif re.search('线下报价', content) and re.search('不接受线下报价|线下报价无效', content) == None:
  160. return 2
  161. elif re.search('(文件提交|文件递交|递交方式|文件的提交|文件送达地点|递交响应文件|证明材料的递交)', content):
  162. b = re.search('(文件提交|文件递交|递交方式|文件的提交|文件送达地点|递交响应文件|证明材料的递交)', content).end()
  163. ser = re.search('联系方式|发布公告的媒介', content[b:b + 200])
  164. text = content[b:b + ser.start()] if ser else content[b:b + 200]
  165. if re.search('平台|线上|客户端|邮寄|网站|网址|http|www|邮箱|寄送|采购网|系统|邮件|网页', text):
  166. return 1
  167. elif re.search('[\d一二三四五六七八九十]号|接待室|现场递交|开标室|会议室|[\d一二三四五六七八九十]楼|线下递交|办公楼', text):
  168. return 2
  169. if re.search('(平台|线上|客户端|网站|网址|http|www|采购网|系统|网页).{,10}递交.{,10}文件', content):
  170. return 1
  171. if re.search('(开标地点|投标地点|开标时间和地点|开标时间及地点|开标方式)', content):
  172. b = re.search('(开标地点|投标地点|开标时间和地点|开标时间及地点|开标方式)', content).end()
  173. ser = re.search('联系方式|发布公告的媒介', content[b:b + 70])
  174. text = content[b:b + ser.start()] if ser else content[b:b + 70]
  175. if re.search(
  176. '平台|线上|客户端|网站|网址|http|www|线上开标|采购网|非现场开标|不见面开标|远程异地开启|系统|线上观看开标|网上开标|在线直播的方式开标|远程开标|现场开启|电子卖场|电子开标|开标现场电话联系',
  177. text):
  178. return 1
  179. elif re.search('[\d一二三四五六七八九十]号|线下开标|接待室|现场递交|开标室|现场开标|会议室|[\d一二三四五六七八九十]楼|办公楼|街道', text):
  180. return 2
  181. if re.search('(平台|线上|客户端|网站|网址|http|www|采购网|系统|网页).{,10}开标', content):
  182. return 1
  183. elif re.search('不见面开标|非现场开标|远程异地开启|线上观看开标|网上开标|在线直播的方式开标|远程开标|现场开启|非公开开启|电子开标|开标现场电话联系', content):
  184. return 1
  185. elif re.search('开启.{,20}地点', content):
  186. b = re.search('开启.{,20}地点', content).end()
  187. ser = re.search('联系方式|发布公告的媒介', content[b:b + 70])
  188. text = content[b:b + ser.start()] if ser else content[b:b + 70]
  189. if re.search(
  190. '平台|线上|客户端|网站|网址|http|www|线上开标|采购网|非现场开标|不见面开标|远程异地开启|系统|线上观看开标|网上开标|在线直播的方式开标|远程开标|电子卖场|电子开标|开标现场电话联系',
  191. text):
  192. return 1
  193. elif re.search('[\d一二三四五六七八九十]号|线下开标|接待室|现场递交|开标室|现场开标|会议室|[\d一二三四五六七八九十]楼|办公楼|街道', text):
  194. return 2
  195. return 0
  196. # def suitable_small():
  197. # # 适合小微企业投标
  198. # if re.search('属于专门面向中小企业|有招标单位联系方式|无注册年限要求|无注册资本要求|无资质证书要求|无业绩要求', content):
  199. # return 1
  200. # elif re.search('属于企业直采|有招标单位联系方式|无注册年限要求|无注册资本要求|无资质证书要求|无业绩要求', content):
  201. # return 2
  202. # elif re.search('有招标单位联系方式|无注册年限要求|无注册资本要求|无资质证书要求|无业绩要求', content):
  203. # return 3
  204. # return 0
  205. def need_government_policy():
  206. # 有落实政府采购政策需满足的资格要求:1;否则 0
  207. if re.search('落实政府采购政策需满足的资格要求', content):
  208. if re.search('落实政府采购政策需满足的资格要求:无,', content) == None:
  209. return '否'
  210. return '是'
  211. return 0
  212. def consortium_permit():
  213. # 允许联合体:是;不允许:否;无关键词:0
  214. if re.search('(接受|允许|欢迎|同意))?联合体(参与)?投标|联合体投标是合法的|联合体投标的,应|联合体各方应具备承担|投标人可以组成联合体', content):
  215. if re.search('不(接受|允许|欢迎|同意))?联合体(参与)?投标|禁止联合体(参与)?投标|投标人不得为联合体|仅接受独立法人投标|投标人必须为独立法人|不得组成联合体|只有独立法人单位可以参与', content):
  216. return '否'
  217. return '是'
  218. elif re.search('联合(体|方|投标人):|联合体(成员|单位)[12345一二三四五]?:|(联合体)?成员单位[12345一二三四五]?:|特殊普通合伙:|(联合(体|投标人))|(联合体(成员|单位)方?[12345一二三四五]?)|((联合体)?成员单位[12345一二三四五]?)|(特殊普通合伙|成员?)|[,;]成:|(成[),]|与[^,。]{6,100}联合体', content):
  219. return '是'
  220. return 0
  221. label_dic = {}
  222. is_direct_procurement = is_direct_procurement() # 是否直接采购
  223. is_target_small = is_target_small() # 是否面向中小企业
  224. mode_of_partipation = mode_of_partipation() # 参与方式
  225. need_ca = need_ca() # 是否需要CA
  226. need_performance = need_performance() # 有业绩要求
  227. need_qualification = need_qualification() # 资质要求
  228. registered_capital = registered_capital() # 注册资本
  229. registered_years = registered_years() # 注册年限
  230. # suitable_small = suitable_small() # 适合小微企业
  231. government_policy = need_government_policy() # 落实政府采购政策需满足的资格要求
  232. consortium_permit = consortium_permit()
  233. label_dic['is_direct_procurement'] = is_direct_procurement
  234. label_dic['is_target_small'] = is_target_small
  235. label_dic['mode_of_partipation'] = mode_of_partipation
  236. label_dic['need_ca'] = need_ca
  237. label_dic['need_performance'] = need_performance
  238. label_dic['need_qualification'] = need_qualification
  239. label_dic['registered_capital'] = registered_capital
  240. label_dic['registered_years'] = registered_years
  241. # label_dic['suitable_small'] = suitable_small
  242. label_dic['government_policy'] = government_policy
  243. label_dic['consortium_permit'] = consortium_permit
  244. label_dic = {k: v for k, v in label_dic.items() if v!=0}
  245. return label_dic
  246. if __name__ == "__main__":
  247. # with open('D:\html/2.html', 'r', encoding='UTF-8') as f:
  248. # html = f.read()
  249. # rs = get_all_label('', html)
  250. # print('rs: ', rs)
  251. import pandas as pd
  252. from bs4 import BeautifulSoup
  253. import json
  254. df = pd.read_csv(r'E:\导出数据\2024-03-18入库去重后所有公告_输入要素.csv')[:]
  255. df1 = pd.read_csv(r'E:\导出数据\2024-03-18入库去重后所有公告_html.csv')[:]
  256. df = df.merge(df1, how='inner', on='docid')
  257. df.fillna('', inplace=True)
  258. # df = df[:10]
  259. print(df.columns, len(df))
  260. # df.drop_duplicates(subset=['docchannel', 'web_source_name', 'exist_table'], inplace=True)
  261. # print(len(df))
  262. def get_text(html):
  263. soup = BeautifulSoup(html, 'lxml')
  264. text = soup.get_text()
  265. return text
  266. df['content'] = df['dochtmlcon'].apply(lambda x: get_text(x))
  267. df['标签'] = df.apply(lambda x: get_all_label(x['doctitle'], x['content']), axis=1)
  268. for k in ['is_direct_procurement', 'is_target_small', 'mode_of_partipation', 'need_ca', 'need_performance', 'need_qualification', 'registered_capital', 'registered_years']:
  269. df[k] = df['标签'].apply(lambda x: x[k])
  270. df['标签'] = df['标签'].apply(lambda x: json.dumps(x, ensure_ascii=False, indent=2))
  271. df.drop_duplicates(subset=['docchannel', 'web_source_no', 'is_direct_procurement', 'is_target_small', 'mode_of_partipation', 'need_ca', 'need_performance', 'need_qualification', 'registered_capital', 'registered_years'], inplace=True)
  272. df = df[['docid', 'docchannel', 'web_source_no', '标签', 'is_direct_procurement', 'is_target_small', 'mode_of_partipation', 'need_ca', 'need_performance', 'need_qualification', 'registered_capital', 'registered_years']]
  273. df.to_excel('E:/2024-03-18公告标签提取结果.xlsx', index=False)