html_2_kvtree.py 78 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709
  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. from BiddingKG.dl.interface.Preprocessing import tableToText
  11. from uuid import uuid4
  12. def log(msg):
  13. '''
  14. @summary:打印信息
  15. '''
  16. logger.info(msg)
  17. class DotDict(dict):
  18. def __getattr__(self,name):
  19. try:
  20. return self[name]
  21. except KeyError:
  22. raise AttributeError("No attribute '%s'" % name)
  23. def __setattr__(self,name,value):
  24. self[name] = value
  25. def get_tables(soup,dict_table = None):
  26. is_first = False
  27. if dict_table is None:
  28. dict_table = {"children":[]}
  29. is_first = True
  30. if soup and soup.name:
  31. childs = soup.contents
  32. else:
  33. childs = []
  34. # tr+tbody
  35. _flag = False
  36. if len(childs)>=2:
  37. if childs[0].name=="tr" and childs[1].name=="tbody":
  38. childs[1].insert(0,copy.copy(childs[0]))
  39. childs[0].decompose()
  40. _flag = True
  41. childs_bak = childs
  42. # tbody+tbody
  43. _flag = False
  44. if soup and soup.name:
  45. childs = soup.find_all("tbody",recursive=False)
  46. if len(childs)>=2:
  47. if childs[0].name=="tbody" and childs[1].name=="tbody":
  48. child0_tr = childs[0].find_all("tr",recursive=False)
  49. has_td_count = 0
  50. tr_line = None
  51. for tr in child0_tr:
  52. if len(tr.find_all("td",recursive=False))>0:
  53. has_td_count += 1
  54. tr_line = tr
  55. if has_td_count==1:
  56. childs[1].insert(0,copy.copy(tr_line))
  57. childs[0].decompose()
  58. _flag = True
  59. childs = childs_bak
  60. for child in childs:
  61. _d = {"children":[]}
  62. if child.name in ("table","tbody"):
  63. if len(child.find_all("tr",recursive=False))>0:
  64. # _d["table"] = str(child)
  65. _d["table"] = child
  66. dict_table["children"].append(_d)
  67. child_dict_table = get_tables(child,_d)
  68. if is_first:
  69. if soup.name in ("table","tbody"):
  70. if not _flag:
  71. if len(soup.find_all("tr",recursive=False))>0:
  72. # dict_table["table"] = str(soup)
  73. dict_table["table"] = soup
  74. dict_table = squeeze_tables(dict_table)
  75. return dict_table
  76. def squeeze_tables(dict_table):
  77. _i = -1
  78. new_children = []
  79. for child in dict_table["children"]:
  80. _i += 1
  81. child_table = squeeze_tables(child)
  82. if child_table is not None:
  83. new_children.append(child_table)
  84. if dict_table.get("table") is not None:
  85. if len(new_children)>0:
  86. dict_table["children"] = new_children
  87. else:
  88. del dict_table["children"]
  89. return dict_table
  90. if len(new_children)==1:
  91. return new_children[0]
  92. if len(new_children)>1:
  93. dict_table["children"] = new_children
  94. return dict_table
  95. return None
  96. def table_to_tree(soup,json_obj=None):
  97. if json_obj is None:
  98. json_obj = DotDict({"tag": "table","children":[]})
  99. dict_table = get_tables(soup)
  100. children = dict_table.get("children",[])
  101. for child in children:
  102. _d = DotDict({"tag": "table","children":[]})
  103. json_obj["children"].append(_d)
  104. table = child.get("table")
  105. if table is not None:
  106. table_id = str(uuid4())
  107. table_to_tree(table,_d)
  108. table = dict_table.get("table")
  109. if table is not None:
  110. table_id = str(uuid4())
  111. json_obj["table_id"] = table_id
  112. soup, kv_list, text = tableToText(table,return_kv=True)
  113. _flag = False
  114. if soup and soup.name:
  115. if soup.contents:
  116. _flag = True
  117. soup.contents[0].insert_before(table_id)
  118. if not _flag:
  119. soup.insert_before(table_id)
  120. json_obj["text"] = text
  121. json_obj["kv"] = kv_list
  122. for _d in kv_list:
  123. _d["position"] = {"key_begin_sentence":0,
  124. "key_begin_sentence_start":_d.get("key_sen_index",0),
  125. "key_end_sentence":0,
  126. "key_end_sentence_end":_d.get("key_sen_index",0)+len(_d.get("key","")),
  127. "value_begin_sentence":0,
  128. "value_begin_sentence_start":_d.get("value_sen_index",0),
  129. "value_end_sentence":0,
  130. "value_end_sentence_end":_d.get("value_sen_index",0)+len(_d.get("value",""))
  131. }
  132. if "key_sen_index" in _d:
  133. _d.pop("key_sen_index")
  134. if "value_sen_index" in _d:
  135. _d.pop("value_sen_index")
  136. return json_obj
  137. def update_table_position(table,sentence_index):
  138. def get_table_idx_lengths(list_table_id,index):
  139. _length = 0
  140. for _d in list_table_id:
  141. table_id = _d.get("table_id")
  142. idx = _d.get("idx",-1)
  143. if idx>=0 and _idx<=index:
  144. _length += len(table_id)
  145. return _length
  146. def get_sentence_index(list_sent_span,idx):
  147. list_sent_span.sort(key=lambda x:x[0])
  148. for _i in range(len(list_sent_span)):
  149. if list_sent_span[_i][0]<=idx and idx<=list_sent_span[_i][1]:
  150. return _i
  151. return 0
  152. def get_list_tables(table,list_table=[]):
  153. table_id = table.get("table_id")
  154. if table_id:
  155. list_table.append(table)
  156. childs = table.get("children",[])
  157. for child in childs:
  158. get_list_tables(child,list_table)
  159. return list_table
  160. tables = get_list_tables(table)
  161. if tables:
  162. list_table_id = []
  163. text = tables[0].get("text","")
  164. for table in tables:
  165. table_id = table.get("table_id")
  166. if table_id:
  167. _idx = text.find(table_id)
  168. list_table_id.append({"table_id":table_id,"idx":_idx})
  169. if _idx>=0:
  170. kv_list = table.get("kv",[])
  171. for _d in kv_list:
  172. _d["position"]["key_begin_sentence_start"] += _idx
  173. _d["position"]["key_end_sentence_end"] += _idx
  174. _d["position"]["value_begin_sentence_start"] += _idx
  175. _d["position"]["value_end_sentence_end"] += _idx
  176. # remove table_id
  177. for table in tables:
  178. table_id = table.get("table_id")
  179. if table_id:
  180. kv_list = table.get("kv",[])
  181. for _d in kv_list:
  182. _length = get_table_idx_lengths(list_table_id,_d["position"]["key_begin_sentence_start"])
  183. _d["position"]["key_begin_sentence_start"] -= _length
  184. _length = get_table_idx_lengths(list_table_id,_d["position"]["key_end_sentence_end"])
  185. _d["position"]["key_end_sentence_end"] -= _length
  186. _length = get_table_idx_lengths(list_table_id,_d["position"]["value_begin_sentence_start"])
  187. _d["position"]["value_begin_sentence_start"] -= _length
  188. _length = get_table_idx_lengths(list_table_id,_d["position"]["value_end_sentence_end"])
  189. _d["position"]["value_end_sentence_end"] -= _length
  190. for table in tables:
  191. if table.get("table_id"):
  192. text = table.get("text","")
  193. for _d in list_table_id:
  194. table_id = _d.get("table_id")
  195. text = text.replace(table_id,"")
  196. table["text"] = text
  197. # split sentence
  198. text = tables[0].get("text","")
  199. list_sentence = str(text).split("。")
  200. list_sent_span = []
  201. _begin = 0
  202. for _i in range(len(list_sentence)):
  203. list_sentence[_i] += "。"
  204. _end = _begin+len(list_sentence[_i])
  205. list_sent_span.append([_begin,_end])
  206. _begin = _end
  207. tables[0]["sentences"] = list_sentence
  208. for table in tables:
  209. kv_list = table.get("kv",[])
  210. for _d in kv_list:
  211. key_begin_sentence = get_sentence_index(list_sent_span,_d["position"]["key_begin_sentence_start"])
  212. _d["position"]["key_begin_sentence"] = key_begin_sentence+sentence_index
  213. key_end_sentence = get_sentence_index(list_sent_span,_d["position"]["key_end_sentence_end"])
  214. _d["position"]["key_end_sentence"] = key_end_sentence+sentence_index
  215. value_begin_sentence = get_sentence_index(list_sent_span,_d["position"]["value_begin_sentence_start"])
  216. _d["position"]["value_begin_sentence"] = value_begin_sentence+sentence_index
  217. value_end_sentence = get_sentence_index(list_sent_span,_d["position"]["value_end_sentence_end"])
  218. _d["position"]["value_end_sentence"] = value_end_sentence+sentence_index
  219. return sentence_index + len(list_sentence)
  220. return sentence_index
  221. def tree_reposition(tree,sentence_index=None):
  222. if sentence_index is None:
  223. sentence_index = 0
  224. wordOffset_begin = 0
  225. wordOffset_end = 0
  226. for obj in tree:
  227. is_table = True if obj.get("tag","")=="table" else False
  228. if not is_table:
  229. sentence_index += 1
  230. obj["sentence_index"] = sentence_index
  231. obj["sentences"] = [obj.get("text","")]
  232. for _t in obj["sentences"]:
  233. wordOffset_end += len(_t)
  234. obj["wordOffset_begin"] = wordOffset_begin
  235. obj["wordOffset_end"] = wordOffset_end
  236. wordOffset_begin = wordOffset_end
  237. else:
  238. sentence_index += 1
  239. obj["sentence_index"] = sentence_index
  240. obj["sentence_index_start"] = sentence_index
  241. obj["sentences"] = [obj.get("text","")]
  242. sentence_index_end = update_table_position(obj,sentence_index)
  243. obj["sentence_index_end"] = sentence_index_end
  244. sentence_index = sentence_index_end
  245. for _t in obj["sentences"]:
  246. wordOffset_end += len(_t)
  247. obj["wordOffset_begin"] = wordOffset_begin
  248. obj["wordOffset_end"] = wordOffset_end
  249. wordOffset_begin = wordOffset_end
  250. # 递归地将 DOM 转换为 JSON
  251. # 递归地将 DOM 转换为 JSON
  252. def dom_to_tree(node):
  253. if node.name: # 如果是标签节点
  254. json_obj = DotDict({"tag": node.name})
  255. if node.attrs:
  256. json_obj["attributes"] = node.attrs
  257. is_table = False
  258. if node.name in ("table","tbody"):
  259. json_obj = table_to_tree(node)
  260. is_table = True
  261. if not is_table:
  262. children = []
  263. for child in node.contents:
  264. _child = dom_to_tree(child)
  265. if _child is not None:
  266. children.append(_child)
  267. if children:
  268. json_obj["children"] = children
  269. json_obj["name"] = json_obj.get("tag")
  270. return json_obj
  271. elif node.string and node.string.strip(): # 如果是纯文本节点
  272. return DotDict({"tag":"text","name":"text","text": node.string.strip()})
  273. return None # 忽略空白字符
  274. def tree_pop_parent(tree):
  275. if isinstance(tree,list):
  276. for child in tree:
  277. tree_pop_parent(child)
  278. if isinstance(tree,dict):
  279. if "parent" in tree:
  280. del tree["parent"]
  281. for child in tree.get("children",[]):
  282. tree_pop_parent(child)
  283. def html_to_tree(html_content):
  284. # 使用 BeautifulSoup 解析 HTML
  285. soup = BeautifulSoup(html_content, "lxml")
  286. dom_tree = dom_to_tree(soup)
  287. extract_kv_from_tree(dom_tree)
  288. list_objs = get_outobjs_from_tree(dom_tree)
  289. tree_reposition(list_objs)
  290. return dom_tree
  291. def print_tree(dom_tree):
  292. # 转换为 JSON 格式
  293. tree_pop_parent(dom_tree)
  294. json_output = json.dumps(dom_tree,ensure_ascii=False, indent=2)
  295. # kv_pattern = "\s*(?P<key>.{,10})[::]\s*(?P<value>[^::。,()]+?)(\s+|$|;|;)(?![\u4e00-\u9fa5]+:)"
  296. kv_pattern = r"(?P<key>[\u4e00-\u9fa5]+):\s*(?P<value>[^\s,。();;]+)"
  297. def get_kv_pattern():
  298. import re
  299. text = """
  300. name: John age: 30 note: invalid;
  301. """
  302. # 正则模式
  303. kv_pattern = r"(?P<key>[a-zA-Z]+)[::](?P<value>.+(?!.*[::]))"
  304. # 提取匹配
  305. matches = re.findall(kv_pattern, text)
  306. # 打印结果
  307. for match in matches:
  308. key, value = match
  309. print("{%s}: {%s}"%(key,value))
  310. def extract_kv_from_sentence(sentence):
  311. list_kv = []
  312. _iter = re.finditer("[::]", sentence)
  313. if _iter:
  314. list_span = []
  315. for iter in _iter:
  316. list_span.append(iter.span())
  317. if len(list_span)==1:
  318. _begin,_end = list_span[0]
  319. if _begin<20 and _end<len(sentence)-1:
  320. _d = DotDict({"key":sentence[0:_begin],"value":sentence[_end:]})
  321. _d["position"] = {"key_begin_sentence":0,
  322. "key_begin_sentence_start":0,
  323. "key_end_sentence":0,
  324. "key_end_sentence_end":_begin,
  325. "value_begin_sentence":0,
  326. "value_begin_sentence_start":_end,
  327. "value_end_sentence":0,
  328. "value_end_sentence_end":len(sentence)
  329. }
  330. list_kv.append(_d)
  331. else:
  332. _begin = 0
  333. _end = len(sentence)-1
  334. iter = re.search(kv_pattern,sentence[_begin:_end])
  335. if iter is not None:
  336. _d = DotDict({})
  337. _d["key"] = iter.group("key")
  338. _d["value"] = iter.group("value")
  339. _d["position"] = {"key_begin_sentence":0,
  340. "key_begin_sentence_start":iter.span("key")[0],
  341. "key_end_sentence":0,
  342. "key_end_sentence_end":iter.span("key")[0]+len(_d.get("key","")),
  343. "value_begin_sentence":0,
  344. "value_begin_sentence_start":iter.span("value")[0],
  345. "value_end_sentence":0,
  346. "value_end_sentence_end":iter.span("value")[0]+len(_d.get("value",""))
  347. }
  348. list_kv.append(_d)
  349. elif len(list_span)>1:
  350. _begin = 0
  351. for _i in range(len(list_span)-1):
  352. _end = list_span[_i+1][0]
  353. iter = re.search(kv_pattern,sentence[_begin:_end])
  354. _begin = list_span[_i][1]
  355. if iter is not None:
  356. _d = DotDict({})
  357. _d["key"] = iter.group("key")
  358. _d["value"] = iter.group("value")
  359. _d["position"] = {"key_begin_sentence":0,
  360. "key_begin_sentence_start":iter.span("key")[0],
  361. "key_end_sentence":0,
  362. "key_end_sentence_end":iter.span("key")[0]+len(_d.get("key","")),
  363. "value_begin_sentence":0,
  364. "value_begin_sentence_start":iter.span("value")[0],
  365. "value_end_sentence":0,
  366. "value_end_sentence_end":iter.span("value")[0]+len(_d.get("value",""))
  367. }
  368. list_kv.append(_d)
  369. _begin = list_span[-2][1]
  370. _end = len(sentence)
  371. iter = re.search(kv_pattern,sentence[_begin:_end])
  372. if iter is not None:
  373. _d = DotDict({})
  374. _d["key"] = iter.group("key")
  375. _d["value"] = iter.group("value")
  376. _d["position"] = {"key_begin_sentence":0,
  377. "key_begin_sentence_start":iter.span("key")[0],
  378. "key_end_sentence":0,
  379. "key_end_sentence_end":iter.span("key")[0]+len(_d.get("key","")),
  380. "value_begin_sentence":0,
  381. "value_begin_sentence_start":iter.span("value")[0],
  382. "value_end_sentence":0,
  383. "value_end_sentence_end":iter.span("value")[0]+len(_d.get("value",""))
  384. }
  385. list_kv.append(_d)
  386. # for iter in _iter:
  387. # _d = DotDict({})
  388. # _d["key"] = iter.group("key")
  389. # _d["value"] = iter.group("value")
  390. # _d["key_span"] = iter.span("key")
  391. # _d["value_span"] = iter.span("value")
  392. # list_kv.append(_d)
  393. return list_kv
  394. def extract_kv_from_node(node):
  395. list_kv = []
  396. _text = node.get("text")
  397. if _text:
  398. list_kv = extract_kv_from_sentence(_text)
  399. node["kv"] = list_kv
  400. return list_kv
  401. def get_child_text(node):
  402. _text = node.get("text","")
  403. for child in node.get("children",[]):
  404. _text += get_child_text(child)
  405. return _text
  406. def extract_kv_from_tree(tree):
  407. if isinstance(tree,list):
  408. _count = 0
  409. has_table = False
  410. for child in tree:
  411. _c,_t = extract_kv_from_tree(child)
  412. _count += _c
  413. if _t:
  414. has_table = _t
  415. return _count,has_table
  416. if isinstance(tree,dict):
  417. if tree.get("tag","")!="table":
  418. childs = tree.get("children",[])
  419. if len(childs)>0:
  420. _count = 0
  421. has_table = False
  422. for child in childs:
  423. _c,_t = extract_kv_from_tree(child)
  424. _count += _c
  425. if _t:
  426. has_table = _t
  427. if _count==0:
  428. _text = get_child_text(tree)
  429. if "children" in tree:
  430. del tree["children"]
  431. tree["text"] = _text
  432. list_kv = extract_kv_from_node(tree)
  433. _count = len(list_kv)
  434. return _count,has_table
  435. if tree.get("tag","")=="p" and not has_table:
  436. _text = get_child_text(tree)
  437. tree["text"] = _text
  438. p_list_kv = extract_kv_from_node(tree)
  439. if len(p_list_kv)>=_count:
  440. if "children" in tree:
  441. del tree["children"]
  442. else:
  443. tree["text"] = ""
  444. return len(p_list_kv),has_table
  445. return _count,has_table
  446. else:
  447. list_kv = extract_kv_from_node(tree)
  448. return len(list_kv),False
  449. else:
  450. return len(tree.get("kv",[])),True
  451. return 0,False
  452. def update_kv_span(list_kv,append_length):
  453. for _d in list_kv:
  454. _d["position"] = {"key_begin_sentence":0,
  455. "key_begin_sentence_start":_d.get("key_sen_index",0),
  456. "key_end_sentence":0,
  457. "key_end_sentence_end":_d.get("key_sen_index",0)+len(_d.get("key","")),
  458. "value_begin_sentence":0,
  459. "value_begin_sentence_start":_d.get("value_sen_index",0),
  460. "value_end_sentence":0,
  461. "value_end_sentence_end":_d.get("value_sen_index",0)+len(_d.get("value",""))
  462. }
  463. _d["position"]["key_begin_sentence_start"] += append_length
  464. _d["position"]["key_end_sentence_end"] += append_length
  465. _d["position"]["value_begin_sentence_start"] += append_length
  466. _d["position"]["value_end_sentence_end"] += append_length
  467. def get_outobjs_from_tree(tree,list_outobjs=None):
  468. is_first = False
  469. if list_outobjs is None:
  470. list_outobjs = []
  471. is_first = True
  472. if isinstance(tree,list):
  473. for child in tree:
  474. get_outobjs_from_tree(child,list_outobjs)
  475. if isinstance(tree,dict):
  476. childs = tree.get("children",[])
  477. _text = tree.get("text","")
  478. is_table = True if tree.get("tag","")=="table" else False
  479. if is_table:
  480. list_outobjs.append(tree)
  481. else:
  482. if _text!="":
  483. tree.name = tree.tag
  484. list_outobjs.append(tree)
  485. for child in childs:
  486. get_outobjs_from_tree(child,list_outobjs)
  487. return list_outobjs
  488. def standard_title_context(_title_context):
  489. return _title_context.replace("(","(").replace(")",")").replace(":",":").replace(":",";").replace(",",".").replace(",",".").replace("、",".").replace(".",".")
  490. def standard_product(sentence):
  491. return sentence.replace("(","(").replace(")",")")
  492. import Levenshtein
  493. import copy
  494. def jaccard_score(source,target):
  495. source_set = set([s for s in source])
  496. target_set = set([s for s in target])
  497. if len(source_set)==0 or len(target_set)==0:
  498. return 0
  499. return max(len(source_set&target_set)/len(source_set),len(source_set&target_set)/len(target_set))
  500. def judge_pur_chinese(keyword):
  501. """
  502. 中文字符的编码范围为: u'\u4e00' -- u'\u9fff:只要在此范围内就可以判断为中文字符串
  503. @param keyword:
  504. @return:
  505. """
  506. # 定义一个需要删除的标点符号字符串列表
  507. remove_chars = '[·’!"\#$%&\'()#!()*+,-./:;<=>?\@,:?¥★、….>【】[]《》?“”‘’\[\\]^_`{|}~]+'
  508. # 利用re.sub来删除中文字符串中的标点符号
  509. strings = re.sub(remove_chars, "", keyword) # 将keyword中文字符串中remove_chars中包含的标点符号替换为空字符串
  510. for ch in strings:
  511. if u'\u4e00' <= ch <= u'\u9fff':
  512. pass
  513. else:
  514. return False
  515. return True
  516. def is_similar(source,target,_radio=None):
  517. source = str(source).lower()
  518. target = str(target).lower()
  519. max_len = max(len(source),len(target))
  520. min_len = min(len(source),len(target))
  521. min_ratio = 90
  522. if min_len>=3:
  523. min_ratio = 87
  524. if min_len>=5:
  525. min_ratio = 85
  526. if _radio is not None:
  527. min_ratio = _radio
  528. # dis_len = abs(len(source)-len(target))
  529. # min_dis = min(max_len*0.2,4)
  530. if min_len==0 and max_len>0:
  531. return False
  532. if max_len<=2:
  533. if source==target:
  534. return True
  535. if min_len<2:
  536. return False
  537. #判断相似度
  538. similar = Levenshtein.ratio(source,target)*100
  539. if similar>=min_ratio:
  540. log("%s and %s similar_jaro %d"%(source,target,similar))
  541. return True
  542. similar_jaro = Levenshtein.jaro(source,target)
  543. if similar_jaro*100>=min_ratio:
  544. log("%s and %s similar_jaro %d"%(source,target,similar_jaro*100))
  545. return True
  546. similar_jarow = Levenshtein.jaro_winkler(source,target)
  547. if similar_jarow*100>=min_ratio:
  548. log("%s and %s similar_jaro %d"%(source,target,similar_jarow*100))
  549. return True
  550. if min_len>=5:
  551. if len(source)==max_len and str(source).find(target)>=0:
  552. return True
  553. elif len(target)==max_len and target.find(source)>=0:
  554. return True
  555. elif jaccard_score(source, target)==1 and judge_pur_chinese(source) and judge_pur_chinese(target):
  556. return True
  557. return False
  558. end_pattern = "商务要求|评分标准|商务条件|商务条件"
  559. _param_pattern = "(产品|技术|清单|配置|参数|具体|明细|项目|招标|货物|服务|规格|工作|具体)[及和与]?(指标|配置|条件|要求|参数|需求|规格|条款|名称及要求)|配置清单|(质量|技术).{,10}要求|验收标准|^(参数|功能)$"
  560. 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大中小]+码|净重|颜色|[红橙黄绿青蓝紫]色|不锈钢|输入|输出|噪声|认证|配置"
  561. not_meter_pattern = "投标报价|中标金额|商务部分|公章|分值构成|业绩|详见|联系人|联系电话|合同价|金额|采购预算|资金来源|费用|质疑|评审因素|评审标准|商务资信|商务评分|专家论证意见|评标方法|代理服务费|售后服务|评分类型|评分项目|预算金额|得\d+分|项目金额|详见招标文件|乙方"
  562. def getTrs(tbody):
  563. #获取所有的tr
  564. trs = []
  565. if tbody.name=="table":
  566. body = tbody.find("tbody",recursive=False)
  567. if body is not None:
  568. tbody = body
  569. objs = tbody.find_all(recursive=False)
  570. for obj in objs:
  571. if obj.name=="tr":
  572. trs.append(obj)
  573. if obj.name=="tbody" or obj.name=="table":
  574. for tr in obj.find_all("tr",recursive=False):
  575. trs.append(tr)
  576. return trs
  577. def fixSpan(tbody):
  578. # 处理colspan, rowspan信息补全问题
  579. #trs = tbody.findChildren('tr', recursive=False)
  580. trs = getTrs(tbody)
  581. ths_len = 0
  582. ths = list()
  583. trs_set = set()
  584. #修改为先进行列补全再进行行补全,否则可能会出现表格解析混乱
  585. # 遍历每一个tr
  586. for indtr, tr in enumerate(trs):
  587. ths_tmp = tr.findChildren('th', recursive=False)
  588. #不补全含有表格的tr
  589. if len(tr.findChildren('table'))>0:
  590. continue
  591. if len(ths_tmp) > 0:
  592. ths_len = ths_len + len(ths_tmp)
  593. for th in ths_tmp:
  594. ths.append(th)
  595. trs_set.add(tr)
  596. # 遍历每行中的element
  597. tds = tr.findChildren(recursive=False)
  598. for indtd, td in enumerate(tds):
  599. # 若有colspan 则补全同一行下一个位置
  600. if 'colspan' in td.attrs:
  601. if str(re.sub("[^0-9]","",str(td['colspan'])))!="":
  602. col = int(re.sub("[^0-9]","",str(td['colspan'])))
  603. if col<100 and len(td.get_text())<1000:
  604. td['colspan'] = 1
  605. for i in range(1, col, 1):
  606. td.insert_after(copy.copy(td))
  607. for indtr, tr in enumerate(trs):
  608. ths_tmp = tr.findChildren('th', recursive=False)
  609. #不补全含有表格的tr
  610. if len(tr.findChildren('table'))>0:
  611. continue
  612. if len(ths_tmp) > 0:
  613. ths_len = ths_len + len(ths_tmp)
  614. for th in ths_tmp:
  615. ths.append(th)
  616. trs_set.add(tr)
  617. # 遍历每行中的element
  618. tds = tr.findChildren(recursive=False)
  619. for indtd, td in enumerate(tds):
  620. # 若有rowspan 则补全下一行同样位置
  621. if 'rowspan' in td.attrs:
  622. if str(re.sub("[^0-9]","",str(td['rowspan'])))!="":
  623. row = int(re.sub("[^0-9]","",str(td['rowspan'])))
  624. td['rowspan'] = 1
  625. for i in range(1, row, 1):
  626. # 获取下一行的所有td, 在对应的位置插入
  627. if indtr+i<len(trs):
  628. tds1 = trs[indtr + i].findChildren(['td','th'], recursive=False)
  629. if len(tds1) >= (indtd) and len(tds1)>0:
  630. if indtd > 0:
  631. tds1[indtd - 1].insert_after(copy.copy(td))
  632. else:
  633. tds1[0].insert_before(copy.copy(td))
  634. elif indtd-2>0 and len(tds1) > 0 and len(tds1) == indtd - 1: # 修正某些表格最后一列没补全
  635. tds1[indtd-2].insert_after(copy.copy(td))
  636. def getTable(tbody):
  637. #trs = tbody.findChildren('tr', recursive=False)
  638. fixSpan(tbody)
  639. trs = getTrs(tbody)
  640. inner_table = []
  641. for tr in trs:
  642. tr_line = []
  643. tds = tr.findChildren(['td','th'], recursive=False)
  644. if len(tds)==0:
  645. tr_line.append([re.sub('\xa0','',tr.get_text()),0]) # 2021/12/21 修复部分表格没有td 造成数据丢失
  646. for td in tds:
  647. tr_line.append([re.sub('\xa0','',td.get_text()),0])
  648. #tr_line.append([td.get_text(),0])
  649. inner_table.append(tr_line)
  650. return inner_table
  651. def extract_products(list_data,_product,_param_pattern = "产品名称|设备材料|采购内存|标的名称|采购内容|(标的|维修|系统|报价构成|商品|产品|物料|物资|货物|设备|采购品|采购条目|物品|材料|印刷品?|采购|物装|配件|资产|耗材|清单|器材|仪器|器械|备件|拍卖物|标的物|物件|药品|药材|药械|货品|食品|食材|品目|^品名|气体|标项|分项|项目|计划|包组|标段|[分子]?包|子目|服务|招标|中标|成交|工程|招标内容)[\))的]?([、\w]{,4}名称|内容|描述)|标的|标项|项目$|商品|产品|物料|物资|货物|设备|采购品|采购条目|物品|材料|印刷品|物装|配件|资产|招标内容|耗材|清单|器材|仪器|器械|备件|拍卖物|标的物|物件|药品|药材|药械|货品|食品|食材|菜名|^品目$|^品名$|^名称|^内容$"):
  652. _product = standard_product(_product)
  653. list_result = []
  654. list_table_products = []
  655. for _data_i in range(len(list_data)):
  656. _data = list_data[_data_i]
  657. _type = _data["type"]
  658. _text = _data["text"]
  659. if _type=="table":
  660. list_table = _data["list_table"]
  661. if list_table is None:
  662. continue
  663. _check = True
  664. max_length = max([len(a) for a in list_table])
  665. min_length = min([len(a) for a in list_table])
  666. if min_length<max_length/2:
  667. continue
  668. list_head_index = []
  669. _begin_index = 0
  670. head_cell_text = ""
  671. for line_i in range(len(list_table[:2])):
  672. line = list_table[line_i]
  673. line_text = ",".join([cell[0] for cell in line])
  674. for cell_i in range(len(line)):
  675. cell = line[cell_i]
  676. cell_text = cell[0]
  677. if len(cell_text)<10 and re.search(_param_pattern,cell_text) is not None and re.search("单价|数量|预算|限价|总价|品牌|规格|型号|用途|要求|采购量",line_text) is not None:
  678. _begin_index = line_i+1
  679. list_head_index.append(cell_i)
  680. for line_i in range(len(list_table)):
  681. line = list_table[line_i]
  682. for cell_i in list_head_index:
  683. if cell_i>=len(line):
  684. continue
  685. cell = line[cell_i]
  686. cell_text = cell[0]
  687. head_cell_text += cell_text
  688. # print("===head_cell_text",head_cell_text)
  689. if re.search("招标人|采购人|项目编号|项目名称|金额|^\d+$",head_cell_text) is not None:
  690. list_head_index = []
  691. for line in list_table:
  692. line_text = ",".join([cell[0] for cell in line])
  693. for cell_i in range(len(line)):
  694. cell = line[cell_i]
  695. cell_text = cell[0]
  696. 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:
  697. list_head_index.append(cell_i)
  698. list_head_index = list(set(list_head_index))
  699. if len(list_head_index)>0:
  700. has_number = False
  701. for cell_i in list_head_index:
  702. table_products = []
  703. for line_i in range(_begin_index,len(list_table)):
  704. line = list_table[line_i]
  705. for _i in range(len(line)):
  706. cell = line[_i]
  707. cell_text = cell[0]
  708. if re.search("^\d+$",cell_text) is not None:
  709. has_number = True
  710. if cell_i>=len(line):
  711. continue
  712. cell = line[cell_i]
  713. cell_text = cell[0]
  714. if re.search(_param_pattern,cell_text) is None or has_number:
  715. if re.search("^[\da-zA-Z]+$",cell_text) is None:
  716. table_products.append(cell_text)
  717. if len(table_products)>0:
  718. logger.debug("table products %s"%(str(table_products)))
  719. if min([len(x) for x in table_products])>0 and max([len(x) for x in table_products])<=30:
  720. if re.search("招标人|代理人|预算|数量|交货期|品牌|产地","".join(table_products)) is None:
  721. list_table_products.append(table_products)
  722. _find = False
  723. for table_products in list_table_products:
  724. for _p in table_products:
  725. if is_similar(_product,_p,90):
  726. _find = True
  727. logger.debug("similar table_products %s"%(str(table_products)))
  728. list_result = list(set([a for a in table_products if len(a)>1 and len(a)<20 and re.search("费用|预算|合计|金额|万元|运费|^其他$",a) is None]))
  729. break
  730. if not _find:
  731. for table_products in list_table_products:
  732. list_result.extend(table_products)
  733. list_result = list(set([a for a in list_result if len(a)>1 and len(a)<30 and re.search("费用|预算|合计|金额|万元|运费",a) is None]))
  734. return list_result
  735. def get_childs(childs, max_depth=None):
  736. list_data = []
  737. for _child in childs:
  738. list_data.append(_child)
  739. childs2 = _child.get("child_title",[])
  740. if len(childs2)>0 and (max_depth==None or max_depth>0):
  741. for _child2 in childs2:
  742. if max_depth != None:
  743. list_data.extend(get_childs([_child2], max_depth-1))
  744. else:
  745. list_data.extend(get_childs([_child2], None))
  746. return list_data
  747. class Html2KVTree():
  748. def __init__(self,_html,auto_merge_table=True,list_obj = []):
  749. if _html is None:
  750. _html = ""
  751. self.html = _html
  752. self.auto_merge_table = auto_merge_table
  753. if list_obj:
  754. self.list_obj = list_obj
  755. else:
  756. _tree = html_to_tree(html_content)
  757. self.list_obj = get_outobjs_from_tree(_tree)
  758. # for obj in self.list_obj:
  759. # print("obj",obj.get_text()[:20])
  760. self.tree = self.buildParsetree(self.list_obj,[],auto_merge_table)
  761. # #识别目录树
  762. # self.print_tree(self.tree,"-|")
  763. def get_soup_objs(self,soup,list_obj=None):
  764. if list_obj is None:
  765. list_obj = []
  766. childs = soup.find_all(recursive=False)
  767. for _obj in childs:
  768. childs1 = _obj.find_all(recursive=False)
  769. if len(childs1)==0 or len(_obj.get_text())<40 or _obj.name=="table":
  770. list_obj.append(_obj)
  771. elif _obj.name=="p":
  772. list_obj.append(_obj)
  773. else:
  774. self.get_soup_objs(_obj,list_obj)
  775. return list_obj
  776. def fix_tree(self,_product):
  777. products = extract_products(self.tree,_product)
  778. if len(products)>0:
  779. self.tree = self.buildParsetree(self.list_obj,products,self.auto_merge_table)
  780. def print_tree(self,tree,append="",set_tree_id=None):
  781. if set_tree_id is None:
  782. set_tree_id = set()
  783. if append=="":
  784. for t in tree:
  785. 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"]))
  786. for t in tree:
  787. _id = id(t)
  788. if _id in set_tree_id:
  789. continue
  790. set_tree_id.add(_id)
  791. 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"])))
  792. childs = t["child_title"]
  793. self.print_tree(childs,append=append+"-|",set_tree_id=set_tree_id)
  794. def is_title_first(self,title):
  795. if title in ("一","1","Ⅰ","a","A"):
  796. return True
  797. return False
  798. 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>[、章册包标部.::]+))|" \
  799. "([\s★▲\*]*)(?P<title_3>(?P<title_3_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?)(?P<title_3_index_0_1>[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)(?P<title_3_index_0_2>[、章册包标部.::]+))|" \
  800. "([\s★▲\*]*)(?P<title_4>(?P<title_4_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?第?)(?P<title_4_index_1_1>[一二三四五六七八九十]+)(?P<title_4_index_2_0>[节章册部\.::、、]+))|" \
  801. "([\s★▲\*]*)(?P<title_5>(?P<title_5_index_0_0>^)(?P<title_5_index_1_1>[一二三四五六七八九十]+)(?P<title_5_index_2_0>)[^一二三四五六七八九十节章册部\.::、])|" \
  802. "([\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\-]?))|"\
  803. "([\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\-]?))|" \
  804. "([\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\-]?))|" \
  805. "([\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\-]*))|" \
  806. "(^[\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\-包标]*))|" \
  807. "([\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>[))包标\..::、]+))|" \
  808. "([\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>[))包标\..::、]+))|" \
  809. "([\s★▲\*]*)(?P<title_19>(?P<title_19_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?[((]?)(?P<title_19_index_1_1>[一二三四五六七八九十ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)(?P<title_19_index_2_0>[))]))"
  810. ):
  811. _se = re.search(_pattern,_text)
  812. groups = []
  813. if _se is not None:
  814. e = _se.end()
  815. if re.search('(时间|日期|编号|账号|号码|手机|价格|\w价|人民币|金额|得分|分值|总分|满分|最高得|扣|减|数量|评委)[::]?\d', _se.group(0)) or (re.search('\d[.::]?$', _se.group(0)) and re.search('^[\d年月日万元天个分秒台条A-Za-z]|^(小时)', _text[e:])):
  816. return None
  817. elif re.match('[二三四五六七八九十]\w{1,2}[市区县]|五金|四川|八疆|九龙|[一二三四五六七八九十][层天标包]', _text) and re.match('[一二三四五六七八九十]', _se.group(0)): # 289765335 排除三明市等开头作为大纲
  818. return None
  819. elif re.search('^[\u4e00-\u9fa5]+[::]', _text[:e]):
  820. return None
  821. _gd = _se.groupdict()
  822. for k,v in _gd.items():
  823. if v is not None:
  824. groups.append((k,v))
  825. if len(groups):
  826. groups.sort(key=lambda x:x[0])
  827. return groups
  828. return None
  829. def make_increase(self,_sort,_title,_add=1):
  830. if len(_title)==0 and _add==0:
  831. return ""
  832. if len(_title)==0 and _add==1:
  833. return _sort[0]
  834. _index = _sort.index(_title[-1])
  835. next_index = (_index+_add)%len(_sort)
  836. next_chr = _sort[next_index]
  837. if _index==len(_sort)-1:
  838. _add = 1
  839. else:
  840. _add = 0
  841. return next_chr+self.make_increase(_sort,_title[:-1],_add)
  842. def get_next_title(self,_title):
  843. if re.search("^\d+$",_title) is not None:
  844. return str(int(_title)+1)
  845. if re.search("^[一二三四五六七八九十百]+$",_title) is not None:
  846. if _title[-1]=="十":
  847. return _title+"一"
  848. if _title[-1]=="百":
  849. return _title+"零一"
  850. if _title[-1]=="九":
  851. if len(_title)==1:
  852. return "十"
  853. if len(_title)==2:
  854. if _title[0]=="十":
  855. return "二十"
  856. if len(_title)==3:
  857. if _title[0]=="九":
  858. return "一百"
  859. else:
  860. _next_title = self.make_increase(['一','二','三','四','五','六','七','八','九','十'],re.sub("[十百]",'',_title[0]))
  861. return _next_title+"十"
  862. _next_title = self.make_increase(['一','二','三','四','五','六','七','八','九','十'],re.sub("[十百]",'',_title))
  863. _next_title = list(_next_title)
  864. _next_title.reverse()
  865. if _next_title[-1]!="十":
  866. if len(_next_title)>=2:
  867. _next_title.insert(-1,'十')
  868. if len(_next_title)>=4:
  869. _next_title.insert(-3,'百')
  870. if _title[0]=="十":
  871. if _next_title=="十":
  872. _next_title = ["二","十"]
  873. _next_title.insert(0,"十")
  874. _next_title = "".join(_next_title)
  875. return _next_title
  876. if re.search("^[a-z]+$",_title) is not None:
  877. _next_title = self.make_increase([chr(i+ord('a')) for i in range(26)],_title)
  878. _next_title = list(_next_title)
  879. _next_title.reverse()
  880. return "".join(_next_title)
  881. if re.search("^[A-Z]+$",_title) is not None:
  882. _next_title = self.make_increase([chr(i+ord('A')) for i in range(26)],_title)
  883. _next_title = list(_next_title)
  884. _next_title.reverse()
  885. return "".join(_next_title)
  886. if re.search("^[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]$",_title) is not None:
  887. _sort = ["Ⅰ","Ⅱ","Ⅲ","Ⅳ","Ⅴ","Ⅵ","Ⅶ","Ⅷ","Ⅸ","Ⅹ","Ⅺ","Ⅻ"]
  888. _index = _sort.index(_title)
  889. if _index<len(_sort)-1:
  890. return _sort[_index+1]
  891. return None
  892. def count_title_before(self,list_obj):
  893. dict_before = {}
  894. dict_sentence_count = {}
  895. illegal_sentence = set()
  896. for obj_i in range(len(list_obj)):
  897. obj = list_obj[obj_i]
  898. _type = "sentence"
  899. _text = obj.text.strip()
  900. if obj.name=="table":
  901. _type = "table"
  902. _text = str(obj)
  903. _append = False
  904. if _type=="sentence":
  905. if len(_text)>10 and len(_text)<100:
  906. if _text not in dict_sentence_count:
  907. dict_sentence_count[_text] = 0
  908. dict_sentence_count[_text] += 1
  909. if re.search("\d+页",_text) is not None:
  910. illegal_sentence.add(_text)
  911. elif len(_text)<10:
  912. if re.search("第\d+页",_text) is not None:
  913. illegal_sentence.add(_text)
  914. sentence_groups = self.find_title_by_pattern(_text[:10])
  915. if sentence_groups:
  916. # c062f53cf83401e671822003d63c1828print("sentence_groups",sentence_groups)
  917. sentence_title = sentence_groups[0][0]
  918. sentence_title_text = sentence_groups[0][1]
  919. title_index = sentence_groups[-2][1]
  920. title_before = sentence_groups[1][1].replace("(","(").replace(":",":").replace(":",";").replace(",",".").replace(",",".").replace("、",".")
  921. title_after = sentence_groups[-1][1].replace(")",")").replace(":",":").replace(":",";").replace(",",".").replace(",",".").replace("、",".")
  922. next_index = self.get_next_title(title_index)
  923. if title_before not in dict_before:
  924. dict_before[title_before] = 0
  925. dict_before[title_before] += 1
  926. for k,v in dict_sentence_count.items():
  927. if v>10:
  928. illegal_sentence.add(k)
  929. return dict_before,illegal_sentence
  930. def is_page_no(self,sentence):
  931. if len(sentence)<10:
  932. if re.search("\d+页|^\-\d+\-$",sentence) is not None:
  933. return True
  934. def block_tree(self,childs):
  935. for child in childs:
  936. if not child["block"]:
  937. child["block"] = True
  938. childs2 = child["child_title"]
  939. self.block_tree(childs2)
  940. def buildParsetree(self,list_obj,products=[],auto_merge_table=True,auto_append=False):
  941. self.parseTree = None
  942. trees = []
  943. list_length = []
  944. for obj in list_obj[:200]:
  945. if obj.name!="table":
  946. list_length.append(len(obj.text))
  947. if len(list_length)>0:
  948. max_length = max(list_length)
  949. else:
  950. max_length = 40
  951. max_length = min(max_length,40)
  952. logger.debug("%s:%d"%("max_length",max_length))
  953. list_data = []
  954. last_table_index = None
  955. last_table_columns = None
  956. last_table = None
  957. dict_before,illegal_sentence = self.count_title_before(list_obj)
  958. for obj_i in range(len(list_obj)):
  959. obj = list_obj[obj_i]
  960. # logger.debug("==obj %s"%obj.text[:20])
  961. _type = "sentence"
  962. _text = standard_product(obj.text)
  963. if obj.name=="table":
  964. _type = "table"
  965. _text = standard_product(str(obj))
  966. _append = False
  967. sentence_title = None
  968. sentence_title_text = None
  969. sentence_groups = None
  970. title_index = None
  971. next_index = None
  972. parent_title = None
  973. title_before = None
  974. title_after = None
  975. title_next = None
  976. childs = []
  977. # new
  978. sentence_index = obj.sentence_index
  979. wordOffset_begin = obj.wordOffset_begin
  980. wordOffset_end = obj.wordOffset_end
  981. sentences = obj.sentences
  982. list_kv = obj.get("kv",[])
  983. table_id = obj.get("table_id")
  984. list_table = None
  985. block = False
  986. has_product = False
  987. position = obj.get("position",{})
  988. if _type=="sentence":
  989. if _text in illegal_sentence:
  990. continue
  991. sentence_groups = self.find_title_by_pattern(_text[:10])
  992. if sentence_groups:
  993. title_before = standard_title_context(sentence_groups[1][1])
  994. title_after = sentence_groups[-1][1]
  995. sentence_title_text = sentence_groups[0][1]
  996. other_text = _text.replace(sentence_title_text,"")
  997. if (title_before in dict_before and dict_before[title_before]>1) or title_after!="":
  998. sentence_title = sentence_groups[0][0]
  999. title_index = sentence_groups[-2][1]
  1000. next_index = self.get_next_title(title_index)
  1001. other_text = _text.replace(sentence_title_text,"")
  1002. for p in products:
  1003. if other_text.strip()==p.strip():
  1004. has_product = True
  1005. else:
  1006. _fix = False
  1007. for p in products:
  1008. if other_text.strip()==p.strip():
  1009. title_before = "=产品"
  1010. sentence_title = "title_0"
  1011. sentence_title_text = p
  1012. title_index = "0"
  1013. title_after = "产品="
  1014. next_index = "0"
  1015. _fix = True
  1016. has_product = True
  1017. break
  1018. if not _fix:
  1019. title_before = None
  1020. title_after = None
  1021. sentence_title_text = None
  1022. else:
  1023. if len(_text)<40 and re.search(_param_pattern,_text) is not None:
  1024. for p in products:
  1025. if _text.find(p)>=0:
  1026. title_before = "=产品"
  1027. sentence_title = "title_0"
  1028. sentence_title_text = p
  1029. title_index = "0"
  1030. title_after = "产品="
  1031. next_index = "0"
  1032. _fix = True
  1033. has_product = True
  1034. break
  1035. # 合并两个非标题句子 20241106 注销,由于 485441521 招标内容结束位置不对
  1036. if auto_append:
  1037. if _type=="sentence":
  1038. 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:
  1039. list_data[-1]["text"] += _text
  1040. list_data[-1]["line_width"] = len(_text)
  1041. update_kv_span(list_kv,len(_text))
  1042. list_data[-1]["kv"].extend(list_kv)
  1043. list_data[-1]["sentences"].extend(sentences)
  1044. _append = True
  1045. elif sentence_title is None and len(list_data)>0 and _type==list_data[-1]["type"]:
  1046. if list_data[-1]["line_width"]>=max_length*0.7:
  1047. list_data[-1]["text"] += _text
  1048. list_data[-1]["line_width"] = len(_text)
  1049. update_kv_span(list_kv,len(_text))
  1050. list_data[-1]["kv"].extend(list_kv)
  1051. list_data[-1]["sentences"].extend(sentences)
  1052. _append = True
  1053. if not _append:
  1054. _data = {"type":_type,"tag":obj.get("tag"),"table_id":table_id, "text":_text,"sentences":sentences,"list_table":list_table,
  1055. "line_width":len(_text),"sentence_title":sentence_title,"title_index":title_index,
  1056. "sentence_title_text":sentence_title_text,"sentence_groups":sentence_groups,"parent_title":parent_title,
  1057. "child_title":childs,"title_before":title_before,"title_after":title_after,"title_next":title_next,"next_index":next_index,
  1058. "block":block,"has_product":has_product,
  1059. "sentence_index":sentence_index,"wordOffset_begin":wordOffset_begin,"wordOffset_end":wordOffset_end,
  1060. "kv":list_kv,"position":position
  1061. }
  1062. if sentence_title is not None:
  1063. if len(list_data)>0:
  1064. if self.is_title_first(title_index):
  1065. for i in range(1,len(list_data)+1):
  1066. _d = list_data[-i]
  1067. if _d["sentence_title"] is not None:
  1068. _data["parent_title"] = _d
  1069. _d["child_title"].append(_data)
  1070. break
  1071. else:
  1072. _find = False
  1073. for i in range(1,len(list_data)+1):
  1074. if _find:
  1075. break
  1076. _d = list_data[-i]
  1077. if _d.get("sentence_title")==sentence_title and title_before==_d["title_before"] and title_after==_d["title_after"]:
  1078. if _d["next_index"]==title_index and _d["title_next"] is None and not _d["block"]:
  1079. _data["parent_title"] = _d["parent_title"]
  1080. _d["title_next"] = _data
  1081. if len(_d["child_title"])>0:
  1082. _d["child_title"][-1]["title_next"] = ""
  1083. self.block_tree(_d["child_title"])
  1084. if _d["parent_title"] is not None:
  1085. _d["parent_title"]["child_title"].append(_data)
  1086. _find = True
  1087. break
  1088. for i in range(1,len(list_data)+1):
  1089. if _find:
  1090. break
  1091. _d = list_data[-i]
  1092. 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"]:
  1093. _data["parent_title"] = _d["parent_title"]
  1094. _d["title_next"] = _data
  1095. if len(_d["child_title"])>0:
  1096. _d["child_title"][-1]["title_next"] = ""
  1097. self.block_tree(_d["child_title"])
  1098. if _d["parent_title"] is not None:
  1099. _d["parent_title"]["child_title"].append(_data)
  1100. _find = True
  1101. break
  1102. title_before = standard_title_context(title_before)
  1103. title_after = standard_title_context(title_after)
  1104. for i in range(1,len(list_data)+1):
  1105. if _find:
  1106. break
  1107. _d = list_data[-i]
  1108. 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"]):
  1109. if _d["next_index"]==title_index and _d["title_next"] is None and not _d["block"]:
  1110. _data["parent_title"] = _d["parent_title"]
  1111. _d["title_next"] = _data
  1112. if len(_d["child_title"])>0:
  1113. _d["child_title"][-1]["title_next"] = ""
  1114. self.block_tree(_d["child_title"])
  1115. if _d["parent_title"] is not None:
  1116. _d["parent_title"]["child_title"].append(_data)
  1117. _find = True
  1118. break
  1119. for i in range(1,len(list_data)+1):
  1120. if _find:
  1121. break
  1122. _d = list_data[-i]
  1123. 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"]):
  1124. _data["parent_title"] = _d["parent_title"]
  1125. _d["title_next"] = _data
  1126. if len(_d["child_title"])>0:
  1127. _d["child_title"][-1]["title_next"] = ""
  1128. # self.block_tree(_d["child_title"])
  1129. if _d["parent_title"] is not None:
  1130. _d["parent_title"]["child_title"].append(_data)
  1131. _find = True
  1132. break
  1133. for i in range(1,min(len(list_data)+1,20)):
  1134. if _find:
  1135. break
  1136. _d = list_data[-i]
  1137. if not _d["block"] and _d.get("sentence_title")==sentence_title and title_before==standard_title_context(_d["title_before"]):
  1138. _data["parent_title"] = _d["parent_title"]
  1139. _d["title_next"] = _data
  1140. if len(_d["child_title"])>0:
  1141. _d["child_title"][-1]["title_next"] = ""
  1142. # self.block_tree(_d["child_title"])
  1143. if _d["parent_title"] is not None:
  1144. _d["parent_title"]["child_title"].append(_data)
  1145. _find = True
  1146. break
  1147. if not _find:
  1148. if len(list_data)>0:
  1149. for i in range(1,len(list_data)+1):
  1150. _d = list_data[-i]
  1151. if _d.get("sentence_title") is not None:
  1152. _data["parent_title"] = _d
  1153. _d["child_title"].append(_data)
  1154. break
  1155. else:
  1156. if len(list_data)>0:
  1157. for i in range(1,len(list_data)+1):
  1158. _d = list_data[-i]
  1159. if _d.get("sentence_title") is not None:
  1160. _data["parent_title"] = _d
  1161. _d["child_title"].append(_data)
  1162. break
  1163. list_data.append(_data)
  1164. for _data in list_data:
  1165. childs = _data["child_title"]
  1166. for c_i in range(len(childs)):
  1167. cdata = childs[c_i]
  1168. if cdata["has_product"]:
  1169. continue
  1170. else:
  1171. if c_i>0:
  1172. last_cdata = childs[c_i-1]
  1173. 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"]:
  1174. cdata["has_product"] = True
  1175. if c_i<len(childs)-1:
  1176. last_cdata = childs[c_i+1]
  1177. 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"]:
  1178. cdata["has_product"] = True
  1179. for c_i in range(len(childs)):
  1180. cdata = childs[len(childs)-1-c_i]
  1181. if cdata["has_product"]:
  1182. continue
  1183. else:
  1184. if c_i>0:
  1185. last_cdata = childs[c_i-1]
  1186. 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"]:
  1187. cdata["has_product"] = True
  1188. if c_i<len(childs)-1:
  1189. last_cdata = childs[c_i+1]
  1190. 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"]:
  1191. cdata["has_product"] = True
  1192. return list_data
  1193. def get_tree_sentence(self):
  1194. list_sentence = []
  1195. for obj in self.tree:
  1196. list_sentence.extend(obj.get("sentences",[]))
  1197. return list_sentence
  1198. def extract_kvs_from_table(self,list_pattern,tree=None,result_kv=None):
  1199. if result_kv is None:
  1200. result_kv = [[] for i in list_pattern]
  1201. try:
  1202. for pattern in list_pattern:
  1203. re.compile(pattern)
  1204. except Exception as e:
  1205. log("list_pattern error: "+str(e))
  1206. return result_kv
  1207. if tree is None:
  1208. tree = self.tree
  1209. for obj in tree:
  1210. is_table = True if obj.get("tag","")=="table" else False
  1211. if is_table:
  1212. table_id = obj.get("table_id")
  1213. list_kv = obj.get("kv")
  1214. for _pi in range(len(list_pattern)):
  1215. table_kvs = []
  1216. for _d0 in list_kv:
  1217. _k = _d0.get("key","")
  1218. _v = _d0.get("value","")
  1219. _d = {"key":_k,"value":_v,"position":_d0.get("position",{})}
  1220. if re.search(list_pattern[_pi],_k) is not None:
  1221. table_kvs.append(_d)
  1222. if table_kvs:
  1223. result_kv[_pi].append({"table_id":table_id,"kv":table_kvs})
  1224. childs = obj.get("children",[])
  1225. for child in childs:
  1226. self.extract_kvs_from_table(list_pattern,child,result_kv)
  1227. return result_kv
  1228. def extract_kvs_from_sentence(self,list_pattern,tree=None,result_kv=None):
  1229. if result_kv is None:
  1230. result_kv = [[] for i in list_pattern]
  1231. try:
  1232. for pattern in list_pattern:
  1233. re.compile(pattern)
  1234. except Exception as e:
  1235. log("list_pattern error: "+str(e))
  1236. return result_kv
  1237. if tree is None:
  1238. tree = self.tree
  1239. for obj in tree:
  1240. is_table = True if obj.get("tag","")=="table" else False
  1241. if not is_table:
  1242. list_kv = obj.get("kv",[])
  1243. for _pi in range(len(list_pattern)):
  1244. for _d in list_kv:
  1245. _k = _d.get("key","")
  1246. _v = _d.get("value","")
  1247. if re.search(list_pattern[_pi],_k) is not None:
  1248. result_kv[_pi].append(_d)
  1249. return result_kv
  1250. def extract_kvs_from_outline(self,list_pattern,tree=None,result_kv=None):
  1251. if result_kv is None:
  1252. result_kv = [[] for i in list_pattern]
  1253. try:
  1254. for pattern in list_pattern:
  1255. re.compile(pattern)
  1256. except Exception as e:
  1257. log("list_pattern error: "+str(e))
  1258. return result_kv
  1259. if tree is None:
  1260. tree = self.tree
  1261. for obj in tree:
  1262. is_table = True if obj.get("tag","")=="table" else False
  1263. if not is_table:
  1264. _text = obj["text"]
  1265. for _pi in range(len(list_pattern)):
  1266. sentence_index_from = obj["sentence_index"]
  1267. sentence_index_to = sentence_index_from
  1268. if re.search(list_pattern[_pi],_text) is not None and obj.get("sentence_title") is not None:
  1269. childs = get_childs([obj])
  1270. _child_text = ""
  1271. for _child in childs:
  1272. sentence_index_to = _child["sentence_index"]
  1273. _child_text+=_child["text"]
  1274. result_kv[_pi].append({"key":_text,"value":_child_text,"from_outline":True,"key_sentence_index_from":sentence_index_from,
  1275. "key_sentence_index_to":sentence_index_from,"value_sentence_index_from":sentence_index_from,
  1276. "value_sentence_index_to":sentence_index_to,})
  1277. return result_kv
  1278. def extract_kv(self,k_pattern,from_sentence=True,from_outline=True,from_table=True):
  1279. result_kv = []
  1280. try:
  1281. re.compile(k_pattern)
  1282. except Exception as e:
  1283. log("k_pattern error: "+str(e))
  1284. traceback.print_exc()
  1285. return result_kv
  1286. result_kv = []
  1287. if from_table:
  1288. result_kv_table = self.extract_kvs_from_table([k_pattern])
  1289. for table_d in result_kv_table[0]:
  1290. table_id = table_d.get("table_id")
  1291. table_kvs = table_d.get("kv",[])
  1292. for _d in table_kvs:
  1293. _d["from_table"] = True
  1294. result_kv.extend(table_kvs)
  1295. if from_sentence:
  1296. result_kv_sentence = self.extract_kvs_from_sentence([k_pattern])
  1297. for _d in result_kv_sentence[0]:
  1298. _d["from_sentence"] = True
  1299. result_kv.extend(result_kv_sentence[0])
  1300. if from_outline:
  1301. result_kv_outline = self.extract_kvs_from_outline([k_pattern])
  1302. for _d in result_kv_outline[0]:
  1303. _d["from_outline"] = True
  1304. result_kv.extend(result_kv_outline[0])
  1305. return result_kv
  1306. # def extract_kvs_from_table(self,list_pattern):
  1307. if __name__ == '__main__':
  1308. # HTML 文本
  1309. html_content = """
  1310. <div>
  1311. <div>
  1312. <a target="_blank" class="markBlue" href="/bdqyhx/211521581770297344.html" style="color: #3083EB !important;text-decoration: underline;">辽宁机电职业技术学院</a>食堂账户开立项目(二次)竞争性磋商公告
  1313. </div>
  1314. <div>
  1315. <div>
  1316. <blockquote>
  1317. <p>项目概况</p>
  1318. <p>辽宁机电职业技术学院食堂账户开立项目 采购项目的潜在供应商应在<a target="_blank" class="markBlue" href="/bdqyhx/215785544561414148.html" style="color: #3083EB !important;text-decoration: underline;">辽宁顺业工程咨询有限公司</a>(丹东市振兴区纤维南路1-2-7号)获取采购文件,并于2024年11月26日 14点00分(北京时间)前提交响应文件。</p>
  1319. </blockquote>
  1320. <p><strong>一、项目基本情况</strong></p>
  1321. <p>项目编号:LNSY-2024101702</p>
  1322. <p>项目名称:<a target="_blank" class="markBlue" href="/bdqyhx/211521581770297344.html" style="color: #3083EB !important;text-decoration: underline;">辽宁机电职业技术学院</a>食堂账户开立项目</p>
  1323. <p>采购方式:竞争性磋商</p>
  1324. <p>预算金额:0.000000 万元(人民币)</p>
  1325. <p>最高限价(如有):0.000000 万元(人民币)</p>
  1326. <p>采购需求:</p>
  1327. <p>本项目为丹东市<a target="_blank" class="markBlue" href="/bdqyhx/211521581770297344.html" style="color: #3083EB !important;text-decoration: underline;">辽宁机电职业技术学院</a>食堂账户开立项目采购项目,银行须提供给学校所需的包括日常资金结算服务、代收代付、转账汇款、账户对帐服务、年检服务、网银操作、安全保障等在内的一切配套基本服务。(具体详见第三章服务需求)</p>
  1328. <p></p>
  1329. <p>合同履行期限:5年 </p>
  1330. <p>本项目(不接受 )联合体投标。</p>
  1331. <p><strong>二、申请人的资格要求:</strong></p>
  1332. <p>1.满足《中华人民共和国政府采购法》第二十二条规定;</p>
  1333. <p>2.落实政府采购政策需满足的资格要求:</p>
  1334. <p>无</p>
  1335. <p></p>
  1336. <p>3.本项目的特定资格要求:投标人应属于在中华人民共和国境内依法设立的国有商业银行、股份制商业银行、邮政储蓄银行、城市商业银行、农村商业银行、农村合作银行及政策性银行,并符合以下条件;(一)在甲方所在地设有分支机构;(二)在甲方所在地范围内依法开展经营活动,内部管理机制健全,具有较强的风险控制能力,近3年内在经营活动中无重大违法违规记录、未发生金融风险及重大违约事件。(三)投标人若为支行,须提供总行或丹东市分行针对本项目唯一授权书。各投标主体不得隶属于同一法人。不接受联合体投标。投标人若为总行或丹东市分行,须提供业务承办银行确认函(明确中标后承办本项目业务的银行名称)。(四)有专人负责办理相关业务。</p>
  1337. <p><strong>三、获取采购文件</strong></p>
  1338. <p>时间:2024年11月15日 至2024年11月25日,每天上午8:30至11:30,下午13:00至16:30。(北京时间,法定节假日除外)</p>
  1339. <p>地点:<a target="_blank" class="markBlue" href="/bdqyhx/215785544561414148.html" style="color: #3083EB !important;text-decoration: underline;">辽宁顺业工程咨询有限公司</a>(丹东市振兴区纤维南路1-2-7号)</p>
  1340. <p>方式:现场或电子邮件领取</p>
  1341. <p>售价:¥500.0 元(人民币)</p>
  1342. <p><strong>四、响应文件提交</strong></p>
  1343. <p>截止时间:2024年11月26日 14点00分(北京时间)</p>
  1344. <p>地点:<a target="_blank" class="markBlue" href="/bdqyhx/215785544561414148.html" style="color: #3083EB !important;text-decoration: underline;">辽宁顺业工程咨询有限公司</a>(丹东市振兴区纤维南路1-2-7号)</p>
  1345. <p><strong>五、开启</strong></p>
  1346. <p>时间:2024年11月26日 14点00分(北京时间)</p>
  1347. <p>地点:<a target="_blank" class="markBlue" href="/bdqyhx/215785544561414148.html" style="color: #3083EB !important;text-decoration: underline;">辽宁顺业工程咨询有限公司</a>(丹东市振兴区纤维南路1-2-7号)</p>
  1348. <p><strong>六、公告期限</strong></p>
  1349. <p>自本公告发布之日起3个工作日。</p>
  1350. <p><strong>七、其他补充事宜</strong></p>
  1351. <p></p>
  1352. <p>(一)质疑与投诉</p>
  1353. <p>供应商认为自己的权益受到损害的,可以在知道或者应知其权益受到损害之日起七个工作日内,向采购代理机构或采购人提出质疑。</p>
  1354. <p>1、接收质疑函方式:书面纸质或电子质疑函</p>
  1355. <p>2、质疑函内容、格式:应符合《政府采购质疑和投诉办法》相关规定和财政部制定的《政府采购质疑函范本》格式,详见辽宁政府采购网。</p>
  1356. <p>质疑供应商对采购人、采购代理机构的答复不满意,或者采购人、采购代理机构未在规定时间内作出答复的,可以在答复期满后15个工作日内向本级财政部门提起投诉。</p>
  1357. <p>(二)购买采购文件时须提供以下材料(以下材料均须加盖单位公章):</p>
  1358. <ol>
  1359. <li value="NaN">法人或者其他组织的营业执照等主体证明文件或自然人的身份证明复印件(自然人身份证明仅限在自然人作为响应主体时使用);</li>
  1360. <li value="NaN">法定代表人(或非法人组织负责人)身份证明书原件(附法定代表人身份证复印件)(自然人作为响应主体时不需提供);</li>
  1361. <li value="NaN">授权委托书原件(附授权委托人身份证复印件)(法定代表人、非法人组织负责人、自然人本人购买采购文件的无需提供);</li>
  1362. </ol>
  1363. <p>注:电子邮件方式领取采购文件的供应商,将上述材料加盖公章的扫描件发送至指定邮箱(lnsy9688@163.com)并致电0415-2199688,主题写明“供应商名称、项目名称、联系人、联系电话”,在领取采购文件截止时间前资料审查通过后,代理机构将采购文件电子版发送至供应商邮箱。</p>
  1364. <p></p>
  1365. <p></p>
  1366. <p><strong>八、凡对本次采购提出询问,请按以下方式联系。</strong></p>
  1367. <p>1.采购人信息</p>
  1368. <p>名 称:<a target="_blank" class="markBlue" href="/bdqyhx/211521581770297344.html" style="color: #3083EB !important;text-decoration: underline;">辽宁机电职业技术学院</a>     </p>
  1369. <p>地址:丹东市振兴区洋河大街30号        </p>
  1370. <p>联系方式:王老师0415-3853804      </p>
  1371. <p>2.采购代理机构信息</p>
  1372. <p>名 称:<a target="_blank" class="markBlue" href="/bdqyhx/215785544561414148.html" style="color: #3083EB !important;text-decoration: underline;">辽宁顺业工程咨询有限公司</a>            </p>
  1373. <p>地 址:<a target="_blank" class="markBlue" href="/bdqyhx/215785544561414148.html" style="color: #3083EB !important;text-decoration: underline;">辽宁顺业工程咨询有限公司</a>(丹东市振兴区纤维南路1-2-7号)            </p>
  1374. <p>联系方式:吴平0415-2199688            </p>
  1375. <p>3.项目联系方式</p>
  1376. <p>项目联系人:吴平</p>
  1377. <p>电 话:  04152199688</p> 
  1378. <p></p>
  1379. </div>
  1380. </div>
  1381. <div>
  1382. <div>
  1383. 公告概要:
  1384. </div>
  1385. <table width="600">
  1386. <tbody>
  1387. <tr>
  1388. <td colspan="4"><b>公告信息:</b></td>
  1389. </tr>
  1390. <tr>
  1391. <td width="128">采购项目名称</td>
  1392. <td colspan="3" width="430"><a target="_blank" class="markBlue" href="/bdqyhx/211521581770297344.html" style="color: #3083EB !important;text-decoration: underline;">辽宁机电职业技术学院</a>食堂账户开立项目</td>
  1393. </tr>
  1394. <tr>
  1395. <td>品目</td>
  1396. <td colspan="3"><p>服务/金融服务/银行服务/其他银行服务</p></td>
  1397. </tr>
  1398. <tr>
  1399. <td>采购单位</td>
  1400. <td colspan="3"><a target="_blank" class="markBlue" href="/bdqyhx/211521581770297344.html" style="color: #3083EB !important;text-decoration: underline;">辽宁机电职业技术学院</a></td>
  1401. </tr>
  1402. <tr>
  1403. <td>行政区域</td>
  1404. <td width="168">振兴区</td>
  1405. <td width="128">公告时间</td>
  1406. <td width="168">2024年11月14日 15:25</td>
  1407. </tr>
  1408. <tr>
  1409. <td>获取采购文件时间</td>
  1410. <td colspan="3">2024年11月15日至2024年11月25日<br>每日上午:8:30 至 11:30下午:13:00 至 16:30(北京时间,法定节假日除外)</td>
  1411. </tr>
  1412. <tr>
  1413. <td>响应文件递交地点</td>
  1414. <td colspan="3"><a target="_blank" class="markBlue" href="/bdqyhx/215785544561414148.html" style="color: #3083EB !important;text-decoration: underline;">辽宁顺业工程咨询有限公司</a>(丹东市振兴区纤维南路1-2-7号)</td>
  1415. </tr>
  1416. <tr>
  1417. <td>响应文件开启时间</td>
  1418. <td colspan="3">2024年11月26日 14:00</td>
  1419. </tr>
  1420. <tr>
  1421. <td>响应文件开启地点</td>
  1422. <td colspan="3"><a target="_blank" class="markBlue" href="/bdqyhx/215785544561414148.html" style="color: #3083EB !important;text-decoration: underline;">辽宁顺业工程咨询有限公司</a>(丹东市振兴区纤维南路1-2-7号)</td>
  1423. </tr>
  1424. <tr>
  1425. <td>预算金额</td>
  1426. <td colspan="3">¥0.000000万元(人民币)</td>
  1427. </tr>
  1428. <tr>
  1429. <td colspan="4"><b>联系人及联系方式:</b></td>
  1430. </tr>
  1431. <tr>
  1432. <td>项目联系人</td>
  1433. <td colspan="3">吴平</td>
  1434. </tr>
  1435. <tr>
  1436. <td>项目联系电话</td>
  1437. <td colspan="3">04152199688</td>
  1438. </tr>
  1439. <tr>
  1440. <td width="128">采购单位</td>
  1441. <td width="430" colspan="3"><a target="_blank" class="markBlue" href="/bdqyhx/211521581770297344.html" style="color: #3083EB !important;text-decoration: underline;">辽宁机电职业技术学院</a></td>
  1442. </tr>
  1443. <tr>
  1444. <td>采购单位地址</td>
  1445. <td colspan="3">丹东市振兴区洋河大街30号</td>
  1446. </tr>
  1447. <tr>
  1448. <td>采购单位联系方式</td>
  1449. <td colspan="3">王老师0415-3853804</td>
  1450. </tr>
  1451. <tr>
  1452. <td>代理机构名称</td>
  1453. <td colspan="3"><a target="_blank" class="markBlue" href="/bdqyhx/215785544561414148.html" style="color: #3083EB !important;text-decoration: underline;">辽宁顺业工程咨询有限公司</a></td>
  1454. </tr>
  1455. <tr>
  1456. <td>代理机构地址</td>
  1457. <td colspan="3"><a target="_blank" class="markBlue" href="/bdqyhx/215785544561414148.html" style="color: #3083EB !important;text-decoration: underline;">辽宁顺业工程咨询有限公司</a>(丹东市振兴区纤维南路1-2-7号)</td>
  1458. </tr>
  1459. <tr>
  1460. <td>代理机构联系方式</td>
  1461. <td colspan="3">吴平0415-2199688</td>
  1462. </tr>
  1463. </tbody>
  1464. </table>
  1465. </div>
  1466. </div>
  1467. """
  1468. _tree = html_to_tree(html_content)
  1469. _pd = Html2KVTree(html_content)
  1470. _pd.print_tree(_pd.tree,"-|")
  1471. list_kv = _pd.extract_kv("获取采购文件时间")
  1472. print(list_kv)
  1473. #获取预处理后的所有句子,该句子与kv值对应
  1474. print(_pd.get_tree_sentence())
  1475. # soup = BeautifulSoup(html_content,"lxml")
  1476. # table_tree = table_to_tree(soup)
  1477. # print(json.dumps(table_tree,ensure_ascii=False))