Дорогой дневник... или как я вновь начал учиться, заметки начинающего питониста

Короткий блок о себе и почему это здесь

Идея что-то изменить в своей жизни родилась у меня в конце 2021. Идет третий десяток нового тысячелетия, четвертый моей собственной жизни. А за плечами, ВУЗ и специальность по которой не работал ни дня, сложное заболевание и инвалидность, отсутствие перспектив…

Как-то справившись с прострацией, от того что моя жизнь изменилась и больше никогда не станет прежней, на что мне потребовалось шесть лет(О_О), я очутился в точке 26 декабря 2021. Именно тогда родилась идея «стать программистом». Из смежных знаний по теме были лишь хорошие навыки работы в unix среде и благополучно забытый курс алгоритмизации в ВУЗе.

Начальные этапы я освещать не буду. Это все и так очевидно. Алгоритмы, структуры данных, десятки часов в день за литературой и первые попытки что-то написать на python, а также тренировки на codewars… И к концу февраля я уже почти выгорел от взятого темпа.

Возможно, вся история на этом бы и закончилась, я бы постепенно терял мотивацию, и в конечном счете забросил бы все. Никогда не пытайтесь "победить проблему" постоянным вливанием огромного количества времени. Особенно если проблема — обучение чему-то новому. Но случилось 24 февраля. Сон прервался нетипичной активностью родственников и звуками далеких разрывов. Затем новая прострация, смешанная со страхом, на два месяца. В итоге страх до сих пор никуда не ушел, но притупился. За это время я сделал ряд важных для себя выводов об ошибках в организации обучения и в мае подошел иначе к процессу.

Идея начать вести «мой блог» на DTF возникла из постов dtf.ru/u/590177-ali Ведь написать о том, что ты делал — хороший способ повторить и закрепить полученные навыки и знания. Не переживайте, "ронять сосиску" каждый час я не буду, да и в целом в "Свежее" только избранные посты по тематике портала отправлять буду("Игровая физика на python", мини спойлер), остальное "только для подписчиков", т.е. меня самого).

P.S. Качество кода, которое тут будет появляться - за гранью, надеюсь оно будет расти(регулярно перечитываю pep’ы), но этот процесс может происходить не так быстро как хотелось бы. Любая критика приветствуется, в том числе навыков владения русским. Несмотря на то, что это мой родной язык, учить его приходилось не в школе(он был лишь один год как предмет), а самостоятельно. И этот процесс, так же как и обучение программированию далек от завершения.

Собственно код

Вступление вышло не таким коротким, как хотелось бы. Начнем кодить. Как писал выше, посты Ali, подтолкнули меня потренироваться и создать простую страничку-трекер посмотренных фильмов, сериалов. Безусловно, кинопоиск, imdb и другие сайты агрегаторы, имеют такой функционал. Но для этого и существуют учебные проекты, чтобы изобретать свой велосипед, да и что если мы захотим смешать данные из двух источников или сделать функционал еще не реализованный на агрегаторах?

Еще одно маленькое уточнение, мы будем работать не напрямую с api агрегатора(в данном случае imdb) , а с готовым модулем оборачивающим наши запросы в понятную серверу amazon форму. В непосредственно api imdb мы погрузимся в следующий раз.

Далее код с разъяснениями

from os.path import exists import urllib.request from flask import Flask, render_template, url_for, request, redirect from imdb import Cinemagoer

Необходимые импорты, подробнее о том, что это мы остановимся когда встретим это в коде.

app = Flask(__name__) ia = Cinemagoer()

Инициируем Фласк и и модуль через который мы будем обращаться к imdb. Веб документация по модулям Cinemagoer и Flask.

@app.route('/', methods=['POST', 'GET']) def index(): if request.method == 'POST' and request.form['search']: initial_search = ia.search_movie(request.form['search']) global movie_list movie_list = [] for i in initial_search: if i['cover url']: try: poster_suffix = i['cover url'][i['cover url'].rindex('.'):] except ValueError: poster_suffix = '.jpg' try: poster_prefix = i['cover url'][:i['cover url'].rindex('@') + 1] except ValueError: poster_prefix = i['cover url'][:i['cover url'].rindex('.')] poster_url = poster_prefix + poster_suffix static_img_path = f'static/img/{i.movieID}{poster_suffix}' else: poster_url = None static_img_path = 'static/img/default.jpg' if str(i.movieID)[0] == '0': movie_id = str(i.movieID)[1:] else: movie_id = str(i.movieID) movie_list.append({'title': i['title'], 'movie_id': movie_id, 'poster_url': poster_url, 'static_img_path': static_img_path} ) if not (exists(static_img_path)) and bool(poster_url): urllib.request.urlretrieve(poster_url, static_img_path) return redirect('/result') return render_template('index.html')

Основная функция для поиска по ключевому слову. @app.route — декоратор что это и зачем нужно, подробно описано в документации. В данном же случае декоратор изменяет поведение функции index, так чтобы возвращаемое ею значение было в формате http запроса и позволило нам просто работать с веб сервером созданным Flask. if request.method == 'POST' and request.form['search'] — проверка того, что мы отправили post запрос, и в этом запросе есть не пустая форма search с ключевым словом по которому будет происходить поиск. В противном случае мы просто вернем шаблон этой же страницы без изменений. initial_search = ia.search_movie(request.form['search']) — непосредственно поиск по переданному ключевому слову с помощью метода search_movie из модуля Cinemagoer

movie_list — создание переменной в которой мы будем хранить и передавать на страничку выбранные данные.

