htmlparser.py 59 KB

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