html_2_kvtree.py 75 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716
  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. list_kv = obj.get("kv",[])
  238. for _d in list_kv:
  239. _d["position"]["key_begin_sentence"] = sentence_index
  240. _d["position"]["key_end_sentence"] = sentence_index
  241. _d["position"]["value_begin_sentence"] = sentence_index
  242. _d["position"]["value_end_sentence"] = sentence_index
  243. else:
  244. sentence_index += 1
  245. obj["sentence_index"] = sentence_index
  246. obj["sentence_index_start"] = sentence_index
  247. obj["sentences"] = [obj.get("text","")]
  248. sentence_index_end = update_table_position(obj,sentence_index)
  249. obj["sentence_index_end"] = sentence_index_end
  250. sentence_index = sentence_index_end
  251. for _t in obj["sentences"]:
  252. wordOffset_end += len(_t)
  253. obj["wordOffset_begin"] = wordOffset_begin
  254. obj["wordOffset_end"] = wordOffset_end
  255. wordOffset_begin = wordOffset_end
  256. # 递归地将 DOM 转换为 JSON
  257. def dom_to_tree(node):
  258. if node.name: # 如果是标签节点
  259. json_obj = DotDict({"tag": node.name})
  260. if node.attrs:
  261. json_obj["attributes"] = node.attrs
  262. is_table = False
  263. if node.name in ("table","tbody"):
  264. json_obj = table_to_tree(node)
  265. is_table = True
  266. if not is_table:
  267. children = []
  268. for child in node.contents:
  269. _child = dom_to_tree(child)
  270. if _child is not None:
  271. children.append(_child)
  272. if children:
  273. json_obj["children"] = children
  274. json_obj["name"] = json_obj.get("tag")
  275. return json_obj
  276. elif node.string and node.string.strip(): # 如果是纯文本节点
  277. _text = node.string.strip()
  278. _text = re.sub('\xa0','',_text)
  279. list_text = re.split("\s",_text)
  280. _text = ""
  281. for _t in list_text:
  282. if len(_t)<3:
  283. if len(_t)>0:
  284. _text += _t
  285. else:
  286. _text += _t+" "
  287. _text = _text.strip()
  288. return DotDict({"tag":"text","name":"text","text": _text})
  289. return None # 忽略空白字符
  290. def tree_pop_parent(tree):
  291. if isinstance(tree,list):
  292. for child in tree:
  293. tree_pop_parent(child)
  294. if isinstance(tree,dict):
  295. if "parent" in tree:
  296. del tree["parent"]
  297. for child in tree.get("children",[]):
  298. tree_pop_parent(child)
  299. def html_to_tree(html_content):
  300. # 使用 BeautifulSoup 解析 HTML
  301. soup = BeautifulSoup(html_content, "lxml")
  302. dom_tree = dom_to_tree(soup)
  303. extract_kv_from_tree(dom_tree)
  304. list_objs = get_outobjs_from_tree(dom_tree)
  305. tree_reposition(list_objs)
  306. return dom_tree
  307. def print_tree(dom_tree):
  308. # 转换为 JSON 格式
  309. tree_pop_parent(dom_tree)
  310. json_output = json.dumps(dom_tree,ensure_ascii=False, indent=2)
  311. # kv_pattern = "\s*(?P<key>.{,10})[::]\s*(?P<value>[^::。,()]+?)(\s+|$|;|;)(?![\u4e00-\u9fa5]+:)"
  312. kv_pattern = r"(?P<key>[\u4e00-\u9fa5]+):\s*(?P<value>[^\s,。();;]+)"
  313. def get_kv_pattern():
  314. import re
  315. text = """
  316. name: John age: 30 note: invalid;
  317. """
  318. # 正则模式
  319. kv_pattern = r"(?P<key>[a-zA-Z]+)[::](?P<value>.+(?!.*[::]))"
  320. # 提取匹配
  321. matches = re.findall(kv_pattern, text)
  322. # 打印结果
  323. for match in matches:
  324. key, value = match
  325. print("{%s}: {%s}"%(key,value))
  326. def extract_kv_from_sentence(sentence):
  327. list_kv = []
  328. _iter = re.finditer("[::]", sentence)
  329. if _iter:
  330. list_span = []
  331. for iter in _iter:
  332. list_span.append(iter.span())
  333. if len(list_span)==1:
  334. _begin,_end = list_span[0]
  335. if _begin<20 and _end<len(sentence)-1:
  336. _d = DotDict({"key":sentence[0:_begin],"value":sentence[_end:]})
  337. _d["position"] = {"key_begin_sentence":0,
  338. "key_begin_sentence_start":0,
  339. "key_end_sentence":0,
  340. "key_end_sentence_end":_begin,
  341. "value_begin_sentence":0,
  342. "value_begin_sentence_start":_end,
  343. "value_end_sentence":0,
  344. "value_end_sentence_end":len(sentence)
  345. }
  346. list_kv.append(_d)
  347. else:
  348. _begin = 0
  349. _end = len(sentence)-1
  350. iter = re.search(kv_pattern,sentence[_begin:_end])
  351. if iter is not None:
  352. _d = DotDict({})
  353. _d["key"] = iter.group("key")
  354. _d["value"] = iter.group("value")
  355. _d["position"] = {"key_begin_sentence":0,
  356. "key_begin_sentence_start":iter.span("key")[0],
  357. "key_end_sentence":0,
  358. "key_end_sentence_end":iter.span("key")[0]+len(_d.get("key","")),
  359. "value_begin_sentence":0,
  360. "value_begin_sentence_start":iter.span("value")[0],
  361. "value_end_sentence":0,
  362. "value_end_sentence_end":iter.span("value")[0]+len(_d.get("value",""))
  363. }
  364. list_kv.append(_d)
  365. elif len(list_span)>1:
  366. _begin,_end = list_span[0]
  367. if _begin<20 and len(sentence)>100:
  368. _d = DotDict({"key":sentence[0:_begin],"value":sentence[_end:]})
  369. _d["position"] = {"key_begin_sentence":0,
  370. "key_begin_sentence_start":0,
  371. "key_end_sentence":0,
  372. "key_end_sentence_end":_begin,
  373. "value_begin_sentence":0,
  374. "value_begin_sentence_start":_end,
  375. "value_end_sentence":0,
  376. "value_end_sentence_end":len(sentence)
  377. }
  378. list_kv.append(_d)
  379. else:
  380. _begin = 0
  381. for _i in range(len(list_span)-1):
  382. _end = list_span[_i+1][0]
  383. iter = re.search(kv_pattern,sentence[_begin:_end])
  384. _begin = list_span[_i][1]
  385. if iter is not None:
  386. _d = DotDict({})
  387. _d["key"] = iter.group("key")
  388. _d["value"] = iter.group("value")
  389. _d["position"] = {"key_begin_sentence":0,
  390. "key_begin_sentence_start":iter.span("key")[0],
  391. "key_end_sentence":0,
  392. "key_end_sentence_end":iter.span("key")[0]+len(_d.get("key","")),
  393. "value_begin_sentence":0,
  394. "value_begin_sentence_start":iter.span("value")[0],
  395. "value_end_sentence":0,
  396. "value_end_sentence_end":iter.span("value")[0]+len(_d.get("value",""))
  397. }
  398. list_kv.append(_d)
  399. _begin = list_span[-2][1]
  400. _end = len(sentence)
  401. iter = re.search(kv_pattern,sentence[_begin:_end])
  402. if iter is not None:
  403. _d = DotDict({})
  404. _d["key"] = iter.group("key")
  405. _d["value"] = iter.group("value")
  406. _d["position"] = {"key_begin_sentence":0,
  407. "key_begin_sentence_start":iter.span("key")[0],
  408. "key_end_sentence":0,
  409. "key_end_sentence_end":iter.span("key")[0]+len(_d.get("key","")),
  410. "value_begin_sentence":0,
  411. "value_begin_sentence_start":iter.span("value")[0],
  412. "value_end_sentence":0,
  413. "value_end_sentence_end":iter.span("value")[0]+len(_d.get("value",""))
  414. }
  415. list_kv.append(_d)
  416. # for iter in _iter:
  417. # _d = DotDict({})
  418. # _d["key"] = iter.group("key")
  419. # _d["value"] = iter.group("value")
  420. # _d["key_span"] = iter.span("key")
  421. # _d["value_span"] = iter.span("value")
  422. # list_kv.append(_d)
  423. return list_kv
  424. def extract_kv_from_node(node):
  425. list_kv = []
  426. list_text = []
  427. childs = node.get("children",[])
  428. _text = ""
  429. has_br = False
  430. if childs:
  431. for child in childs:
  432. node_name = child.get("tag","")
  433. child_text = child.get("text")
  434. if node_name=="br":
  435. list_text.append([])
  436. has_br = True
  437. if child_text:
  438. if len(list_text)==0:
  439. list_text.append([])
  440. list_text[-1].append(child)
  441. node["kv"] = []
  442. if has_br:
  443. new_children = []
  444. for texts in list_text:
  445. if texts:
  446. _text = "".join([a.get("text") for a in texts])
  447. tag = texts[0]
  448. list_kv = extract_kv_from_sentence(_text)
  449. _n = DotDict({"tag":tag,"name":tag,"text":_text,"children":[],"kv":list_kv})
  450. new_children.append(_n)
  451. node["children"] = new_children
  452. else:
  453. for texts in list_text:
  454. _text = "".join([a.get("text") for a in texts])
  455. if _text:
  456. list_kv = extract_kv_from_sentence(_text)
  457. node["kv"].extend(list_kv)
  458. else:
  459. _text = node.get("text")
  460. if _text:
  461. list_kv = extract_kv_from_sentence(_text)
  462. node["kv"] = list_kv
  463. return list_kv
  464. def get_child_text(node):
  465. _text = node.get("text","")
  466. for child in node.get("children",[]):
  467. _text += get_child_text(child)
  468. return _text
  469. def extract_kv_from_tree(tree):
  470. if isinstance(tree,list):
  471. _count = 0
  472. has_table = False
  473. for child in tree:
  474. _c,_t = extract_kv_from_tree(child)
  475. _count += _c
  476. if _t:
  477. has_table = _t
  478. return _count,has_table
  479. if isinstance(tree,dict):
  480. if tree.get("tag","")!="table":
  481. childs = tree.get("children",[])
  482. if len(childs)>0:
  483. _count = 0
  484. has_table = False
  485. child_has_p_div = False
  486. child_has_br = False
  487. for child in childs:
  488. _c,_t = extract_kv_from_tree(child)
  489. _count += _c
  490. if _t:
  491. has_table = _t
  492. if child.get("tag","") in ("p","div","li"):
  493. child_has_p_div = True
  494. if child.get("tag","")=="br":
  495. child_has_br = True
  496. if _count==0:
  497. if not has_table and not child_has_p_div and not child_has_br:
  498. _text = get_child_text(tree)
  499. if "children" in tree:
  500. del tree["children"]
  501. tree["text"] = _text
  502. list_kv = extract_kv_from_node(tree)
  503. _count = len(list_kv)
  504. return _count,has_table
  505. if tree.get("tag","") in ("p","div","li") and not has_table and not child_has_p_div:
  506. if not child_has_br:
  507. _text = get_child_text(tree)
  508. tree["text"] = _text
  509. if "children" in tree:
  510. del tree["children"]
  511. p_list_kv = extract_kv_from_node(tree)
  512. return len(p_list_kv),has_table
  513. return _count,has_table
  514. else:
  515. list_kv = extract_kv_from_node(tree)
  516. return len(list_kv),False
  517. else:
  518. return len(tree.get("kv",[])),True
  519. return 0,False
  520. def update_kv_span(list_kv,append_length):
  521. for _d in list_kv:
  522. _d["position"] = {"key_begin_sentence":0,
  523. "key_begin_sentence_start":_d.get("key_sen_index",0),
  524. "key_end_sentence":0,
  525. "key_end_sentence_end":_d.get("key_sen_index",0)+len(_d.get("key","")),
  526. "value_begin_sentence":0,
  527. "value_begin_sentence_start":_d.get("value_sen_index",0),
  528. "value_end_sentence":0,
  529. "value_end_sentence_end":_d.get("value_sen_index",0)+len(_d.get("value",""))
  530. }
  531. _d["position"]["key_begin_sentence_start"] += append_length
  532. _d["position"]["key_end_sentence_end"] += append_length
  533. _d["position"]["value_begin_sentence_start"] += append_length
  534. _d["position"]["value_end_sentence_end"] += append_length
  535. def get_outobjs_from_tree(tree,list_outobjs=None):
  536. is_first = False
  537. if list_outobjs is None:
  538. list_outobjs = []
  539. is_first = True
  540. if isinstance(tree,list):
  541. for child in tree:
  542. get_outobjs_from_tree(child,list_outobjs)
  543. if isinstance(tree,dict):
  544. childs = tree.get("children",[])
  545. _text = tree.get("text","")
  546. is_table = True if tree.get("tag","")=="table" else False
  547. if is_table:
  548. list_outobjs.append(tree)
  549. else:
  550. if _text!="":
  551. tree.name = tree.tag
  552. list_outobjs.append(tree)
  553. for child in childs:
  554. get_outobjs_from_tree(child,list_outobjs)
  555. return list_outobjs
  556. def standard_title_context(_title_context):
  557. return _title_context.replace("(","(").replace(")",")").replace(":",":").replace(":",";").replace(",",".").replace(",",".").replace("、",".").replace(".",".")
  558. def standard_product(sentence):
  559. return sentence.replace("(","(").replace(")",")")
  560. import Levenshtein
  561. import copy
  562. def jaccard_score(source,target):
  563. source_set = set([s for s in source])
  564. target_set = set([s for s in target])
  565. if len(source_set)==0 or len(target_set)==0:
  566. return 0
  567. return max(len(source_set&target_set)/len(source_set),len(source_set&target_set)/len(target_set))
  568. def judge_pur_chinese(keyword):
  569. """
  570. 中文字符的编码范围为: u'\u4e00' -- u'\u9fff:只要在此范围内就可以判断为中文字符串
  571. @param keyword:
  572. @return:
  573. """
  574. # 定义一个需要删除的标点符号字符串列表
  575. remove_chars = '[·’!"\#$%&\'()#!()*+,-./:;<=>?\@,:?¥★、….>【】[]《》?“”‘’\[\\]^_`{|}~]+'
  576. # 利用re.sub来删除中文字符串中的标点符号
  577. strings = re.sub(remove_chars, "", keyword) # 将keyword中文字符串中remove_chars中包含的标点符号替换为空字符串
  578. for ch in strings:
  579. if u'\u4e00' <= ch <= u'\u9fff':
  580. pass
  581. else:
  582. return False
  583. return True
  584. def is_similar(source,target,_radio=None):
  585. source = str(source).lower()
  586. target = str(target).lower()
  587. max_len = max(len(source),len(target))
  588. min_len = min(len(source),len(target))
  589. min_ratio = 90
  590. if min_len>=3:
  591. min_ratio = 87
  592. if min_len>=5:
  593. min_ratio = 85
  594. if _radio is not None:
  595. min_ratio = _radio
  596. # dis_len = abs(len(source)-len(target))
  597. # min_dis = min(max_len*0.2,4)
  598. if min_len==0 and max_len>0:
  599. return False
  600. if max_len<=2:
  601. if source==target:
  602. return True
  603. if min_len<2:
  604. return False
  605. #判断相似度
  606. similar = Levenshtein.ratio(source,target)*100
  607. if similar>=min_ratio:
  608. log("%s and %s similar_jaro %d"%(source,target,similar))
  609. return True
  610. similar_jaro = Levenshtein.jaro(source,target)
  611. if similar_jaro*100>=min_ratio:
  612. log("%s and %s similar_jaro %d"%(source,target,similar_jaro*100))
  613. return True
  614. similar_jarow = Levenshtein.jaro_winkler(source,target)
  615. if similar_jarow*100>=min_ratio:
  616. log("%s and %s similar_jaro %d"%(source,target,similar_jarow*100))
  617. return True
  618. if min_len>=5:
  619. if len(source)==max_len and str(source).find(target)>=0:
  620. return True
  621. elif len(target)==max_len and target.find(source)>=0:
  622. return True
  623. elif jaccard_score(source, target)==1 and judge_pur_chinese(source) and judge_pur_chinese(target):
  624. return True
  625. return False
  626. end_pattern = "商务要求|评分标准|商务条件|商务条件"
  627. _param_pattern = "(产品|技术|清单|配置|参数|具体|明细|项目|招标|货物|服务|规格|工作|具体)[及和与]?(指标|配置|条件|要求|参数|需求|规格|条款|名称及要求)|配置清单|(质量|技术).{,10}要求|验收标准|^(参数|功能)$"
  628. 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大中小]+码|净重|颜色|[红橙黄绿青蓝紫]色|不锈钢|输入|输出|噪声|认证|配置"
  629. not_meter_pattern = "投标报价|中标金额|商务部分|公章|分值构成|业绩|详见|联系人|联系电话|合同价|金额|采购预算|资金来源|费用|质疑|评审因素|评审标准|商务资信|商务评分|专家论证意见|评标方法|代理服务费|售后服务|评分类型|评分项目|预算金额|得\d+分|项目金额|详见招标文件|乙方"
  630. def getTrs(tbody):
  631. #获取所有的tr
  632. trs = []
  633. if tbody.name=="table":
  634. body = tbody.find("tbody",recursive=False)
  635. if body is not None:
  636. tbody = body
  637. objs = tbody.find_all(recursive=False)
  638. for obj in objs:
  639. if obj.name=="tr":
  640. trs.append(obj)
  641. if obj.name=="tbody" or obj.name=="table":
  642. for tr in obj.find_all("tr",recursive=False):
  643. trs.append(tr)
  644. return trs
  645. def fixSpan(tbody):
  646. # 处理colspan, rowspan信息补全问题
  647. #trs = tbody.findChildren('tr', recursive=False)
  648. trs = getTrs(tbody)
  649. ths_len = 0
  650. ths = list()
  651. trs_set = set()
  652. #修改为先进行列补全再进行行补全,否则可能会出现表格解析混乱
  653. # 遍历每一个tr
  654. for indtr, tr in enumerate(trs):
  655. ths_tmp = tr.findChildren('th', recursive=False)
  656. #不补全含有表格的tr
  657. if len(tr.findChildren('table'))>0:
  658. continue
  659. if len(ths_tmp) > 0:
  660. ths_len = ths_len + len(ths_tmp)
  661. for th in ths_tmp:
  662. ths.append(th)
  663. trs_set.add(tr)
  664. # 遍历每行中的element
  665. tds = tr.findChildren(recursive=False)
  666. for indtd, td in enumerate(tds):
  667. # 若有colspan 则补全同一行下一个位置
  668. if 'colspan' in td.attrs:
  669. if str(re.sub("[^0-9]","",str(td['colspan'])))!="":
  670. col = int(re.sub("[^0-9]","",str(td['colspan'])))
  671. if col<100 and len(td.get_text())<1000:
  672. td['colspan'] = 1
  673. for i in range(1, col, 1):
  674. td.insert_after(copy.copy(td))
  675. for indtr, tr in enumerate(trs):
  676. ths_tmp = tr.findChildren('th', recursive=False)
  677. #不补全含有表格的tr
  678. if len(tr.findChildren('table'))>0:
  679. continue
  680. if len(ths_tmp) > 0:
  681. ths_len = ths_len + len(ths_tmp)
  682. for th in ths_tmp:
  683. ths.append(th)
  684. trs_set.add(tr)
  685. # 遍历每行中的element
  686. tds = tr.findChildren(recursive=False)
  687. for indtd, td in enumerate(tds):
  688. # 若有rowspan 则补全下一行同样位置
  689. if 'rowspan' in td.attrs:
  690. if str(re.sub("[^0-9]","",str(td['rowspan'])))!="":
  691. row = int(re.sub("[^0-9]","",str(td['rowspan'])))
  692. td['rowspan'] = 1
  693. for i in range(1, row, 1):
  694. # 获取下一行的所有td, 在对应的位置插入
  695. if indtr+i<len(trs):
  696. tds1 = trs[indtr + i].findChildren(['td','th'], recursive=False)
  697. if len(tds1) >= (indtd) and len(tds1)>0:
  698. if indtd > 0:
  699. tds1[indtd - 1].insert_after(copy.copy(td))
  700. else:
  701. tds1[0].insert_before(copy.copy(td))
  702. elif indtd-2>0 and len(tds1) > 0 and len(tds1) == indtd - 1: # 修正某些表格最后一列没补全
  703. tds1[indtd-2].insert_after(copy.copy(td))
  704. def getTable(tbody):
  705. #trs = tbody.findChildren('tr', recursive=False)
  706. fixSpan(tbody)
  707. trs = getTrs(tbody)
  708. inner_table = []
  709. for tr in trs:
  710. tr_line = []
  711. tds = tr.findChildren(['td','th'], recursive=False)
  712. if len(tds)==0:
  713. tr_line.append([re.sub('\xa0','',tr.get_text()),0]) # 2021/12/21 修复部分表格没有td 造成数据丢失
  714. for td in tds:
  715. tr_line.append([re.sub('\xa0','',td.get_text()),0])
  716. #tr_line.append([td.get_text(),0])
  717. inner_table.append(tr_line)
  718. return inner_table
  719. def extract_products(list_data,_product,_param_pattern = "产品名称|设备材料|采购内存|标的名称|采购内容|(标的|维修|系统|报价构成|商品|产品|物料|物资|货物|设备|采购品|采购条目|物品|材料|印刷品?|采购|物装|配件|资产|耗材|清单|器材|仪器|器械|备件|拍卖物|标的物|物件|药品|药材|药械|货品|食品|食材|品目|^品名|气体|标项|分项|项目|计划|包组|标段|[分子]?包|子目|服务|招标|中标|成交|工程|招标内容)[\))的]?([、\w]{,4}名称|内容|描述)|标的|标项|项目$|商品|产品|物料|物资|货物|设备|采购品|采购条目|物品|材料|印刷品|物装|配件|资产|招标内容|耗材|清单|器材|仪器|器械|备件|拍卖物|标的物|物件|药品|药材|药械|货品|食品|食材|菜名|^品目$|^品名$|^名称|^内容$"):
  720. _product = standard_product(_product)
  721. list_result = []
  722. list_table_products = []
  723. for _data_i in range(len(list_data)):
  724. _data = list_data[_data_i]
  725. _type = _data["type"]
  726. _text = _data["text"]
  727. if _type=="table":
  728. list_table = _data["list_table"]
  729. if list_table is None:
  730. continue
  731. _check = True
  732. max_length = max([len(a) for a in list_table])
  733. min_length = min([len(a) for a in list_table])
  734. if min_length<max_length/2:
  735. continue
  736. list_head_index = []
  737. _begin_index = 0
  738. head_cell_text = ""
  739. for line_i in range(len(list_table[:2])):
  740. line = list_table[line_i]
  741. line_text = ",".join([cell[0] for cell in line])
  742. for cell_i in range(len(line)):
  743. cell = line[cell_i]
  744. cell_text = cell[0]
  745. if len(cell_text)<10 and re.search(_param_pattern,cell_text) is not None and re.search("单价|数量|预算|限价|总价|品牌|规格|型号|用途|要求|采购量",line_text) is not None:
  746. _begin_index = line_i+1
  747. list_head_index.append(cell_i)
  748. for line_i in range(len(list_table)):
  749. line = list_table[line_i]
  750. for cell_i in list_head_index:
  751. if cell_i>=len(line):
  752. continue
  753. cell = line[cell_i]
  754. cell_text = cell[0]
  755. head_cell_text += cell_text
  756. # print("===head_cell_text",head_cell_text)
  757. if re.search("招标人|采购人|项目编号|项目名称|金额|^\d+$",head_cell_text) is not None:
  758. list_head_index = []
  759. for line in list_table:
  760. line_text = ",".join([cell[0] for cell in line])
  761. for cell_i in range(len(line)):
  762. cell = line[cell_i]
  763. cell_text = cell[0]
  764. 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:
  765. list_head_index.append(cell_i)
  766. list_head_index = list(set(list_head_index))
  767. if len(list_head_index)>0:
  768. has_number = False
  769. for cell_i in list_head_index:
  770. table_products = []
  771. for line_i in range(_begin_index,len(list_table)):
  772. line = list_table[line_i]
  773. for _i in range(len(line)):
  774. cell = line[_i]
  775. cell_text = cell[0]
  776. if re.search("^\d+$",cell_text) is not None:
  777. has_number = True
  778. if cell_i>=len(line):
  779. continue
  780. cell = line[cell_i]
  781. cell_text = cell[0]
  782. if re.search(_param_pattern,cell_text) is None or has_number:
  783. if re.search("^[\da-zA-Z]+$",cell_text) is None:
  784. table_products.append(cell_text)
  785. if len(table_products)>0:
  786. logger.debug("table products %s"%(str(table_products)))
  787. if min([len(x) for x in table_products])>0 and max([len(x) for x in table_products])<=30:
  788. if re.search("招标人|代理人|预算|数量|交货期|品牌|产地","".join(table_products)) is None:
  789. list_table_products.append(table_products)
  790. _find = False
  791. for table_products in list_table_products:
  792. for _p in table_products:
  793. if is_similar(_product,_p,90):
  794. _find = True
  795. logger.debug("similar table_products %s"%(str(table_products)))
  796. list_result = list(set([a for a in table_products if len(a)>1 and len(a)<20 and re.search("费用|预算|合计|金额|万元|运费|^其他$",a) is None]))
  797. break
  798. if not _find:
  799. for table_products in list_table_products:
  800. list_result.extend(table_products)
  801. list_result = list(set([a for a in list_result if len(a)>1 and len(a)<30 and re.search("费用|预算|合计|金额|万元|运费",a) is None]))
  802. return list_result
  803. def get_childs(childs, max_depth=None):
  804. list_data = []
  805. for _child in childs:
  806. list_data.append(_child)
  807. childs2 = _child.get("child_title",[])
  808. if len(childs2)>0 and (max_depth==None or max_depth>0):
  809. for _child2 in childs2:
  810. if max_depth != None:
  811. list_data.extend(get_childs([_child2], max_depth-1))
  812. else:
  813. list_data.extend(get_childs([_child2], None))
  814. return list_data
  815. class Html2KVTree():
  816. def __init__(self,_html,auto_merge_table=True,list_obj = []):
  817. if _html is None:
  818. _html = ""
  819. self.html = _html
  820. self.auto_merge_table = auto_merge_table
  821. if list_obj:
  822. self.list_obj = list_obj
  823. else:
  824. _tree = html_to_tree(html_content)
  825. self.list_obj = get_outobjs_from_tree(_tree)
  826. # for obj in self.list_obj:
  827. # print("obj",obj.get_text()[:20])
  828. self.tree = self.buildParsetree(self.list_obj,[],auto_merge_table)
  829. # #识别目录树
  830. # self.print_tree(self.tree,"-|")
  831. def get_soup_objs(self,soup,list_obj=None):
  832. if list_obj is None:
  833. list_obj = []
  834. childs = soup.find_all(recursive=False)
  835. for _obj in childs:
  836. childs1 = _obj.find_all(recursive=False)
  837. if len(childs1)==0 or len(_obj.get_text())<40 or _obj.name=="table":
  838. list_obj.append(_obj)
  839. elif _obj.name=="p":
  840. list_obj.append(_obj)
  841. else:
  842. self.get_soup_objs(_obj,list_obj)
  843. return list_obj
  844. def fix_tree(self,_product):
  845. products = extract_products(self.tree,_product)
  846. if len(products)>0:
  847. self.tree = self.buildParsetree(self.list_obj,products,self.auto_merge_table)
  848. def print_tree(self,tree,append="",set_tree_id=None):
  849. if set_tree_id is None:
  850. set_tree_id = set()
  851. if append=="":
  852. for t in tree:
  853. 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"]))
  854. for t in tree:
  855. _id = id(t)
  856. if _id in set_tree_id:
  857. continue
  858. set_tree_id.add(_id)
  859. 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"])))
  860. childs = t["child_title"]
  861. self.print_tree(childs,append=append+"-|",set_tree_id=set_tree_id)
  862. def is_title_first(self,title):
  863. if title in ("一","1","Ⅰ","a","A"):
  864. return True
  865. return False
  866. 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>[、章册包标部.::]+))|" \
  867. "([\s★▲\*]*)(?P<title_3>(?P<title_3_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?)(?P<title_3_index_0_1>[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)(?P<title_3_index_0_2>[、章册包标部.::]+))|" \
  868. "([\s★▲\*]*)(?P<title_4>(?P<title_4_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?第?)(?P<title_4_index_1_1>[一二三四五六七八九十]+)(?P<title_4_index_2_0>[节章册部\.::、、]+))|" \
  869. "([\s★▲\*]*)(?P<title_5>(?P<title_5_index_0_0>^)(?P<title_5_index_1_1>[一二三四五六七八九十]+)(?P<title_5_index_2_0>)[^一二三四五六七八九十节章册部\.::、])|" \
  870. "([\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\-]?))|"\
  871. "([\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\-]?))|" \
  872. "([\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\-]?))|" \
  873. "([\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\-]*))|" \
  874. "(^[\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\-包标]*))|" \
  875. "([\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>[))包标\..::、]+))|" \
  876. "([\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>[))包标\..::、]+))|" \
  877. "([\s★▲\*]*)(?P<title_19>(?P<title_19_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?[((]?)(?P<title_19_index_1_1>[一二三四五六七八九十ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)(?P<title_19_index_2_0>[))]))"
  878. ):
  879. _se = re.search(_pattern,_text)
  880. groups = []
  881. if _se is not None:
  882. e = _se.end()
  883. if re.search('(时间|日期|编号|账号|号码|手机|价格|\w价|人民币|金额|得分|分值|总分|满分|最高得|扣|减|数量|评委)[::]?\d', _se.group(0)) or (re.search('\d[.::]?$', _se.group(0)) and re.search('^[\d年月日万元天个分秒台条A-Za-z]|^(小时)', _text[e:])):
  884. return None
  885. elif re.match('[二三四五六七八九十]\w{1,2}[市区县]|五金|四川|八疆|九龙|[一二三四五六七八九十][层天标包]', _text) and re.match('[一二三四五六七八九十]', _se.group(0)): # 289765335 排除三明市等开头作为大纲
  886. return None
  887. elif re.search('^[\u4e00-\u9fa5]+[::]', _text[:e]):
  888. return None
  889. _gd = _se.groupdict()
  890. for k,v in _gd.items():
  891. if v is not None:
  892. groups.append((k,v))
  893. if len(groups):
  894. groups.sort(key=lambda x:x[0])
  895. return groups
  896. return None
  897. def make_increase(self,_sort,_title,_add=1):
  898. if len(_title)==0 and _add==0:
  899. return ""
  900. if len(_title)==0 and _add==1:
  901. return _sort[0]
  902. _index = _sort.index(_title[-1])
  903. next_index = (_index+_add)%len(_sort)
  904. next_chr = _sort[next_index]
  905. if _index==len(_sort)-1:
  906. _add = 1
  907. else:
  908. _add = 0
  909. return next_chr+self.make_increase(_sort,_title[:-1],_add)
  910. def get_next_title(self,_title):
  911. if re.search("^\d+$",_title) is not None:
  912. return str(int(_title)+1)
  913. if re.search("^[一二三四五六七八九十百]+$",_title) is not None:
  914. if _title[-1]=="十":
  915. return _title+"一"
  916. if _title[-1]=="百":
  917. return _title+"零一"
  918. if _title[-1]=="九":
  919. if len(_title)==1:
  920. return "十"
  921. if len(_title)==2:
  922. if _title[0]=="十":
  923. return "二十"
  924. if len(_title)==3:
  925. if _title[0]=="九":
  926. return "一百"
  927. else:
  928. _next_title = self.make_increase(['一','二','三','四','五','六','七','八','九','十'],re.sub("[十百]",'',_title[0]))
  929. return _next_title+"十"
  930. _next_title = self.make_increase(['一','二','三','四','五','六','七','八','九','十'],re.sub("[十百]",'',_title))
  931. _next_title = list(_next_title)
  932. _next_title.reverse()
  933. if _next_title[-1]!="十":
  934. if len(_next_title)>=2:
  935. _next_title.insert(-1,'十')
  936. if len(_next_title)>=4:
  937. _next_title.insert(-3,'百')
  938. if _title[0]=="十":
  939. if _next_title=="十":
  940. _next_title = ["二","十"]
  941. _next_title.insert(0,"十")
  942. _next_title = "".join(_next_title)
  943. return _next_title
  944. if re.search("^[a-z]+$",_title) is not None:
  945. _next_title = self.make_increase([chr(i+ord('a')) for i in range(26)],_title)
  946. _next_title = list(_next_title)
  947. _next_title.reverse()
  948. return "".join(_next_title)
  949. if re.search("^[A-Z]+$",_title) is not None:
  950. _next_title = self.make_increase([chr(i+ord('A')) for i in range(26)],_title)
  951. _next_title = list(_next_title)
  952. _next_title.reverse()
  953. return "".join(_next_title)
  954. if re.search("^[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]$",_title) is not None:
  955. _sort = ["Ⅰ","Ⅱ","Ⅲ","Ⅳ","Ⅴ","Ⅵ","Ⅶ","Ⅷ","Ⅸ","Ⅹ","Ⅺ","Ⅻ"]
  956. _index = _sort.index(_title)
  957. if _index<len(_sort)-1:
  958. return _sort[_index+1]
  959. return None
  960. def count_title_before(self,list_obj):
  961. dict_before = {}
  962. dict_sentence_count = {}
  963. illegal_sentence = set()
  964. for obj_i in range(len(list_obj)):
  965. obj = list_obj[obj_i]
  966. _type = "sentence"
  967. _text = obj.text.strip()
  968. if obj.name=="table":
  969. _type = "table"
  970. _text = str(obj)
  971. _append = False
  972. if _type=="sentence":
  973. if len(_text)>10 and len(_text)<100:
  974. if _text not in dict_sentence_count:
  975. dict_sentence_count[_text] = 0
  976. dict_sentence_count[_text] += 1
  977. if re.search("\d+页",_text) is not None:
  978. illegal_sentence.add(_text)
  979. elif len(_text)<10:
  980. if re.search("第\d+页",_text) is not None:
  981. illegal_sentence.add(_text)
  982. sentence_groups = self.find_title_by_pattern(_text[:10])
  983. if sentence_groups:
  984. # c062f53cf83401e671822003d63c1828print("sentence_groups",sentence_groups)
  985. sentence_title = sentence_groups[0][0]
  986. sentence_title_text = sentence_groups[0][1]
  987. title_index = sentence_groups[-2][1]
  988. title_before = sentence_groups[1][1].replace("(","(").replace(":",":").replace(":",";").replace(",",".").replace(",",".").replace("、",".")
  989. title_after = sentence_groups[-1][1].replace(")",")").replace(":",":").replace(":",";").replace(",",".").replace(",",".").replace("、",".")
  990. next_index = self.get_next_title(title_index)
  991. if title_before not in dict_before:
  992. dict_before[title_before] = 0
  993. dict_before[title_before] += 1
  994. for k,v in dict_sentence_count.items():
  995. if v>10:
  996. illegal_sentence.add(k)
  997. return dict_before,illegal_sentence
  998. def is_page_no(self,sentence):
  999. if len(sentence)<10:
  1000. if re.search("\d+页|^\-\d+\-$",sentence) is not None:
  1001. return True
  1002. def block_tree(self,childs):
  1003. for child in childs:
  1004. if not child["block"]:
  1005. child["block"] = True
  1006. childs2 = child["child_title"]
  1007. self.block_tree(childs2)
  1008. def buildParsetree(self,list_obj,products=[],auto_merge_table=True,auto_append=False):
  1009. self.parseTree = None
  1010. trees = []
  1011. list_length = []
  1012. for obj in list_obj[:200]:
  1013. if obj.name!="table":
  1014. list_length.append(len(obj.text))
  1015. if len(list_length)>0:
  1016. max_length = max(list_length)
  1017. else:
  1018. max_length = 40
  1019. max_length = min(max_length,40)
  1020. logger.debug("%s:%d"%("max_length",max_length))
  1021. list_data = []
  1022. last_table_index = None
  1023. last_table_columns = None
  1024. last_table = None
  1025. dict_before,illegal_sentence = self.count_title_before(list_obj)
  1026. for obj_i in range(len(list_obj)):
  1027. obj = list_obj[obj_i]
  1028. # logger.debug("==obj %s"%obj.text[:20])
  1029. _type = "sentence"
  1030. _text = standard_product(obj.text)
  1031. if obj.name=="table":
  1032. _type = "table"
  1033. _text = standard_product(str(obj))
  1034. _append = False
  1035. sentence_title = None
  1036. sentence_title_text = None
  1037. sentence_groups = None
  1038. title_index = None
  1039. next_index = None
  1040. parent_title = None
  1041. title_before = None
  1042. title_after = None
  1043. title_next = None
  1044. childs = []
  1045. # new
  1046. sentence_index = obj.sentence_index
  1047. wordOffset_begin = obj.wordOffset_begin
  1048. wordOffset_end = obj.wordOffset_end
  1049. sentences = obj.sentences
  1050. list_kv = obj.get("kv",[])
  1051. table_id = obj.get("table_id")
  1052. list_table = None
  1053. block = False
  1054. has_product = False
  1055. position = obj.get("position",{})
  1056. if _type=="sentence":
  1057. if _text in illegal_sentence:
  1058. continue
  1059. sentence_groups = self.find_title_by_pattern(_text[:10])
  1060. if sentence_groups:
  1061. title_before = standard_title_context(sentence_groups[1][1])
  1062. title_after = sentence_groups[-1][1]
  1063. sentence_title_text = sentence_groups[0][1]
  1064. other_text = _text.replace(sentence_title_text,"")
  1065. if (title_before in dict_before and dict_before[title_before]>1) or title_after!="":
  1066. sentence_title = sentence_groups[0][0]
  1067. title_index = sentence_groups[-2][1]
  1068. next_index = self.get_next_title(title_index)
  1069. other_text = _text.replace(sentence_title_text,"")
  1070. for p in products:
  1071. if other_text.strip()==p.strip():
  1072. has_product = True
  1073. else:
  1074. _fix = False
  1075. for p in products:
  1076. if other_text.strip()==p.strip():
  1077. title_before = "=产品"
  1078. sentence_title = "title_0"
  1079. sentence_title_text = p
  1080. title_index = "0"
  1081. title_after = "产品="
  1082. next_index = "0"
  1083. _fix = True
  1084. has_product = True
  1085. break
  1086. if not _fix:
  1087. title_before = None
  1088. title_after = None
  1089. sentence_title_text = None
  1090. else:
  1091. if len(_text)<40 and re.search(_param_pattern,_text) is not None:
  1092. for p in products:
  1093. if _text.find(p)>=0:
  1094. title_before = "=产品"
  1095. sentence_title = "title_0"
  1096. sentence_title_text = p
  1097. title_index = "0"
  1098. title_after = "产品="
  1099. next_index = "0"
  1100. _fix = True
  1101. has_product = True
  1102. break
  1103. # 合并两个非标题句子 20241106 注销,由于 485441521 招标内容结束位置不对
  1104. if auto_append:
  1105. if _type=="sentence":
  1106. 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:
  1107. list_data[-1]["text"] += _text
  1108. list_data[-1]["line_width"] = len(_text)
  1109. update_kv_span(list_kv,len(_text))
  1110. list_data[-1]["kv"].extend(list_kv)
  1111. list_data[-1]["sentences"].extend(sentences)
  1112. _append = True
  1113. elif sentence_title is None and len(list_data)>0 and _type==list_data[-1]["type"]:
  1114. if list_data[-1]["line_width"]>=max_length*0.7:
  1115. list_data[-1]["text"] += _text
  1116. list_data[-1]["line_width"] = len(_text)
  1117. update_kv_span(list_kv,len(_text))
  1118. list_data[-1]["kv"].extend(list_kv)
  1119. list_data[-1]["sentences"].extend(sentences)
  1120. _append = True
  1121. if not _append:
  1122. _data = {"type":_type,"tag":obj.get("tag"),"table_id":table_id, "text":_text,"sentences":sentences,"list_table":list_table,
  1123. "line_width":len(_text),"sentence_title":sentence_title,"title_index":title_index,
  1124. "sentence_title_text":sentence_title_text,"sentence_groups":sentence_groups,"parent_title":parent_title,
  1125. "child_title":childs,"title_before":title_before,"title_after":title_after,"title_next":title_next,"next_index":next_index,
  1126. "block":block,"has_product":has_product,
  1127. "sentence_index":sentence_index,"wordOffset_begin":wordOffset_begin,"wordOffset_end":wordOffset_end,
  1128. "kv":list_kv,"position":position
  1129. }
  1130. if sentence_title is not None:
  1131. if len(list_data)>0:
  1132. if self.is_title_first(title_index):
  1133. for i in range(1,len(list_data)+1):
  1134. _d = list_data[-i]
  1135. if _d["sentence_title"] is not None:
  1136. _data["parent_title"] = _d
  1137. _d["child_title"].append(_data)
  1138. break
  1139. else:
  1140. _find = False
  1141. for i in range(1,len(list_data)+1):
  1142. if _find:
  1143. break
  1144. _d = list_data[-i]
  1145. if _d.get("sentence_title")==sentence_title and title_before==_d["title_before"] and title_after==_d["title_after"]:
  1146. if _d["next_index"]==title_index and _d["title_next"] is None and not _d["block"]:
  1147. _data["parent_title"] = _d["parent_title"]
  1148. _d["title_next"] = _data
  1149. if len(_d["child_title"])>0:
  1150. _d["child_title"][-1]["title_next"] = ""
  1151. self.block_tree(_d["child_title"])
  1152. if _d["parent_title"] is not None:
  1153. _d["parent_title"]["child_title"].append(_data)
  1154. _find = True
  1155. break
  1156. for i in range(1,len(list_data)+1):
  1157. if _find:
  1158. break
  1159. _d = list_data[-i]
  1160. 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"]:
  1161. _data["parent_title"] = _d["parent_title"]
  1162. _d["title_next"] = _data
  1163. if len(_d["child_title"])>0:
  1164. _d["child_title"][-1]["title_next"] = ""
  1165. self.block_tree(_d["child_title"])
  1166. if _d["parent_title"] is not None:
  1167. _d["parent_title"]["child_title"].append(_data)
  1168. _find = True
  1169. break
  1170. title_before = standard_title_context(title_before)
  1171. title_after = standard_title_context(title_after)
  1172. for i in range(1,len(list_data)+1):
  1173. if _find:
  1174. break
  1175. _d = list_data[-i]
  1176. 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"]):
  1177. if _d["next_index"]==title_index and _d["title_next"] is None and not _d["block"]:
  1178. _data["parent_title"] = _d["parent_title"]
  1179. _d["title_next"] = _data
  1180. if len(_d["child_title"])>0:
  1181. _d["child_title"][-1]["title_next"] = ""
  1182. self.block_tree(_d["child_title"])
  1183. if _d["parent_title"] is not None:
  1184. _d["parent_title"]["child_title"].append(_data)
  1185. _find = True
  1186. break
  1187. for i in range(1,len(list_data)+1):
  1188. if _find:
  1189. break
  1190. _d = list_data[-i]
  1191. 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"]):
  1192. _data["parent_title"] = _d["parent_title"]
  1193. _d["title_next"] = _data
  1194. if len(_d["child_title"])>0:
  1195. _d["child_title"][-1]["title_next"] = ""
  1196. # self.block_tree(_d["child_title"])
  1197. if _d["parent_title"] is not None:
  1198. _d["parent_title"]["child_title"].append(_data)
  1199. _find = True
  1200. break
  1201. for i in range(1,min(len(list_data)+1,20)):
  1202. if _find:
  1203. break
  1204. _d = list_data[-i]
  1205. if not _d["block"] and _d.get("sentence_title")==sentence_title and title_before==standard_title_context(_d["title_before"]):
  1206. _data["parent_title"] = _d["parent_title"]
  1207. _d["title_next"] = _data
  1208. if len(_d["child_title"])>0:
  1209. _d["child_title"][-1]["title_next"] = ""
  1210. # self.block_tree(_d["child_title"])
  1211. if _d["parent_title"] is not None:
  1212. _d["parent_title"]["child_title"].append(_data)
  1213. _find = True
  1214. break
  1215. if not _find:
  1216. if len(list_data)>0:
  1217. for i in range(1,len(list_data)+1):
  1218. _d = list_data[-i]
  1219. if _d.get("sentence_title") is not None:
  1220. _data["parent_title"] = _d
  1221. _d["child_title"].append(_data)
  1222. break
  1223. else:
  1224. if len(list_data)>0:
  1225. for i in range(1,len(list_data)+1):
  1226. _d = list_data[-i]
  1227. if _d.get("sentence_title") is not None:
  1228. _data["parent_title"] = _d
  1229. _d["child_title"].append(_data)
  1230. break
  1231. list_data.append(_data)
  1232. for _data in list_data:
  1233. childs = _data["child_title"]
  1234. for c_i in range(len(childs)):
  1235. cdata = childs[c_i]
  1236. if cdata["has_product"]:
  1237. continue
  1238. else:
  1239. if c_i>0:
  1240. last_cdata = childs[c_i-1]
  1241. 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"]:
  1242. cdata["has_product"] = True
  1243. if c_i<len(childs)-1:
  1244. last_cdata = childs[c_i+1]
  1245. 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"]:
  1246. cdata["has_product"] = True
  1247. for c_i in range(len(childs)):
  1248. cdata = childs[len(childs)-1-c_i]
  1249. if cdata["has_product"]:
  1250. continue
  1251. else:
  1252. if c_i>0:
  1253. last_cdata = childs[c_i-1]
  1254. 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"]:
  1255. cdata["has_product"] = True
  1256. if c_i<len(childs)-1:
  1257. last_cdata = childs[c_i+1]
  1258. 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"]:
  1259. cdata["has_product"] = True
  1260. return list_data
  1261. def get_tree_sentence(self):
  1262. list_sentence = []
  1263. for obj in self.tree:
  1264. list_sentence.extend(obj.get("sentences",[]))
  1265. return list_sentence
  1266. def extract_kvs_from_table(self,list_pattern,tree=None,result_kv=None):
  1267. if result_kv is None:
  1268. result_kv = [[] for i in list_pattern]
  1269. try:
  1270. for pattern in list_pattern:
  1271. re.compile(pattern)
  1272. except Exception as e:
  1273. log("list_pattern error: "+str(e))
  1274. return result_kv
  1275. if tree is None:
  1276. tree = self.tree
  1277. for obj in tree:
  1278. is_table = True if obj.get("tag","")=="table" else False
  1279. if is_table:
  1280. table_id = obj.get("table_id")
  1281. list_kv = obj.get("kv")
  1282. for _pi in range(len(list_pattern)):
  1283. table_kvs = []
  1284. for _d0 in list_kv:
  1285. _k = _d0.get("key","")
  1286. _v = _d0.get("value","")
  1287. _d = {"key":_k,"value":_v,"position":_d0.get("position",{})}
  1288. if re.search(list_pattern[_pi],_k) is not None:
  1289. table_kvs.append(_d)
  1290. if table_kvs:
  1291. result_kv[_pi].append({"table_id":table_id,"kv":table_kvs})
  1292. childs = obj.get("children",[])
  1293. for child in childs:
  1294. self.extract_kvs_from_table(list_pattern,child,result_kv)
  1295. return result_kv
  1296. def extract_kvs_from_sentence(self,list_pattern,tree=None,result_kv=None):
  1297. if result_kv is None:
  1298. result_kv = [[] for i in list_pattern]
  1299. try:
  1300. for pattern in list_pattern:
  1301. re.compile(pattern)
  1302. except Exception as e:
  1303. log("list_pattern error: "+str(e))
  1304. return result_kv
  1305. if tree is None:
  1306. tree = self.tree
  1307. for obj in tree:
  1308. is_table = True if obj.get("tag","")=="table" else False
  1309. if not is_table:
  1310. list_kv = obj.get("kv",[])
  1311. for _pi in range(len(list_pattern)):
  1312. for _d in list_kv:
  1313. _k = _d.get("key","")
  1314. _v = _d.get("value","")
  1315. if re.search(list_pattern[_pi],_k) is not None:
  1316. result_kv[_pi].append(_d)
  1317. return result_kv
  1318. def extract_kvs_from_outline(self,list_pattern,tree=None,result_kv=None):
  1319. if result_kv is None:
  1320. result_kv = [[] for i in list_pattern]
  1321. try:
  1322. for pattern in list_pattern:
  1323. re.compile(pattern)
  1324. except Exception as e:
  1325. log("list_pattern error: "+str(e))
  1326. return result_kv
  1327. if tree is None:
  1328. tree = self.tree
  1329. for obj in tree:
  1330. is_table = True if obj.get("tag","")=="table" else False
  1331. if not is_table:
  1332. _text = obj["text"]
  1333. for _pi in range(len(list_pattern)):
  1334. sentence_index_from = obj["sentence_index"]
  1335. sentence_index_to = sentence_index_from
  1336. if re.search(list_pattern[_pi],_text) is not None and obj.get("sentence_title") is not None:
  1337. childs = get_childs([obj])
  1338. _child_text = ""
  1339. for _child in childs:
  1340. sentence_index_to = _child["sentence_index"]
  1341. _child_text+=_child["text"]+"\n"
  1342. result_kv[_pi].append({"key":_text,"value":_child_text,"from_outline":True,"key_sentence_index_from":sentence_index_from,
  1343. "key_sentence_index_to":sentence_index_from,"value_sentence_index_from":sentence_index_from,
  1344. "value_sentence_index_to":sentence_index_to,})
  1345. return result_kv
  1346. def extract_kv(self,k_pattern,from_sentence=True,from_outline=True,from_table=True):
  1347. result_kv = []
  1348. try:
  1349. re.compile(k_pattern)
  1350. except Exception as e:
  1351. log("k_pattern error: "+str(e))
  1352. traceback.print_exc()
  1353. return result_kv
  1354. result_kv = []
  1355. if from_table:
  1356. result_kv_table = self.extract_kvs_from_table([k_pattern])
  1357. for table_d in result_kv_table[0]:
  1358. table_id = table_d.get("table_id")
  1359. table_kvs = table_d.get("kv",[])
  1360. for _d in table_kvs:
  1361. _d["from_table"] = True
  1362. result_kv.extend(table_kvs)
  1363. if from_sentence:
  1364. result_kv_sentence = self.extract_kvs_from_sentence([k_pattern])
  1365. for _d in result_kv_sentence[0]:
  1366. _d["from_sentence"] = True
  1367. result_kv.extend(result_kv_sentence[0])
  1368. if from_outline:
  1369. result_kv_outline = self.extract_kvs_from_outline([k_pattern])
  1370. for _d in result_kv_outline[0]:
  1371. _d["from_outline"] = True
  1372. result_kv.extend(result_kv_outline[0])
  1373. return result_kv
  1374. # def extract_kvs_from_table(self,list_pattern):
  1375. if __name__ == '__main__':
  1376. # HTML 文本
  1377. html_content = """
  1378. <div>
  1379. <div>
  1380. 工程造价咨询
  1381. </div>
  1382. <div>
  1383. <div>
  1384. 关于为【意溪镇四宁村农村人居环境及村道整治提升项目-前期费用结算审核】公开选取【工程造价咨询】机构的公告
  1385. </div>
  1386. </div>
  1387. <div>
  1388. <p> 2024-12-24 17:30 ,在广东省网上中介服务超市为<a target="_blank" class="markBlue" href="/bdqyhx/216515788655505408.html" style="color: #3083EB !important;text-decoration: underline;">潮州市湘桥区意溪镇四宁村民委员会</a> 公开选取工程造价咨询中介服务机构,现将相关事项公告如下: </p>
  1389. <p>此项目采用多选一的直接选取方式,项目业主将在报名的若干家中介机构中,自主选定一家作为中选机构,未被选中的机构不应有任何异议。</p>
  1390. <ul>
  1391. <li> <b>项目业主</b>
  1392. <div data-purorgcode="G19187326" data-tyshxydm="54445102G191873262">
  1393. <a target="_blank" class="markBlue" href="/bdqyhx/216515788655505408.html" style="color: #3083EB !important;text-decoration: underline;">潮州市湘桥区意溪镇四宁村民委员会</a>
  1394. </div> </li>
  1395. <li> <b>采购项目名称</b>
  1396. <div data-divisioncode="445100">
  1397. 意溪镇四宁村农村人居环境及村道整治提升项目-前期费用结算审核
  1398. </div> </li>
  1399. <li> <b>中介服务事项</b>
  1400. <div data-servicesubjectcode="">
  1401. 无(属于非行政管理的中介服务项目采购)
  1402. </div> </li>
  1403. <li> <b>投资审批项目</b>
  1404. <div>
  1405. </div> </li>
  1406. <li> <b>采购项目编码</b>
  1407. <div>
  1408. 445100G191873262412200638
  1409. </div> </li>
  1410. <li> <b>项目规模</b>
  1411. <div data-restrictionsforehead="1500000.0" data-restrictionsforeheadtype="amountInvested">
  1412. <p>投资额(¥1,500,000.00元)</p>
  1413. </div> </li>
  1414. <li> <b>所需服务</b>
  1415. <div>
  1416. 工程造价咨询
  1417. </div> </li>
  1418. <li> <b>服务内容</b>
  1419. <div>
  1420. 本次工程为意溪镇四宁村农村人居环境及村道整治提升项目,位于潮州市湘桥区意溪镇四宁村,投资额为1500000.00元,建设内容包括路面便底化约800平方米,黑底化约2150平方米,新安装太阳能路灯14盏等,现已竣工验收完成,根据业主要求,对本项目进行前期费用结算审核(概算编制费2571.86元,施工图审查费5024.5元,设计费54700元,预算编制费5315.66元,工程监理费35275.46元,工程测绘费22634.89元,建设方案编制费10500元),并出具前期费用结算审核报告及定案表。
  1421. </div> </li>
  1422. <li> <b>中介机构要求</b>
  1423. <div>
  1424. 仅承诺服务即可
  1425. </div> </li>
  1426. <li> <b>其他要求说明:</b>
  1427. <div>
  1428. </div> </li>
  1429. <li> <b>服务时限说明</b>
  1430. <div>
  1431. 无要求,按合同约定。
  1432. </div> </li>
  1433. <li> <b>服务金额</b>
  1434. <div data-biddingmode="" data-highprice="" data-lowprice="1600.0">
  1435. ¥1,600.00元
  1436. </div> </li>
  1437. <li> <b>金额说明</b>
  1438. <div>
  1439. 按广东省物价局关于调整我省建设工程造价咨询服务收费的复函(粤价函[2011]742号)规定的造价咨询行业收费标准,最终价格以财政审核或第三方工程造价公司审核价为准。
  1440. </div> </li>
  1441. <li> <b>选取中介服务机构方式</b>
  1442. <div data-selectmodetype="DXYZJXQ">
  1443. 直接选取
  1444. </div> </li>
  1445. <li> <b>是否选取中介</b>
  1446. <div>
  1447. </div> </li>
  1448. <li> <b>有无回避情况</b>
  1449. <div>
  1450. </div> </li>
  1451. <li> <b> 截止报名时间 </b>
  1452. <div>
  1453. 2024-12-24 17:30
  1454. </div> </li>
  1455. <li> <b>业主单位咨询电话</b>
  1456. <div>
  1457. <a target="_blank" class="markBlue" href="/bdqyhx/216515788655505408.html" style="color: #3083EB !important;text-decoration: underline;">潮州市湘桥区意溪镇四宁村民委员会</a> (登录后查看)
  1458. </div> </li>
  1459. <li> <b>采购需求书下载</b>
  1460. <div>
  1461. <a target="_blank" class="markBlue" filelink="277a2f13e4a4d41149766c82adfc8762" href="https://attachment-hub.oss-cn-hangzhou.aliyuncs.com/277a/20230710/2023-07-10/04733/1688982497468.jpg?Expires=1734688561&amp;OSSAccessKeyId=LTAI5tHoEUDSy6FnZjMKsNiZ&amp;Signature=G3SAJQuJlYZ5lOpHNc%2BWHspfDpE%3D" original="https://ygp.gdzwfw.gov.cn/zjfwcs/gd-zjcs-pub/file/downloadfile/PjAttachment/7618f614-ae9c-48cc-97f7-17ffd05d4200" rel="noreferrer">资金说明1.jpg</a>
  1462. <br>
  1463. <a target="_blank" class="markBlue" filelink="3e3795ea0244f1b4cc77123512edd30a" href="https://attachment-hub.oss-cn-hangzhou.aliyuncs.com/3e37/20230710/2023-07-10/04733/1688982505603.jpg?Expires=1734688561&amp;OSSAccessKeyId=LTAI5tHoEUDSy6FnZjMKsNiZ&amp;Signature=ZP8xdy%2F1a%2Blbb%2FOAhFyzjpadprg%3D" original="https://ygp.gdzwfw.gov.cn/zjfwcs/gd-zjcs-pub/file/downloadfile/PjAttachment/beb5d765-4ce3-471f-a290-14a12d9ad64e" rel="noreferrer">资金说明2.jpg</a>
  1464. <br>
  1465. </div> </li>
  1466. </ul>
  1467. </div>
  1468. <p> 广东省网上中介服务超市已经向符合资质条件的在库中介服务机构的业务授权人手机号码和中介专属网页发送通知,诚邀符合资质条件的在库中介服务机构登录中介专属网页进行报名。 </p>
  1469. <p><span>潮州市公共资源交易中心</span><br> <span>2024-12-20</span></p>
  1470. </div>
  1471. """
  1472. _tree = html_to_tree(html_content)
  1473. _pd = Html2KVTree(html_content)
  1474. _pd.print_tree(_pd.tree,"-|")
  1475. list_kv = _pd.extract_kv("资质要求")
  1476. print(list_kv)
  1477. #获取预处理后的所有句子,该句子与kv值对应
  1478. print(_pd.get_tree_sentence())
  1479. # soup = BeautifulSoup(html_content,"lxml")
  1480. # table_tree = table_to_tree(soup)
  1481. # print(json.dumps(table_tree,ensure_ascii=False))