Затем в цикле for i in initial_search проходим по ответу на наш запрос, каждая итерация цикла — информация по одному фильму. Собирая в movie_list необходимую для дальнейшей передачи данные, для отрисовки страницы в браузере с поисковой выдачей. Чтобы сформировать список фильмов отвечающих нашему поисковому запросу, нам нужны: постер, название фильма, а также id этого фильма в базе imdb, для получения дополнительной информации о каждом конкретном кинофильме. В блоке if i[«cover url»] мы проверяем в ответе imdb наличие ссылки на постер. Изначально, в этом поле ссылка на уменьшенный постер, для полно размерного нам придется немного поработать над url

try: poster_suffix = i['cover url'][i['cover url'].rindex('.'):] except ValueError: poster_suffix = '.jpg' try: poster_prefix = i['cover url'][:i['cover url'].rindex('@') + 1] except ValueError: poster_prefix = i['cover url'][:i['cover url'].rindex('.')] poster_url = poster_prefix + poster_suffix static_img_path = f'static/img/{i.movieID}{poster_suffix}' else: poster_url = None static_img_path = 'static/img/default.jpg'

Начнем с конца, если в ответе сервера нет изображение, задаем переменную poster_url как None, а вместо ссылки на скачанную на наш сервер обложку(это немного ниже) — задаем ссылку на лежащий на сервере файл "по умолчанию". poster_suffix переменная в которую мы положем расширение нашего файла. Все просто, если мы получили какую-то строку в поле 'cover url', то там всегда будет такой формат "ссылка.расширение". Так что блок try except тут не нужен, но пусть будет. В итоге мы ищем точку начиная просмотр url справа и как только находим берем срез по найденному индексу от точки до конца ссылки. Вся полученная строка — наше расширение. С префиксом, т.е. самой ссылкой сложнее. В ней может быть символ '@' до которого, включая его ссылка ведет на полноразмерный постер, так и полное отсутствие символа собаки если полноразмерного постера у фильма нет. Поэтому, тут блок try except необходим, если в ссылке отсутствует символ '@' мы берем ее всю, как ссылку на постер, если символ есть — берем срез отбрасывая правую часть. И сразу же создаем переменную с путем до постера на нашем сервере, мы еще его не скачали, но если мы в этой ветке выполнения программы то url для скачивания у нас уже есть. Путь до файла на нашем сервере нам пригодится, в том числе, для того, чтобы указать программе куда положить скачанный файл. Имя файла при этом, будет состоять из id фильма в базе imdb и ранее полученного префикса.

if str(i.movieID)[0] == '0': movie_id = str(i.movieID)[1:] else: movie_id = str(i.movieID)

Еще один интересный момент. В ответе от imdb id у фильмов в одних местах имеют формат вида «111111" в других к этому id впереди добавляется '0', для того, чтобы точно знать что мы отсылаем в качестве запроса и используем в нашей программе, если видим в ответе 'id' первый символ '0» отбрасываем его, если первый символ не ноль то ничего не меняем.

movie_list.append({'title': i['title'], 'movie_id': movie_id, 'poster_url': poster_url, 'static_img_path': static_img_path} ) if not (exists(static_img_path)) and bool(poster_url): urllib.request.urlretrieve(poster_url, static_img_path)

В ранее созданный пустой список movie_list добавляем словарь с названием фильма, его id и ссылками на обложку. Две последние строки, проверяем наличие локального файла с постером на сервере с помощью функции exists, если не находим, то с помощью метода request из модуля urllib скачиваем, передавая ему заранее сохраненный url постера и путь по которому сохраняем файл.

В случае, если мы отправляем поисковый запрос то перенаправляем пользователя на страницу с результатами возвращая функцию redirect('/result') и передавая ей путь на который отправляем пользователя.

@app.route('/result') def result(): return render_template('result.html', movie_list=movie_list)

Простая функция, которая возвращает render_template и набор параметров movie_list нашему серверу, затем с помощью html и css формируем страницу с результатами поиска. Выглядит это все так:

Страница с результатами поиска по ключевому слову Matrix
Страница с результатами поиска по ключевому слову Matrix

Функция позволяющая получить больше информации по конкретному фильму.

@app.route('/detail/<int:movie_id>', methods=['POST', 'GET']) def detail_movie(movie_id): detail = ia.get_movie(movie_id) for i in movie_list: if str(i['movie_id']) == str(movie_id): one_movie = i return render_template('detail.html', one_movie=one_movie, detail=detail)

'/detail/<int:movie_id>' Интересный момент мы получаем нужный нам для поиска id фильма из http запроса. Для этого на странице с результатами формируем ссылку вида <a class="" href="/detail/{{ element['movie_id'] }}"> —{} шаблонизатор jinja позволяющий нам исполнять код python и использовать его для формирования страницы с помощью html. element['movie_id'] — это id конкретного фильма, который мы молучаем пройдясь по всему списку movie_list переданому серверу, опять же с помощью jinja {% for element in movie_list% } {% end for% }. Средствами функции ia. get_movie(movie_id) , которой мы передаем id нашего фильма, мы получаем более подробную информацию о фильме: рейтинги, актерский состав, реценции, бюджет и сборы и т. д. и т. п. куча информации с которой можно работать.

Подробнее о фильме
Подробнее о фильме

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

Спасибо всем осилившим сумбурное и криво написанное эссе. Следующий материал будет интереснее и нагляднее, и тематика будет менее офтоповая для DTF. Буду разбираться в методах реализации игровой физики на python.

2424
3 комментария

Комментарий недоступен

Непременно)

1

some text test