Как грамотно получить статью из html документа

Тема в разделе "Как сделать...", создана пользователем e64f, 22 ноя 2012.

  1. e64f

    e64f Постоялец

    Регистр.:
    2 ноя 2008
    Сообщения:
    95
    Симпатии:
    6
    Добрый день!

    В рамках одного научного проекта, для задачи определения тематики текста, понадобилось после получения веб странички очищать ее от html и всяких менюшек, слоганов, реклам, анонсов и прочего, что не имеет отношения к основному тексту страницы. Были следующие идеи:

    1) Очищать все HTML теги, функцией strip_tags(), смириться что остается лишний текст.
    2) С помощью класса сконвертировать html документ в DOM, выбрать например самый длинный по содержимому тег TD или DIV. Но проблема в том что внутри текста может встретиться DIV и таблица. Также некоторые пытаются засунуть в середину текста рекламу.
    3) Перед очисткой от html поудалять подряд стоящие ссылки, так мы уберем меню.
    4) Выполнить п.1 для двух страничек сайта, удалить в начале и конце одинаковый текст

    Вопрос: может уже есть готовые решения на эту проблему? Подскажите пожалуйста.
     
  2. Горбушка

    Горбушка Ищу её...

    Регистр.:
    2 май 2008
    Сообщения:
    3.223
    Симпатии:
    2.252
    Конечно же есть готовые варианты =)
    1) Написать регулярное выражение, которое содержит какой-либо уникальный элемент до текста статьи и после него... Например:
    - соответсвенно выбираем всё между <div id=""content> и </div> - условие может быть сложнее
    2) Существует 100500 видов т.н. грабберов, как RSS, так и HTML. Они работают по той же схеме, но регулярку писать вручную не нужно.

    Далее тем же strip_tags() чистим от лишнего и готово.
     
  3. denik

    denik Постоялец

    Регистр.:
    1 июл 2011
    Сообщения:
    80
    Симпатии:
    43
    Вообще есть довольно хороший класс для парсинга HTML DOM.
    Называется - simplehtmldom не знаю, ссылки тут можно или нет выкладывать - рисковать не буду. Ищите в гугл по ключевому слову.

    Добавлю:
    Контент там вообще просто парсится (вырезка) :
    Если jQuery знаете - это аналог, только под PHP ;)
     
    e64f нравится это.
  4. e64f

    e64f Постоялец

    Регистр.:
    2 ноя 2008
    Сообщения:
    95
    Симпатии:
    6
    Можно пример грабера
     
  5. DeviLlundead

    DeviLlundead Писатель

    Заблокирован
    Регистр.:
    31 июл 2011
    Сообщения:
    10
    Симпатии:
    15
    Перейти по ссылке

    ИЛИ CURL + preg_match
    например preg_match('#<span class="file-info-name">([^<]+)&nbsp;</span>#U',$html4,$list2);
    echo $list2[1];(Выведет содержимое ([^<]+)
    а дальше стандартными методами php делай что хочешь
     
  6. recasher2k12

    recasher2k12

    Регистр.:
    19 фев 2012
    Сообщения:
    156
    Симпатии:
    80
    Недавно столкнулся с такой же задачей, решил ее с помощью алгоритма Перейти по ссылке.
    Я переписал его на свой лад, чтобы использовать на сервере. Но код оставил на javascript, так как алгоритм лаконичен и прост. На пых думаю переписать будет проблемно...
    Вот моя версия

    PHP:
    var cheerio = require('cheerio');
     
    var 
    regexps = {
        
    unlikelyCandidates:    /copyright|combx|comment|community|disqus|extra|foot|header|menu|remark|rss|shoutbox|sidebar|sponsor|ad-break|agegate|pagination|pager|popup|tweet|twitter/i,
        
    okMaybeItsACandidate:  /and|article|body|column|main|shadow/i,
        
    positive:              /article|body|content|entry|hentry|main|page|pagination|post|text|blog|story/i,
        
    negative:              /combx|comment|com-|contact|foot|footer|footnote|masthead|media|meta|outbrain|promo|related|scroll|shoutbox|sidebar|sponsor|shopping|tags|tool|widget/i,
        
    extraneous:            /print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single/i,
        
    articleTags:          /p|div|ul|ol|li|table|tr|th|td|img|a/i
    };
     
    module.exports = function(html){
        return 
    readability(html);
    };
     
    /**
    *
    * @param {String} html
    * @param {Object} [options]
    * @return {Object}
    */
    function readability(htmloptions){
        var 
    config = {
            
    stripUnlikelyCandidatestrue
            
    weightClassestrue
        
    };
        if(
    options)
            for(var 
    option in options)
                if(
    options.hasOwnProperty(option))
                    
    config[option] = options[option];
        try{
            var $ = 
    cheerio.load(html);
        }catch(
    e){
            return {
    errortruehandlee};
        }
     
        if($(
    'frameset').length 0)
            return {
    errortruehandle: new Error('Frames are not supported')};
     
        $(
    'script').remove();
        $(
    'noscript').remove();
        $(
    'style').remove();
        
    replaceBr2P($);
     
        var 
    title getTitle($);
        var 
    contents grabContents();
     
        return {
            
    titletitle
            
    contentscontents
        
    };
     
        function 
    grabContents(){
            var 
    nodesToScore = [];
            $(
    'body *').each(function(indexnode){
                if(
    config.stripUnlikelyCandidates) {
                    var 
    unlikelyMatchString = ($(node).attr('class') ? $(node).attr('class').toString() : '')
                        + ($(
    node).attr('id') ? $(node).attr('id').toString() : '');
                    if (
    unlikelyMatchString.search(regexps.unlikelyCandidates) > -&&
                        
    unlikelyMatchString.search(regexps.okMaybeItsACandidate) == -1){
                        $(
    node).remove();
                    }
                }
                if(
    node.name == "p" || node.name == "td" || node.name == "pre")
                    
    nodesToScore.push(node);
                if(
    node.name == 'div') {
                    if($(
    node).find('p').length == 0) {
                        
    node.name 'p';
                        
    nodesToScore.push(node);
                    }
                    else{
                        
    node.children.forEach(function(el){
                            if(
    el.type == 'text' && el.data.trim().length 0)
                                $(
    el).wrap('<p />');
                        });
                    }
                }
            });
            var 
    candidates = [];
            
    nodesToScore.forEach(function(node){
                var 
    parentNode = $(node).parent()[0];
                var 
    grandParentNode = $(node).parent().parent()[0];
                var 
    innerText = $(node).text();
                if(
    parentNode.type == 'root') return;
                if(
    innerText.length 25) return;
                if(!
    parentNode.readability) {
                    
    initNode(parentNode);
                    
    candidates.push(parentNode);
                }
                if(
    grandParentNode.type != 'root' && !grandParentNode.readability) {
                    
    initNode(grandParentNode);
                    
    candidates.push(grandParentNode);
                }
                var 
    contentScore 1;
                
    contentScore += innerText.split(',').length;
                
    contentScore += Math.min(Math.floor(innerText.length 100), 3);
                
    parentNode.readability.contentScore += contentScore;
                if(
    grandParentNode.type != 'root')
                    
    grandParentNode.readability.contentScore += contentScore 2;
            });
            var 
    topCandidate null;
            
    candidates.forEach(function(node){
                
    node.readability.contentScore *= (getLinkDensity(node));
                if(!
    topCandidate || node.readability.contentScore topCandidate.readability.contentScore)
                    
    topCandidate node;
            });
            if (
    topCandidate == null || topCandidate.name == "body")
                return 
    '';
            var 
    siblingScoreThreshold Math.max(10topCandidate.readability.contentScore .2);
            var 
    contents = [];
            $(
    topCandidate).parent().children().each(function(indexsiblingNode){
                var 
    append      false;
                if(
    siblingNode === topCandidate)
                    
    append true;
                var 
    contentBonus 0;
                if($(
    siblingNode).attr('class') && $(topCandidate).attr('class'))
                    if($(
    topCandidate).attr('class') != '')
                        if($(
    siblingNode).attr('class') == $(topCandidate).attr('class'))
                            
    contentBonus += topCandidate.readability.contentScore .2;
                if(
    siblingNode.readability)
                    if((
    siblingNode.readability.contentScore contentBonus) >= siblingScoreThreshold)
                        
    append true;
                if(
    siblingNode.name == 'p') {
                    var 
    linkDensity getLinkDensity(siblingNode);
                    var 
    innerText = $(siblingNode).text();
                    if(
    innerText.length 80 && linkDensity 0.25)
                        
    append true;
                    else if(
    innerText.length 80 && linkDensity == && innerText.search(/\.( |$)/) > -1)
                        
    append true;
                }
                if(
    append) {
                    var 
    contentsAppend null;
                    if(
    siblingNode.name != 'div' && siblingNode.name != 'p') {
                        
    contentsAppend = $(siblingNode).html();
                    } else {
                        
    contentsAppend = $.html(siblingNode);
                    }
                    if(
    contentsAppend != null)
                        
    contents.push(contentsAppend);
                }
            });
            return 
    clearContents(contents.join(''));
        }
     
        function 
    initNode(node){
            
    node.readability = {contentScore0};
            switch(
    node.name) {
                case 
    'div':
                    
    node.readability.contentScore += 5;
                    break;
     
                case 
    'pre':
                case 
    'td':
                case 
    'blockquote':
                    
    node.readability.contentScore += 3;
                    break;
     
                case 
    'address':
                case 
    'ol':
                case 
    'ul':
                case 
    'dl':
                case 
    'dd':
                case 
    'dt':
                case 
    'li':
                case 
    'form':
                    
    node.readability.contentScore -= 3;
                    break;
     
                case 
    'h1':
                case 
    'h2':
                case 
    'h3':
                case 
    'h4':
                case 
    'h5':
                case 
    'h6':
                case 
    'th':
                    
    node.readability.contentScore -= 5;
                    break;
            }
            
    node.readability.contentScore += getClassWeight(node);
        }
     
        function 
    getClassWeight(node){
            if(!
    config.weightClasses)
                return 
    0;
            var 
    weight 0;
            var 
    className = $(node).attr('class');
            if(
    className) {
                if(
    className.search(regexps.negative) != -1)
                    
    weight -= 25;
                if(
    className.search(regexps.positive) != -1)
                    
    weight += 25;
            }
            var 
    id = $(node).attr('id');
            if(
    id) {
                if(
    id.search(regexps.negative) !== -1)
                    
    weight -= 25;
                if(
    id.search(regexps.positive) !== -1)
                    
    weight += 25;
            }
            return 
    weight;
        }
     
        function 
    getLinkDensity(node){
            var 
    linkLength 0;
            $(
    node).find('a').each(function(){
                
    linkLength += this.text().length;
            });
            return 
    linkLength / $(node).text().length;
        }
    }
     
    function 
    replaceBr2P($){
        var 
    brs;
        while((
    brs = $('br')).length 0) {
            var 
    parent brs.first().parent();
            var 
    html '';
            
    parent.html().split('<br>').forEach(function(part){
                if(
    part.trim().length 0)
                    
    html += '<p>' part '</p>';
            });
            
    parent.html(html);
        }
    }
     
    function 
    getTitle($){
        var 
    curTitle,origTitle;
        
    curTitle origTitle = $('title').html();
        if(
    curTitle.match(/ [\|\-]|[:]+ /)){
            
    curTitle origTitle.replace(/(.*)[\|\-]|[:]+ .*/gi,'$1');
            if(
    curTitle.split(' ').length 3)
                
    curTitle origTitle.replace(/[^\|\-:]*[\|\-]|[:]+(.*)/gi,'$1');
        }
        else if(
    curTitle.indexOf(': ') > -1){
            
    curTitle origTitle.replace(/.*:(.*)/gi'$1');
            if(
    curTitle.split(' ').length 3)
                
    curTitle origTitle.replace(/[^:]*[:](.*)/gi,'$1');
        }
        else if(
    curTitle.length 150 || curTitle.length 15){
            var 
    h1 = $('h1');
            if(
    h1.length == 1)
                
    curTitle h1.first().html();
        }
        
    curTitle curTitle.replace(/^\s+|\s+$/g'');
        if(
    curTitle.split(' ').length <= 4) {
            
    curTitle origTitle;
        }
        return 
    curTitle;
    }
     
    function 
    clearContents(contents){
        var $ = 
    cheerio.load(contents);
        $(
    'a[rel=nofollow]').remove();
        $(
    'a').filter(function(){
            var 
    href = $(this).attr('href');
            return 
    href ? (href.trim().length == 0) : true;
        }).
    remove();
        $(
    '*').each(function(indexnode){
            var 
    newAttribsattrName;
            if(
    node.name.search(regexps.articleTags) > -1) {
                if(
    node.name == 'img') {
                    
    newAttribs = {};
                    for(
    attrName in node.attribs)
                        if(
    node.attribs.hasOwnProperty(attrName))
                            if(
    attrName.search(/src|alt|title/i) > -1)
                                
    newAttribs[attrName] = node.attribs[attrName];
                    
    node.attribs newAttribs;
                } else if(
    node.name == 'a'){
                    
    newAttribs = {};
                    for(
    attrName in node.attribs)
                        if(
    node.attribs.hasOwnProperty(attrName))
                            if(
    attrName.search(/href|alt|title/i) > -1)
                                
    newAttribs[attrName] = node.attribs[attrName];
                    
    node.attribs newAttribs;
                } else {
                    
    node.attribs = {};
                }
            } else {
                $(
    node).replaceWith($(this.html()));
            }
        });
     
        var 
    render = $.html();
     
        
    render render.replace(/<!--(.*?)-->/g'')
            .
    replace(/>[\s]+</g'><')
            .
    replace(/&nbsp;/gi'')
            .
    replace(/[\s]+/gi' ')
            .
    replace(/<fb:like><[/]fb:like>/gi'');
        while(
    render.search(/<li>[\s]*<[/]li>/i) > -1)
            
    render render.replace(/<li>[\s]*<\/li>/gi'');
        while(
    render.search(/<[uo]l>[\s]*<[/][uo]l>/i) > -1)
            
    render render.replace(/<[uo]l>[\s]*<\/[uo]l>/gi'');
        while(
    render.search(/<p>[\s]*<[/]p>/i) > -1)
            
    render render.replace(/<p>[\s]*<[/]p>/gi'');
     
        return 
    render;
    }
     
  7. cjcat

    cjcat Создатель

    Регистр.:
    20 дек 2012
    Сообщения:
    31
    Симпатии:
    1
    если заранее известен формат страницы, ну или они из одного сайта, то все очень просто. смотри в сторону phpQuery(или Simple HTML DOM Parser) + cURL + регулярки. Я в парсерах юзаю все. если текст лежит в одном диве на пример <div class="content"> %text% </div> - то с помощью той же пхпКвери можно взять только этот див, почистить его, написать правила для таблиц и тд и тп. Если страничек много, то я бы посоветовал все это закешировать на локальный комп, чтоб не тянуть каждую с инета. Курл поддерживает проксю, её тож желательно юзать, если страниц много.
    Короче - дофига "если")) Дайте поконкретней пример, дам поконкретней ответ.
     
  8. Ranger_Hunter

    Ranger_Hunter Постоялец

    Заблокирован
    Регистр.:
    20 апр 2009
    Сообщения:
    127
    Симпатии:
    48
    Здесь интересная статья по теме:


    в частности:


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