Как стать автором
Обновить

Рейтинг хабов и компаний по постам/подписчикам

Время на прочтение8 мин
Количество просмотров14K
На данный момент на хабре около 350 хабов. Функционал сайта позволяет сортировать их по имени и по индексу. А по другим параметрам — например по количеству постов — нет, а хотелось бы.

Меня вдохновила статья рейтинг постов хаба, и я решил сделать похожую, но составить уже рейтинг самих хабов.

В первой половине статьи я представлю вам рейтинги хабов и компаний, а также небольшой их анализ. А во второй — подробно распишу, как я на Java с помощью библиотеки JSoup парсил HTML страницы хабры, с какими интересными явлениями и проблемами столкнулся. И в конце статьи выложу полный исходный код программы.



Все 4 рейтинга (полные) в виде веб-страницы

Рейтинг хабов


Когда я отсортировал хабы, обнаружились интересные вещи. Например я не знал, что существуют хабы с нулевым количеством постов. А их оказалось целых 4 штуки! Причём на каждый из них подписано более 500 человек.

Тройка хабов — Чулан, Я пиарюсь и Веб-разработка — лидируют как по количеству постов, так и по количеству читателей. Чулан на 1 месте потому, что туда администрация удаляет статьи. Далее идёт Информационная безопасность, которая пользуется на хабре бешеной популярностью.

К сожалению, я так и не понял, почему хаб Хабрахабр — оффтопик. По количеству постов он будет на 13 месте, да и подписчиков у него >80К. Получается, что писать на сайте об этом же сайте — отход от темы?

Огорчило, что хаб Java находится не так высоко, как хотелось бы.

Рейтинг компаний

Хотя изначально я планировал построить рейтинг только для хабов, в комментариях к статье выдвинули хорошую идею — сделать тоже самое и для компаний. Код при этом сильно менять не пришлось.

Компаний очень много — 1343. Поэтому я выложу только ТОП-30 и последние 10 компаний. Вот ведь интересный момент — хабра почему-то показывает Все (1331), хотя моя программа насчитала их 1343 — и, собственно, это правильно. Если вручную их сосчитать — умножить количество страниц 67 на 20 компаний да ещё 3 — получается 1343.


Для начала меня удивил тот факт, что есть 2 вида отсутствия компании — «компания деактивирована» и «страница не найдена». Хотя повторюсь — все компании были взяты из списка. Первый вид я помечал количеством постов -2. Таких компаний довольно много. И три компании, название которых состоит из цифр — ведут на «страница не найдена». Их я пометил -3. Такие вот дела. Также полно компаний с нулевым количеством постов — например Apple. Интересно, зачем создавать аккаунт для компании и вообще не писать с него?

Собственно, если из тех 1343, зарегистрированных на хабре, вычеркнуть несуществующие и компании без постов, то останется всего 321. Такие вот дела.

Разработка

Очень долго я пытался разобраться в Хабрахабр Api. Как выяснилось, он закрыт и пока что в процессе разработки. Однако в переписке с support@habrahabr.ru мне сказали, что они не имеют ничего против парсинга их страниц. Собственно, именно так и работают хабраклиенты для Android (на данный момент).

Когда речь идёт о проектах «для себя», я выбираю любимую Джаву. Она и на этот раз меня не подвела — библиотека JSoup позволила в несколько строчек получить необходимые данные с HTML страницы. Но давайте сперва обсудим, как устроены хабы.

Страницы с хабами расположены по адресам habrahabr.ru/hubs/pageN/, где N — номер от 1 и далее. Посему, если мы хотим получить полный список из всех хабов — нам нужно загружать и анализировать эти страницы, пока они не закончатся. На каждой странице присутствует список из хабов. Формат элемента списка довольно простой и легко анализируется. Выглядит он так:
<div class="hub " id="hub_50">
	<div class="habraindex">1 280,58</div>
	<div class="info">
		<div class="title">
			<a href="http://habrahabr.ru/hub/infosecurity/">Информационная безопасность</a>
			<span class="profiled_hub" title="Профильный хаб"></span>
			
		</div>
			<div class="buttons">
				<input type="button" class="mini blue  subscribeHub" value="Подписаться" data-id="50">
				<input type="button" class="mini hidden unsubscribeHub" value="Подписан" data-id="50" "="">
			</div>
		<div class="clear"></div>
		<div class="stat"><a href="http://habrahabr.ru/hub/infosecurity/subscribers/" class="members_count">91741 подписчик</a>, <a href="http://habrahabr.ru/hub/infosecurity/posts/">3385 постов</a><a></a></div><a>
	</a></div><a>
</a></div>


Давайте напишем метод, который возвращает нам список из всех хабов на сайте:
static List<Hub> getAllHubs() {
        ArrayList<Hub> fullHubsList = new ArrayList<>();
        String urlHubsIncomplete = "http://habrahabr.ru/hubs/page";
        int pageNum = 1;

        do {
            String urlHubs = urlHubsIncomplete + pageNum;

            try {
                Document doc = Jsoup.connect(urlHubs).get();
                Elements hubs = doc.select(".hub");
                if (hubs.size() == 0) {
                    break;
                }
                for (Element hubElem : hubs) {
                    Hub hub = new Hub(hubElem);
                    fullHubsList.add(hub);
                }

                pageNum++;
            } catch (Exception e) {
                e.printStackTrace();
                break;
            }

        } while (true);

        return fullHubsList;
    }

Мы крутим бесконечный цикл while, формируя с каждой итерацией новый URL. Затем, с помощью Jsoup.connect(urlHubs).get() получаем непосредственно HTML-документ со списком хабов и их параметрами. Как несложно заметить — div с информацией о хабе имеет класс hub — и, вызвав doc.select(".hub"), мы получаем список из этих элементов. Если его размер равен нулю — значит мы прошли последнюю страницу и уже проанализировали все хабы — тогда мы выходим из цикла.

