Создание резюме блога в Python?

Есть ли хорошая библиотека (или магия регулярных выражений), которая может преобразовать запись в блоге в сводку блога? Я бы хотел, чтобы в сводке отображались первые четыре предложения, первый абзац или первые X символов... не совсем уверен, что будет лучше. В идеале я хотел бы сохранить теги форматирования html, такие как <a>, <b>, <u> и <i>, но он мог бы удалить все остальные теги html, javascript и css.

В частности, в качестве входных данных я бы дал строку html, представляющую всю запись в блоге. В качестве вывода мне нужна строка html, содержащая первые несколько предложений, абзац или X символов. Все потенциально небезопасные HTML-теги удалены. На питоне пожалуйста.


person speedplane    schedule 08.03.2011    source источник
comment
«не совсем уверен, что было бы лучше» — если даже вы этого не знаете, то как какой-то сценарий должен волшебным образом работать лучше всего? У вас может быть только некоторая скриптовая работа с некоторыми правилами, чтобы получить желаемый результат, но это требует, чтобы вы сначала придумали эти правила.   -  person poke    schedule 08.03.2011
comment
вы должны указать <a>, <b>, <u> and <i> с обратной кавычкой   -  person Xavier Combelle    schedule 08.03.2011
comment
@poke - кто знает, что лучше не пробовать? Идея состоит в том, чтобы попробовать несколько вещей и посмотреть, что мне нравится больше всего. Скрипты не волшебные, их пишут люди, которые предположительно пробовали несколько решений.   -  person speedplane    schedule 08.03.2011


Ответы (3)


Если вы смотрите на HTML, вам нужно его проанализировать. В дополнение к вышеупомянутому BeautifulSoup, lxml.html имеет несколько хороших инструментов для обработки HTML.

Однако, если это блог, вам может быть даже проще работать с каналами RSS/Atom. Feedparser просто великолепен и упростил бы его. Вы получите совместимость и долговечность (поскольку RSS более определен, вещи будут меняться меньше), но если лента не включает то, что вам нужно, это вам не поможет.

person chmullig    schedule 08.03.2011

В итоге я воспользовался библиотекой gdata и создал свой собственный сводник блогов, который использует библиотеку gdata для извлечения блога Blogspot в Google App Engine (нетрудно было бы перенести его на другие платформы). Код ниже. Чтобы использовать его, сначала установите константу blog_id_constant, а затем вызовите get_blog_info, чтобы вернуть словарь с резюме блога.

Я бы не стал доверять коду для создания резюме любого случайного блога в Интернете, потому что он может не удалить весь небезопасный html из ленты блога. Однако для простого блога, который вы пишете сами, приведенный ниже код должен работать.

Пожалуйста, не стесняйтесь копировать, но если вы видите какие-либо ошибки или хотите внести улучшения, добавьте их в комментарии. (извините за точки с запятой).

import sys
import os
import logging
import time
import urllib
from HTMLParser import HTMLParser
from django.core.cache import cache
# Import the Blogger API
sys.path.insert(0, 'gdata.zip')
from gdata import service

Months = ["Jan.", "Feb.", "Mar.", "Apr.", "May", "June", "July", "Aug.", "Sept.", "Oct.", "Nov.", "Dec."];
blog_id_constant = -1 # YOUR BLOG ID HERE
blog_pages_at_once = 5

# -----------------------------------------------------------------------------
#   Blogger 
class BlogHTMLSummarizer(HTMLParser):
    '''
    An HTML parser which only grabs X number of words and removes
    all tags except for certain safe ones.
    '''

    def __init__(self, max_words = 80):
        self.max_words = max_words
        self.allowed_tags = ["a", "b", "u", "i", "br", "div", "p", "img", "li", "ul", "ol"]
        if self.max_words < 80:
            # If it's really short, don't include layout tags
            self.allowed_tags = ["a", "b", "u", "i"]
        self.reset()
        self.out_html = ""
        self.num_words = 0
        self.no_more_data = False
        self.no_more_tags = False
        self.tag_stack = []

    def handle_starttag(self, tag, attrs):
        if not self.no_more_data and tag in self.allowed_tags:
            val = "<%s %s>"%(tag, 
                " ".join("%s='%s'"%(a,b) for (a,b) in attrs))
            self.tag_stack.append(tag)
            self.out_html += val

    def handle_data(self, data):
        if self.no_more_data:
            return
        data = data.split(" ")
        if self.num_words + len(data) >= self.max_words:
            data = data[:self.max_words-self.num_words]
            data.append("...")
            self.no_more_data = True
        self.out_html  += " ".join(data)
        self.num_words += len(data)

    def handle_endtag(self, tag):
        if self.no_more_data and not self.tag_stack:
            self.no_more_tags = True
        if not self.no_more_tags and self.tag_stack and tag == self.tag_stack[-1]:
            if not self.tag_stack:
                logging.warning("mixed up blogger tags")
            else:
                self.out_html += "</%s>"%tag
                self.tag_stack.pop()

