clean_html.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import re
  2. __all__ = ['cleaner']
  3. # 独立元素
  4. INDEPENDENT_TAGS = {
  5. '<head>[\s\S]*?</head>': '',
  6. '<html>|<html [^>]*>|</html>': '',
  7. '<body>|<body [^>]*>|</body>': '',
  8. '<meta[^<>]*>|<meta [^<>]*>|<meta[^<>]*>[\s\S]*?</meta>|</meta>': '', # 元数据
  9. '&(nbsp|e[mn]sp|thinsp|zwn?j|#13);': '', # 空格
  10. '\\xa0|\\u3000': '', # 空格
  11. '<!--[\s\S]*?-->': '', # 注释
  12. '<style[^<>]*>[\s\S]*?</style>': '', # 样式
  13. '<script[^<>]*>[\s\S]*?</script>': '', # JavaScript
  14. '<input>': '', # 输入框
  15. '<img[^>]*>': '<br>', # 图片
  16. }
  17. # 行内元素
  18. INLINE_TAGS = {
  19. '<a>|<a [^>]*>|</a>': '', # 超链接
  20. '<span>|<span [^>]*>|</span>': '', # span
  21. '<label>|<label [^>]*>|</label>': '<br>', # label
  22. '<font>|<font [^>]*>|</font>': '', # font
  23. }
  24. # 块级元素
  25. BLOCK_TAGS = {
  26. '<h[1-6][^>]*>[\s\S]*?</h[1-6]>': '', # 标题
  27. # '<h[1-6][^>]*>|</h[1-6]>': '', # 标题
  28. '<p>|<p [^>]*>|</p>': '<br>', # 段落
  29. '<div>|<div [^>]*>|</div>': '<br>', # 分割 division
  30. '<o:p>|<o:p [^>]*>|</o:p>': '' # OFFICE微软WORD段落
  31. }
  32. # 其他
  33. OTHER = {
  34. '<?xml[^>]*>|<?xml [^>]*>|<?xml:.*?>': '',
  35. '<epointform>': '',
  36. '<!doctype html>|<!doctype html [^>]*>': '',
  37. '【关闭】|关闭': '',
  38. '【打印】|打印本页': '',
  39. '【字体:[\s\S]*】': '',
  40. '文章来源:[\u4e00-\u9fa5]+': '',
  41. '浏览次数:.*[<]+': '',
  42. '(责任编辑:.*?)': '',
  43. '分享到[:]': '',
  44. '阅读数[::]\d+': '',
  45. }
  46. # 样式
  47. CSS_STYLE = {
  48. 'style="[\s\S]*?"|style ="[\s\S]*?"': '',
  49. 'bgcolor="[\s\S]*?"|bgcolor ="[\s\S]*?"': '',
  50. 'bordercolor="[\s\S]*?"|bordercolor ="[\s\S]*?"': '',
  51. 'class="[\s\S]*?"|class ="[\s\S]*?"': '',
  52. 'align="[\s\S]*?"|align ="[\s\S]*?"': '',
  53. 'cellpadding="(\d+)"|cellspacing="(\d+)"': '',
  54. }
  55. # 空白符
  56. BLANKS = {
  57. '\n\s*\n': '\n',
  58. '\s*\n\s*': '\n',
  59. '[^\S\n]': ' ',
  60. '\s+': ' ',
  61. }
  62. # css标签集合
  63. TAGS = {'table', 'tr', 'td', 'div', 'span', 'p'}
  64. # css属性集合
  65. ATTRS = {'id', 'class', 'style', 'width'}
  66. def _repair_tag():
  67. """异常的标签组合,用来替换非标准页面的标签"""
  68. _repairs = {}
  69. for tag in TAGS:
  70. for attr in ATTRS:
  71. key = '{}{}'.format(tag, attr)
  72. val = '{} {}'.format(tag, attr)
  73. _repairs[key] = val
  74. return _repairs
  75. def _escape_character(html):
  76. """转义字符"""
  77. html = html.replace('&lt;', '<')
  78. html = html.replace('&gt;', '>')
  79. html = html.replace('&quot;', '"')
  80. html = html.replace('&amp;', '&')
  81. return html
  82. def _lowercase_tag(html):
  83. """标签归一化处理(全部小写)"""
  84. tags = re.findall("<[^>]+>", html)
  85. for tag in tags:
  86. html = html.replace(tag, str(tag).lower())
  87. repair_tags = _repair_tag()
  88. for err, right in repair_tags.items():
  89. html = html.replace(err, right)
  90. return html
  91. def cleaner(html, special=None, completely=False):
  92. """
  93. 数据清洗
  94. :param html: 清洗的页面
  95. :param special: 额外指定页面清洗规则
  96. :param completely: 是否完全清洗页面
  97. :return: 清洗后的页面源码
  98. """
  99. if special is None:
  100. special = {}
  101. OTHER.update(special)
  102. remove_tags = {
  103. **INDEPENDENT_TAGS,
  104. **INLINE_TAGS,
  105. **BLOCK_TAGS,
  106. **OTHER,
  107. **CSS_STYLE,
  108. **BLANKS,
  109. }
  110. html = _lowercase_tag(html)
  111. for tag, repl in remove_tags.items():
  112. html = re.sub(tag, repl, html)
  113. if completely:
  114. html = re.sub(r'<canvas[^<>]*>[\s\S]*?</canvas>', '', html) # 画布
  115. html = re.sub(r'<iframe[^<>]*>[\s\S]*?</iframe>', '', html) # 内框架
  116. html = re.sub('<([^<>\u4e00-\u9fa5]|微软雅黑|宋体|仿宋)+>', '', html)
  117. html = _escape_character(html)
  118. return html