html_2_kv.py 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176
  1. #coding:utf8
  2. from bs4 import BeautifulSoup
  3. import json
  4. import re
  5. import traceback
  6. import logging
  7. logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
  8. logger = logging.getLogger(__name__)
  9. logger.setLevel(logging.INFO)
  10. def log(msg):
  11. '''
  12. @summary:打印信息
  13. '''
  14. logger.info(msg)
  15. class DotDict(dict):
  16. def __getattr__(self,name):
  17. try:
  18. return self[name]
  19. except KeyError:
  20. raise AttributeError("No attribute '%s'" % name)
  21. def __setattr__(self,name,value):
  22. self[name] = value
  23. # 递归地将 DOM 转换为 JSON
  24. # 递归地将 DOM 转换为 JSON
  25. def dom_to_tree(node):
  26. if node.name: # 如果是标签节点
  27. json_obj = DotDict({"tag": node.name})
  28. if node.attrs:
  29. json_obj["attributes"] = node.attrs
  30. children = []
  31. for child in node.contents:
  32. _child = dom_to_tree(child)
  33. if _child is not None:
  34. _child["parent"] = json_obj
  35. children.append(_child)
  36. if children:
  37. json_obj["children"] = children
  38. return json_obj
  39. elif node.string and node.string.strip(): # 如果是纯文本节点
  40. return DotDict({"tag":"text","text": node.string.strip()})
  41. return None # 忽略空白字符
  42. def tree_pop_parent(tree):
  43. if isinstance(tree,list):
  44. for child in tree:
  45. tree_pop_parent(child)
  46. if isinstance(tree,dict):
  47. if "parent" in tree:
  48. del tree["parent"]
  49. for child in tree.get("children",[]):
  50. tree_pop_parent(child)
  51. def html_to_tree(html_content):
  52. # 使用 BeautifulSoup 解析 HTML
  53. soup = BeautifulSoup(html_content, "lxml")
  54. dom_tree = dom_to_tree(soup)
  55. return dom_tree
  56. def print_tree(dom_tree):
  57. # 转换为 JSON 格式
  58. tree_pop_parent(dom_tree)
  59. json_output = json.dumps(dom_tree,ensure_ascii=False, indent=2)
  60. print(json_output)
  61. # kv_pattern = "\s*(?P<key>.{,10})[::]\s*(?P<value>[^::。,()]+?)(\s+|$|;|;)(?![\u4e00-\u9fa5]+:)"
  62. kv_pattern = r"(?P<key>[\u4e00-\u9fa5]+):\s*(?P<value>[^\s,。();;]+)"
  63. def get_kv_pattern():
  64. import re
  65. text = """
  66. name: John age: 30 note: invalid;
  67. """
  68. # 正则模式
  69. kv_pattern = r"(?P<key>[a-zA-Z]+)[::](?P<value>.+(?!.*[::]))"
  70. # 提取匹配
  71. matches = re.findall(kv_pattern, text)
  72. # 打印结果
  73. for match in matches:
  74. key, value = match
  75. print("{%s}: {%s}"%(key,value))
  76. def extract_kv_from_sentence(sentence):
  77. list_kv = []
  78. _iter = re.finditer("[::]", sentence)
  79. if _iter:
  80. list_span = []
  81. for iter in _iter:
  82. list_span.append(iter.span())
  83. if len(list_span)==1:
  84. _begin,_end = list_span[0]
  85. if _begin<20 and _end<len(sentence)-1:
  86. _d = DotDict({"key":sentence[0:_begin],"value":sentence[_end:],"key_span":[0,_begin],"value_span":[_end,len(sentence)]})
  87. list_kv.append(_d)
  88. else:
  89. _begin = 0
  90. _end = len(sentence)-1
  91. iter = re.search(kv_pattern,sentence[_begin:_end])
  92. if iter is not None:
  93. _d = DotDict({})
  94. _d["key"] = iter.group("key")
  95. _d["value"] = iter.group("value")
  96. _d["key_span"] = iter.span("key")
  97. _d["value_span"] = iter.span("value")
  98. list_kv.append(_d)
  99. elif len(list_span)>1:
  100. _begin = 0
  101. for _i in range(len(list_span)-1):
  102. _end = list_span[_i+1][0]
  103. iter = re.search(kv_pattern,sentence[_begin:_end])
  104. _begin = list_span[_i][1]
  105. if iter is not None:
  106. _d = DotDict({})
  107. _d["key"] = iter.group("key")
  108. _d["value"] = iter.group("value")
  109. _d["key_span"] = iter.span("key")
  110. _d["value_span"] = iter.span("value")
  111. list_kv.append(_d)
  112. _begin = list_span[-2][1]
  113. _end = len(sentence)
  114. iter = re.search(kv_pattern,sentence[_begin:_end])
  115. if iter is not None:
  116. _d = DotDict({})
  117. _d["key"] = iter.group("key")
  118. _d["value"] = iter.group("value")
  119. _d["key_span"] = iter.span("key")
  120. _d["value_span"] = iter.span("value")
  121. list_kv.append(_d)
  122. # for iter in _iter:
  123. # _d = DotDict({})
  124. # _d["key"] = iter.group("key")
  125. # _d["value"] = iter.group("value")
  126. # _d["key_span"] = iter.span("key")
  127. # _d["value_span"] = iter.span("value")
  128. # list_kv.append(_d)
  129. return list_kv
  130. def extract_kv_from_node(node):
  131. list_kv = []
  132. _text = node.get("text")
  133. if _text:
  134. list_kv = extract_kv_from_sentence(_text)
  135. node["kv"] = list_kv
  136. return list_kv
  137. def get_child_text(node):
  138. _text = node.get("text","")
  139. for child in node.get("children",[]):
  140. _text += get_child_text(child)
  141. return _text
  142. def extract_kv_from_tree(tree):
  143. if isinstance(tree,list):
  144. _count = 0
  145. for child in tree:
  146. _count += extract_kv_from_tree(child)
  147. return _count
  148. if isinstance(tree,dict):
  149. childs = tree.get("children",[])
  150. if len(childs)>0:
  151. _count = 0
  152. for child in childs:
  153. _count += extract_kv_from_tree(child)
  154. if _count==0:
  155. _text = get_child_text(tree)
  156. if "children" in tree:
  157. del tree["children"]
  158. tree["text"] = _text
  159. list_kv = extract_kv_from_node(tree)
  160. _count = len(list_kv)
  161. return _count
  162. if tree.get("tag","")=="p":
  163. _text = get_child_text(tree)
  164. tree["text"] = _text
  165. p_list_kv = extract_kv_from_node(tree)
  166. if len(p_list_kv)>=_count:
  167. if "children" in tree:
  168. del tree["children"]
  169. else:
  170. tree["text"] = ""
  171. return len(p_list_kv)
  172. return _count
  173. else:
  174. list_kv = extract_kv_from_node(tree)
  175. return len(list_kv)
  176. def update_kv_span(list_kv,append_length):
  177. for _d in list_kv:
  178. _d["key_span"][0] += append_length
  179. _d["key_span"][1] += append_length
  180. _d["value_span"][0] += append_length
  181. _d["value_span"][1] += append_length
  182. def get_sentence_from_tree(tree,list_sentences=None):
  183. is_first = False
  184. if list_sentences is None:
  185. list_sentences = []
  186. is_first = True
  187. if isinstance(tree,list):
  188. for child in tree:
  189. get_sentence_from_tree(child,list_sentences)
  190. if isinstance(tree,dict):
  191. childs = tree.get("children",[])
  192. _text = tree.get("text","")
  193. if _text!="":
  194. tree.name = tree.tag
  195. list_sentences.append(tree)
  196. for child in childs:
  197. get_sentence_from_tree(child,list_sentences)
  198. if is_first:
  199. wordOffset_begin = 0
  200. wordOffset_end = 0
  201. for _i in range(len(list_sentences)):
  202. list_sentences[_i].sentence_index = _i
  203. list_sentences[_i].wordOffset_begin = wordOffset_begin
  204. wordOffset_end = wordOffset_begin+len(list_sentences[_i].text)
  205. list_sentences[_i].wordOffset_end = wordOffset_end
  206. wordOffset_begin = wordOffset_end
  207. return list_sentences
  208. def standard_title_context(_title_context):
  209. return _title_context.replace("(","(").replace(")",")").replace(":",":").replace(":",";").replace(",",".").replace(",",".").replace("、",".").replace(".",".")
  210. def standard_product(sentence):
  211. return sentence.replace("(","(").replace(")",")")
  212. import Levenshtein
  213. import copy
  214. def jaccard_score(source,target):
  215. source_set = set([s for s in source])
  216. target_set = set([s for s in target])
  217. if len(source_set)==0 or len(target_set)==0:
  218. return 0
  219. return max(len(source_set&target_set)/len(source_set),len(source_set&target_set)/len(target_set))
  220. def judge_pur_chinese(keyword):
  221. """
  222. 中文字符的编码范围为: u'\u4e00' -- u'\u9fff:只要在此范围内就可以判断为中文字符串
  223. @param keyword:
  224. @return:
  225. """
  226. # 定义一个需要删除的标点符号字符串列表
  227. remove_chars = '[·’!"\#$%&\'()#!()*+,-./:;<=>?\@,:?¥★、….>【】[]《》?“”‘’\[\\]^_`{|}~]+'
  228. # 利用re.sub来删除中文字符串中的标点符号
  229. strings = re.sub(remove_chars, "", keyword) # 将keyword中文字符串中remove_chars中包含的标点符号替换为空字符串
  230. for ch in strings:
  231. if u'\u4e00' <= ch <= u'\u9fff':
  232. pass
  233. else:
  234. return False
  235. return True
  236. def is_similar(source,target,_radio=None):
  237. source = str(source).lower()
  238. target = str(target).lower()
  239. max_len = max(len(source),len(target))
  240. min_len = min(len(source),len(target))
  241. min_ratio = 90
  242. if min_len>=3:
  243. min_ratio = 87
  244. if min_len>=5:
  245. min_ratio = 85
  246. if _radio is not None:
  247. min_ratio = _radio
  248. # dis_len = abs(len(source)-len(target))
  249. # min_dis = min(max_len*0.2,4)
  250. if min_len==0 and max_len>0:
  251. return False
  252. if max_len<=2:
  253. if source==target:
  254. return True
  255. if min_len<2:
  256. return False
  257. #判断相似度
  258. similar = Levenshtein.ratio(source,target)*100
  259. if similar>=min_ratio:
  260. log("%s and %s similar_jaro %d"%(source,target,similar))
  261. return True
  262. similar_jaro = Levenshtein.jaro(source,target)
  263. if similar_jaro*100>=min_ratio:
  264. log("%s and %s similar_jaro %d"%(source,target,similar_jaro*100))
  265. return True
  266. similar_jarow = Levenshtein.jaro_winkler(source,target)
  267. if similar_jarow*100>=min_ratio:
  268. log("%s and %s similar_jaro %d"%(source,target,similar_jarow*100))
  269. return True
  270. if min_len>=5:
  271. if len(source)==max_len and str(source).find(target)>=0:
  272. return True
  273. elif len(target)==max_len and target.find(source)>=0:
  274. return True
  275. elif jaccard_score(source, target)==1 and judge_pur_chinese(source) and judge_pur_chinese(target):
  276. return True
  277. return False
  278. end_pattern = "商务要求|评分标准|商务条件|商务条件"
  279. _param_pattern = "(产品|技术|清单|配置|参数|具体|明细|项目|招标|货物|服务|规格|工作|具体)[及和与]?(指标|配置|条件|要求|参数|需求|规格|条款|名称及要求)|配置清单|(质量|技术).{,10}要求|验收标准|^(参数|功能)$"
  280. meter_pattern = "[><≤≥±]\d+|\d+(?:[μucmkK微毫千]?[米升LlgGmMΩ]|摄氏度|英寸|度|天|VA|dB|bpm|rpm|kPa|mol|cmH20|%|°|Mpa|Hz|K?HZ|℃|W|min|[*×xX])|[*×xX]\d+|/min|\ds[^a-zA-Z]|GB.{,20}标准|PVC|PP|角度|容积|色彩|自动|流量|外径|轴位|折射率|帧率|柱镜|振幅|磁场|镜片|防漏|强度|允差|心率|倍数|瞳距|底座|色泽|噪音|间距|材质|材料|表面|频率|阻抗|浓度|兼容|防尘|防水|内径|实时|一次性|误差|性能|距离|精确|温度|超温|范围|跟踪|对比度|亮度|[横纵]向|均压|负压|正压|可调|设定值|功能|检测|高度|厚度|宽度|深度|[单双多]通道|效果|指数|模式|尺寸|重量|峰值|谷值|容量|寿命|稳定性|高温|信号|电源|电流|转换率|效率|释放量|转速|离心力|向心力|弯曲|电压|功率|气量|国标|标准协议|灵敏度|最大值|最小值|耐磨|波形|高压|性强|工艺|光源|低压|压力|压强|速度|湿度|重量|毛重|[MLX大中小]+码|净重|颜色|[红橙黄绿青蓝紫]色|不锈钢|输入|输出|噪声|认证|配置"
  281. not_meter_pattern = "投标报价|中标金额|商务部分|公章|分值构成|业绩|详见|联系人|联系电话|合同价|金额|采购预算|资金来源|费用|质疑|评审因素|评审标准|商务资信|商务评分|专家论证意见|评标方法|代理服务费|售后服务|评分类型|评分项目|预算金额|得\d+分|项目金额|详见招标文件|乙方"
  282. def getTrs(tbody):
  283. #获取所有的tr
  284. trs = []
  285. if tbody.name=="table":
  286. body = tbody.find("tbody",recursive=False)
  287. if body is not None:
  288. tbody = body
  289. objs = tbody.find_all(recursive=False)
  290. for obj in objs:
  291. if obj.name=="tr":
  292. trs.append(obj)
  293. if obj.name=="tbody" or obj.name=="table":
  294. for tr in obj.find_all("tr",recursive=False):
  295. trs.append(tr)
  296. return trs
  297. def fixSpan(tbody):
  298. # 处理colspan, rowspan信息补全问题
  299. #trs = tbody.findChildren('tr', recursive=False)
  300. trs = getTrs(tbody)
  301. ths_len = 0
  302. ths = list()
  303. trs_set = set()
  304. #修改为先进行列补全再进行行补全,否则可能会出现表格解析混乱
  305. # 遍历每一个tr
  306. for indtr, tr in enumerate(trs):
  307. ths_tmp = tr.findChildren('th', recursive=False)
  308. #不补全含有表格的tr
  309. if len(tr.findChildren('table'))>0:
  310. continue
  311. if len(ths_tmp) > 0:
  312. ths_len = ths_len + len(ths_tmp)
  313. for th in ths_tmp:
  314. ths.append(th)
  315. trs_set.add(tr)
  316. # 遍历每行中的element
  317. tds = tr.findChildren(recursive=False)
  318. for indtd, td in enumerate(tds):
  319. # 若有colspan 则补全同一行下一个位置
  320. if 'colspan' in td.attrs:
  321. if str(re.sub("[^0-9]","",str(td['colspan'])))!="":
  322. col = int(re.sub("[^0-9]","",str(td['colspan'])))
  323. if col<100 and len(td.get_text())<1000:
  324. td['colspan'] = 1
  325. for i in range(1, col, 1):
  326. td.insert_after(copy.copy(td))
  327. for indtr, tr in enumerate(trs):
  328. ths_tmp = tr.findChildren('th', recursive=False)
  329. #不补全含有表格的tr
  330. if len(tr.findChildren('table'))>0:
  331. continue
  332. if len(ths_tmp) > 0:
  333. ths_len = ths_len + len(ths_tmp)
  334. for th in ths_tmp:
  335. ths.append(th)
  336. trs_set.add(tr)
  337. # 遍历每行中的element
  338. tds = tr.findChildren(recursive=False)
  339. for indtd, td in enumerate(tds):
  340. # 若有rowspan 则补全下一行同样位置
  341. if 'rowspan' in td.attrs:
  342. if str(re.sub("[^0-9]","",str(td['rowspan'])))!="":
  343. row = int(re.sub("[^0-9]","",str(td['rowspan'])))
  344. td['rowspan'] = 1
  345. for i in range(1, row, 1):
  346. # 获取下一行的所有td, 在对应的位置插入
  347. if indtr+i<len(trs):
  348. tds1 = trs[indtr + i].findChildren(['td','th'], recursive=False)
  349. if len(tds1) >= (indtd) and len(tds1)>0:
  350. if indtd > 0:
  351. tds1[indtd - 1].insert_after(copy.copy(td))
  352. else:
  353. tds1[0].insert_before(copy.copy(td))
  354. elif indtd-2>0 and len(tds1) > 0 and len(tds1) == indtd - 1: # 修正某些表格最后一列没补全
  355. tds1[indtd-2].insert_after(copy.copy(td))
  356. def getTable(tbody):
  357. #trs = tbody.findChildren('tr', recursive=False)
  358. fixSpan(tbody)
  359. trs = getTrs(tbody)
  360. inner_table = []
  361. for tr in trs:
  362. tr_line = []
  363. tds = tr.findChildren(['td','th'], recursive=False)
  364. if len(tds)==0:
  365. tr_line.append([re.sub('\xa0','',tr.get_text()),0]) # 2021/12/21 修复部分表格没有td 造成数据丢失
  366. for td in tds:
  367. tr_line.append([re.sub('\xa0','',td.get_text()),0])
  368. #tr_line.append([td.get_text(),0])
  369. inner_table.append(tr_line)
  370. return inner_table
  371. def extract_products(list_data,_product,_param_pattern = "产品名称|设备材料|采购内存|标的名称|采购内容|(标的|维修|系统|报价构成|商品|产品|物料|物资|货物|设备|采购品|采购条目|物品|材料|印刷品?|采购|物装|配件|资产|耗材|清单|器材|仪器|器械|备件|拍卖物|标的物|物件|药品|药材|药械|货品|食品|食材|品目|^品名|气体|标项|分项|项目|计划|包组|标段|[分子]?包|子目|服务|招标|中标|成交|工程|招标内容)[\))的]?([、\w]{,4}名称|内容|描述)|标的|标项|项目$|商品|产品|物料|物资|货物|设备|采购品|采购条目|物品|材料|印刷品|物装|配件|资产|招标内容|耗材|清单|器材|仪器|器械|备件|拍卖物|标的物|物件|药品|药材|药械|货品|食品|食材|菜名|^品目$|^品名$|^名称|^内容$"):
  372. _product = standard_product(_product)
  373. list_result = []
  374. list_table_products = []
  375. for _data_i in range(len(list_data)):
  376. _data = list_data[_data_i]
  377. _type = _data["type"]
  378. _text = _data["text"]
  379. if _type=="table":
  380. list_table = _data["list_table"]
  381. if list_table is None:
  382. continue
  383. _check = True
  384. max_length = max([len(a) for a in list_table])
  385. min_length = min([len(a) for a in list_table])
  386. if min_length<max_length/2:
  387. continue
  388. list_head_index = []
  389. _begin_index = 0
  390. head_cell_text = ""
  391. for line_i in range(len(list_table[:2])):
  392. line = list_table[line_i]
  393. line_text = ",".join([cell[0] for cell in line])
  394. for cell_i in range(len(line)):
  395. cell = line[cell_i]
  396. cell_text = cell[0]
  397. if len(cell_text)<10 and re.search(_param_pattern,cell_text) is not None and re.search("单价|数量|预算|限价|总价|品牌|规格|型号|用途|要求|采购量",line_text) is not None:
  398. _begin_index = line_i+1
  399. list_head_index.append(cell_i)
  400. for line_i in range(len(list_table)):
  401. line = list_table[line_i]
  402. for cell_i in list_head_index:
  403. if cell_i>=len(line):
  404. continue
  405. cell = line[cell_i]
  406. cell_text = cell[0]
  407. head_cell_text += cell_text
  408. # print("===head_cell_text",head_cell_text)
  409. if re.search("招标人|采购人|项目编号|项目名称|金额|^\d+$",head_cell_text) is not None:
  410. list_head_index = []
  411. for line in list_table:
  412. line_text = ",".join([cell[0] for cell in line])
  413. for cell_i in range(len(line)):
  414. cell = line[cell_i]
  415. cell_text = cell[0]
  416. if cell_text is not None and _product is not None and len(cell_text)<len(_product)*10 and cell_text.find(_product)>=0 and re.search("单价|数量|总价|规格|品牌|型号|用途|要求|采购量",line_text) is not None:
  417. list_head_index.append(cell_i)
  418. list_head_index = list(set(list_head_index))
  419. if len(list_head_index)>0:
  420. has_number = False
  421. for cell_i in list_head_index:
  422. table_products = []
  423. for line_i in range(_begin_index,len(list_table)):
  424. line = list_table[line_i]
  425. for _i in range(len(line)):
  426. cell = line[_i]
  427. cell_text = cell[0]
  428. if re.search("^\d+$",cell_text) is not None:
  429. has_number = True
  430. if cell_i>=len(line):
  431. continue
  432. cell = line[cell_i]
  433. cell_text = cell[0]
  434. if re.search(_param_pattern,cell_text) is None or has_number:
  435. if re.search("^[\da-zA-Z]+$",cell_text) is None:
  436. table_products.append(cell_text)
  437. if len(table_products)>0:
  438. logger.debug("table products %s"%(str(table_products)))
  439. if min([len(x) for x in table_products])>0 and max([len(x) for x in table_products])<=30:
  440. if re.search("招标人|代理人|预算|数量|交货期|品牌|产地","".join(table_products)) is None:
  441. list_table_products.append(table_products)
  442. _find = False
  443. for table_products in list_table_products:
  444. for _p in table_products:
  445. if is_similar(_product,_p,90):
  446. _find = True
  447. logger.debug("similar table_products %s"%(str(table_products)))
  448. list_result = list(set([a for a in table_products if len(a)>1 and len(a)<20 and re.search("费用|预算|合计|金额|万元|运费|^其他$",a) is None]))
  449. break
  450. if not _find:
  451. for table_products in list_table_products:
  452. list_result.extend(table_products)
  453. list_result = list(set([a for a in list_result if len(a)>1 and len(a)<30 and re.search("费用|预算|合计|金额|万元|运费",a) is None]))
  454. return list_result
  455. def get_childs(childs, max_depth=None):
  456. list_data = []
  457. for _child in childs:
  458. list_data.append(_child)
  459. childs2 = _child.get("child_title",[])
  460. if len(childs2)>0 and (max_depth==None or max_depth>0):
  461. for _child2 in childs2:
  462. if max_depth != None:
  463. list_data.extend(get_childs([_child2], max_depth-1))
  464. else:
  465. list_data.extend(get_childs([_child2], None))
  466. return list_data
  467. class Html2KVTree():
  468. def __init__(self,_html,auto_merge_table=True,list_obj = []):
  469. if _html is None:
  470. _html = ""
  471. self.html = _html
  472. self.auto_merge_table = auto_merge_table
  473. if list_obj:
  474. self.list_obj = list_obj
  475. else:
  476. _tree = html_to_tree(html_content)
  477. extract_kv_from_tree(_tree)
  478. self.list_obj = get_sentence_from_tree(_tree)
  479. print(len(self.list_obj))
  480. # for obj in self.list_obj:
  481. # print("obj",obj.get_text()[:20])
  482. self.tree = self.buildParsetree(self.list_obj,[],auto_merge_table)
  483. # #识别目录树
  484. # self.print_tree(self.tree,"-|")
  485. def get_soup_objs(self,soup,list_obj=None):
  486. if list_obj is None:
  487. list_obj = []
  488. childs = soup.find_all(recursive=False)
  489. for _obj in childs:
  490. childs1 = _obj.find_all(recursive=False)
  491. if len(childs1)==0 or len(_obj.get_text())<40 or _obj.name=="table":
  492. list_obj.append(_obj)
  493. elif _obj.name=="p":
  494. list_obj.append(_obj)
  495. else:
  496. self.get_soup_objs(_obj,list_obj)
  497. return list_obj
  498. def fix_tree(self,_product):
  499. products = extract_products(self.tree,_product)
  500. if len(products)>0:
  501. self.tree = self.buildParsetree(self.list_obj,products,self.auto_merge_table)
  502. def print_tree(self,tree,append="",set_tree_id=None):
  503. if set_tree_id is None:
  504. set_tree_id = set()
  505. if append=="":
  506. for t in tree:
  507. logger.debug("%s text:%s title:%s title_text:%s before:%s after%s product:%s"%("==>",t["text"][:50],t["sentence_title"],t["sentence_title_text"],t["title_before"],t["title_after"],t["has_product"]))
  508. for t in tree:
  509. _id = id(t)
  510. if _id in set_tree_id:
  511. continue
  512. set_tree_id.add(_id)
  513. logger.info("%s text:%s title:%s title_text:%s before:%s after%s product:%s kv:%s"%(append,t["text"][:50],t["sentence_title"],t["sentence_title_text"],t["title_before"],t["title_after"],t["has_product"],str(t["kv"])))
  514. childs = t["child_title"]
  515. self.print_tree(childs,append=append+"-|",set_tree_id=set_tree_id)
  516. def is_title_first(self,title):
  517. if title in ("一","1","Ⅰ","a","A"):
  518. return True
  519. return False
  520. def find_title_by_pattern(self,_text,_pattern="(^|★|▲|:|:|\s+)(?P<title_1>(?P<title_1_index_0_0>第?)(?P<title_1_index_1_1>[一二三四五六七八九十ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)(?P<title_1_index_2_0>[、章册包标部.::]+))|" \
  521. "([\s★▲\*]*)(?P<title_3>(?P<title_3_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?)(?P<title_3_index_0_1>[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)(?P<title_3_index_0_2>[、章册包标部.::]+))|" \
  522. "([\s★▲\*]*)(?P<title_4>(?P<title_4_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?第?)(?P<title_4_index_1_1>[一二三四五六七八九十]+)(?P<title_4_index_2_0>[节章册部\.::、、]+))|" \
  523. "([\s★▲\*]*)(?P<title_5>(?P<title_5_index_0_0>^)(?P<title_5_index_1_1>[一二三四五六七八九十]+)(?P<title_5_index_2_0>)[^一二三四五六七八九十节章册部\.::、])|" \
  524. "([\s★▲\*]*)(?P<title_12>(?P<title_12_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?\d{1,2}[\..、\s\-]\d{1,2}[\..、\s\-]\d{1,2}[\..、\s\-]\d{1,2}[\..、\s\-])(?P<title_12_index_1_1>\d{1,2})(?P<title_12_index_2_0>[\..、\s\-]?))|"\
  525. "([\s★▲\*]*)(?P<title_11>(?P<title_11_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?\d{1,2}[\..、\s\-]\d{1,2}[\..、\s\-]\d{1,2}[\..、\s\-])(?P<title_11_index_1_1>\d{1,2})(?P<title_11_index_2_0>[\..、\s\-]?))|" \
  526. "([\s★▲\*]*)(?P<title_10>(?P<title_10_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?\d{1,2}[\..、\s\-]\d{1,2}[\..、\s\-])(?P<title_10_index_1_1>\d{1,2})(?P<title_10_index_2_0>[\..、\s\-]?))|" \
  527. "([\s★▲\*]*)(?P<title_7>(?P<title_7_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?\d{1,2}[\..\s\-])(?P<title_7_index_1_1>\d{1,2})(?P<title_7_index_2_0>[\..包标::、\s\-]*))|" \
  528. "(^[\s★▲\*]*)(?P<title_6>(?P<title_6_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?包?)(?P<title_6_index_0_1>\d{1,2})(?P<title_6_index_2_0>[\..、\s\-包标]*))|" \
  529. "([\s★▲\*]*)(?P<title_15>(?P<title_15_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?[((]?)(?P<title_15_index_1_1>\d{1,2})(?P<title_15_index_2_0>[))包标\..::、]+))|" \
  530. "([\s★▲\*]+)(?P<title_17>(?P<title_17_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?[((]?)(?P<title_17_index_1_1>[a-zA-Z]+)(?P<title_17_index_2_0>[))包标\..::、]+))|" \
  531. "([\s★▲\*]*)(?P<title_19>(?P<title_19_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?[((]?)(?P<title_19_index_1_1>[一二三四五六七八九十ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)(?P<title_19_index_2_0>[))]))"
  532. ):
  533. _se = re.search(_pattern,_text)
  534. groups = []
  535. if _se is not None:
  536. e = _se.end()
  537. if re.search('(时间|日期|编号|账号|号码|手机|价格|\w价|人民币|金额|得分|分值|总分|满分|最高得|扣|减|数量|评委)[::]?\d', _se.group(0)) or (re.search('\d[.::]?$', _se.group(0)) and re.search('^[\d年月日万元天个分秒台条A-Za-z]|^(小时)', _text[e:])):
  538. return None
  539. elif re.match('[二三四五六七八九十]\w{1,2}[市区县]|五金|四川|八疆|九龙|[一二三四五六七八九十][层天标包]', _text) and re.match('[一二三四五六七八九十]', _se.group(0)): # 289765335 排除三明市等开头作为大纲
  540. return None
  541. elif re.search('^[\u4e00-\u9fa5]+[::]', _text[:e]):
  542. return None
  543. _gd = _se.groupdict()
  544. for k,v in _gd.items():
  545. if v is not None:
  546. groups.append((k,v))
  547. if len(groups):
  548. groups.sort(key=lambda x:x[0])
  549. return groups
  550. return None
  551. def make_increase(self,_sort,_title,_add=1):
  552. if len(_title)==0 and _add==0:
  553. return ""
  554. if len(_title)==0 and _add==1:
  555. return _sort[0]
  556. _index = _sort.index(_title[-1])
  557. next_index = (_index+_add)%len(_sort)
  558. next_chr = _sort[next_index]
  559. if _index==len(_sort)-1:
  560. _add = 1
  561. else:
  562. _add = 0
  563. return next_chr+self.make_increase(_sort,_title[:-1],_add)
  564. def get_next_title(self,_title):
  565. if re.search("^\d+$",_title) is not None:
  566. return str(int(_title)+1)
  567. if re.search("^[一二三四五六七八九十百]+$",_title) is not None:
  568. if _title[-1]=="十":
  569. return _title+"一"
  570. if _title[-1]=="百":
  571. return _title+"零一"
  572. if _title[-1]=="九":
  573. if len(_title)==1:
  574. return "十"
  575. if len(_title)==2:
  576. if _title[0]=="十":
  577. return "二十"
  578. if len(_title)==3:
  579. if _title[0]=="九":
  580. return "一百"
  581. else:
  582. _next_title = self.make_increase(['一','二','三','四','五','六','七','八','九','十'],re.sub("[十百]",'',_title[0]))
  583. return _next_title+"十"
  584. _next_title = self.make_increase(['一','二','三','四','五','六','七','八','九','十'],re.sub("[十百]",'',_title))
  585. _next_title = list(_next_title)
  586. _next_title.reverse()
  587. if _next_title[-1]!="十":
  588. if len(_next_title)>=2:
  589. _next_title.insert(-1,'十')
  590. if len(_next_title)>=4:
  591. _next_title.insert(-3,'百')
  592. if _title[0]=="十":
  593. if _next_title=="十":
  594. _next_title = ["二","十"]
  595. _next_title.insert(0,"十")
  596. _next_title = "".join(_next_title)
  597. return _next_title
  598. if re.search("^[a-z]+$",_title) is not None:
  599. _next_title = self.make_increase([chr(i+ord('a')) for i in range(26)],_title)
  600. _next_title = list(_next_title)
  601. _next_title.reverse()
  602. return "".join(_next_title)
  603. if re.search("^[A-Z]+$",_title) is not None:
  604. _next_title = self.make_increase([chr(i+ord('A')) for i in range(26)],_title)
  605. _next_title = list(_next_title)
  606. _next_title.reverse()
  607. return "".join(_next_title)
  608. if re.search("^[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]$",_title) is not None:
  609. _sort = ["Ⅰ","Ⅱ","Ⅲ","Ⅳ","Ⅴ","Ⅵ","Ⅶ","Ⅷ","Ⅸ","Ⅹ","Ⅺ","Ⅻ"]
  610. _index = _sort.index(_title)
  611. if _index<len(_sort)-1:
  612. return _sort[_index+1]
  613. return None
  614. def count_title_before(self,list_obj):
  615. dict_before = {}
  616. dict_sentence_count = {}
  617. illegal_sentence = set()
  618. for obj_i in range(len(list_obj)):
  619. obj = list_obj[obj_i]
  620. _type = "sentence"
  621. _text = obj.text.strip()
  622. if obj.name=="table":
  623. _type = "table"
  624. _text = str(obj)
  625. _append = False
  626. if _type=="sentence":
  627. if len(_text)>10 and len(_text)<100:
  628. if _text not in dict_sentence_count:
  629. dict_sentence_count[_text] = 0
  630. dict_sentence_count[_text] += 1
  631. if re.search("\d+页",_text) is not None:
  632. illegal_sentence.add(_text)
  633. elif len(_text)<10:
  634. if re.search("第\d+页",_text) is not None:
  635. illegal_sentence.add(_text)
  636. sentence_groups = self.find_title_by_pattern(_text[:10])
  637. if sentence_groups:
  638. # c062f53cf83401e671822003d63c1828print("sentence_groups",sentence_groups)
  639. sentence_title = sentence_groups[0][0]
  640. sentence_title_text = sentence_groups[0][1]
  641. title_index = sentence_groups[-2][1]
  642. title_before = sentence_groups[1][1].replace("(","(").replace(":",":").replace(":",";").replace(",",".").replace(",",".").replace("、",".")
  643. title_after = sentence_groups[-1][1].replace(")",")").replace(":",":").replace(":",";").replace(",",".").replace(",",".").replace("、",".")
  644. next_index = self.get_next_title(title_index)
  645. if title_before not in dict_before:
  646. dict_before[title_before] = 0
  647. dict_before[title_before] += 1
  648. for k,v in dict_sentence_count.items():
  649. if v>10:
  650. illegal_sentence.add(k)
  651. return dict_before,illegal_sentence
  652. def is_page_no(self,sentence):
  653. if len(sentence)<10:
  654. if re.search("\d+页|^\-\d+\-$",sentence) is not None:
  655. return True
  656. def block_tree(self,childs):
  657. for child in childs:
  658. if not child["block"]:
  659. child["block"] = True
  660. childs2 = child["child_title"]
  661. self.block_tree(childs2)
  662. def buildParsetree(self,list_obj,products=[],auto_merge_table=True,auto_append=False):
  663. self.parseTree = None
  664. trees = []
  665. list_length = []
  666. for obj in list_obj[:200]:
  667. if obj.name!="table":
  668. list_length.append(len(obj.text))
  669. if len(list_length)>0:
  670. max_length = max(list_length)
  671. else:
  672. max_length = 40
  673. max_length = min(max_length,40)
  674. logger.debug("%s:%d"%("max_length",max_length))
  675. list_data = []
  676. last_table_index = None
  677. last_table_columns = None
  678. last_table = None
  679. dict_before,illegal_sentence = self.count_title_before(list_obj)
  680. for obj_i in range(len(list_obj)):
  681. obj = list_obj[obj_i]
  682. # logger.debug("==obj %s"%obj.text[:20])
  683. _type = "sentence"
  684. _text = standard_product(obj.text)
  685. if obj.name=="table":
  686. _type = "table"
  687. _text = standard_product(str(obj))
  688. _append = False
  689. sentence_title = None
  690. sentence_title_text = None
  691. sentence_groups = None
  692. title_index = None
  693. next_index = None
  694. parent_title = None
  695. title_before = None
  696. title_after = None
  697. title_next = None
  698. childs = []
  699. # new
  700. sentence_index = obj.sentence_index
  701. wordOffset_begin = obj.wordOffset_begin
  702. wordOffset_end = obj.wordOffset_end
  703. list_kv = obj.get("kv",[])
  704. list_table = None
  705. block = False
  706. has_product = False
  707. if _type=="sentence":
  708. if _text in illegal_sentence:
  709. continue
  710. sentence_groups = self.find_title_by_pattern(_text[:10])
  711. if sentence_groups:
  712. title_before = standard_title_context(sentence_groups[1][1])
  713. title_after = sentence_groups[-1][1]
  714. sentence_title_text = sentence_groups[0][1]
  715. other_text = _text.replace(sentence_title_text,"")
  716. if (title_before in dict_before and dict_before[title_before]>1) or title_after!="":
  717. sentence_title = sentence_groups[0][0]
  718. title_index = sentence_groups[-2][1]
  719. next_index = self.get_next_title(title_index)
  720. other_text = _text.replace(sentence_title_text,"")
  721. for p in products:
  722. if other_text.strip()==p.strip():
  723. has_product = True
  724. else:
  725. _fix = False
  726. for p in products:
  727. if other_text.strip()==p.strip():
  728. title_before = "=产品"
  729. sentence_title = "title_0"
  730. sentence_title_text = p
  731. title_index = "0"
  732. title_after = "产品="
  733. next_index = "0"
  734. _fix = True
  735. has_product = True
  736. break
  737. if not _fix:
  738. title_before = None
  739. title_after = None
  740. sentence_title_text = None
  741. else:
  742. if len(_text)<40 and re.search(_param_pattern,_text) is not None:
  743. for p in products:
  744. if _text.find(p)>=0:
  745. title_before = "=产品"
  746. sentence_title = "title_0"
  747. sentence_title_text = p
  748. title_index = "0"
  749. title_after = "产品="
  750. next_index = "0"
  751. _fix = True
  752. has_product = True
  753. break
  754. # 合并两个非标题句子 20241106 注销,由于 485441521 招标内容结束位置不对
  755. if auto_append:
  756. if _type=="sentence":
  757. if sentence_title is None and len(list_data)>0 and list_data[-1]["sentence_title"] is not None and list_data[-1]["line_width"]>=max_length*0.6:
  758. list_data[-1]["text"] += _text
  759. list_data[-1]["line_width"] = len(_text)
  760. update_kv_span(list_kv,len(_text))
  761. list_data[-1]["kv"].extend(list_kv)
  762. _append = True
  763. elif sentence_title is None and len(list_data)>0 and _type==list_data[-1]["type"]:
  764. if list_data[-1]["line_width"]>=max_length*0.7:
  765. list_data[-1]["text"] += _text
  766. list_data[-1]["line_width"] = len(_text)
  767. update_kv_span(list_kv,len(_text))
  768. list_data[-1]["kv"].extend(list_kv)
  769. _append = True
  770. if _type=="table":
  771. _soup = BeautifulSoup(_text,"lxml")
  772. _table = _soup.find("table")
  773. if _table is not None:
  774. list_table = getTable(_table)
  775. if len(list_table)==0:
  776. continue
  777. table_columns = len(list_table[0])
  778. if auto_merge_table:
  779. if last_table_index is not None and abs(obj_i-last_table_index)<=2 and last_table_columns is not None and last_table_columns==table_columns:
  780. if last_table is not None:
  781. trs = getTrs(_table)
  782. last_tbody = BeautifulSoup(last_table["text"],"lxml")
  783. _table = last_tbody.find("table")
  784. last_trs = getTrs(_table)
  785. _append = True
  786. for _line in list_table:
  787. last_table["list_table"].append(_line)
  788. if len(last_trs)>0:
  789. for _tr in trs:
  790. last_trs[-1].insert_after(copy.copy(_tr))
  791. last_table["text"] = re.sub("</?html>|</?body>","",str(last_tbody))
  792. last_table_index = obj_i
  793. last_table_columns = len(list_table[-1])
  794. if not _append:
  795. _data = {"type":_type, "text":_text,"list_table":list_table,"line_width":len(_text),"sentence_title":sentence_title,"title_index":title_index,
  796. "sentence_title_text":sentence_title_text,"sentence_groups":sentence_groups,"parent_title":parent_title,
  797. "child_title":childs,"title_before":title_before,"title_after":title_after,"title_next":title_next,"next_index":next_index,
  798. "block":block,"has_product":has_product,
  799. "sentence_index":sentence_index,"wordOffset_begin":wordOffset_begin,"wordOffset_end":wordOffset_end,
  800. "kv":list_kv
  801. }
  802. if _type=="table":
  803. last_table = _data
  804. last_table_index = obj_i
  805. if list_table:
  806. last_table_columns = last_table_columns = len(list_table[-1])
  807. if sentence_title is not None:
  808. if len(list_data)>0:
  809. if self.is_title_first(title_index):
  810. for i in range(1,len(list_data)+1):
  811. _d = list_data[-i]
  812. if _d["sentence_title"] is not None:
  813. _data["parent_title"] = _d
  814. _d["child_title"].append(_data)
  815. break
  816. else:
  817. _find = False
  818. for i in range(1,len(list_data)+1):
  819. if _find:
  820. break
  821. _d = list_data[-i]
  822. if _d.get("sentence_title")==sentence_title and title_before==_d["title_before"] and title_after==_d["title_after"]:
  823. if _d["next_index"]==title_index and _d["title_next"] is None and not _d["block"]:
  824. _data["parent_title"] = _d["parent_title"]
  825. _d["title_next"] = _data
  826. if len(_d["child_title"])>0:
  827. _d["child_title"][-1]["title_next"] = ""
  828. self.block_tree(_d["child_title"])
  829. if _d["parent_title"] is not None:
  830. _d["parent_title"]["child_title"].append(_data)
  831. _find = True
  832. break
  833. for i in range(1,len(list_data)+1):
  834. if _find:
  835. break
  836. _d = list_data[-i]
  837. if i==1 and not _d["block"] and _d.get("sentence_title")==sentence_title and title_before==_d["title_before"] and title_after==_d["title_after"]:
  838. _data["parent_title"] = _d["parent_title"]
  839. _d["title_next"] = _data
  840. if len(_d["child_title"])>0:
  841. _d["child_title"][-1]["title_next"] = ""
  842. self.block_tree(_d["child_title"])
  843. if _d["parent_title"] is not None:
  844. _d["parent_title"]["child_title"].append(_data)
  845. _find = True
  846. break
  847. title_before = standard_title_context(title_before)
  848. title_after = standard_title_context(title_after)
  849. for i in range(1,len(list_data)+1):
  850. if _find:
  851. break
  852. _d = list_data[-i]
  853. if _d.get("sentence_title")==sentence_title and title_before==standard_title_context(_d["title_before"]) and title_after==standard_title_context(_d["title_after"]):
  854. if _d["next_index"]==title_index and _d["title_next"] is None and not _d["block"]:
  855. _data["parent_title"] = _d["parent_title"]
  856. _d["title_next"] = _data
  857. if len(_d["child_title"])>0:
  858. _d["child_title"][-1]["title_next"] = ""
  859. self.block_tree(_d["child_title"])
  860. if _d["parent_title"] is not None:
  861. _d["parent_title"]["child_title"].append(_data)
  862. _find = True
  863. break
  864. for i in range(1,len(list_data)+1):
  865. if _find:
  866. break
  867. _d = list_data[-i]
  868. if not _d["block"] and _d.get("sentence_title")==sentence_title and title_before==standard_title_context(_d["title_before"]) and title_after==standard_title_context(_d["title_after"]):
  869. _data["parent_title"] = _d["parent_title"]
  870. _d["title_next"] = _data
  871. if len(_d["child_title"])>0:
  872. _d["child_title"][-1]["title_next"] = ""
  873. # self.block_tree(_d["child_title"])
  874. if _d["parent_title"] is not None:
  875. _d["parent_title"]["child_title"].append(_data)
  876. _find = True
  877. break
  878. for i in range(1,min(len(list_data)+1,20)):
  879. if _find:
  880. break
  881. _d = list_data[-i]
  882. if not _d["block"] and _d.get("sentence_title")==sentence_title and title_before==standard_title_context(_d["title_before"]):
  883. _data["parent_title"] = _d["parent_title"]
  884. _d["title_next"] = _data
  885. if len(_d["child_title"])>0:
  886. _d["child_title"][-1]["title_next"] = ""
  887. # self.block_tree(_d["child_title"])
  888. if _d["parent_title"] is not None:
  889. _d["parent_title"]["child_title"].append(_data)
  890. _find = True
  891. break
  892. if not _find:
  893. if len(list_data)>0:
  894. for i in range(1,len(list_data)+1):
  895. _d = list_data[-i]
  896. if _d.get("sentence_title") is not None:
  897. _data["parent_title"] = _d
  898. _d["child_title"].append(_data)
  899. break
  900. else:
  901. if len(list_data)>0:
  902. for i in range(1,len(list_data)+1):
  903. _d = list_data[-i]
  904. if _d.get("sentence_title") is not None:
  905. _data["parent_title"] = _d
  906. _d["child_title"].append(_data)
  907. break
  908. list_data.append(_data)
  909. for _data in list_data:
  910. childs = _data["child_title"]
  911. for c_i in range(len(childs)):
  912. cdata = childs[c_i]
  913. if cdata["has_product"]:
  914. continue
  915. else:
  916. if c_i>0:
  917. last_cdata = childs[c_i-1]
  918. if cdata["sentence_title"] is not None and last_cdata["sentence_title"] is not None and last_cdata["title_before"]==cdata["title_before"] and last_cdata["title_after"]==cdata["title_after"] and last_cdata["has_product"]:
  919. cdata["has_product"] = True
  920. if c_i<len(childs)-1:
  921. last_cdata = childs[c_i+1]
  922. if cdata["sentence_title"] is not None and last_cdata["sentence_title"] is not None and last_cdata["title_before"]==cdata["title_before"] and last_cdata["title_after"]==cdata["title_after"] and last_cdata["has_product"]:
  923. cdata["has_product"] = True
  924. for c_i in range(len(childs)):
  925. cdata = childs[len(childs)-1-c_i]
  926. if cdata["has_product"]:
  927. continue
  928. else:
  929. if c_i>0:
  930. last_cdata = childs[c_i-1]
  931. if cdata["sentence_title"] is not None and last_cdata["sentence_title"] is not None and last_cdata["title_before"]==cdata["title_before"] and last_cdata["title_after"]==cdata["title_after"] and last_cdata["has_product"]:
  932. cdata["has_product"] = True
  933. if c_i<len(childs)-1:
  934. last_cdata = childs[c_i+1]
  935. if cdata["sentence_title"] is not None and last_cdata["sentence_title"] is not None and last_cdata["title_before"]==cdata["title_before"] and last_cdata["title_after"]==cdata["title_after"] and last_cdata["has_product"]:
  936. cdata["has_product"] = True
  937. return list_data
  938. def get_sentence_tree(self):
  939. return self.tree
  940. def extract_kv(self,k_pattern,from_kv=True,from_outline=True):
  941. result_kv = []
  942. try:
  943. re.compile(k_pattern)
  944. except Exception as e:
  945. log("k_pattern error: "+str(e))
  946. traceback.print_exc()
  947. return result_kv
  948. for sentence in self.tree:
  949. _text = sentence["text"]
  950. list_kv = sentence.get("kv",[])
  951. sentence_index_from = sentence["sentence_index"]
  952. sentence_index_to = sentence_index_from
  953. if from_kv:
  954. for _d in list_kv:
  955. _k = _d.get("key","")
  956. _v = _d.get("value","")
  957. _k_span = _d.get("key_span",[])
  958. _v_span = _d.get("value_span",[])
  959. if re.search(k_pattern,_k) is not None:
  960. result_kv.append({"key":_k,"value":_v,"from_kv":True,"key_sentence_index_from":sentence_index_from,
  961. "key_sentence_index_to":sentence_index_to,"value_sentence_index_from":sentence_index_from,
  962. "value_sentence_index_to":sentence_index_to,
  963. "key_span":_k_span,
  964. "value_span":_v_span})
  965. if from_outline:
  966. if re.search(k_pattern,_text) is not None and sentence.get("sentence_title") is not None:
  967. childs = get_childs([sentence])
  968. _child_text = ""
  969. for _child in childs:
  970. sentence_index_to = _child["sentence_index"]
  971. _child_text+=_child["text"]
  972. result_kv.append({"key":_text,"value":_child_text,"from_outline":True,"key_sentence_index_from":sentence_index_from,
  973. "key_sentence_index_to":sentence_index_from,"value_sentence_index_from":sentence_index_from,
  974. "value_sentence_index_to":sentence_index_to,})
  975. return result_kv
  976. # def extract_kvs_from_table(self,list_pattern):
  977. if __name__ == '__main__':
  978. # HTML 文本
  979. html_content = """
  980. <div>
  981. <div>
  982. 中介服务交易|中介服务交易;中选公示;广东省中介超市交易系统
  983. </div>
  984. <div>
  985. 采购项目编码: 4451024573035462411180923
  986. </div>
  987. <div>
  988. 采购项目名称: 意溪中学办公楼一楼卫生间、保密室、运动场围墙及零碎修缮工程(结算审核)
  989. </div>
  990. <div>
  991. 项目业主名称: <a target="_blank" class="markBlue" href="/bdqyhx/212724763956953088.html" style="color: #3083EB !important;text-decoration: underline;">潮州市湘桥区意溪中学</a>
  992. </div>
  993. <div>
  994. 中介服务事项: 无(属于非行政管理的中介服务项目采购)
  995. </div>
  996. <div>
  997. 投资审批项目编码:
  998. </div>
  999. <div>
  1000. 服务金额: 暂不做评估与测算
  1001. </div>
  1002. <div>
  1003. 金额说明: 按粤价函【2011】742号文及潮财建【2019】19号文的收费标准执行 (行业收费标准)计算。
  1004. </div>
  1005. <div>
  1006. 选取中介机构方式: 直接选取
  1007. </div>
  1008. <div>
  1009. 业务单位咨询电话: 13715781262
  1010. </div>
  1011. <div>
  1012. 监督举报:
  1013. </div>
  1014. <div>
  1015. 中选中介机构名称: <a target="_blank" class="markBlue" href="/bdqyhx/216479817075470337.html" style="color: #3083EB !important;text-decoration: underline;">中庭国际设计有限公司</a>
  1016. </div>
  1017. <div>
  1018. 中介机构联系地址: 广州市天河区大观南路26号C203A
  1019. </div>
  1020. </div>
  1021. """
  1022. _tree = html_to_tree(html_content)
  1023. extract_kv_from_tree(_tree)
  1024. list_sentences = get_sentence_from_tree(_tree)
  1025. _pd = Html2KVTree(html_content)
  1026. _pd.print_tree(_pd.tree,"-|")
  1027. list_kv = _pd.extract_kv("交货地点")
  1028. print(list_kv)