Возникновение идеи
Недавно был в гостях у друзей и мы выбирали фильм, а я как прожжённый киноман (на самом деле, не то чтобы прям прожжённый) отбраковывал всё как просмотренные. И мне задали логичный вопрос, а что ты вообще не смотрел? На что я рассказал, что веду кинопоиск и каждый фильм, которые посмотрел отмечаю либо оценкой, либо просто галочкой, что просмотр состоялся. И тут в голове у меня возник вопрос, а сколько я вообще времени то потратил на фильмы? В Steam есть удобная статистика по игре, а по фильмам ничего такого нет. Вот и решил я заняться данной идеей.
Что там с реализацией?
Я уже несколько лет разрабатываю на ASP.NET и привык к C#, сначала на нём и хотел написать данную утилиту, но тут возникла проблема с тяжеловесным окружением и, так как я немного знаком с Python, именно к его помощи я прибегнул.
А где взять данные?
И вот тут я столкнулся с первой проблемой. Я наивно предполагал, что у кинопоиска есть официальное публичное API и какая-нибудь бесплатная версия. Но ничего такого я не нашёл. Есть возможность запросить через техподдержку, но и там выдают только за n-ую сумму, а я писал это для себя и никак не хотел платить за это.
Естественно, пришлось рассматривать вариант парсинга страниц и именно на нём я и остановился.
У каждого в профиле есть список просмотренных фильм с небольшим описанием, которое включает продолжительность картины. Таким образом я могу получить всего несколько страниц (У меня 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. Также буду рад услышать советы по улучшению кода, так как на питоне писал очень мало и даже синтаксисом владею не в полной мере.
Комментариев нет:
Отправить комментарий