HTML在Python中截断

Jas*_*uit 6 html python truncation

是否有一个纯Python工具来获取一些HTML并将其截断尽可能接近给定长度,但是确保生成的片段格式正确吗?例如,给定此HTML:

<h1>This is a header</h1>
<p>This is a paragraph</p>
Run Code Online (Sandbox Code Playgroud)

它不会产生:

<h1>This is a hea
Run Code Online (Sandbox Code Playgroud)

但:

<h1>This is a header</h1>
Run Code Online (Sandbox Code Playgroud)

或至少:

<h1>This is a hea</h1>
Run Code Online (Sandbox Code Playgroud)

我找不到一个有效的,虽然我找到了一个依赖的pullparser,既过时又死了.

小智 6

如果您正在使用DJANGO lib,您可以简单地:

from django.utils import text, html

    class class_name():


        def trim_string(self, stringf, limit, offset = 0):
            return stringf[offset:limit]

        def trim_html_words(self, html, limit, offset = 0):
            return text.truncate_html_words(html, limit)


        def remove_html(self, htmls, tag, limit = 'all', offset = 0):
            return html.strip_tags(htmls)
Run Code Online (Sandbox Code Playgroud)

无论如何,这里是来自django的truncate_html_words的代码:

import re

def truncate_html_words(s, num):
    """
    Truncates html to a certain number of words (not counting tags and comments).
    Closes opened tags if they were correctly closed in the given html.
    """
    length = int(num)
    if length <= 0:
        return ''
    html4_singlets = ('br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input')
    # Set up regular expressions
    re_words = re.compile(r'&.*?;|<.*?>|([A-Za-z0-9][\w-]*)')
    re_tag = re.compile(r'<(/)?([^ ]+?)(?: (/)| .*?)?>')
    # Count non-HTML words and keep note of open tags
    pos = 0
    ellipsis_pos = 0
    words = 0
    open_tags = []
    while words <= length:
        m = re_words.search(s, pos)
        if not m:
            # Checked through whole string
            break
        pos = m.end(0)
        if m.group(1):
            # It's an actual non-HTML word
            words += 1
            if words == length:
                ellipsis_pos = pos
            continue
        # Check for tag
        tag = re_tag.match(m.group(0))
        if not tag or ellipsis_pos:
            # Don't worry about non tags or tags after our truncate point
            continue
        closing_tag, tagname, self_closing = tag.groups()
        tagname = tagname.lower()  # Element names are always case-insensitive
        if self_closing or tagname in html4_singlets:
            pass
        elif closing_tag:
            # Check for match in open tags list
            try:
                i = open_tags.index(tagname)
            except ValueError:
                pass
            else:
                # SGML: An end tag closes, back to the matching start tag, all unclosed intervening start tags with omitted end tags
                open_tags = open_tags[i+1:]
        else:
            # Add it to the start of the open tags list
            open_tags.insert(0, tagname)
    if words <= length:
        # Don't try to close tags if we don't need to truncate
        return s
    out = s[:ellipsis_pos] + ' ...'
    # Close any tags still open
    for tag in open_tags:
        out += '</%s>' % tag
    # Return string
    return out
Run Code Online (Sandbox Code Playgroud)


小智 6

我认为你不需要一个完整的解析器 - 你只需要将输入字符串标记为以下之一:

  • 文本
  • 打开标签
  • 关闭标记
  • 自动关闭标签
  • 字符实体

一旦有了这样的令牌流,就可以很容易地使用堆栈来跟踪需要关闭的标记.我刚刚遇到这个问题并编写了一个小型库来执行此操作:

https://github.com/eentzel/htmltruncate.py

它对我来说效果很好,可以很好地处理大多数边角情况,包括任意嵌套标记,将字符实体计为单个字符,在格式不正确的标记上返回错误等.

它会产生:

<h1>This is a hea</h1>
Run Code Online (Sandbox Code Playgroud)

在你的例子上.这可能会改变,但在一般情况下很难 - 如果你试图截断到10个字符,但<h1>标签没有关闭另一个,比如300个字符怎么办?


小智 5

我发现 slacy 的答案非常有帮助,如果我有声誉,我会投票赞成它,但还有一件事需要注意。在我的环境中,我安装了 html5lib 以及 BeautifulSoup4。BeautifulSoup 使用了 html5lib 解析器,这导致我的 html 片段被包裹在 html 和 body 标签中,这不是我想要的。

>>> truncate_html("<p>sdfsdaf</p>", 4)
u'<html><head></head><body><p>s</p></body></html>'
Run Code Online (Sandbox Code Playgroud)

为了解决这些问题,我告诉 BeautifulSoup 使用 python 解析器:

from bs4 import BeautifulSoup
def truncate_html(html, length): 
    return unicode(BeautifulSoup(html[:length], "html.parser"))

>>> truncate_html("<p>sdfsdaf</p>", 4)
u'<p>s</p>'
Run Code Online (Sandbox Code Playgroud)