[Из песочницы] А сколько вы потратили времени на фильмы?

Возникновение идеи


Недавно был в гостях у друзей и мы выбирали фильм, а я как прожжённый киноман (на самом деле, не то чтобы прям прожжённый) отбраковывал всё как просмотренные. И мне задали логичный вопрос, а что ты вообще не смотрел? На что я рассказал, что веду кинопоиск и каждый фильм, которые посмотрел отмечаю либо оценкой, либо просто галочкой, что просмотр состоялся. И тут в голове у меня возник вопрос, а сколько я вообще времени то потратил на фильмы? В Steam есть удобная статистика по игре, а по фильмам ничего такого нет. Вот и решил я заняться данной идеей.

Что там с реализацией?


Я уже несколько лет разрабатываю на ASP.NET и привык к C#, сначала на нём и хотел написать данную утилиту, но тут возникла проблема с тяжеловесным окружением и, так как я немного знаком с Python, именно к его помощи я прибегнул.

А где взять данные?


И вот тут я столкнулся с первой проблемой. Я наивно предполагал, что у кинопоиска есть официальное публичное API и какая-нибудь бесплатная версия. Но ничего такого я не нашёл. Есть возможность запросить через техподдержку, но и там выдают только за n-ую сумму, а я писал это для себя и никак не хотел платить за это.

Естественно, пришлось рассматривать вариант парсинга страниц и именно на нём я и остановился.

image

У каждого в профиле есть список просмотренных фильм с небольшим описанием, которое включает продолжительность картины. Таким образом я могу получить всего несколько страниц (У меня 762 фильма и необходимо было получить всего 17 страниц) и рассчитать потраченное время.

Сказано — сделано.

class KinopoiskParser:
    def __init__(self, user_id, current_page=1):
        self._user_id = user_id
        self._current_page = current_page
        self._wasted_time_in_minutes = 0

    def calculate_wasted_time(self):
        while True:
            film_list_url = f'https://www.kinopoisk.ru/user/{self._user_id}' \
                f'/votes/list/ord/date/genre/films/page/{self._current_page}/#list'

            try:
                film_response = requests.get(film_list_url).text
            except BaseException:
                proxy_manager.update_proxy()
                continue

            user_page = BeautifulSoup(film_response, "html.parser")

            is_end = kinopoisk_parser._check_that_is_end_of_film_list(user_page)
            if is_end:
                break

            wasted_time = self._get_film_duration_on_page(user_page)
            self._wasted_time_in_minutes += wasted_time

            print(f'Page {self._current_page}, wasted time {self._wasted_time_in_minutes}')

            self._move_next_page()

    def get_wasted_time(self):
        return self._wasted_time_in_minutes

    def _move_next_page(self):
        self._current_page += 1

    @staticmethod
    def _get_film_duration_on_page(user_page):
        try:
            wasted_time = 0
            film_list = user_page.findAll("div", {"class": "profileFilmsList"})[0].findAll("div", {"class": "item"})
            for film in film_list:
                film_description = film.findAll("span")
                if len(film_description) <= 1:
                    continue

                film_duration_in_minutes = int(film_description[1].string.split(" ")[0])
                wasted_time = wasted_time + film_duration_in_minutes

            return wasted_time
        except BaseException:
            print("Something went wrong.")
            return 0

    @staticmethod
    def _check_that_is_captcha(html):
        captcha_element = html.find_all("a", {"href": "//yandex.ru/support/captcha/"})
        return len(captcha_element) > 0

    @staticmethod
    def _check_that_is_end_of_film_list(html):
        error_element = html.find_all("div", {"class": "error-page__container-left"})
        return len(error_element) > 0


Но уже на этапе отладки я столкнулся с проблемой, что кинопоиск блокирует запросы (примерно, на 4 итерации) и считает их подозрительными. И он ведь прав! Но такой вариант я тоже предполагал и перешёл к плану Б.

План Б — меняем прокси как перчатки


Взяв первый попавшийся сервер, который предоставляет API для получения ip proxy (не рекламирую никакие сервисы, взял первые две ссылки из гугла), криво прикрутил его и продолжил писать основной код. И уже через час, когда я был близок к завершению меня заблокировал и сервер, которые предоставляет API! Пришлось сменить его на другой, который выдаёт фиксированный список, каждые полчаса, для моей задачи этого достаточно. Но если вдруг кончится список, можно вернутся к предыдущему варианту (они выдают каждые 24 часа где-то 10–20 proxy).

class ProxyManager:
    def __init__(self):
        self._current_proxy = ""
        self._current_proxy_index = -1
        self._proxy_list = []
        self._get_proxy_list()

    def get_proxies(self):
        proxies = {
            "http": self._current_proxy,
            "https": self._current_proxy
        }

        return proxies

    def update_proxy(self):
        self._current_proxy_index += 1
        if self._current_proxy_index == len(self._proxy_list):
            print("Proxies are ended")
            print("Try get alternative proxy")
            proxy_ip_with_port = self._get_another_proxy()
            print("Proxy updated to " + proxy_ip_with_port)

            self._current_proxy = f'http://{proxy_ip_with_port}'
            return self._current_proxy

        proxy_ip_with_port = self._proxy_list[self._current_proxy_index]

        print("Proxy updated to " + proxy_ip_with_port)

        self._current_proxy = f'http://{proxy_ip_with_port}'
        return self._current_proxy

    @staticmethod
    def _get_another_proxy():
        proxy_response = requests.get("https://api.getproxylist.com/proxy?protocol[]=http", headers={
            'Content-Type': 'application/json'
        }).json()

        ip = proxy_response['ip']
        port = proxy_response['port']
        proxy = f'{ip}:{port}'

        return proxy

    def _get_proxy_list(self):
        proxy_response = requests.get("http://www.freeproxy-list.ru/api/proxy?anonymity=false&token=demo")
        self._proxy_list = proxy_response.text.split("\n")


Соединив всё это вместе (в конце приведу ссылку на гитхаб с конечной версией), я получил отличную штуку для подсчёта времени потраченного на фильмы. И получил заветное число, тадам: «You wasted 84542 minutes or 1409.03 hours or 58.71 day».

Зря потратил время для подсчёта зря потраченного времени


На самом деле, не зря. Задача была интересная, хоть и вряд ли нужная хоть кому-то.
Да и теперь я могу всем говорить, что, почти, два месяца своей жизни я занимался просмотром кино!

Если кому-то будет тоже интересно получить такую «важную» статистику для себя, просто скопируйте id своего профиля и запустите проект с этим параметром и если несложно скиньте в комментарии результат, мне интересно «киноман» я или любитель начинающий.

Ссылка на исходный код

P.S. Также буду рад услышать советы по улучшению кода, так как на питоне писал очень мало и даже синтаксисом владею не в полной мере.

© Habrahabr.ru