{"id":3881,"url":"\/distributions\/3881\/click?bit=1&hash=e456b64697230d93edcda0dd20f3c8aa5d4abd88effca1a7571a12fa6564c38a","title":"\u0413\u0435\u0439\u043c\u0434\u0435\u0432-\u043a\u043e\u043d\u043a\u0443\u0440\u0441, \u0433\u0434\u0435 \u043f\u0440\u0438\u0437\u044b: \u0438\u043d\u0432\u0435\u0441\u0442\u0438\u0446\u0438\u0438 \u0438 \u043f\u0440\u043e\u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0435","buttonText":"\u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c","imageUuid":"a410dbd1-804c-54dc-84ea-e6227d71d9b4","isPaidAndBannersEnabled":false}
harbiter

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

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

Идея что-то изменить в своей жизни родилась у меня в конце 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

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

@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.

0
3 комментария
Hyperion Graas

Чел, я в похожей ситуации, разве что взрывов за окнами нет, но если ты когда-ни6удь выяснишь, как 6ыстро рендерить 6есконечный процедурный красиво выглядящий ландшафт, не важно на каком языке или движке - напиши мне. 6уду очень 6лагодарен. Ну и... Мирного не6а над головой!

Ответить
Развернуть ветку
harbiter
Автор

Непременно)

Ответить
Развернуть ветку

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

Развернуть ветку

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

Развернуть ветку
harbiter
Автор

some text test

Ответить
Развернуть ветку
Читать все 3 комментария
null