2011-02-11 19 views
6

いくつかのHTMLを取り込み、できるだけ特定の長さに切り捨てる純粋なPythonツールがありますが、結果のスニペットが整形式であることを確認してください。PythonでHTMLを切り捨てる

<h1>This is a header</h1> 
<p>This is a paragraph</p> 

それが生成しない:

<h1>This is a hea 

しかし:

<h1>This is a header</h1> 

あるいは少なくとも、例えば、このHTML与えられ

<h1>This is a hea</h1> 

を私がすることはできません私はそれに依存するものを見つけたが、動作するものを見つけるpullparserは、古くなって死んでいます。

+0

..何パラメータが与えられた?私はPythonのパーサを使用するためにBeautifulSoupを告げ、これらの問題を解決するには

>>> truncate_html("<p>sdfsdaf</p>", 4) u'<html><head></head><body><p>s</p></body></html>' 

行の文字数? dom要素の数、階層? – akira

+0

多くのコンテンツ文字またはHTML文字の数のいずれかです。私は気難しくない。 – JasonFruit

答えて

6

私はあなたが本格的なパーサを必要とは思わない - あなただけの1に入力文字列をトークン化する必要があります。

  • テキスト
  • オープンタグ
  • 近いタグ
  • 自動閉鎖をタグ
  • 文字エンティティ

あなたがそのようなトークンのストリームを持っていたら、それはタグが閉じて必要なものを追跡するためにスタックを使用するのは簡単です。私は実際にしばらく前にこの問題に遭遇してこれを行うための小さなライブラリを書いた:それは文字エンティティを数える、私のためによく働いて、任意にネストされたマークアップを含めて、うまくコーナーケースのほとんどを処理しています

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

不正なマークアップでエラーを返すなど、単一の文字として

これは、生成されます:あなたの例で

<h1>This is a hea</h1> 

を。これはおそらく変更される可能性がありますが、一般的なケースでは難しいです。たとえば、10文字に切り捨てようとしているのに、というタグが閉じられていないとしたらどうでしょうか?

+0

これはまさに私が取り組んだことであり、自分自身を書いたものです。あなたと私の間の唯一の実用的な違いは、単語間の位置だけを切り詰めることが可能になったことです。 – JasonFruit

+0

私はそれを正確に必要とし、単語間の区切りを実装しました。それは非常にシンプルですが、オリジナルとの差異は5行のようです - https://github.com/enkore/typeflow/blob/master/htmltruncate.py 50行前後 – dom0

0

私の最初の考えはXMLパーサ(おそらくpython's sax parser)を使用して、おそらく各xml要素のテキスト文字を数えます。タグの文字数を無視して、一貫性を高め、シンプルにすることもできますが、可能でなければなりません。

+0

私はfunktkuの答えにコメントしましたが、*誰かがすでにそれをしていませんか? – JasonFruit

+0

@JasonFruitオハイオ州私はあなたが今何を意味するかを見る - 私はその本当の共通点が率直に、そしてそれが簡単であるかどうかはわかりません。 – Petriborg

0

まず、HTMLを完全に解析し、切り捨てることをお勧めします。 Python用の素晴らしいHTMLパーサーはlxmlです。解析して切り捨てた後、HTML形式で印刷することができます。

+0

しかし、誰かがすでにそれをしていないのですか?私はこの問題を理解していますが、それは誰かが解決策を持たなければならないような共通のもののようです。 – JasonFruit

0

HTML Tidyを参照して、HTMLのクリーンアップ/再フォーマット/再インデントを行ってください。

+0

最高の選択肢ではなく、実際にはPythonのことではありません。 – JasonFruit

+0

TidyにバインドするいくつかのPythonライブラリがあります。チェックアウトしてください。いくつかのユーザーがCMSに貼り付けるMS-Word HTMLをクリーンアップするために使用します。 –

+0

私は、純粋なPythonライブラリしか紹介できないGoogle App Engineを使用しているとも指定していません。 – JasonFruit

5

あなたは単に、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) 

はとにかく、ここジャンゴから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 
+0

私はCherryPyを使用していますが、起動コストをあまり追加しないと 'django.utils.text'をインポートする価値があります。私はそれを試してみます。 – JasonFruit

+1

'truncate_html_words'関数はhttp://code.djangoproject.com/browser/django/trunk/django/utils/text.pyにあります。 –

+0

正規表現を使用してHTMLを解析する(上記のDjangoのように)のは本当に本当に悪い考えです。 – slacy

2

これは使いやすいあなたのrequirement.Anを提供しますHTMLパーサと悪いマークアップ補正

http://www.crummy.com/software/BeautifulSoup/

+0

質問をする前にまずここを見ました。悪いことではありませんが、コンテンツの文字を数え、正しいポイントで切り捨てるのは私の責任ですが、マークアップが完了したらそれを修正するのはいい仕事です。 – JasonFruit

3

あなたはBeautifulSoupで1行(あなたがソース特定の文字数ではなく、コンテンツの文字数で切り捨てるにしたいと仮定して)で行うことができます。

from BeautifulSoup import BeautifulSoup 

def truncate_html(html, length): 
    return unicode(BeautifulSoup(html[:length])) 
3

私は非常にslacyことで答えを見つけました私が評判を覚えていれば助けになり、それをアップヴォートするだろうが、もう一つ注意すべきことがあった。私の環境では、html5libとBeautifulSoup4がインストールされていました。 BeautifulSoupはhtml5libパーサーを使用していました。この結果、自分のhtmlスニペットがhtmlタグとbodyタグで囲まれてしまいました。 "それが生じるであろう:"

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>' 
関連する問題