PHP

индекс
206,80

Строим график по статистике хаброюзера

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

В скрипте используются:
  • XPath
  • Регулярные выражения
  • Google Charts


Итак, привожу класс который обрабатывает страницы хабра с комментариями пользователя:

class Parser
{
  
  private $_sumPages;
  private $_username;
  private $_myMarks;
  private $_uniqName;
  
  public function __construct()
  {
    $this->_uniqName = time().rand().".png";
  }
  
  public function setUsername($un)
  {
    $this->_username = $un;
    return $this;
  }

  public function getNumPages()
  {
    if(!empty($this->_username))
    {
      $page = file_get_contents("http://".$this->_username.".habrahabr.ru/comments");
      $dom = new DOMDocument();
      $dom->preserveWhiteSpace = false;
      $dom->strictErrorChecking = false;
      $dom->recover = true;
      $dom->loadHTML($page);      
    }
    else
    {
      return false;
    }

    $xpath = new DOMXPath($dom);
    $pages = $xpath->query('//ul[@id="nav-pages"]/li[last()]//a/@href');
    foreach($pages as $page)
    {
      $lastPage = $page->nodeValue;
      preg_match('/([0-9]+)/is', $lastPage, $sumPages);
      $this->_sumPages = $sumPages[0];
    }
    if(empty($this->_sumPages))
    {
      $this->_sumPages = 1;
    }
    
    return $this;
  }




В конструкторе создаем уникальное имя для текущей диаграммы. Метод setUsername задает свойства $_username чтобы в дальнейшем класс мог его использовать. Метод getNumPages получает страничку с комментариями пользователя имя которого хранится в $_username, создает объект DOMDocument в который передается контент загруженной странички. Далее происходит XPath запрос который возвращает объект типа DOMNodeList, и с помощью итератора происходит перебор полученных элементов(на самом деле элемент только один) и на выходе получаем URL последней странички с комментариями в виде /comments/page7/, через рег.выр. вытаскиваем число — номер последней страницы и сохраняем в переменной $this->_sumPages.

public function getAllMarks()
  {
    $marks = array();
    if($this->_sumPages != 0)
    {
      for($i = 1; $i <= $this->_sumPages; $i++)
      {
        $page = file_get_contents("http://".$this->_username.".habrahabr.ru/comments/page".$i."/");
        $dom = new DOMDocument();
        $dom->preserveWhiteSpace = false;
        $dom->strictErrorChecking = false;
        $dom->recover = true;
        $dom->loadHTML($page);
        $xpath = new DOMXPath($dom);
        $marks = $xpath->query('//ul[@class="vote"]/li');
//        $myMarks = array();
        foreach($marks as $mark)
        {
          $newMark = str_replace("–", "-", $mark->nodeValue);
          if($newMark <= -5)
          {
            $myMarks["<=%20-5"]++;
          }
          else
          {
            if($newMark < 0)
            {
              $myMarks[">%20-5%20and%20<%200"]++;
            }
            else
            {
              if($newMark > 0 && $newMark <= 5)
              {
                $myMarks[">%200%20and%20<=%205"]++;
              }
              else
              {
                if($newMark > 5)
                {
                  $myMarks[">%205"]++;
                }
                else
                {
                  $myMarks["0"]++;
                }
              }
            }
          }          
        }
      }
      $this->_myMarks = $myMarks;
      return $this;
    }
  }




Этот метод идет по всем страницам, количество которых мы получили с помощью метода getNumPages, и XPath запросом '//ul[@class=«vote»]/li' вытаскивает оценки за каждый комментарий который сделал пользователь. Далее все оценки пишутся в хэш и ключами которого являются диапазоны в которые эти оценки входят. Пробелы заменены на %20 потому что эти ключи будут участвовать в формировании URL'а. На выходе мы получаем хэш где ключи это диапазоны, а значения — количество оценок которые входят в этот диапазон.

public function getAllCats()
  {
    if($this->_sumPages != 0)
    {
      $allCats = array();
      for($i = 1; $i <= $this->_sumPages; $i++)
      {
        $page = file_get_contents("http://".$this->_username.".habrahabr.ru/comments/page".$i."/");
        $dom = new DOMDocument();
        $dom->preserveWhiteSpace = false;
        $dom->strictErrorChecking = false;
        $dom->recover = true;
        $dom->loadHTML($page);
        $xpath = new DOMXPath($dom);
        $cats = $xpath->query("//a[@class='where']");
        foreach($cats as $cat)
        {
          $allCats[] = $cat->nodeValue;
        }
      }
      $newCats = array();
      $sumCats = count($allCats);
      foreach($allCats as $cat)
      {
        $newCats[$cat]++;
      }
      foreach($newCats as $key => $value)
      {
        $newCats[$key] = $value/$sumCats*100;
        if($newCats[$key] < 5)
        {
          $newCats["Другие разделы"] += $newCats[$key];
          unset($newCats[$key]);
        }
      }
      $this->_myCats = $newCats;
      return $this;
    }
  }




Этот метод схож с предыдущим методом, только здесь другим XPath запросом вытаскиваются все блоги в которые комментировал пользователь, далее создается хэш в котором в качестве ключей выступают блоги, а значение — количество комментариев в этот раздел, потом происходит проверка сколько процентов от общего количества комментариев приходится на текущий блог и если <5%, то этот блог удаляется их хэша а его процент прибавляется к «Другим блогам», так сделано потому что если у пользователя огромное количество комментариев на диаграмме получится каша, а цель у нас получить основные блоги куда комментировал пользователь. В конце получаем хэш с блогами в качестве ключа и процентом комментариев в качестве значения.

public function getChart($type)
  {
    switch($type)
    {
      case "marks":
        $strMarks = join(",", $this->_myMarks);
        $strKeys = join("|", array_keys($this->_myMarks));
        break;
      case "cats":
        $keys = array_keys($this->_myCats);
        foreach($keys as $key => $value)
        {
          $keys[$key] = str_replace(" ", "%20", $keys[$key]);
        }
        $strMarks = join(",", $this->_myCats);
        $strKeys = join("|", $keys);
        break;
    }
    $chart = file_get_contents("http://chart.apis.google.com/chart?cht=p3&chd=t:".$strMarks."&chs=850x300&chl=".$strKeys);
    if($f = file_put_contents("charts/".$this->_uniqName, $chart))
    {
      return($this->_uniqName);
    }
  }
}