Далее — проходим по всем хабам-элементам и для каждого создаём объект типа Hub, передав в конструктор наш org.jsoup.nodes.Element. В нём располагается HTML-код такого же формата, как указан выше. Теперь давайте абстрагируемся от всего. Для этого и существует ООП. Перед нами есть только тот кусочек HTML, представленный выше, и класс, в который его нужно запихнуть. Напишем каркас для нашего класса:
import org.jsoup.nodes.Element;

public class Hub {
    String title;
    int posts;
    boolean profiled;
    int membersCount;
    float habraindex;
    String url;

    public Hub(Element hubElem) {
       
    }
}

Напишем конструктор. Для начала сделаем самое простое — получим данные из заголовочного тега. Для этого мы сначала извлекаем сам div вида
<div class="title">
    <a href="http://habrahabr.ru/hub/infosecurity/">Информационная безопасность</a>
    <span class="profiled_hub" title="Профильный хаб"></span>
</div>

Парсим через
Element titleDiv = hubElem.select(".title").get(0);
Element tagA = titleDiv.getElementsByTag("a").get(0);
title = tagA.text();
url = tagA.attr("href");
profiled = (hubElem.select(".profiled_hub").size() != 0);

Далее, мы хотим пропарсить количество подписчиков и постов — собственно те параметры, по которым мы и будем сортировать. Но сразу же сталкиваемся с первой проблемой — тег содержит строку «91741 подписчик», которую мы не можем просто так взять и преобразовать в Integer — она содержит буквы! Тут нам на помощь приходят регулярные выражения. Быстренько пишем ловкий метод, который получает строку и вырезает из неё всё, кроме цифр, да ещё и преобразует результат в int. \D — это НЕ цифра, а + — «встречается 1 или более раз». Т.е. мы в данном случае заменяем буквы на пустоту.
private int getNumbers(String str) {             
    String numbers = str.replaceAll("\\D+", ""); 
    return Integer.valueOf(numbers);             
}

Вот теперь мы уже можем со спокойной душой получить наши значения:
String membersCountFullStr = hubElem.select(".members_count").get(0).text();
membersCount = getNumbers(membersCountFullStr);

String statFullStr = hubElem.select(".stat").get(0).getAllElements().get(2).text();
posts = getNumbers(statFullStr);

В принципе, на этом можно было остановится, но я решил ради интереса извлечь всю возможную информацию о хабе. Тут возникла весьма интересная вторая проблема, которая будет изюминкой статьи. Как пропарсить хабраиндекс?

Для начала, следует заменить запятую на точку и убрать лишние пробелы. Но этого не достаточно! Парсер всё равно выдаёт ошибку, если скопировать и вставить хабраиндекс в код — Double.valueOf("–1.11"). А если ввести вручную то же самое число — всё окей. Причём визуально в моей IDEA они абсолютно идентично выглядят!

Оказывается, дизайнеры хабры просто использовали dash вместо minus — c иным кодом символа, и парсер его, понятное дело, не ест. Возьмите на заметку. Суть проблемы в следующем:
System.out.println((int)'-');//45
System.out.println((int)'–');//8211

Когда-то в своей статье Хитрые задачи по Java я рассмотрел подвох, когда L маленькую можно не отличить от 1. Собственно, сейчас я напоролся на аналогичную проблему.

Посему, код для извлечения хабраиндекса будет чуть сложнее:
String rawHabraIndex = hubElem.select(".habraindex").get(0).text();//1 265,92
char minus = 45;//'-'
char dash = 8211;//'–'
String niceHabraIndex = rawHabraIndex.replaceAll(" ", "").replace(",", ".").replace(dash,minus);//1266.72
habraindex = Float.valueOf(niceHabraIndex);

Далее, пишем компаратор по постам как вложенный статический класс для Hub
public static class ComparePosts implements Comparator<Hub> { 
    @Override                                                 
    public int compare(Hub o1, Hub o2) {                      
        return o2.posts - o1.posts;                           
    }                                                         
}                                      

И сортируем по нему где-нибудь в main
List<Hub> hubs = getAllHubs();                 
Collections.sort(hubs, new Hub.ComparePosts());

Всё, задача выполнена! С количеством подписчиков аналогично. Далее я написал код, который выводит в консоль два списка в таком виде, чтобы их сразу можно было вставить в статью — и сделал это вначале.

На получение всех хабов уходит примерно 10 секунд. Исходный код можно скачать здесь. Собираем и запускаем вот так, не забыв установить Jsoup и заменить путь на ваш:
javac -cp .;"C:\prog\lib\jsoup-1.7.3.jar" com/kciray/habrahubs/Main.java
java -cp .;"C:\prog\lib\jsoup-1.7.3.jar" com.kciray.habrahubs.Main

Кроме того, я переделал эти же классы для сбора статистики по компаниям. Там, казалось бы, всё аналогично — однако чтобы узнать количество постов в блоге компании, пришлось подгружать страничку для каждой по отдельности — а это заняло уже около 5 минут. Я сделал многопоточную загрузку для ускорения. Обнаружил, что хабра не позволяет грузить больше чем 5-7 страниц одновременно. Собственно, сериализовал ArrayList<CompanyBlog> и записал. Этот файлик на 100 килобайт лежит вместе с вторыми исходниками — можете поработать с ним.

Если вас интересует полный рейтинг и в более компактном виде — я выложил его в виде веб-страницы.
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+39
Комментарии14

Публикации

Истории

Работа

Java разработчик
358 вакансий

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн