htmlparser.py 60 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252
  1. #coding:utf8
  2. import re
  3. # from BaseDataMaintenance.maintenance.product.productUtils import is_similar
  4. # from BiddingKG.dl.common.Utils import log
  5. import logging
  6. logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
  7. logger = logging.getLogger(__name__)
  8. logger.setLevel(logging.INFO)
  9. def log(msg):
  10. '''
  11. @summary:打印信息
  12. '''
  13. logger.info(msg)
  14. from bs4 import BeautifulSoup
  15. import copy
  16. import Levenshtein
  17. def jaccard_score(source,target):
  18. source_set = set([s for s in source])
  19. target_set = set([s for s in target])
  20. if len(source_set)==0 or len(target_set)==0:
  21. return 0
  22. return max(len(source_set&target_set)/len(source_set),len(source_set&target_set)/len(target_set))
  23. def judge_pur_chinese(keyword):
  24. """
  25. 中文字符的编码范围为: u'\u4e00' -- u'\u9fff:只要在此范围内就可以判断为中文字符串
  26. @param keyword:
  27. @return:
  28. """
  29. # 定义一个需要删除的标点符号字符串列表
  30. remove_chars = '[·’!"\#$%&\'()#!()*+,-./:;<=>?\@,:?¥★、….>【】[]《》?“”‘’\[\\]^_`{|}~]+'
  31. # 利用re.sub来删除中文字符串中的标点符号
  32. strings = re.sub(remove_chars, "", keyword) # 将keyword中文字符串中remove_chars中包含的标点符号替换为空字符串
  33. for ch in strings:
  34. if u'\u4e00' <= ch <= u'\u9fff':
  35. pass
  36. else:
  37. return False
  38. return True
  39. def is_similar(source,target,_radio=None):
  40. source = str(source).lower()
  41. target = str(target).lower()
  42. max_len = max(len(source),len(target))
  43. min_len = min(len(source),len(target))
  44. min_ratio = 90
  45. if min_len>=3:
  46. min_ratio = 87
  47. if min_len>=5:
  48. min_ratio = 85
  49. if _radio is not None:
  50. min_ratio = _radio
  51. # dis_len = abs(len(source)-len(target))
  52. # min_dis = min(max_len*0.2,4)
  53. if min_len==0 and max_len>0:
  54. return False
  55. if max_len<=2:
  56. if source==target:
  57. return True
  58. if min_len<2:
  59. return False
  60. #判断相似度
  61. similar = Levenshtein.ratio(source,target)*100
  62. if similar>=min_ratio:
  63. log("%s and %s similar_jaro %d"%(source,target,similar))
  64. return True
  65. similar_jaro = Levenshtein.jaro(source,target)
  66. if similar_jaro*100>=min_ratio:
  67. log("%s and %s similar_jaro %d"%(source,target,similar_jaro*100))
  68. return True
  69. similar_jarow = Levenshtein.jaro_winkler(source,target)
  70. if similar_jarow*100>=min_ratio:
  71. log("%s and %s similar_jaro %d"%(source,target,similar_jarow*100))
  72. return True
  73. if min_len>=5:
  74. if len(source)==max_len and str(source).find(target)>=0:
  75. return True
  76. elif len(target)==max_len and target.find(source)>=0:
  77. return True
  78. elif jaccard_score(source, target)==1 and judge_pur_chinese(source) and judge_pur_chinese(target):
  79. return True
  80. return False
  81. end_pattern = "商务要求|评分标准|商务条件|商务条件"
  82. _param_pattern = "(产品|技术|清单|配置|参数|具体|明细|项目|招标|货物|服务|规格|工作|具体)[及和与]?(指标|配置|条件|要求|参数|需求|规格|条款|名称及要求)|配置清单|(质量|技术).{,10}要求|验收标准|^(参数|功能)$"
  83. 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大中小]+码|净重|颜色|[红橙黄绿青蓝紫]色|不锈钢|输入|输出|噪声|认证|配置"
  84. not_meter_pattern = "投标报价|中标金额|商务部分|公章|分值构成|业绩|详见|联系人|联系电话|合同价|金额|采购预算|资金来源|费用|质疑|评审因素|评审标准|商务资信|商务评分|专家论证意见|评标方法|代理服务费|售后服务|评分类型|评分项目|预算金额|得\d+分|项目金额|详见招标文件|乙方"
  85. def getTrs(tbody):
  86. #获取所有的tr
  87. trs = []
  88. if tbody.name=="table":
  89. body = tbody.find("tbody",recursive=False)
  90. if body is not None:
  91. tbody = body
  92. objs = tbody.find_all(recursive=False)
  93. for obj in objs:
  94. if obj.name=="tr":
  95. trs.append(obj)
  96. if obj.name=="tbody" or obj.name=="table":
  97. for tr in obj.find_all("tr",recursive=False):
  98. trs.append(tr)
  99. return trs
  100. def fixSpan(tbody):
  101. # 处理colspan, rowspan信息补全问题
  102. #trs = tbody.findChildren('tr', recursive=False)
  103. trs = getTrs(tbody)
  104. ths_len = 0
  105. ths = list()
  106. trs_set = set()
  107. #修改为先进行列补全再进行行补全,否则可能会出现表格解析混乱
  108. # 遍历每一个tr
  109. for indtr, tr in enumerate(trs):
  110. ths_tmp = tr.findChildren('th', recursive=False)
  111. #不补全含有表格的tr
  112. if len(tr.findChildren('table'))>0:
  113. continue
  114. if len(ths_tmp) > 0:
  115. ths_len = ths_len + len(ths_tmp)
  116. for th in ths_tmp:
  117. ths.append(th)
  118. trs_set.add(tr)
  119. # 遍历每行中的element
  120. tds = tr.findChildren(recursive=False)
  121. for indtd, td in enumerate(tds):
  122. # 若有colspan 则补全同一行下一个位置
  123. if 'colspan' in td.attrs:
  124. if str(re.sub("[^0-9]","",str(td['colspan'])))!="":
  125. col = int(re.sub("[^0-9]","",str(td['colspan'])))
  126. if col<100 and len(td.get_text())<1000:
  127. td['colspan'] = 1
  128. for i in range(1, col, 1):
  129. td.insert_after(copy.copy(td))
  130. for indtr, tr in enumerate(trs):
  131. ths_tmp = tr.findChildren('th', recursive=False)
  132. #不补全含有表格的tr
  133. if len(tr.findChildren('table'))>0:
  134. continue
  135. if len(ths_tmp) > 0:
  136. ths_len = ths_len + len(ths_tmp)
  137. for th in ths_tmp:
  138. ths.append(th)
  139. trs_set.add(tr)
  140. # 遍历每行中的element
  141. tds = tr.findChildren(recursive=False)
  142. for indtd, td in enumerate(tds):
  143. # 若有rowspan 则补全下一行同样位置
  144. if 'rowspan' in td.attrs:
  145. if str(re.sub("[^0-9]","",str(td['rowspan'])))!="":
  146. row = int(re.sub("[^0-9]","",str(td['rowspan'])))
  147. td['rowspan'] = 1
  148. for i in range(1, row, 1):
  149. # 获取下一行的所有td, 在对应的位置插入
  150. if indtr+i<len(trs):
  151. tds1 = trs[indtr + i].findChildren(['td','th'], recursive=False)
  152. if len(tds1) >= (indtd) and len(tds1)>0:
  153. if indtd > 0:
  154. tds1[indtd - 1].insert_after(copy.copy(td))
  155. else:
  156. tds1[0].insert_before(copy.copy(td))
  157. elif indtd-2>0 and len(tds1) > 0 and len(tds1) == indtd - 1: # 修正某些表格最后一列没补全
  158. tds1[indtd-2].insert_after(copy.copy(td))
  159. def getTable(tbody):
  160. #trs = tbody.findChildren('tr', recursive=False)
  161. fixSpan(tbody)
  162. trs = getTrs(tbody)
  163. inner_table = []
  164. for tr in trs:
  165. tr_line = []
  166. tds = tr.findChildren(['td','th'], recursive=False)
  167. if len(tds)==0:
  168. tr_line.append([re.sub('\xa0','',tr.get_text()),0]) # 2021/12/21 修复部分表格没有td 造成数据丢失
  169. for td in tds:
  170. tr_line.append([re.sub('\xa0','',td.get_text()),0])
  171. #tr_line.append([td.get_text(),0])
  172. inner_table.append(tr_line)
  173. return inner_table
  174. class Sentence2():
  175. def __init__(self,text,sentence_index,wordOffset_begin,wordOffset_end):
  176. self.name = 'sentence2'
  177. self.text = text
  178. self.sentence_index = sentence_index
  179. self.wordOffset_begin = wordOffset_begin
  180. self.wordOffset_end = wordOffset_end
  181. def get_text(self):
  182. return self.text
  183. class ParseDocument():
  184. def __init__(self,_html,auto_merge_table=True,list_obj = []):
  185. if _html is None:
  186. _html = ""
  187. self.html = _html
  188. self.auto_merge_table = auto_merge_table
  189. if list_obj:
  190. self.list_obj = list_obj
  191. else:
  192. self.soup = BeautifulSoup(self.html, "lxml")
  193. _body = self.soup.find("body")
  194. if _body is not None:
  195. self.soup = _body
  196. self.list_obj = self.get_soup_objs(self.soup)
  197. self.list_obj = [re.sub('\s+', ' ', it.get_text().strip()) for it in self.list_obj]
  198. self.list_obj = [Sentence2(text, 1,1,5) for text in self.list_obj]
  199. # for obj in self.list_obj:
  200. # print("obj",obj.get_text()[:20])
  201. self.tree = self.buildParsetree(self.list_obj,[],auto_merge_table)
  202. # #识别目录树
  203. # if self.parseTree:
  204. # self.parseTree.printParseTree()
  205. # self.print_tree(self.tree,"-|")
  206. def get_soup_objs(self,soup,list_obj=None):
  207. if list_obj is None:
  208. list_obj = []
  209. childs = soup.find_all(recursive=False)
  210. for _obj in childs:
  211. childs1 = _obj.find_all(recursive=False)
  212. if len(childs1)==0 or len(_obj.get_text())<40 or _obj.name=="table":
  213. list_obj.append(_obj)
  214. elif _obj.name=="p":
  215. list_obj.append(_obj)
  216. else:
  217. self.get_soup_objs(_obj,list_obj)
  218. return list_obj
  219. def fix_tree(self,_product):
  220. products = extract_products(self.tree,_product)
  221. if len(products)>0:
  222. self.tree = self.buildParsetree(self.list_obj,products,self.auto_merge_table)
  223. def print_tree(self,tree,append=""):
  224. self.set_tree_id = set()
  225. if append=="":
  226. for t in tree:
  227. 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"]))
  228. for t in tree:
  229. _id = id(t)
  230. if _id in self.set_tree_id:
  231. continue
  232. self.set_tree_id.add(_id)
  233. logger.info("%s text:%s title:%s title_text:%s before:%s after%s product:%s"%(append,t["text"][:50],t["sentence_title"],t["sentence_title_text"],t["title_before"],t["title_after"],t["has_product"]))
  234. childs = t["child_title"]
  235. self.print_tree(childs,append=append+"-|")
  236. def is_title_first(self,title):
  237. if title in ("一","1","Ⅰ","a","A"):
  238. return True
  239. return False
  240. 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>[、章册包标部.::、、]+))|" \
  241. "([\s★▲\*]*)(?P<title_3>(?P<title_3_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?)(?P<title_3_index_0_1>[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)(?P<title_3_index_0_2>[、章册包标部.::、、]+))|" \
  242. "([\s★▲\*]*)(?P<title_4>(?P<title_4_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?第?)(?P<title_4_index_1_1>[一二三四五六七八九十]+)(?P<title_4_index_2_0>[节章册部\.::、、]+))|" \
  243. "([\s★▲\*]*)(?P<title_5>(?P<title_5_index_0_0>^)(?P<title_5_index_1_1>[一二三四五六七八九十]+)(?P<title_5_index_2_0>)[^一二三四五六七八九十节章册部\.::、、])|" \
  244. "([\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\-]?))|"\
  245. "([\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\-]?))|" \
  246. "([\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\-]?))|" \
  247. "([\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\-]*))|" \
  248. "(^[\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\-包标]*))|" \
  249. "([\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>[))包标\..::、]+))|" \
  250. "([\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>[))包标\..::、]+))|" \
  251. "([\s★▲\*]*)(?P<title_19>(?P<title_19_index_0_0>[^一二三四五六七八九十\dⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]{,3}?[((]?)(?P<title_19_index_1_1>[一二三四五六七八九十ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]+)(?P<title_19_index_2_0>[))]))"
  252. ):
  253. _se = re.search(_pattern,_text)
  254. groups = []
  255. if _se is not None:
  256. e = _se.end()
  257. if re.search('(时间|日期|编号|账号|号码|手机|价格|\w价|人民币|金额|得分|分值|总分|满分|最高得|扣|减|数量|评委)[::]?\d', _se.group(0)) or (re.search('\d[.::]?$', _se.group(0)) and re.search('^[\d年月日万元天个分秒台条A-Za-z]|^(小时)', _text[e:])):
  258. return None
  259. elif re.match('[二三四五六七八九十]\w{1,2}[市区县]|五金|四川|八疆|九龙|[一二三四五六七八九十][层天标包]', _text) and re.match('[一二三四五六七八九十]', _se.group(0)): # 289765335 排除三明市等开头作为大纲
  260. return None
  261. elif re.search('^[\u4e00-\u9fa5]+[::]', _text[:e]):
  262. return None
  263. _gd = _se.groupdict()
  264. for k,v in _gd.items():
  265. if v is not None:
  266. groups.append((k,v))
  267. if len(groups):
  268. groups.sort(key=lambda x:x[0])
  269. return groups
  270. return None
  271. def make_increase(self,_sort,_title,_add=1):
  272. if len(_title)==0 and _add==0:
  273. return ""
  274. if len(_title)==0 and _add==1:
  275. return _sort[0]
  276. _index = _sort.index(_title[-1])
  277. next_index = (_index+_add)%len(_sort)
  278. next_chr = _sort[next_index]
  279. if _index==len(_sort)-1:
  280. _add = 1
  281. else:
  282. _add = 0
  283. return next_chr+self.make_increase(_sort,_title[:-1],_add)
  284. def get_next_title(self,_title):
  285. if re.search("^\d+$",_title) is not None:
  286. return str(int(_title)+1)
  287. if re.search("^[一二三四五六七八九十百]+$",_title) is not None:
  288. if _title[-1]=="十":
  289. return _title+"一"
  290. if _title[-1]=="百":
  291. return _title+"零一"
  292. if _title[-1]=="九":
  293. if len(_title)==1:
  294. return "十"
  295. if len(_title)==2:
  296. if _title[0]=="十":
  297. return "二十"
  298. if len(_title)==3:
  299. if _title[0]=="九":
  300. return "一百"
  301. else:
  302. _next_title = self.make_increase(['一','二','三','四','五','六','七','八','九','十'],re.sub("[十百]",'',_title[0]))
  303. return _next_title+"十"
  304. _next_title = self.make_increase(['一','二','三','四','五','六','七','八','九','十'],re.sub("[十百]",'',_title))
  305. _next_title = list(_next_title)
  306. _next_title.reverse()
  307. if _next_title[-1]!="十":
  308. if len(_next_title)>=2:
  309. _next_title.insert(-1,'十')
  310. if len(_next_title)>=4:
  311. _next_title.insert(-3,'百')
  312. if _title[0]=="十":
  313. if _next_title=="十":
  314. _next_title = ["二","十"]
  315. _next_title.insert(0,"十")
  316. _next_title = "".join(_next_title)
  317. return _next_title
  318. if re.search("^[a-z]+$",_title) is not None:
  319. _next_title = self.make_increase([chr(i+ord('a')) for i in range(26)],_title)
  320. _next_title = list(_next_title)
  321. _next_title.reverse()
  322. return "".join(_next_title)
  323. if re.search("^[A-Z]+$",_title) is not None:
  324. _next_title = self.make_increase([chr(i+ord('A')) for i in range(26)],_title)
  325. _next_title = list(_next_title)
  326. _next_title.reverse()
  327. return "".join(_next_title)
  328. if re.search("^[ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ]$",_title) is not None:
  329. _sort = ["Ⅰ","Ⅱ","Ⅲ","Ⅳ","Ⅴ","Ⅵ","Ⅶ","Ⅷ","Ⅸ","Ⅹ","Ⅺ","Ⅻ"]
  330. _index = _sort.index(_title)
  331. if _index<len(_sort)-1:
  332. return _sort[_index+1]
  333. return None
  334. def count_title_before(self,list_obj):
  335. dict_before = {}
  336. dict_sentence_count = {}
  337. illegal_sentence = set()
  338. for obj_i in range(len(list_obj)):
  339. obj = list_obj[obj_i]
  340. _type = "sentence"
  341. _text = obj.text.strip()
  342. if obj.name=="table":
  343. _type = "table"
  344. _text = str(obj)
  345. _append = False
  346. if _type=="sentence":
  347. if len(_text)>10 and len(_text)<100:
  348. if _text not in dict_sentence_count:
  349. dict_sentence_count[_text] = 0
  350. dict_sentence_count[_text] += 1
  351. if re.search("\d+页",_text) is not None:
  352. illegal_sentence.add(_text)
  353. elif len(_text)<10:
  354. if re.search("第\d+页",_text) is not None:
  355. illegal_sentence.add(_text)
  356. sentence_groups = self.find_title_by_pattern(_text[:10])
  357. if sentence_groups:
  358. # c062f53cf83401e671822003d63c1828print("sentence_groups",sentence_groups)
  359. sentence_title = sentence_groups[0][0]
  360. sentence_title_text = sentence_groups[0][1]
  361. title_index = sentence_groups[-2][1]
  362. title_before = sentence_groups[1][1].replace("(","(").replace(":",":").replace(":",";").replace(",",".").replace(",",".").replace("、",".")
  363. title_after = sentence_groups[-1][1].replace(")",")").replace(":",":").replace(":",";").replace(",",".").replace(",",".").replace("、",".")
  364. next_index = self.get_next_title(title_index)
  365. if title_before not in dict_before:
  366. dict_before[title_before] = 0
  367. dict_before[title_before] += 1
  368. for k,v in dict_sentence_count.items():
  369. if v>10:
  370. illegal_sentence.add(k)
  371. return dict_before,illegal_sentence
  372. def is_page_no(self,sentence):
  373. if len(sentence)<10:
  374. if re.search("\d+页|^\-\d+\-$",sentence) is not None:
  375. return True
  376. def block_tree(self,childs):
  377. for child in childs:
  378. if not child["block"]:
  379. child["block"] = True
  380. childs2 = child["child_title"]
  381. self.block_tree(childs2)
  382. def buildParsetree(self,list_obj,products=[],auto_merge_table=True):
  383. self.parseTree = None
  384. trees = []
  385. list_length = []
  386. for obj in list_obj[:200]:
  387. if obj.name!="table":
  388. list_length.append(len(obj.get_text()))
  389. if len(list_length)>0:
  390. max_length = max(list_length)
  391. else:
  392. max_length = 40
  393. max_length = min(max_length,40)
  394. logger.debug("%s:%d"%("max_length",max_length))
  395. list_data = []
  396. last_table_index = None
  397. last_table_columns = None
  398. last_table = None
  399. dict_before,illegal_sentence = self.count_title_before(list_obj)
  400. for obj_i in range(len(list_obj)):
  401. obj = list_obj[obj_i]
  402. # logger.debug("==obj %s"%obj.text[:20])
  403. _type = "sentence"
  404. _text = standard_product(obj.text)
  405. if obj.name=="table":
  406. _type = "table"
  407. _text = standard_product(str(obj))
  408. _append = False
  409. sentence_title = None
  410. sentence_title_text = None
  411. sentence_groups = None
  412. title_index = None
  413. next_index = None
  414. parent_title = None
  415. title_before = None
  416. title_after = None
  417. title_next = None
  418. childs = []
  419. # new
  420. sentence_index = obj.sentence_index
  421. wordOffset_begin = obj.wordOffset_begin
  422. wordOffset_end = obj.wordOffset_end
  423. list_table = None
  424. block = False
  425. has_product = False
  426. if _type=="sentence":
  427. if _text in illegal_sentence:
  428. continue
  429. sentence_groups = self.find_title_by_pattern(_text[:10])
  430. if sentence_groups:
  431. title_before = standard_title_context(sentence_groups[1][1])
  432. title_after = sentence_groups[-1][1]
  433. sentence_title_text = sentence_groups[0][1]
  434. other_text = _text.replace(sentence_title_text,"")
  435. if (title_before in dict_before and dict_before[title_before]>1) or title_after!="":
  436. sentence_title = sentence_groups[0][0]
  437. title_index = sentence_groups[-2][1]
  438. next_index = self.get_next_title(title_index)
  439. other_text = _text.replace(sentence_title_text,"")
  440. for p in products:
  441. if other_text.strip()==p.strip():
  442. has_product = True
  443. else:
  444. _fix = False
  445. for p in products:
  446. if other_text.strip()==p.strip():
  447. title_before = "=产品"
  448. sentence_title = "title_0"
  449. sentence_title_text = p
  450. title_index = "0"
  451. title_after = "产品="
  452. next_index = "0"
  453. _fix = True
  454. has_product = True
  455. break
  456. if not _fix:
  457. title_before = None
  458. title_after = None
  459. sentence_title_text = None
  460. else:
  461. if len(_text)<40 and re.search(_param_pattern,_text) is not None:
  462. for p in products:
  463. if _text.find(p)>=0:
  464. title_before = "=产品"
  465. sentence_title = "title_0"
  466. sentence_title_text = p
  467. title_index = "0"
  468. title_after = "产品="
  469. next_index = "0"
  470. _fix = True
  471. has_product = True
  472. break
  473. # 合并两个非标题句子 20241106 注销,由于 485441521 招标内容结束位置不对
  474. # if _type=="sentence":
  475. # 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:
  476. # list_data[-1]["text"] += _text
  477. # list_data[-1]["line_width"] = len(_text)
  478. # _append = True
  479. # elif sentence_title is None and len(list_data)>0 and _type==list_data[-1]["type"]:
  480. # if list_data[-1]["line_width"]>=max_length*0.7:
  481. # list_data[-1]["text"] += _text
  482. # list_data[-1]["line_width"] = len(_text)
  483. # _append = True
  484. if _type=="table":
  485. _soup = BeautifulSoup(_text,"lxml")
  486. _table = _soup.find("table")
  487. if _table is not None:
  488. list_table = getTable(_table)
  489. if len(list_table)==0:
  490. continue
  491. table_columns = len(list_table[0])
  492. if auto_merge_table:
  493. if last_table_index is not None and abs(obj_i-last_table_index)<=2 and last_table_columns is not None and last_table_columns==table_columns:
  494. if last_table is not None:
  495. trs = getTrs(_table)
  496. last_tbody = BeautifulSoup(last_table["text"],"lxml")
  497. _table = last_tbody.find("table")
  498. last_trs = getTrs(_table)
  499. _append = True
  500. for _line in list_table:
  501. last_table["list_table"].append(_line)
  502. if len(last_trs)>0:
  503. for _tr in trs:
  504. last_trs[-1].insert_after(copy.copy(_tr))
  505. last_table["text"] = re.sub("</?html>|</?body>","",str(last_tbody))
  506. last_table_index = obj_i
  507. last_table_columns = len(list_table[-1])
  508. if not _append:
  509. _data = {"type":_type, "text":_text,"list_table":list_table,"line_width":len(_text),"sentence_title":sentence_title,"title_index":title_index,
  510. "sentence_title_text":sentence_title_text,"sentence_groups":sentence_groups,"parent_title":parent_title,
  511. "child_title":childs,"title_before":title_before,"title_after":title_after,"title_next":title_next,"next_index":next_index,
  512. "block":block,"has_product":has_product,
  513. "sentence_index":sentence_index,"wordOffset_begin":wordOffset_begin,"wordOffset_end":wordOffset_end
  514. }
  515. if _type=="table":
  516. last_table = _data
  517. last_table_index = obj_i
  518. if list_table:
  519. last_table_columns = last_table_columns = len(list_table[-1])
  520. if sentence_title is not None:
  521. if len(list_data)>0:
  522. if self.is_title_first(title_index):
  523. for i in range(1,len(list_data)+1):
  524. _d = list_data[-i]
  525. if _d["sentence_title"] is not None:
  526. _data["parent_title"] = _d
  527. _d["child_title"].append(_data)
  528. break
  529. else:
  530. _find = False
  531. for i in range(1,len(list_data)+1):
  532. if _find:
  533. break
  534. _d = list_data[-i]
  535. if _d.get("sentence_title")==sentence_title and title_before==_d["title_before"] and title_after==_d["title_after"]:
  536. if _d["next_index"]==title_index and _d["title_next"] is None and not _d["block"]:
  537. _data["parent_title"] = _d["parent_title"]
  538. _d["title_next"] = _data
  539. if len(_d["child_title"])>0:
  540. _d["child_title"][-1]["title_next"] = ""
  541. self.block_tree(_d["child_title"])
  542. if _d["parent_title"] is not None:
  543. _d["parent_title"]["child_title"].append(_data)
  544. _find = True
  545. break
  546. for i in range(1,len(list_data)+1):
  547. if _find:
  548. break
  549. _d = list_data[-i]
  550. 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"]:
  551. _data["parent_title"] = _d["parent_title"]
  552. _d["title_next"] = _data
  553. if len(_d["child_title"])>0:
  554. _d["child_title"][-1]["title_next"] = ""
  555. self.block_tree(_d["child_title"])
  556. if _d["parent_title"] is not None:
  557. _d["parent_title"]["child_title"].append(_data)
  558. _find = True
  559. break
  560. title_before = standard_title_context(title_before)
  561. title_after = standard_title_context(title_after)
  562. for i in range(1,len(list_data)+1):
  563. if _find:
  564. break
  565. _d = list_data[-i]
  566. 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"]):
  567. if _d["next_index"]==title_index and _d["title_next"] is None and not _d["block"]:
  568. _data["parent_title"] = _d["parent_title"]
  569. _d["title_next"] = _data
  570. if len(_d["child_title"])>0:
  571. _d["child_title"][-1]["title_next"] = ""
  572. self.block_tree(_d["child_title"])
  573. if _d["parent_title"] is not None:
  574. _d["parent_title"]["child_title"].append(_data)
  575. _find = True
  576. break
  577. for i in range(1,len(list_data)+1):
  578. if _find:
  579. break
  580. _d = list_data[-i]
  581. 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"]):
  582. _data["parent_title"] = _d["parent_title"]
  583. _d["title_next"] = _data
  584. if len(_d["child_title"])>0:
  585. _d["child_title"][-1]["title_next"] = ""
  586. # self.block_tree(_d["child_title"])
  587. if _d["parent_title"] is not None:
  588. _d["parent_title"]["child_title"].append(_data)
  589. _find = True
  590. break
  591. for i in range(1,min(len(list_data)+1,20)):
  592. if _find:
  593. break
  594. _d = list_data[-i]
  595. if not _d["block"] and _d.get("sentence_title")==sentence_title and title_before==standard_title_context(_d["title_before"]):
  596. _data["parent_title"] = _d["parent_title"]
  597. _d["title_next"] = _data
  598. if len(_d["child_title"])>0:
  599. _d["child_title"][-1]["title_next"] = ""
  600. # self.block_tree(_d["child_title"])
  601. if _d["parent_title"] is not None:
  602. _d["parent_title"]["child_title"].append(_data)
  603. _find = True
  604. break
  605. if not _find:
  606. if len(list_data)>0:
  607. for i in range(1,len(list_data)+1):
  608. _d = list_data[-i]
  609. if _d.get("sentence_title") is not None:
  610. _data["parent_title"] = _d
  611. _d["child_title"].append(_data)
  612. break
  613. else:
  614. if len(list_data)>0:
  615. for i in range(1,len(list_data)+1):
  616. _d = list_data[-i]
  617. if _d.get("sentence_title") is not None:
  618. _data["parent_title"] = _d
  619. _d["child_title"].append(_data)
  620. break
  621. list_data.append(_data)
  622. for _data in list_data:
  623. childs = _data["child_title"]
  624. for c_i in range(len(childs)):
  625. cdata = childs[c_i]
  626. if cdata["has_product"]:
  627. continue
  628. else:
  629. if c_i>0:
  630. last_cdata = childs[c_i-1]
  631. 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"]:
  632. cdata["has_product"] = True
  633. if c_i<len(childs)-1:
  634. last_cdata = childs[c_i+1]
  635. 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"]:
  636. cdata["has_product"] = True
  637. for c_i in range(len(childs)):
  638. cdata = childs[len(childs)-1-c_i]
  639. if cdata["has_product"]:
  640. continue
  641. else:
  642. if c_i>0:
  643. last_cdata = childs[c_i-1]
  644. 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"]:
  645. cdata["has_product"] = True
  646. if c_i<len(childs)-1:
  647. last_cdata = childs[c_i+1]
  648. 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"]:
  649. cdata["has_product"] = True
  650. return list_data
  651. def standard_title_context(_title_context):
  652. return _title_context.replace("(","(").replace(")",")").replace(":",":").replace(":",";").replace(",",".").replace(",",".").replace("、",".").replace(".",".")
  653. def standard_product(sentence):
  654. return sentence.replace("(","(").replace(")",")")
  655. def extract_products(list_data,_product,_param_pattern = "产品名称|设备材料|采购内存|标的名称|采购内容|(标的|维修|系统|报价构成|商品|产品|物料|物资|货物|设备|采购品|采购条目|物品|材料|印刷品?|采购|物装|配件|资产|耗材|清单|器材|仪器|器械|备件|拍卖物|标的物|物件|药品|药材|药械|货品|食品|食材|品目|^品名|气体|标项|分项|项目|计划|包组|标段|[分子]?包|子目|服务|招标|中标|成交|工程|招标内容)[\))的]?([、\w]{,4}名称|内容|描述)|标的|标项|项目$|商品|产品|物料|物资|货物|设备|采购品|采购条目|物品|材料|印刷品|物装|配件|资产|招标内容|耗材|清单|器材|仪器|器械|备件|拍卖物|标的物|物件|药品|药材|药械|货品|食品|食材|菜名|^品目$|^品名$|^名称|^内容$"):
  656. _product = standard_product(_product)
  657. list_result = []
  658. list_table_products = []
  659. for _data_i in range(len(list_data)):
  660. _data = list_data[_data_i]
  661. _type = _data["type"]
  662. _text = _data["text"]
  663. if _type=="table":
  664. list_table = _data["list_table"]
  665. if list_table is None:
  666. continue
  667. _check = True
  668. max_length = max([len(a) for a in list_table])
  669. min_length = min([len(a) for a in list_table])
  670. if min_length<max_length/2:
  671. continue
  672. list_head_index = []
  673. _begin_index = 0
  674. head_cell_text = ""
  675. for line_i in range(len(list_table[:2])):
  676. line = list_table[line_i]
  677. line_text = ",".join([cell[0] for cell in line])
  678. for cell_i in range(len(line)):
  679. cell = line[cell_i]
  680. cell_text = cell[0]
  681. if len(cell_text)<10 and re.search(_param_pattern,cell_text) is not None and re.search("单价|数量|预算|限价|总价|品牌|规格|型号|用途|要求|采购量",line_text) is not None:
  682. _begin_index = line_i+1
  683. list_head_index.append(cell_i)
  684. for line_i in range(len(list_table)):
  685. line = list_table[line_i]
  686. for cell_i in list_head_index:
  687. if cell_i>=len(line):
  688. continue
  689. cell = line[cell_i]
  690. cell_text = cell[0]
  691. head_cell_text += cell_text
  692. # print("===head_cell_text",head_cell_text)
  693. if re.search("招标人|采购人|项目编号|项目名称|金额|^\d+$",head_cell_text) is not None:
  694. list_head_index = []
  695. for line in list_table:
  696. line_text = ",".join([cell[0] for cell in line])
  697. for cell_i in range(len(line)):
  698. cell = line[cell_i]
  699. cell_text = cell[0]
  700. 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:
  701. list_head_index.append(cell_i)
  702. list_head_index = list(set(list_head_index))
  703. if len(list_head_index)>0:
  704. has_number = False
  705. for cell_i in list_head_index:
  706. table_products = []
  707. for line_i in range(_begin_index,len(list_table)):
  708. line = list_table[line_i]
  709. for _i in range(len(line)):
  710. cell = line[_i]
  711. cell_text = cell[0]
  712. if re.search("^\d+$",cell_text) is not None:
  713. has_number = True
  714. if cell_i>=len(line):
  715. continue
  716. cell = line[cell_i]
  717. cell_text = cell[0]
  718. if re.search(_param_pattern,cell_text) is None or has_number:
  719. if re.search("^[\da-zA-Z]+$",cell_text) is None:
  720. table_products.append(cell_text)
  721. if len(table_products)>0:
  722. logger.debug("table products %s"%(str(table_products)))
  723. if min([len(x) for x in table_products])>0 and max([len(x) for x in table_products])<=30:
  724. if re.search("招标人|代理人|预算|数量|交货期|品牌|产地","".join(table_products)) is None:
  725. list_table_products.append(table_products)
  726. _find = False
  727. for table_products in list_table_products:
  728. for _p in table_products:
  729. if is_similar(_product,_p,90):
  730. _find = True
  731. logger.debug("similar table_products %s"%(str(table_products)))
  732. list_result = list(set([a for a in table_products if len(a)>1 and len(a)<20 and re.search("费用|预算|合计|金额|万元|运费|^其他$",a) is None]))
  733. break
  734. if not _find:
  735. for table_products in list_table_products:
  736. list_result.extend(table_products)
  737. list_result = list(set([a for a in list_result if len(a)>1 and len(a)<30 and re.search("费用|预算|合计|金额|万元|运费",a) is None]))
  738. return list_result
  739. def get_childs(childs, max_depth=None):
  740. list_data = []
  741. for _child in childs:
  742. list_data.append(_child)
  743. childs2 = _child.get("child_title",[])
  744. if len(childs2)>0 and (max_depth==None or max_depth>0):
  745. for _child2 in childs2:
  746. if max_depth != None:
  747. list_data.extend(get_childs([_child2], max_depth-1))
  748. else:
  749. list_data.extend(get_childs([_child2], None))
  750. return list_data
  751. def get_range_data_by_childs(list_data,childs):
  752. range_data = []
  753. list_child = get_childs(childs)
  754. list_index = []
  755. set_child = set([id(x) for x in list_child])
  756. for _data_i in range(len(list_data)):
  757. _data = list_data[_data_i]
  758. _id = id(_data)
  759. if _id in set_child:
  760. list_index.append(_data_i)
  761. if len(list_index)>0:
  762. range_data = list_data[min(list_index):max(list_index)+1]
  763. return range_data
  764. def get_correct_product(product,products):
  765. list_data = []
  766. for p in products:
  767. is_sim = is_similar(product,p)
  768. _d = {"product":p,"distance":abs(len(product)-len(p)),"is_sim":is_sim}
  769. list_data.append(_d)
  770. list_data.sort(key=lambda x:x["distance"])
  771. for _d in list_data:
  772. is_sim = _d["is_sim"]
  773. if is_sim:
  774. if len(_d["product"])>len(product) and _d["product"].find(product)>=0:
  775. return product
  776. return _d["product"]
  777. return product
  778. def get_childs_text(childs,_product,products,is_begin=False,is_end=False):
  779. _text = ""
  780. end_next = False
  781. for _child in childs:
  782. child_text = _child.get("text")
  783. if child_text.find(_product)>=0:
  784. if not is_begin:
  785. is_begin = True
  786. if not end_next:
  787. if _child["sentence_title"] is not None and isinstance(_child["title_next"],dict) and _child["title_next"]["sentence_title"] is not None:
  788. end_next = True
  789. end_title = _child["title_next"]
  790. logger.debug("end_title %s "%end_title["text"])
  791. logger.debug("%s-%s-%s"%("get_childs_text",child_text[:10],str(is_begin)))
  792. for p in products:
  793. if child_text.find(p)>=0 and is_similar(_product,p,90):
  794. is_begin = True
  795. if child_text.find(_product)<0 and not is_similar(_product,p,80) and (child_text.find(p)>=0 or _child["has_product"]):
  796. if is_begin:
  797. is_end = True
  798. logger.debug("%s-%s-%s"%("get_childs_text end",child_text[:10],p))
  799. break
  800. if re.search(end_pattern,child_text) is not None:
  801. if is_begin:
  802. is_end = True
  803. logger.debug("%s-%s-%s"%("get_childs_text end",child_text[:10],str(is_end)))
  804. if is_begin and is_end:
  805. break
  806. if is_begin:
  807. _text += _child.get("text")+"\r\n"
  808. childs2 = _child.get("child_title",[])
  809. if len(childs2)>0:
  810. for _child2 in childs2:
  811. child_text,is_begin,is_end = get_childs_text([_child2],_product,products,is_begin)
  812. if is_begin:
  813. _text += child_text
  814. if is_end:
  815. break
  816. if end_next:
  817. is_end = True
  818. # logger.debug("%s-%s-%s"%("get_childs_text1",_text,str(is_begin)))
  819. # logger.debug("%s-%s-%s"%("get_childs_text2",_text,str(is_begin)))
  820. return _text,is_begin,is_end
  821. def extract_parameters_by_tree(_product,products,list_data,_data_i,parent_title,list_result,):
  822. _data = list_data[_data_i]
  823. childs = _data.get("child_title",[])
  824. if len(childs)>0:
  825. child_text,_,_ = get_childs_text([_data],_product,products)
  826. if len(child_text)>0:
  827. logger.info("extract_type by_tree child_text:%s"%child_text)
  828. list_result.append(child_text)
  829. if parent_title is not None:
  830. child_text,_,_ = get_childs_text([parent_title],_product,products)
  831. if len(child_text)>0:
  832. logger.info("extract_type by_tree child_text:%s"%child_text)
  833. list_result.append(child_text)
  834. childs = parent_title.get("child_title",[])
  835. if len(childs)>0:
  836. range_data = get_range_data_by_childs(list_data[_data_i:],childs)
  837. p_text = ""
  838. _find = False
  839. end_id = id(_data["title_next"]) if isinstance(_data["sentence_title"],dict) and _data["title_next"] is not None and _data["title_next"]["sentence_title"] is not None else None
  840. for pdata in range_data:
  841. ptext = pdata["text"]
  842. for p in products:
  843. if ptext.find(_product)<0 and (ptext.find(p)>=0 or pdata["has_product"]):
  844. _find = True
  845. break
  846. if re.search(end_pattern,ptext) is not None:
  847. _find = True
  848. if _find:
  849. break
  850. if id(pdata)==end_id:
  851. break
  852. p_text += ptext+"\r\n"
  853. if len(p_text)>0:
  854. logger.debug("extract_type by parent range_text:%s"%p_text)
  855. list_result.append(p_text)
  856. return True
  857. return False
  858. def get_table_pieces(_text,_product,products,list_result,_find):
  859. _soup = BeautifulSoup(_text,"lxml")
  860. _table = _soup.find("table")
  861. if _table is not None:
  862. trs = getTrs(_table)
  863. list_trs = []
  864. for tr in trs:
  865. tr_text = tr.get_text()
  866. if tr_text.find(_product)>=0:
  867. _find = True
  868. logger.debug("%s-%s"%("table_html_tr",tr_text))
  869. for p in products:
  870. if _find and p!=_product and tr_text.find(p)>=0:
  871. _find = False
  872. break
  873. if re.search(end_pattern,tr_text) is not None:
  874. _find = False
  875. break
  876. if _find:
  877. list_trs.append(tr)
  878. if len(list_trs)>0:
  879. table_html = "<table>%s</table>"%("\r\n".join([str(a) for a in list_trs]))
  880. logger.debug("extract_type table slices %s"%(table_html))
  881. list_result.append(table_html)
  882. def extract_parameters_by_table(_product,products,_param_pattern,list_data,_data_i,list_result):
  883. _data = list_data[_data_i]
  884. _text = _data["text"]
  885. list_table = _data["list_table"]
  886. parent_title = _data["parent_title"]
  887. if list_table is not None:
  888. _check = True
  889. max_length = max([len(a) for a in list_table])
  890. min_length = min([len(a) for a in list_table])
  891. text_line_first = ",".join(a[0] for a in list_table[0])
  892. if max_length>10:
  893. if min_length<max_length/2:
  894. return
  895. last_data = list_data[_data_i-1]
  896. _flag = False
  897. if last_data["type"]=="sentence" and last_data["text"].find(_product)>=0:
  898. logger.debug("last sentence find product %s-%s"%(_product,last_data["text"]))
  899. _flag = True
  900. # print(text_line_first,"text_line_first",re.search(_param_pattern,text_line_first) is not None and text_line_first.find(_product)>=0)
  901. if re.search(_param_pattern,text_line_first) is not None and text_line_first.find(_product)>=0:
  902. _flag = True
  903. if _flag:
  904. if len(products)==0:
  905. logger.debug("extract_type whole table by param and product %s"%(_text))
  906. list_result.append(_text)
  907. else:
  908. for p in products:
  909. if p!=_product and _text.find(p)>=0:
  910. logger.debug("extract_type add all table failed %s-%s"%(_product,p))
  911. _flag = False
  912. break
  913. if _flag:
  914. logger.debug("extract_type add all table succeed")
  915. get_table_pieces(_text,_product,products,list_result,True)
  916. else:
  917. list_head_index = []
  918. for line in list_table[:2]:
  919. for cell_i in range(len(line)):
  920. cell = line[cell_i]
  921. cell_text = cell[0]
  922. if len(cell_text)<20 and re.search(_param_pattern,cell_text) is not None:
  923. list_head_index.append(cell_i)
  924. list_head_index = list(set(list_head_index))
  925. for line in list_table:
  926. for cell in line:
  927. cell_text = cell[0]
  928. if len(cell_text)>50 and len(re.findall(meter_pattern,cell_text))>5 and cell_text.find(_product)>=0:
  929. _f = True
  930. for cell in line:
  931. if not _f:
  932. break
  933. cell_text = cell[0]
  934. for p in products:
  935. if cell_text.find(p)>=0 and p!=_product:
  936. _f = False
  937. break
  938. if _f:
  939. logger.debug("extract_type param column %s"%(cell_text))
  940. list_result.append(cell_text)
  941. if len(cell_text)<len(_product)*10 and str(cell_text).find(_product)>=0:
  942. for _index in list_head_index:
  943. if _index>=len(line):
  944. continue
  945. _cell = line[_index]
  946. if len(cell[0])>0:
  947. logger.info("%s-%s"%("extract_type add on table text:",_cell[0]))
  948. list_result.append(_cell[0])
  949. if not _flag and (re.search(_param_pattern,_text) is not None or (parent_title is not None and re.search(_param_pattern,parent_title["text"]) is not None)) and _text.find(_product)>=0:
  950. get_table_pieces(_text,_product,products,list_result,False)
  951. def extract_parameters_by_sentence(list_data,_data,_data_i,_product,products,list_result,is_project):
  952. _text = _data["text"]
  953. if _text.find(_product)>=0:
  954. parent_title = _data.get("parent_title")
  955. parent_text = ""
  956. parent_parent_title = None
  957. parent_parent_text = ""
  958. parent_title_index = None
  959. parent_parent_title_index = None
  960. childs = get_childs([_data])
  961. child_find = False
  962. for c in childs:
  963. if re.search(_param_pattern,c["text"]) is not None and len(c["text"])<30:
  964. logger.debug("child text %s"%(c["text"]))
  965. child_find = True
  966. break
  967. extract_text,_,_ = get_childs_text([_data],_product,products)
  968. logger.debug("childs found extract_text %s %s"%(str(child_find),extract_text))
  969. if child_find:
  970. if len(extract_text)>0:
  971. list_result.append(extract_text)
  972. else:
  973. limit_nums = len(_product)*2+5
  974. if len(_product)<=3:
  975. limit_nums += 6
  976. if _text.find("数量")>=0:
  977. limit_nums += 6
  978. if len(_text)<=limit_nums and _data["sentence_title"] is not None:
  979. if re.search(meter_pattern,extract_text) is not None:
  980. list_result.append(extract_text)
  981. elif len(re.findall(meter_pattern,extract_text))>2:
  982. list_result.append(extract_text)
  983. if parent_title is not None:
  984. parent_text = parent_title.get("text","")
  985. parent_parent_title = parent_title.get("parent_title")
  986. parent_title_index = parent_title["title_index"]
  987. if parent_parent_title is not None:
  988. parent_parent_text = parent_parent_title.get("text","")
  989. parent_parent_title_index = parent_parent_title["title_index"]
  990. _suit = False
  991. if re.search(_param_pattern,_text) is not None and len(_text)<50:
  992. _suit = True
  993. if re.search(_param_pattern,parent_text) is not None and len(parent_text)<50:
  994. _suit = True
  995. if re.search(_param_pattern,parent_parent_text) is not None and len(parent_parent_text)<50:
  996. _suit = True
  997. if _suit:
  998. logger.debug("extract_type sentence %s"%("extract_parameters_by_tree"))
  999. if not extract_parameters_by_tree(_product,products,list_data,_data_i,parent_title,list_result):
  1000. logger.debug("extract_type sentence %s"%("extract_parameters_by_tree"))
  1001. extract_parameters_by_tree(_product,products,list_data,_data_i,parent_parent_title,list_result)
  1002. if re.search(_param_pattern,_text) is not None and len(_text)<50:
  1003. childs = _data["child_title"]
  1004. if len(childs)>0:
  1005. extract_text,_,_ = get_childs_text([_data],_product,products)
  1006. if len(extract_text)>0:
  1007. logger.debug("extract_type param-product %s"%(extract_text))
  1008. list_result.append(extract_text)
  1009. elif is_project:
  1010. extract_text,_,_ = get_childs_text([_data],_product,products,is_begin=True)
  1011. if len(extract_text)>0 and re.search(meter_pattern,extract_text) is not None:
  1012. logger.debug("extract_type sentence is_project param-product is product %s"%(extract_text))
  1013. list_result.append(extract_text)
  1014. def getBestProductText(list_result,_product,products):
  1015. list_result.sort(key=lambda x:len(re.findall(meter_pattern+"|"+'[::;;]|\d+[%A-Za-z]+',BeautifulSoup(x,"lxml").get_text())), reverse=True)
  1016. logger.debug("+++++++++++++++++++++")
  1017. for i in range(len(list_result)):
  1018. logger.debug("result%d %s"%(i,list_result[i]))
  1019. logger.debug("+++++++++++++++++++++")
  1020. for i in range(len(list_result)):
  1021. _result = list_result[i]
  1022. _check = True
  1023. _result_text = BeautifulSoup(_result,"lxml").get_text()
  1024. _search = re.search("项目编号[::]|项目名称[::]|联合体投标|开户银行",_result)
  1025. if _search is not None:
  1026. logger.debug("result%d error illegal text %s"%(i,str(_search)))
  1027. _check = False
  1028. if not (len(_result_text)<1000 and _result[:6]!="<table"):
  1029. for p in products:
  1030. if _result_text.find(p)>0 and not (is_similar(_product,p,80) or p.find(_product)>=0 or _product.find(p)>=0):
  1031. logger.debug("result%d error product scoss %s"%(i,p))
  1032. _check = False
  1033. if len(_result_text)<100:
  1034. if re.search(meter_pattern,_result_text) is None:
  1035. logger.debug("result%d error text min count"%(i))
  1036. _check = False
  1037. if len(_result_text)>5000:
  1038. if len(_result_text)>10000:
  1039. logger.debug("result%d error text max count"%(i))
  1040. _check = False
  1041. elif len(re.findall(meter_pattern,_result_text))<10:
  1042. logger.debug("result%d error text max count less meter"%(i))
  1043. _check = False
  1044. list_find = list(set(re.findall(meter_pattern,_result_text)))
  1045. not_list_find = list(set(re.findall(not_meter_pattern,_result_text)))
  1046. _count = len(list_find)-len(not_list_find)
  1047. has_num = False
  1048. for _find in list_find:
  1049. if re.search('[0-9a-zA-Z]',_find) is not None:
  1050. has_num = True
  1051. break
  1052. if not(_count>=2 and has_num or _count>=5):
  1053. logger.debug("result%d error match not enough"%(i))
  1054. _check = False
  1055. if _check:
  1056. return _result
  1057. def format_text(_result):
  1058. list_result = re.split("\r|\n",_result)
  1059. _result = ""
  1060. for _r in list_result:
  1061. if len(_r)>0:
  1062. _result+="%s\n"%(_r)
  1063. _result = '<div style="white-space:pre">%s</div>'%(_result)
  1064. return _result
  1065. def extract_product_parameters(list_data,_product):
  1066. list_result = []
  1067. _product = standard_product(_product.strip())
  1068. products = extract_products(list_data,_product)
  1069. _product = get_correct_product(_product,products)
  1070. logger.debug("all products %s-%s"%(_product,str(products)))
  1071. is_project = False
  1072. if re.search("项目名称|采购项目",_product) is not None:
  1073. is_project = True
  1074. if len(products)==1 and is_similar(products[0],_product,90):
  1075. is_project = True
  1076. _find_count = 0
  1077. for _data_i in range(len(list_data)):
  1078. _data = list_data[_data_i]
  1079. _type = _data["type"]
  1080. _text = _data["text"]
  1081. if _type=="sentence":
  1082. if _text.find(_product)>=0:
  1083. _find_count += 1
  1084. if re.search("项目名称|采购项目",_text) is not None and re.search("等",_text) is not None:
  1085. is_project = True
  1086. extract_parameters_by_sentence(list_data,_data,_data_i,_product,products,list_result,is_project)
  1087. elif _type=="table":
  1088. if _text.find(_product)>=0:
  1089. _find_count += 1
  1090. extract_parameters_by_table(_product,products,_param_pattern,list_data,_data_i,list_result)
  1091. _text = getBestProductText(list_result,_product,products)
  1092. return _text,_find_count
  1093. if __name__ == '__main__':
  1094. filepath = "download/4597dcc128bfabc7584d10590ae50656.html"
  1095. _product = "彩色多普勒超声诊断仪"
  1096. _html = open(filepath, "r", encoding="utf8").read()
  1097. pd = ParseDocument(_html,False)
  1098. pd.fix_tree(_product)
  1099. list_data = pd.tree
  1100. pd.print_tree(list_data)
  1101. _text,_count = extract_product_parameters(list_data,_product)
  1102. logger.info("find count:%d"%(_count))
  1103. logger.info("extract_parameter_text::%s"%(_text))