Последний метод обрабатывает хэш, в зависимости от того диаграмму чего мы хотим получить мы обрабатываем разные хэши. Обрабатываем их для того чтобы можно было подставить их значения и ключи в запрос к Google Charts. Далее делаем запрос к Google Charts и получаем PNG картинку, и в конце пишем ее на диск с именем которое было сгенерировано в конструкторе класса. Вот собственно и весь класс, используется он так:

$parser = new Parser();
print $parser->setUsername('username')->getNumPages()->getAllMarks()->getChart("marks"); //получаем диаграмму пользователя username с оценками его комментариев.

$parser2 = new Parser();
print $parser2->setUsername('username')->getNumPages()->getAllCats()->getChart("cats"); //получаем диаграмму пользователя username с его комментариями в блоги.



Работающая версия есть тут: habr.zakatnov.ru
Скачать исходники с уже прикрученной мордой можно тут: habr.zakatnov.ru/parser.rar

P.S.:
Почитать о XPath можно тут — ru.wikipedia.org/wiki/Xpath
Почитать о Google Charts можно тут — code.google.com/intl/ru-RU/apis/charttools/

P.P.S:
Это мой первый пост на хабр, не судите строго, жду объективной критики.

UPDATE:
Обновил немного код, теперь диаграммы отдаются в таком виде:
Диаграмма

Справа теперь появилась легенда, в оценках комментариев это количество комментариев в каждом диапазоне. В блогах процентное отношение количества комментариев по блогам.
+39
16 марта 2010, 16:34
30

комментарии (41)

+5
bondbig #
оригинальный индикатор загрузки )
Диаграмму можно и поцветастей сделать, на мой вкус.
+1
homm #
Что-то ничего кроме индикатора не происходит, Opera 10.5.
+1
Maximuzzz #
Opera 10.50 — всё происходит, но ждать относительно долго приходится.
–3
bondbig #
у меня в ФФ плагин noscript блокировал скрипты от googleapps, без них не работает. Может и с Оперой похожая проблема?
0
Zaakk #
Там как бы нет скриптов от googleapps, все происходит на стороне сервера, вы получаете просто картинку.
0
bondbig #
ну не знаю, у меня не заработало без этого.
+1
Isis #
Не сразу увидел вообще что-то крмое пустой странице в намороке.
Индикатор загрузки не похож на индикатор. Т.к. я ждал 2 минуты, то подумал бы что что-то не работает и это просто картинка
+1
khizhaster #
Как-то совсем уж мало: i41.tinypic.com/yyot5.jpg
0
Zaakk #
Вообще странно, сейчас нет времени проверить, приду домой, проверю. Но вообще если часть «Другие блоги» большая это говорит о том что очень большой разброс комментариев по блогам и выделить удалось только блог «Линукс для всех», комментарии в остальные блоги составляли < 5%.
+1
Vass #
чот фигня какая то, первый раз:

второй

+2
Zubchick #
Аналогично второму :)

