Меня вдохновила статья рейтинг постов хаба, и я решил сделать похожую, но составить уже рейтинг самих хабов.
В первой половине статьи я представлю вам рейтинги хабов по постам и подписчикам, а также небольшой их анализ. А во второй — подробно распишу, как я на Java с помощью библиотеки JSoup парсил HTML страницы хабры, с какими интересными явлениями и проблемами столкнулся. И в конце статьи выложу полный исходный код программы.
Когда я отсортировал хабы, обнаружились интересные вещи. Например я не знал, что существуют хабы с нулевым количеством постов. А их оказалось целых 4 штуки! Причём на каждый из них подписано более 500 человек. Наверно это очень перспективные направления — не имея постов на них уже набежало столько народу. У хаба MySpace нету ни одного поста/события, зато более 55К подписчиков — это о чём-то говорит!
Тройка хабов — Чулан, Я пиарюсь и Веб-разработка — лидируют как по количеству постов, так и по количеству читателей. И в этом нет ничего удивительного — первые два хаба весьма общие и свободные, а уж веб-разработка интересна многим. Далее идёт Информационная безопасность, которая пользуется на хабре бешеной популярностью. За ней — Google, которую можно считать компанией №1 в области IT — по ней больше всего постов, по сравнению с другими. Отчасти благодаря её ОС Android, которой выпало 4 место по количеству подписчиков.
К сожалению, я так и не понял, почему хаб Хабрахабр — оффтопик. По количеству постов он будет на 13 месте, да и подписчиков у него >80К. Получается, что писать на сайте об этом же сайте — отход от темы?
Огорчило, что хаб Java находится не так высоко, как хотелось бы.
Очень долго я пытался разобраться в Хабрахабр Api. Как выяснилось, он закрыт и пока что в процессе разработки. Однако в переписке с support@habrahabr.ru мне сказали, что они не имеют ничего против парсинга их страниц. Собственно, именно так и работают хабраклиенты для Android (на данный момент).
Когда речь идёт о проектах «для себя», я выбираю любимую Джаву. Она и на этот раз меня не подвела — библиотека JSoup позволила в несколько строчек получить необходимые данные с HTML страницы. Но давайте сперва обсудим, как устроены хабы.
Страницы с хабами расположены по адресам http://ift.tt/1eHJyfS, где N — номер от 1 и далее. Посему, если мы хотим получить полный список из всех хабов — нам нужно загружать и анализировать эти страницы, пока они не закончатся. На каждой странице присутствует список из хабов. Формат элемента списка довольно простой и легко анализируется. Выглядит он так:
<div class="hub " id="hub_50">
<div class="habraindex">1 280,58</div>
<div class="info">
<div class="title">
<a href="http://ift.tt/1kOHAit">Информационная безопасность</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://ift.tt/1lcYUdX" class="members_count">91741 подписчик</a>, <a href="http://ift.tt/1lcYUdZ">3385 постов</a><a></a></div><a>
</a></div><a>
</a></div>
Давайте напишем метод, который возвращает нам список из всех хабов на сайте:
static List<Hub> getAllHubs() {
ArrayList<Hub> fullHubsList = new ArrayList<>();
String urlHubsIncomplete = "http://ift.tt/1eHJAEv";
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://ift.tt/1kOHAit">Информационная безопасность</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
This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий