Скачиваем стикеры VK на Java и с кучей потоков |GrakovNe - Пробуя, создавать лучшее

hello


Времени затрачено: 0,240 сек.
SQL запросов: 24
ОЗУ использовано: 4 MB
Войти как администратор

Скачиваем стикеры VK на Java и с кучей потоков

Бесплатных стикеров всем!

На самом деле, с детства я очень любил бросаться смайликами: и эмоцию передал и писать долго нечего. Так что, когда в одной синей-социальной-сети подуло модой на стикеры, я был очень доволен. Котик, щенок, кто там еще… А кстати, кто там еще? Давайте посмотрим!

Куда смотрим?

Во Вконтакте конечно — больше всего симпатичных стикеров именно там, даже в мой любимый телеграм, они обычно мигрируют именно оттуда. Первое, что приходит в голову — открыть переписку с кем-нибудь особенно вредным и начать отправлять ему стикеры по очереди. Правда, если бы кто-то решил такое поотправлять мне, дальше второго набора он бы уйти не смог — черный список вот он, рядом. А потом — многие наборы стикеров у ВК платные, а нам только посмотреть. Так что давайте вспомним, что мы с вами приличные программисты и что-нибудь наваяем

Что пишем?

Давайте для начала напишем маленькую библиотечку, которая будет сливать стикеры на жесткий диск в виде картинок, да еще и как можно быстрее. Писать будем на Java (а я не говорил, что пересел на него? Теперь знаете), так что попытаемся сделать код не совсем ужасным.

Где лежат стикеры?

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

Все стикеры можно получить по ссылочке вида:

http://vk.com/images/stickers/[Number]/[Resolution].png

где Number и Resolution — обычные числа, причем разрешений для одного стикера поддерживается целая куча: от мелких 128*128 до вполне себе приличных 512*512.

Например, по ссылочке http://vk.com/images/stickers/90/512.png лежит очень милый котик.

Значит все, что нам придется сделать — научиться формировать целую кучу ссылок и скачивать по ним картиночки. Поехали!

Формируем ссылку

Раз уж мы решили писать говнокод библиотечку, давайте засунем все ее методы в отдельный класс, который назовем Grabber, и определим там кучу значений

private static String URL = "http://vk.com/images/stickers/";
int resolution = 512;
private static String URL_DELIMITER = "/";
private static String FILE_EXTENSION = ".png";

private String workingDirectory = "stickers";

Ну вот. Теперь для разминки напишем простой-препростой метод для получения ссылки на стикер по его номеру и разрешению:

public URL getURL(int stickNumber, int resolution) {
    StringBuilder builder = new StringBuilder();
    builder
            .append(URL)
            .append(String.valueOf(stickNumber))
            .append(URL_DELIMITER)
            .append("512")
            .append(FILE_EXTENSION);

    try {
        return new URL(builder.toString());
    } catch (MalformedURLException e) {
        throw new GrabberException("URL can't be created");
    }
}

Да все просто: завели билдер, напихали туда кучу всякого и отдали пользователю ссылку, если все получилось. Ну, или плюнули исключением, если пользователь выдал нам какие-то совсем страшные значения, мало ли. Это было просто и даже не интересно.

А сколько всего стикеров?

Хороший вопрос. Все стикеры пронумерованы от 1 до N, что наводит на размышления о том, что они когда-нибудь да закончатся. Не верите? Смотрите — по запросу http://vk.com/images/stickers/10000/512.png получаем вполне себе нормальную 404-ю ошибку.

Значит — нам придется найти максимальный номер стикера, который еще существует.

Впрочем, начнем с малого: будем проверять доступен ли нам стикер с нужным нам номером:

public boolean isStickerAvailable(int stickNumber) {
    int resultCode = 0;
    try {
        HttpURLConnection connection = (HttpURLConnection) getURL(stickNumber).openConnection();
        connection.setRequestMethod("GET");
        connection.connect();
        resultCode = connection.getResponseCode();
    } catch (IOException ex) {
        throw new GrabberException("Cant access network: " + ex.getMessage() + " " + getURL(stickNumber));
    }

    return (resultCode == 200);
}

Ага, формируем ссылочку, стучимся на сервер, спрашиваем, есть ли кто-нибудь дома и если стикер нашелся — выдаем true. Ну или false, если случилась какая-то бяка.

Теперь, хочется сотворить что-нибудь вида

public int countStickers() {
    int stickNumber = 1;
    while (isStickerAvailable(stickNumber)){
        stickNumber++;
    }
    return stickNumber;
}

Только на самом деле это Страшно Плохая И Ужасная Идея. Почему? Ну, сегодня с утра, у Вконтакте есть примерно 3 с копейками тысячи стикеров. Если запустить то, что мы с вами только что написали, придется сделать три тысячи запросов. А если на каждый будет уходить хотя бы по полсекунды… В общем, к обеду успеем. Давайте быстрее, а?

Сотрем вот этот кошмар и сделаем сначала вот что:

int i = 1;
final int JUMP_RANGE = 1000;
for (i = 1; isStickerAvailable(i); i += JUMP_RANGE) ;

int topEdge = i - 1;
int bottomEdge = topEdge - JUMP_RANGE;
int average = (topEdge + bottomEdge) / 2;

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

А теперь, сделаем вот чего:

while (topEdge - bottomEdge > 1) {
    if (isStickerAvailable(average)) {
        bottomEdge = average;
    } else {
        topEdge = average;
    }

    average = (topEdge + bottomEdge) / 2;
}

return averange;

Ага, бинарный поиск. Так, за минимальное количество запросов мы абсолютно точно выясним, какой стикер на сегодня последний. Запустим? Ага! На второе июля у ВК есть аж 3144 стикера. Осталось их спереть скачать!

Сливаем стикеры

Давайте напишем метод, который будет скачивать нам один-единственный, зато любой стикер? Можно сделать по-честному: получить ответ сервера, вытащить тело ответа, перелить все эти байты в файлик на диске… А можно — прикинуться нормальным Java — разработчиком и запользовать уже готовую либу, которая будет нам делать все то же самое, только сама. Какую? Apache Commons IO конечно. Давайте добавим ее в список зависимостей?

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

Ну вот. Почему ее? Потому что, там есть один классный метод, который позволяет сохранить абсолютно любой файл по абсолютно любой ссылке — достаточно просто передать ему парочку аргументов и — готово. А еще, эта библиотека сама умеет создавать несуществующие папки, перезаписывать существующие файлы, разбираться с правами доступа и варить кофе — то, что надо. Так что берем и пишем:

public File getSticker(int stickNumber, int resolution) {
    URL url = getURL(stickNumber, resolution);

    StringBuilder filenameBuilder = new StringBuilder();

    filenameBuilder
            .append(workingDirectory)
            .append(File.separator)
            .append(resolution)
            .append(File.separator)
            .append(stickNumber)
            .append(FILE_EXTENSION);

    File stickFile = new File(filenameBuilder.toString());

    try {
        FileUtils.copyURLToFile(url, stickFile);
    } catch (IOException e) {
        System.out.print(e.toString());
        throw new GrabberException("Can't save sticker #" + stickNumber);
    }

    return stickFile;
}

Угу, дайте мне номер и разрешение и получите файлик. Классно? Вот и мне нравится.

Теперь — давайте скачаем все стикеры разом?

public void getAll(){
    int stickersCount = getStickersCount();
    for (int i = 1; i <= stickersCount; i++){
        getSticker(i, resolution);
    }
}

Так, запускаем… Это еще что такое?!

Ага, новость хорошая: стикеры скачиваются и — даже падают в нужную папку. Новость плохая — все происходит дико медленно, причем ни диск ни сеть не загружается даже на 10%. Тем временем, каждый стикер сливается независимо от любого другого и ни с чем не взаимодействует… Что делать? Параллелить!

Быстрее, выше, сильнее!

Многопоточность в Java хорошая. Настолько хорошая, что не всегда используется явно: например в Java 8 есть целая куча сортировок, которые работают в кучу потоков, не особо утруждаясь тем, чтобы рассказать об этом пользователю: быстро да и ладно. Давайте что-нибудь такое же сделаем? Будем сливать все стикеры в кучу потоков сами, а юзеру останется только вызвать один-единственный метод. Ну, и заодно, сделаем этот метод универсальным — пусть он принимает диапазон стикеров, который надо слить.

Поскольку работать с потоками руками я не особо люблю, давайте создадим какого-нибудь экзекутора и будем пичкать его задачами. А он будет их нам выполнять. Классно? А еще — пусть он сам с количеством потоков разбирается — ему там виднее.

ExecutorService executor = Executors.newCachedThreadPool();

for (int i = bottomEdge; i < topEdge + 1; i++) {
    int currentSticker = i;
    executor.submit(() -> {
        getSticker(currentSticker);
        latch.countDown();
    });
}

Запускаем… Где мои стикеры, так тебя растак?!

Ну, так и есть. Потоки отлично создаются, чего-то делают и… кто-то их убивает. Давайте испепелим найдем виноватого?

Ага, ладно. Похоже, что виновата сама JVM. Я-то, по своей наивности думал, что процесс будет работать до тех пор, пока в нем не отработает последний поток, но вот Oracle решила иначе: JVM убивает весь наш процесс целиком, как только выполнился код в основном потоке, прихватывая только-только запустившиеся остальные. Что бы с этим сделать… О!

public void grabStickers(int bottomEdge, int topEdge) {
    CountDownLatch latch = new CountDownLatch(topEdge - bottomEdge + 1);
    ExecutorService executor = Executors.newCachedThreadPool();

    for (int i = bottomEdge; i < topEdge + 1; i++) {
        int currentSticker = i;
        executor.submit(() -> {
            getSticker(currentSticker);
            latch.countDown();
        });
    }

    try {
        latch.await();
    } catch (InterruptedException e) {
        throw new GrabberException("Can't download all stickers");
    }

}

Синхронизация, ага. Куда ж без нее. Сначала посчитали количество задач, которые должны запуститься, сунули это число в специальный счетчик и отщелкиваем каждую исполненную задачу. И пусть весь Main Thread подождет, ага. Нечего тут.

Запускаем еще раз… О! Вот и котики!

stickers-cats

Правда, милые? А еще тут енотики есть… Впрочем, нам-то надо скачать все стикеры сразу, а не какой-то там диапазон. Пишем еще один метод:

public void grabStickers() {
    grabStickers(1, getStickersCount());
}

Люблю полиморфизм: посчитали, сколько у нас там стикеров всего да и объявили это диапазоном для скачивания.

Так, ну и чего?

Милых моему глазу котиков с енотами и всяких там прочих собак скачали. Теперь их можно отправить… ну, кому-нибудь можно, наверное. Это уже без меня. А я пошел на настоящих кошек смотреть, больно они красивые. Например, вот на такого:

angora-cat

Искренне ваш, сливающий библиотеку Лувра в 256 потоков, GrakovNe


Просмотров: 5262
Теги:




Оставить комментарий

Вы должны войти чтобы оставить комментарий.

vodafone tl yükleme kontör yükleme hamile giyim turkcell fatura alanya escort işbankası kredi kartı borç sorgulama kredi kartı ile elektrik faturası ödeme turkcell tl yükleme tl yükleme hgs yükleme emek serverler site ekle r57 shell antalya escort yapı kredi borç sorgulama finansbank borç sorgulama akbank borç sorgulama ogs yükleme enerjisa elektrik faturası ödeme clk akdeniz elektrik faturası ödeme elektrik faturası ödeme escort antalya hgs bakiye yükleme script indir fatura ödeme vodafone fatura ödeme hgs yükleme sex sohbet antalya escort