Сервис интересен, но требует допила!
0
Zubchick #
А блин, дошло… Второй график это плюсики-минусики за комментарии пользователя кажется…
0
Zaakk #
ну да, там же две кнопки, оценки за комментарии и процентаж сообщений по блогам :)
0
Zubchick #
Дак вот так сразу и не разберешься… Вы бы приписали чего да как +)
0
Zaakk #
это разные диаграммы :)
НЛО прилетело и опубликовало эту надпись здесь
0
Vass #
Ага, вот ты и вычислен :)
0
kibizoidus #
Интересно, а как сами пользователи Хабра отнесутся к сбору персональной информации о них? ^_^
+4
Bzzz #
хабраэффект такой хабраэффект
0
kuzvac #
Мне даже значок индикатора не показывает:)
+3
huze #
Спасибо за рекламу =)
0
taliban #
есть замечательный тег pre замените все свои теги code в топике на него, код стазу станет красивым и шелковистым
0
Agent_Smith #
А еще есть замечательный Source Code Highlighter а так же HabraEditor
0
ruzzz #
Расскажите потом что хостер сказал :). Возле кнопки «опубликовать» явно пора разместить ссылку на Coral CDN.
0
betal #
В данном случае CDN не поможет, он же не сможет получить доступ к php коду и сам его выполнить :)
0
Zaakk #
это VDS :) им все равно пока я свой трафик не съем :)
0
deniamnet #
что-то habr.zakatnov.ru лег
хабраволна!
0
betal #
А вы чего хотели??))

for($i = 1; $i <= $this->_sumPages; $i++)
{
$page = file_get_contents(«http://».$this->_username.".habrahabr.ru/comments/page".$i."/");
+3
Bzzz #
Хотели красивые, разноцветные, информационные графики…

а получили « Страница не найдена – ошибка подключения. Ошибка. Ссылка не работает. Перезагрузить эту страницу позже. »
0
Zaakk #
я просто выложил ознакомительную версию :)
и да, как еще можно обойти все страницы с комментариями пользователя? :)
0
z0rg #
хабраэффект :(
0
mecommayou #
Подсвети и отформатирую код.
0
Zaakk #
если б я знал как подсветить, тут есть один тег — code и все, и то, то ты видишь это работа этого тега :(
0
mecommayou #
Гугль наше все: source.virtser.net/, выбирай синтаксис C# и копируй полученный HTML в статью.
0
mecommayou #
Для совершенства не хватает временной диаграммы, данные получить для ее создания не составляет труда.
0
Zaakk #
Можно поподробнее?
+2
mecommayou #
На примере твоего комментария:
<li id="comment_2635788" class="comment_holder vote_holder">
  <div class="msg-meta new-reply">
    <div class="folding-dot-holder">
      <div class="folding-dot"></div>
    </div>
    <ul class="menu info author hcard">
      <li class="avatar">
        <a href="http://Zaakk.habrahabr.ru/" title="Zaakk">
          <img src="http://habrahabr.ru/media/thumb/2d/d0/07/30644/30644_24x24.jpg" alt="Zaakk" />
          <strong></strong>
        </a>
      </li>
      <li class="fn nickname username">
        <a href="http://Zaakk.habrahabr.ru/" class="url">Zaakk</a>
      </li>
      <li class="date">
        <abbr class="published" title="2010-03-18T00:52:37+03:00">18 марта 2010, 00:52</abbr>
      </li>
      <li class="bookmark">
        <a href="#comment_2635788" title="Ссылка на комментарий" rel="bookmark">#</a>
      </li>
      <li class="to-favs js-to_favs_holder">
        <a href="#" class="js-to_favs_add" onclick="favsHandler.favoritesSend(this, 'comments', 2635788); return false;" title="Добавить в избранное"></a>
      </li>
      <li class="up-to-parent"><a title="Ответ на" onclick="return commentForm.goToParentComment(this);" href="#comment_2630922"></a></li>
      <li class="down-to-child hidden"><a title="Обратно" onclick="return commentForm.goToChildComment(this);" href="#"></a></li>
      <!--<li class="single-tree">
      <a class="js-serv js-single-tree" href='#comment_2630922'>ветка</a>
      <a class="js-serv js-multiplay-tree" href="#comment_2635788">восстановить</a>
      </li>-->
      <li>
        <ul class="vote voting ">
          <li class="mark"><span>0</span></li>
          <li class="buttons">
            <a rev="voter-for-comment:2635788" class="vote_minus vote-for-comment" title="Плохой комментарий"></a>
            <a rev="voter-for-comment:2635788" class="vote_plus vote-for-comment" title="Хороший комментарий"></a>
          </li>
        </ul>
      </li>
    </ul>
  </div>
  <div class="entry-content">
    <div class="entry-content-only">
      Можно поподробнее?
    </div>
    <p class="reply"><a href="?reply_to=2635788#comment_2635788" onclick="commentForm.moveForm('reply_form_2635788'); return false;">ответить</a></p>
  </div>
  <div id="reply_form_2635788" class="reply_form">
  </div>
</li>

* This source code was highlighted with Source Code Highlighter.

Имеем title=«2010-03-18T00:52:37+03:00» что является датой в стандарте ISO 8601, на основании этого можно определить время наибольшей комментируемости пользователя в 24-ом формате например…
0
Zaakk #
хм, круто же, спасибо, возьму на заметку
+1
hr0nix #
There are three kinds of lies: lies, damned lies, and statistics.
0
Ierixon #
когда скопипастил, немного криво (с пробелом), ник — ничего не показало
добавьте нечто похожее на trim()
0
Zaakk #
спасибо

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.