def get_blog_info(short_summary = False, page = 1, year = "", month = "", day = "", post = None):
    '''
    Returns summaries of several recent blog posts to be displayed on the front page
        page: which page of blog posts to get. Starts at 1.
    '''
    blogger_service = service.GDataService()
    blogger_service.source = 'exampleCo-exampleApp-1.0'
    blogger_service.service = 'blogger'
    blogger_service.account_type = 'GOOGLE'
    blogger_service.server = 'www.blogger.com'
    blog_dict = {}

    # Do the common stuff first
    query = service.Query()
    query.feed = '/feeds/' + blog_id_constant + '/posts/default'
    query.order_by = "published"
    blog_dict['entries'] = []

    def get_common_entry_data(entry, summarize_len = None):
        '''
        Convert an entry to a dictionary object.
        '''
        content = entry.content.text
        if summarize_len != None:
            parser = BlogHTMLSummarizer(summarize_len)
            parser.feed(entry.content.text)
            content = parser.out_html
        pubstr = time.strptime(entry.published.text[:-10], '%Y-%m-%dT%H:%M:%S')
        safe_title = entry.title.text.replace(" ","_")
        for c in ":,.<>!@#$%^&*()+-=?/'[]{}\\\"":
            # remove nasty characters
            safe_title = safe_title.replace(c, "")
        link = "%d/%d/%d/%s/"%(pubstr.tm_year, pubstr.tm_mon, pubstr.tm_mday, 
            urllib.quote_plus(safe_title))
        return {
                'title':entry.title.text,
                'alllinks':[x.href for x in entry.link] + [link], #including blogger links
                'link':link,
                'content':content,
                'day':pubstr.tm_mday,
                'month':Months[pubstr.tm_mon-1],
                'summary': True if summarize_len != None else False,
            }

    def get_blogger_feed(query):
        feed = cache.get(query.ToUri())
        if not feed:
            logging.info("GET Blogger Page: " + query.ToUri())
            try:
                feed = blogger_service.Get(query.ToUri())
            except DownloadError:
                logging.error("Cant download blog, rate limited? %s"%str(query.ToUri()))
                return None
            except Exception, e:
                web_exception('get_blogger_feed', e)
                return None
            cache.set(query.ToUri(), feed, 3600)
        return feed

    def _in_one(a, allBs):
        # Return true if a is in one of allBs
        for b in allBs:
            if a in b:
                return True
        return False

    def _get_int(i):
        try:
            return int(i)
        except ValueError:
            return None
    (year, month, day) = (_get_int(year), _get_int(month), _get_int(day))

    if not short_summary and year and month and day:
        # Get one more than we need so we can see if we have more
        query.published_min = "%d-%02d-%02dT00:00:00-08:00"%(year, month, day)
        query.published_max = "%d-%02d-%02dT23:59:59-08:00"%(year, month, day)
        feed = get_blogger_feed(query)
        if not feed:
            return {}
        blog_dict['detail_view'] = True
        blog_dict['entries'] = map(lambda e: get_common_entry_data(e, None), feed.entry)
    elif not short_summary and year and month and not day:
        # Get one more than we need so we can see if we have more
        query.published_min = "%d-%02d-%02dT00:00:00-08:00"%(year, month, 1)
        query.published_max = "%d-%02d-%02dT23:59:59-08:00"%(year, month, 31)
        feed = get_blogger_feed(query)
        if not feed:
            return {}
        blog_dict['detail_view'] = True
        blog_dict['entries'] = map(lambda e: get_common_entry_data(e, None), feed.entry)
        if post:
            blog_dict['entries'] = filter(lambda f: _in_one(post, f['alllinks']), blog_dict['entries'])
    elif short_summary:
        # Get a summary of all posts
        query.max_results = str(3) 
        query.start_index = str(1)
        feed = get_blogger_feed(query)
        if not feed:
            return {}
        feed.entry = feed.entry[:3]
        blog_dict['entries'] = map(lambda e: get_common_entry_data(e, 18), feed.entry)
    else:
        # Get a summary of all posts
        try:
            page = int(page)
        except ValueError:
            page = 1

        # Get one more than we need so we can see if we have more
        query.max_results = str(blog_pages_at_once + 1) 
        query.start_index = str((page - 1)* blog_pages_at_once + 1)
        logging.info("GET Blogger Page: " + query.ToUri())
        feed = blogger_service.Get(query.ToUri())

        has_older = len(feed.entry) > blog_pages_at_once
        feed.entry = feed.entry[:blog_pages_at_once]
        if page > 1:
            blog_dict['newer_page'] = str(page-1)
        if has_older:
            blog_dict['older_page'] = str(page+1)       
        blog_dict['entries'] = map(lambda e: get_common_entry_data(e, 80), feed.entry)

    return blog_dict
person speedplane    schedule 08.02.2012

Вам нужно будет разобрать html. Хорошая библиотека для этого — BeautifulSoup. Это позволит удалить определенные теги и извлечь значения (текст между тегами). Текст можно относительно легко сократить до четырех предложений, хотя я бы выбрал фиксированное количество символов, так как длина предложения может сильно различаться.

person Bernhard    schedule 08.03.2011