2011-02-11 18 views
6

Czy istnieje narzędzie czysto Python do pobrania kodu HTML i obcięcia go tak blisko określonej długości, jak to możliwe, ale upewnij się, że wynikowy fragment jest dobrze sformułowany? Na przykład, biorąc pod uwagę to HTML:HTML Obcięcie w Pythonie

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

nie byłoby produkcji:

<h1>This is a hea 

ale:

<h1>This is a header</h1> 

lub co najmniej:

<h1>This is a hea</h1> 

nie mogę znajdź taki, który działa, choć znalazłem taki, który opiera się na pullparser, która jest zarówno przestarzała, jak i martwa.

+0

"spowodowałoby to:" .. podane jakie parametry? liczba znaków z rzędu? liczba elementów-domowników, hierarchia? – akira

+0

Prawdopodobnie liczba znaków zawartości lub liczba znaków HTML. Nie jestem wybredna. – JasonFruit

Odpowiedz

6

nie sądzę, musisz się pełnoprawnym parser - trzeba tylko tokenize ciąg wejściu do jednego z:

  • tekstu
  • otwarte tag
  • blisko tag
  • samozamykającego tag
  • jednostka charakter

Gdy masz strumień żetony tak, to łatwy w użyciu stosu, aby śledzić, co znaczniki muszą zamknięciem. I rzeczywiście wpadł na ten problem jakiś czas temu i napisałem małą bibliotekę, aby to zrobić:

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

To działa dobrze dla mnie, i obsługuje większość przypadków narożnych dobrze, w tym dowolnie zagnieżdżonych znaczników, licząc encji znakowych jako pojedyncza postać, zwracając błąd na zniekształconym znaczniku itp.

To będzie produkować:

<h1>This is a hea</h1> 

na swoim przykładzie. Może to być może być zmienione, ale w ogólnym przypadku jest to trudne - co, jeśli próbujesz skrócić do 10 znaków, ale znacznik <h1> nie jest zamknięty dla innego, powiedzmy 300 znaków?

+0

To jest dokładnie to, co wypracowałem i napisałem sam. Jedyną praktyczną różnicą między twoją i moją było to, że umożliwiłem obcięcie tylko w miejscach między słowami. – JasonFruit

+0

Potrzebowałem dokładnie tego i wykonałem zaimplementowane przerwy między wyrazami. To bardzo proste, różnica w stosunku do oryginału jest jak 5 linii - https://github.com/enkore/typeflow/blob/master/htmltruncate.py około linii pięćdziesiąt – dom0

0

Moja początkowa myśl będzie używać parsera XML (może python's sax parser), a następnie prawdopodobnie policzyć znaki tekstowe w każdym elemencie xml. Zignorowałbym znaczniki, które liczą znaki, aby były bardziej spójne i prostsze, ale powinno być możliwe.

+0

Jak skomentowałem odpowiedź funktku, czy ktoś * już tego nie zrobił? – JasonFruit

+0

@JasonFruit Oh Widzę, co masz na myśli teraz - nie wiem, czy to naprawdę tak powszechne i proste do wykonania. – Petriborg

0

Polecam najpierw całkowicie parsować HTML, a następnie skrócić. Doskonały analizator HTML dla Pythona to lxml. Po przeanalizowaniu i obcięciu można wydrukować je z powrotem do formatu HTML.

+0

Ale czy ktoś * już tego nie zrobił? Rozumiem ten problem, ale wygląda na to, że ktoś taki musi mieć rozwiązanie. – JasonFruit

0

Spójrz na HTML Tidy, aby oczyścić/sformatować/ponownie wyświetlić kod HTML.

+0

Nie jest to najlepsza opcja, a właściwie nie jest to Python. – JasonFruit

+0

Istnieje kilka bibliotek Pythona wiążących Tidy, sprawdź to. Używam go do czyszczenia MS-Word HTML, który niektórzy użytkownicy wklejają do systemu CMS. –

+0

Nie sprecyzowałem też, że używam Google App Engine, gdzie mogę tylko wprowadzać biblioteki czystego Pythona. – JasonFruit

5

Jeśli używasz Django lib, można po prostu:

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) 

Tak czy inaczej, oto kod z truncate_html_words od Django:

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

Używam CherryPy, ale może warto importować 'django.utils.text', jeśli nie jest to zbytnio dodany koszt uruchomienia. Spróbuję tego. – JasonFruit

+1

Funkcja 'truncate_html_words' znajduje się w http://code.djangoproject.com/browser/django/trunk/django/utils/text.py. –

+0

Parsowanie HTML za pomocą wyrażeń regularnych (jak to robi Django powyżej) jest naprawdę, bardzo złym pomysłem. – slacy

2

ta będzie służyć swoją requirement.An łatwy w użyciu parser HTML i złe markup korektor

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

+0

Zajrzałem tutaj najpierw, zanim zadałem pytanie. To nie jest złe, ale ja muszę policzyć znaki zawartości i uciąć we właściwym miejscu, chociaż dobrze jest naprawić znaczniki, gdy już to zrobi. – JasonFruit

3

Można to zrobić w jednej linii z BeautifulSoup (zakładając, że chcesz obciąć w pewnej liczbie znaków źródłowych, a nie na liczbie znaków treści):

from BeautifulSoup import BeautifulSoup 

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

Znalazłem odpowiedź przez slacy bardzo pomocny i wziąłby go pod uwagę, gdybym miał reputację, - jednak była jedna dodatkowa uwaga. W moim środowisku zainstalowałem html5lib oraz BeautifulSoup4. BeautifulSoup użył parsera html5lib, co spowodowało, że mój fragment HTML był zawijany w znaczniki html i body, co nie było tym, czego chciałem.

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

Aby rozwiązać te problemy Powiedziałem BeautifulSoup użyć parsera Pythona:

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>' 
Powiązane problemy