├── .DS_Store ├── Homeworks ├── Homework_1 │ ├── HW_1_Python.ipynb │ └── archive.zip ├── Homework_2 │ └── HW_2_Python.ipynb ├── Homework_3 │ ├── HW_3_Python.ipynb │ ├── Steam_1.png │ └── Steam_2.png └── Homework_4 │ └── HW_4_Python.ipynb ├── Lectures ├── .DS_Store ├── Lecture_1 │ └── Lecture_1.pdf ├── Lecture_10 │ └── Lecture_10_Python.ipynb ├── Lecture_11 │ └── Lecture_11_Python.ipynb ├── Lecture_12 │ └── Lecture_12_Python.ipynb ├── Lecture_13 │ └── Lecture_13_Python.ipynb ├── Lecture_2 │ ├── Lecture_2.ipynb │ └── archive.zip ├── Lecture_3 │ └── Lecture_3_Python.ipynb ├── Lecture_4 │ └── Lecture_4_Python.ipynb ├── Lecture_5 │ └── Lecture_5_Python.ipynb ├── Lecture_6 │ └── Lecture_6_Python.ipynb ├── Lecture_7 │ └── Lecture_7_Python.ipynb ├── Lecture_8 │ ├── Amazon.txt │ └── Lecture_8_Python.ipynb └── Lecture_9 │ └── Lecture_9_Python.ipynb ├── README.md └── Seminars ├── .DS_Store ├── Seminar_1 ├── Seminar_1_Python.ipynb └── test_git │ └── Hello.txt ├── Seminar_10 └── Seminar_10_Python.ipynb ├── Seminar_11 ├── .DS_Store ├── Dash │ ├── .DS_Store │ ├── county_statistics.csv │ ├── main.py │ └── pages │ │ ├── __pycache__ │ │ ├── compare.cpython-310.pyc │ │ ├── home.cpython-310.pyc │ │ └── main_dash.cpython-310.pyc │ │ ├── compare.py │ │ ├── home.py │ │ └── main_dash.py └── Seminar_11_Python.ipynb ├── Seminar_12 └── Seminar_12_Python.ipynb ├── Seminar_13 └── Seminar_13_Python.ipynb ├── Seminar_2 ├── Seminar_2_Python.ipynb ├── Seminar_2_Python_solved.ipynb └── archive.zip ├── Seminar_3 ├── Seminar_3_Python.ipynb └── Seminar_3_Python_solved.ipynb ├── Seminar_4 ├── Seminar_4_Python.ipynb └── Seminar_4_Python_solved.ipynb ├── Seminar_5 └── Seminar_5_Python_solved.ipynb ├── Seminar_6 └── Seminar_6_Python.ipynb ├── Seminar_7 ├── Seminar_7_Python.ipynb └── external-40779.xml ├── Seminar_9 ├── .DS_Store ├── Project │ ├── .DS_Store │ ├── app.py │ ├── flaskr.db │ ├── schema.sql │ ├── static │ │ ├── meme.jpg │ │ └── style.css │ └── templates │ │ ├── layout.html │ │ ├── login.html │ │ └── show_entries.html └── Seminar_9_Python.ipynb └── Seminars_8 ├── .DS_Store ├── Aliexpress.txt ├── Aliexpress.zip ├── Aliexpress ├── .DS_Store ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-310.pyc │ ├── items.cpython-310.pyc │ ├── pipelines.cpython-310.pyc │ └── settings.cpython-310.pyc ├── items.json ├── items.py ├── middlewares.py ├── pipelines.py ├── settings.py └── spiders │ ├── AliexpressSpider.py │ ├── __init__.py │ └── __pycache__ │ ├── AliexpressSpider.cpython-310.pyc │ └── __init__.cpython-310.pyc ├── Aliexpress_item.txt └── Seminar_8_Python.ipynb /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/.DS_Store -------------------------------------------------------------------------------- /Homeworks/Homework_1/HW_1_Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "provenance": [], 7 | "collapsed_sections": [] 8 | }, 9 | "kernelspec": { 10 | "name": "python3", 11 | "display_name": "Python 3" 12 | }, 13 | "language_info": { 14 | "name": "python" 15 | } 16 | }, 17 | "cells": [ 18 | { 19 | "cell_type": "markdown", 20 | "source": [ 21 | "# Домашнее задание 1\n", 22 | "\n", 23 | "**Выполнил:** <Вставьте ФИО>\n", 24 | "\n", 25 | "**На сколько выполнил:** <Укажите, на сколько баллов по вашему мнению вы сделали>" 26 | ], 27 | "metadata": { 28 | "id": "ARoAqlMbArNx" 29 | } 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "source": [ 34 | "**Правила игры:**\n", 35 | "\n", 36 | "* возле каждой задачи указано число баллов (в сококупности можно получить 10 баллов)\n", 37 | "\n", 38 | "* дополнительные задачи выделены звездочкой (также указано число баллов). Данные баллы можно будет использовать для улучшения результата за любое домашнее задание\n", 39 | "\n", 40 | "* все подсчеты необходимо делать с помощью pandas-numpy (использовать как можно меньше циклов, за использование циклов, где это не требуются, будем штрафовать)\n", 41 | "\n", 42 | "* для визуализации можно использовать matplotlib-seaborn-plotly (учтите, что все графики должны выглядеть опрятно, иметь название-подписи, за нечитабельные графики будем штрафовать)\n", 43 | "\n", 44 | "* возможно, что в данных есть пустоты и невалидные значения. Перед началом любой работы необходимо исследовать, и если такие есть, убрать их\n", 45 | "\n", 46 | "* соблюдайте чистоту кода: переменные должны быть осмысленными, не повторяйтесь, если в этом нет необходимости (иначе используйте функции). Чем чище код - тем проще нам поставить вам хорошую оценку :)\n", 47 | "\n", 48 | "* общая рекомендация: на вопросы со звездочкой отвечать в самом конце, возможно, будет проще)" 49 | ], 50 | "metadata": { 51 | "id": "V9LS6Z01Dcsq" 52 | } 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "source": [ 57 | "## Данные " 58 | ], 59 | "metadata": { 60 | "id": "7IJVSWwiA3j5" 61 | } 62 | }, 63 | { 64 | "cell_type": "markdown", 65 | "source": [ 66 | "В этом домашнем задании вам придется оказаться на месте аналитика в бразильском маркетплейсе [Olist](https://olist.com/pt-br/). Вам необходимо исследовать данные и на их основании сделать выводы, которые помогут бизнесу расцветать!\n", 67 | "\n", 68 | "Данные находятся тут: (будет ссылка)" 69 | ], 70 | "metadata": { 71 | "id": "TBeriAVkA5uJ" 72 | } 73 | }, 74 | { 75 | "cell_type": "markdown", 76 | "source": [ 77 | "Ван дано 9 датасетов, которые содержат в себе все данные по 100 000 заказам со всей Бразилии. Чтобы облегчить вам жизнь, вот связи по этим датасетам (файл product_category_name_translation является переводом названий категорий с португальского на английский)" 78 | ], 79 | "metadata": { 80 | "id": "KKjQDN3VDDGa" 81 | } 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "source": [ 86 | "![](https://i.imgur.com/HRhd2Y0.png)" 87 | ], 88 | "metadata": { 89 | "id": "cKOQJO1_C9MA" 90 | } 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "source": [ 95 | "Ну что же, начнем, пожалуй!" 96 | ], 97 | "metadata": { 98 | "id": "Hsz3LJm5Dzv3" 99 | } 100 | }, 101 | { 102 | "cell_type": "markdown", 103 | "source": [ 104 | "## Задание 1 (0.4 балла)\n", 105 | "\n", 106 | "Определите:\n", 107 | "\n", 108 | "* Число товаров\n", 109 | "* Среднюю стоимость товара\n", 110 | "\n", 111 | "в разрезе категорий (все категории должны быть на английском языке)" 112 | ], 113 | "metadata": { 114 | "id": "b2mqJGRpYVx6" 115 | } 116 | }, 117 | { 118 | "cell_type": "markdown", 119 | "source": [ 120 | "## Задание 2 (1,5 балла)\n", 121 | "\n", 122 | "Определите для каждого продавца основную категорию их продаж.\n", 123 | "\n", 124 | "Отобразите категории по числу продавцов, для которых данная категория основная, а также создайте график, по которому можно выбрать отдельно штат и посмотреть такое же распределение\n", 125 | "\n" 126 | ], 127 | "metadata": { 128 | "id": "Qwla1I91drgU" 129 | } 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "source": [ 134 | "## Задание 3 (1,5 балл)\n", 135 | "\n", 136 | "Покажите процент\n", 137 | "\n", 138 | "* суммы покупок в деньгах\n", 139 | "\n", 140 | "* суммы покупок в штуках\n", 141 | "\n", 142 | "по категории для доставленных заказов, а также разбивку по штатам" 143 | ], 144 | "metadata": { 145 | "id": "F5Alu7yBboG2" 146 | } 147 | }, 148 | { 149 | "cell_type": "markdown", 150 | "source": [ 151 | "## Задание 4 (0.6 балла)\n", 152 | "\n", 153 | "Визуализируйте зависимость между средним числом покупок и:\n", 154 | "\n", 155 | "* числом фотографий товара\n", 156 | "\n", 157 | "* кол-во символов в описании товара (аггрегируйте с шагом 20)\n", 158 | "\n", 159 | "* кол-во символов в названии (аггрегируйте с шагом 5)" 160 | ], 161 | "metadata": { 162 | "id": "HlePiiebib7W" 163 | } 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "source": [ 168 | "## Задание 5* (1 балл)\n", 169 | "\n", 170 | "Имея полученные данные по товарам, чекам и категориям, а также сделав дополнительные расчеты (если необходимо), предположите:\n", 171 | "\n", 172 | "1. Продавцов каких категорий необходимо привлекать в маркетплейс?\n", 173 | "\n", 174 | "2. Какие категории в каких штатах необходимо развивать?\n", 175 | "\n", 176 | "3. Развитие каких категорий не принесут большого эффекта и почему?\n", 177 | "\n", 178 | "4. Какие рекомендации можно дать для продавцов, чтобы их товар лучше продавался?" 179 | ], 180 | "metadata": { 181 | "id": "G76_5kWld8xD" 182 | } 183 | }, 184 | { 185 | "cell_type": "markdown", 186 | "source": [ 187 | "## Задание 6 (0.5 балла)\n", 188 | "\n", 189 | "Определите средний чек покупки (добавьте разбивку на стоимость самого заказ и стоимость доставки) и среднее число товаров в заказе" 190 | ], 191 | "metadata": { 192 | "id": "qcw4MaMnYKuu" 193 | } 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "source": [ 198 | "## Задание 7 (0.2 балла)\n", 199 | "\n", 200 | "Определите среднее число покупок на пользователя (обратите внимание на идентификаторы)" 201 | ], 202 | "metadata": { 203 | "id": "8xia3fPtY7Nz" 204 | } 205 | }, 206 | { 207 | "cell_type": "markdown", 208 | "source": [ 209 | "## Задание 8 (0.2 балла)\n", 210 | "\n", 211 | "Отобразите среднюю сумму оплаты по типу оплаты" 212 | ], 213 | "metadata": { 214 | "id": "GoZhcGiKj4gX" 215 | } 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "source": [ 220 | "## Задание 9 (0.4 балла)\n", 221 | "\n", 222 | "Обратите внимание на число платежей (payment_installments). Выясните, какая сумма оплаты указана: за целую покупку или только за ее часть?\n", 223 | "\n", 224 | "Также покажите средний чек в зависимости от числа оплаты. Есть ли какая-то связь между ними?" 225 | ], 226 | "metadata": { 227 | "id": "RMo-voFDakG9" 228 | } 229 | }, 230 | { 231 | "cell_type": "markdown", 232 | "source": [ 233 | "## Задание 10 (0.4 балла)\n", 234 | "\n", 235 | "Визуализируйте зависимость между ценой заказа и ценой доставки по штатам покупки. Есть ли какая-нибудь зависимость и как вы можете ее обосновать?" 236 | ], 237 | "metadata": { 238 | "id": "0qfmQzlDW1S6" 239 | } 240 | }, 241 | { 242 | "cell_type": "markdown", 243 | "source": [ 244 | "## Задание 11 (0.4 балла)\n", 245 | "\n", 246 | "Выведите график по числу покупок (в разбивке по статусам) и временем покупки (аггрегируйте покупки до дня)" 247 | ], 248 | "metadata": { 249 | "id": "kuS3HQ7hXK0H" 250 | } 251 | }, 252 | { 253 | "cell_type": "markdown", 254 | "source": [ 255 | "## Задание 12* (1 балл)\n", 256 | "\n", 257 | "Используя полученные данные, а также сделав дополнительные вычисления (если необходимо), предположите:\n", 258 | "\n", 259 | "1. Есть ли сезонность в покупках на маркетплейсе?\n", 260 | "\n", 261 | "2. Есть ли какие-то необъяснимые падение/рост продаж? С чем это может быть связано?" 262 | ], 263 | "metadata": { 264 | "id": "s9X976jZlNSC" 265 | } 266 | }, 267 | { 268 | "cell_type": "markdown", 269 | "source": [ 270 | "## Задание 13 (0.3 балла)\n", 271 | "\n", 272 | "Посчитайте CSAT (customer satisfaction - средняя оценка ревью) и отобразите средний CSAT по дням\n", 273 | "\n" 274 | ], 275 | "metadata": { 276 | "id": "jC99B-ajXnyZ" 277 | } 278 | }, 279 | { 280 | "cell_type": "markdown", 281 | "source": [ 282 | "## Задание 14 (0.3 балла)\n", 283 | "\n", 284 | "Найдите топ-5 лучших и топ-5 худших продавцов по средней оценке и посчитайте их число продаж" 285 | ], 286 | "metadata": { 287 | "id": "GgVCaC_amHpJ" 288 | } 289 | }, 290 | { 291 | "cell_type": "markdown", 292 | "source": [ 293 | "## Задание 15 (0.3 балла)\n", 294 | "\n", 295 | "Посмотрите, как быстро отвечают пользователи (сделайте аггреграцию по дням) и влияет ли это на оценку?" 296 | ], 297 | "metadata": { 298 | "id": "baLiZjiOmTwr" 299 | } 300 | }, 301 | { 302 | "cell_type": "markdown", 303 | "source": [ 304 | "## Задание 16 (0.5 балла)\n", 305 | "\n", 306 | "Отобразите распределение оценок для заказов, которые были доставлены вовремя и которые пришли с опозданием. Насколько влияет факт опоздания на оценку?" 307 | ], 308 | "metadata": { 309 | "id": "Wp9r35zpui6D" 310 | } 311 | }, 312 | { 313 | "cell_type": "markdown", 314 | "source": [ 315 | "## Задание 17* (1,5 балла)\n", 316 | "\n", 317 | "Есть ли среди пользователей хейтеры? Определите их и вычислите средний CSAT без их участия" 318 | ], 319 | "metadata": { 320 | "id": "peGQZI9sv5F3" 321 | } 322 | }, 323 | { 324 | "cell_type": "markdown", 325 | "source": [ 326 | "## Задание 18 (0.4 балла)\n", 327 | "\n", 328 | "Изобразите зависимость между ценой заказа и числом дней между покупкой и доставкой с разбивкой по признаку \"есть заказ из другого штата\". Есть ли зависимость?" 329 | ], 330 | "metadata": { 331 | "id": "nXjzKf3Cxa1m" 332 | } 333 | }, 334 | { 335 | "cell_type": "markdown", 336 | "source": [ 337 | "## Задание 19 (0.6 баллов)\n", 338 | "\n", 339 | "Отобразите на карте все точки продавцов (если они находятся в одном месте, то необходимо отметить кол-во)\n", 340 | "\n", 341 | "Где больше всего продавцов находится?" 342 | ], 343 | "metadata": { 344 | "id": "otHb2D44ZmZV" 345 | } 346 | }, 347 | { 348 | "cell_type": "markdown", 349 | "source": [ 350 | "## Задание 20 (1,5 балл)\n", 351 | "\n", 352 | "Определите топ-5 продавцов, которые чаще всего отсылают свою посылку в другие регионы и визуализируйте их отсылки" 353 | ], 354 | "metadata": { 355 | "id": "hsdKx1F7ZKqh" 356 | } 357 | }, 358 | { 359 | "cell_type": "markdown", 360 | "source": [ 361 | "## Задание 21* (2 балла)\n", 362 | "\n", 363 | "Сделайте анимацию по датам с доставкой между продавцами и пользователями" 364 | ], 365 | "metadata": { 366 | "id": "RMpfvF-UxHKf" 367 | } 368 | }, 369 | { 370 | "cell_type": "markdown", 371 | "source": [ 372 | "## Задание 22* (творческое, отдельно поставим баллы)\n", 373 | "\n", 374 | "Проведите дополнительно исследование между параметрами, которые вам интересны и могут быть полезными (обоснуйте, зачем это смотреть). Сделайте выводы" 375 | ], 376 | "metadata": { 377 | "id": "ldPjOGbIyFYw" 378 | } 379 | }, 380 | { 381 | "cell_type": "markdown", 382 | "source": [ 383 | "## Попугай для ДЗ" 384 | ], 385 | "metadata": { 386 | "id": "A4MnvHjmDNQy" 387 | } 388 | }, 389 | { 390 | "cell_type": "markdown", 391 | "source": [ 392 | "![](https://upload.wikimedia.org/wikipedia/commons/thumb/c/ca/Rose-ringed_parakeet_%28Psittacula_krameri_manillensis%29.jpg/1024px-Rose-ringed_parakeet_%28Psittacula_krameri_manillensis%29.jpg)" 393 | ], 394 | "metadata": { 395 | "id": "_Do1QwYBEKr6" 396 | } 397 | }, 398 | { 399 | "cell_type": "markdown", 400 | "source": [ 401 | "А это ожереловый попугай (или индийский кольчатый попугай). Этот вид явно выделяет наличие такого колечка на шее. Самый распространенный попугай в мире\n", 402 | "\n", 403 | "Они очень хорошо и быстро летают, но ходят максимально неуклюже)\n", 404 | "\n", 405 | "В отличии от многих попугаев, для которых человеческая деятельность является вредом и уменьшает их естественный ареал обитания, то как раз для этих птиц человеческая деятельность - это плюс, потому что они находят корм внутри человеческих обществ (не боятся людей, лучше всего приспосабливаются, едят пшеницу). Поговаривают, что в качестве домашних попугаев их держали еще в древней Греции и Риме, а в средневековой Индии ожереловые попугаи часто были питомцами знатных особ и монархов, причём владение этим попугаем считалось элементом роскоши и престижа" 406 | ], 407 | "metadata": { 408 | "id": "2aljCpwJEM-z" 409 | } 410 | } 411 | ] 412 | } 413 | -------------------------------------------------------------------------------- /Homeworks/Homework_1/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Homeworks/Homework_1/archive.zip -------------------------------------------------------------------------------- /Homeworks/Homework_3/HW_3_Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "provenance": [] 7 | }, 8 | "kernelspec": { 9 | "name": "python3", 10 | "display_name": "Python 3" 11 | }, 12 | "language_info": { 13 | "name": "python" 14 | } 15 | }, 16 | "cells": [ 17 | { 18 | "cell_type": "markdown", 19 | "source": [ 20 | "# Домашнее задание 3\n", 21 | "\n", 22 | "**Выполнил:** <ФИО>\n", 23 | "\n", 24 | "**На сколько выполнил:** <Укажите, на сколько баллов по вашему мнению вы сделали>" 25 | ], 26 | "metadata": { 27 | "id": "zmiXZzivobao" 28 | } 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "source": [ 33 | "**Правила игры:**\n", 34 | "\n", 35 | "* возле каждой задачи указано число баллов (в сококупности можно получить 10 баллов)\n", 36 | "\n", 37 | "* хотим чистый, читабельный код, сайты - с user-friendly интерфейсом)\n", 38 | "\n", 39 | "* к каждому заданию должен быть приложен код проекта, выложенный на Github (если вы пользуетесь ScraperAPI или другими ключами, то ключи необходимо убирать)\n", 40 | "\n", 41 | "* Оценивание будет происзодить в первую очередь по функциональности - все ли работает и работает так, как должно (но за полное отсутствие стиля мы будем снижать)\n", 42 | "\n", 43 | "P.S. В данном задании вам не потребуется ScraperAPI :з\n", 44 | "\n", 45 | "**Любые extra-вещи будут оцениваться в плюс как доп баллы**" 46 | ], 47 | "metadata": { 48 | "id": "rvIl1icooicD" 49 | } 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "source": [ 54 | "## Задание 1 (3 балла)" 55 | ], 56 | "metadata": { 57 | "id": "-kAwYv9PrIJz" 58 | } 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "source": [ 63 | "Парсим [Steam](https://store.steampowered.com/) с помощью Scrapy!\n", 64 | "\n", 65 | "Необходимо выбрать 3 запроса (например, \"инди\", \"стратегии\", \"minecraft\", на каждый запрос должно быть минимум 90 результатов), и вытащить по ним:\n", 66 | "\n", 67 | "* Все игры на первых 2 страницах (найдите, каким образом отображаются страницы)\n", 68 | "\n", 69 | "И для каждой игры вытащить:\n", 70 | "\n", 71 | "* Название\n", 72 | "\n", 73 | "* Категорию (весь путь, за исключением Все игры и самого названия игры)\n", 74 | "\n", 75 | "* Число всех обзоров и общая оценка\n", 76 | "\n", 77 | "* Дата выхода\n", 78 | "\n", 79 | "* Разработчик\n", 80 | "\n", 81 | "* Метки (тэги) игры\n", 82 | "\n", 83 | "* Цена\n", 84 | "\n", 85 | "* Доступные платформы\n", 86 | "\n", 87 | "Сохраните игры в формате json, оставив только игры, выпущенные после 2000 года\n", 88 | "\n", 89 | "Чтобы было проще, в репозитории с ДЗ выложены скрины, где можно посмотреть, где и какие поля находятся\n", 90 | "\n" 91 | ], 92 | "metadata": { 93 | "id": "yowliMx-rtYN" 94 | } 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": null, 99 | "metadata": { 100 | "id": "phKF5yNMoYxk" 101 | }, 102 | "outputs": [], 103 | "source": [] 104 | }, 105 | { 106 | "cell_type": "markdown", 107 | "source": [ 108 | "## Задание 2 (4 балла)" 109 | ], 110 | "metadata": { 111 | "id": "CY1K_uvOrLJs" 112 | } 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "source": [ 117 | "Создайте сайт с помощью Flask или Django. Будем делать конструктор резюме!\n", 118 | "\n", 119 | "Общая структура сайта:\n", 120 | "\n", 121 | "1. Страничка с авторизацией и регистрацией\n", 122 | "\n", 123 | "2. После авторизации, должна быть страница с формой, в которой необходимо заполнить форму (если пользователь уже заполнял форму, то в формы уже должен быть подгружен текст, который был заполнен, для этого должна быть отдельная кнопочка для сохранения в черновик)\n", 124 | "\n", 125 | "3. Страница со скоинструированным резюме, который может быть превращен в PDF и выгружен (для этого можно использовать [pdfkit](https://pypi.org/project/pdfkit/))\n", 126 | "\n", 127 | "На всякий случай: пользователь не может посмотреть чужие резюме" 128 | ], 129 | "metadata": { 130 | "id": "QxqzhOMnvKV2" 131 | } 132 | }, 133 | { 134 | "cell_type": "code", 135 | "source": [], 136 | "metadata": { 137 | "id": "CJ4o2WKxrOGC" 138 | }, 139 | "execution_count": null, 140 | "outputs": [] 141 | }, 142 | { 143 | "cell_type": "markdown", 144 | "source": [ 145 | "## Задание 3* (2 балла)\n", 146 | "\n", 147 | "Добавьте возможность загрузить картинку в резюме (для исходного задания это не требуется)\n", 148 | "\n", 149 | "Разверните приложение на Yandex.Cloud или на любом другом хосте (если знаете куда можно)" 150 | ], 151 | "metadata": { 152 | "id": "C5C8395jyD4q" 153 | } 154 | }, 155 | { 156 | "cell_type": "markdown", 157 | "source": [ 158 | "## Задание 4 (3 балла)" 159 | ], 160 | "metadata": { 161 | "id": "gwDi1HLorOkc" 162 | } 163 | }, 164 | { 165 | "cell_type": "markdown", 166 | "source": [ 167 | "Отрисуйте с помощью Dash или StreamLit данные из данного [датасета](https://www.kaggle.com/datasets/michaelbryantds/crimedata)\n", 168 | "\n", 169 | "Здесь необходимо провести самостоятельный анализ данных (можно даже в ноутбуке), в качестве результата необходим дашборд, внутри которого будет:\n", 170 | "\n", 171 | "* Не менее 5 селекторов\n", 172 | "\n", 173 | "* Не менее 5 различных графиков, которые показывали бы зависимость между преступлениями (на ваш выбор, там можно выбрать) и выбранными вами показателями\n", 174 | "\n", 175 | "Дашборд должен быть стилистически оформлен" 176 | ], 177 | "metadata": { 178 | "id": "8uCXyKzpwst9" 179 | } 180 | }, 181 | { 182 | "cell_type": "code", 183 | "source": [], 184 | "metadata": { 185 | "id": "ap6d3GdNwtUH" 186 | }, 187 | "execution_count": null, 188 | "outputs": [] 189 | } 190 | ] 191 | } -------------------------------------------------------------------------------- /Homeworks/Homework_3/Steam_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Homeworks/Homework_3/Steam_1.png -------------------------------------------------------------------------------- /Homeworks/Homework_3/Steam_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Homeworks/Homework_3/Steam_2.png -------------------------------------------------------------------------------- /Homeworks/Homework_4/HW_4_Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "provenance": [] 7 | }, 8 | "kernelspec": { 9 | "name": "python3", 10 | "display_name": "Python 3" 11 | }, 12 | "language_info": { 13 | "name": "python" 14 | } 15 | }, 16 | "cells": [ 17 | { 18 | "cell_type": "markdown", 19 | "source": [ 20 | "# Домашнее задание 4\n", 21 | "\n", 22 | "**Выполнил:** <ФИО>\n", 23 | "\n", 24 | "**На сколько выполнил:** <Укажите, на сколько баллов по вашему мнению вы сделали>" 25 | ], 26 | "metadata": { 27 | "id": "PWAqo3SH8dC9" 28 | } 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "source": [ 33 | "**Правила игры:**\n", 34 | "\n", 35 | "* к заданию должен быть приложен код проекта, выложенный на Github\n", 36 | "\n", 37 | "* взаимодействие с БД реализуйте с помощью SQLAlchemy (в качестве инструмента для создания БД используйте SQLite3, создать БД можно через простой connect)\n", 38 | "\n", 39 | "**Любые extra-вещи будут оцениваться в плюс как доп баллы**" 40 | ], 41 | "metadata": { 42 | "id": "Cjqpzjcz8gED" 43 | } 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "source": [ 48 | "## Задание 1 (10 баллов)" 49 | ], 50 | "metadata": { 51 | "id": "vWyhmL-68nD2" 52 | } 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "source": [ 57 | "Будем делать небольшую асинхронную текстовую игру в Телеге!\n", 58 | "\n", 59 | "Наша БД будет выглядеть следующим образом (которую необходимо создать):\n", 60 | "\n", 61 | "**Person - персонаж (пользователь):**\n", 62 | "\n", 63 | "* UserID - ID пользователя\n", 64 | "\n", 65 | "* Nickname - никнейм персонажа\n", 66 | "\n", 67 | "* Level - уровень\n", 68 | "\n", 69 | "* HP - здоровье\n", 70 | "\n", 71 | "* CurHP - текущее здоровье\n", 72 | "\n", 73 | "* Money - число денег\n", 74 | "\n", 75 | "* Attack - базовая атака персонажа\n", 76 | "\n", 77 | "* Magic Attack - базовая магическая атака персонажа\n", 78 | "\n", 79 | "* XP - опыт\n", 80 | "\n", 81 | "* Armour - базовая броня персонажа\n", 82 | "\n", 83 | "* Magic Armour - базовая магическая броня персонажа\n", 84 | "\n", 85 | "* LocationID - в какой локации сейчас находится персонаж\n", 86 | "\n", 87 | "**Mobs - монстры:**\n", 88 | "\n", 89 | "* MobID - ID монстра\n", 90 | "\n", 91 | "* HP - здоровье\n", 92 | "\n", 93 | "* XP - опыт\n", 94 | "\n", 95 | "* ReqLevel - необходимый уровень для появления у персонажа \n", 96 | "\n", 97 | "* AttackType - тип атаки (физический/магический)\n", 98 | "\n", 99 | "* Attack - размер атаки\n", 100 | "\n", 101 | "* Armour - броня монстра\n", 102 | "\n", 103 | "* Magic Armour - магическая броня монстра\n", 104 | "\n", 105 | "**Locations - места**\n", 106 | "\n", 107 | "* LocationID - ID места\n", 108 | "\n", 109 | "* XCoord - X координата\n", 110 | "\n", 111 | "* YCoord - Y координата\n", 112 | "\n", 113 | "* LocationType - тип локации (бывает город, бывает подземелье)\n", 114 | "\n", 115 | "**Items - предметы**\n", 116 | "\n", 117 | "* ItemID - ID товара\n", 118 | "\n", 119 | "* Cost - цена товара\n", 120 | "\n", 121 | "* CostToSale - цена продажи товара\n", 122 | "\n", 123 | "* ItemType - тип товара (оружие, броня, шлем, сапоги, наручи, зелье)\n", 124 | "\n", 125 | "* HP - Дополнительное HP (которое дает зелье-предмет)\n", 126 | "\n", 127 | "* Mana - Дополнительная мана (которая дает зелье-предмет)\n", 128 | "\n", 129 | "* Attack - дополнительная атака\n", 130 | "\n", 131 | "* Magic Attack - дополнительная магическая атака\n", 132 | "\n", 133 | "* Armour - дополнительная броня\n", 134 | "\n", 135 | "* Magic Armour - дополнительная магическая броня\n", 136 | "\n", 137 | "* ReqLevel - нужный уровень для ношения предмета\n", 138 | "\n", 139 | "\n", 140 | "\n", 141 | "\n" 142 | ], 143 | "metadata": { 144 | "id": "YIf8gj7W9GFS" 145 | } 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "source": [ 150 | "Так же:\n", 151 | "\n", 152 | "0. Дефолтные значения HP, Mana etc можно задавать самостоятельно (как и все остальные константы для предметов). Уровень повышается при получении 100 XP.\n", 153 | "\n", 154 | "1. Для каждого персонажа должны быть его предметы (отдельная таблица: связка UserID - ItemID - quantity - индиктор ношения). Пользователь может носить только 1 тип оружия-брони-шлема-сапогов-наручей, зелий сколько угодно (при этом любая вещь может быть в неограниченном кол-ве, хоть и носит только одну)\n", 155 | "\n", 156 | "2. Пользователь может переходить по локациям, которые находятся на расстоянии по координатам не более 10 (лучше всего завести таблицу между локациями, откуда можно и куда, чтобы не считать это каждый раз). Время перемещения = расстояние в секундах (например, если от точки A до точки B расстояние по координатам по прямой = 10, то персонаж идет 10 секунд), во время перемещения персонаж ничего не может сделать\n", 157 | "\n", 158 | "3. Внутри города пользователь восстанавливает полностью ману и здоровье, а также в городе можно прикупить вещей (стоит создать таблицу с товарами и городом, где их можно купить)\n", 159 | "\n", 160 | "4. Внутри подземелий нападает монстр, рандомно генерурющийся по уровню персонажа (не может быть выше, чем уровень игрока). Бой происходит поэтапно:\n", 161 | "\n", 162 | " * Вначале действие игрока (получить информацию о монстре, выпить зелье, атаковать выбранным типом урона)\n", 163 | "\n", 164 | " * После действие монстра (атака минус броня персонажа по типу атаки)\n", 165 | "\n", 166 | " * И так далее до победы/поражения (HP <= 0)\n", 167 | "\n", 168 | " Ожидание ответа игрока длится 1 минуту. После этого он погибает и возрождается с нуля в самой первой локации" 169 | ], 170 | "metadata": { 171 | "id": "Y_IxGaHWITKC" 172 | } 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "source": [ 177 | "Итого:\n", 178 | "\n", 179 | "* Персонаж появляется в первой локации (городе) с начальным числом денег. В городе он может что-то прикупить себе или продать, а также покопаться в инвентаре и примерить одежду и получить информацию по себе\n", 180 | "\n", 181 | "* Далее он может отправиться в любое место, доступное в радиусе\n", 182 | "\n", 183 | "* Внутри подземелья пользователь убивает монстра, получает XP, и затем может также покопаться в инвентаре, получить по себе статистику и пойти далее в любую доступную в радиусе локацию" 184 | ], 185 | "metadata": { 186 | "id": "Sgt2CZtzKLCI" 187 | } 188 | }, 189 | { 190 | "cell_type": "markdown", 191 | "source": [ 192 | "Реализация по интерфейсу может быть любой (кнопки etc, главное - функциональность и возможность им пользоваться...)\n", 193 | "\n", 194 | "Что будет оцениваться? Возможность создать персонажа, возможность купить-продать вещь, посмотреть статистику персонажа-монстра (с учетом надетых вещей), ожидание пути до подземелья и ход боя. Если все работает - 10 баллов" 195 | ], 196 | "metadata": { 197 | "id": "B2P_HjL5MN2O" 198 | } 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": null, 203 | "metadata": { 204 | "id": "x8cjG7T8ynUH" 205 | }, 206 | "outputs": [], 207 | "source": [] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "source": [ 212 | "## Задание 2* (5 баллов)" 213 | ], 214 | "metadata": { 215 | "id": "S03zyMkY8o6s" 216 | } 217 | }, 218 | { 219 | "cell_type": "markdown", 220 | "source": [ 221 | "* Добавьте классы персонажей (которые могут давать различные бонусы на выбор) (1 балл)\n", 222 | "\n", 223 | "* Сделайте возможность рандомного нападения новых монстров в подземелье (2 балла)\n", 224 | "\n", 225 | "* Все, что в голову придумается, оценим)\n" 226 | ], 227 | "metadata": { 228 | "id": "M8OszSzHLXNW" 229 | } 230 | }, 231 | { 232 | "cell_type": "code", 233 | "source": [], 234 | "metadata": { 235 | "id": "agfuBiHtNZeF" 236 | }, 237 | "execution_count": null, 238 | "outputs": [] 239 | }, 240 | { 241 | "cell_type": "markdown", 242 | "source": [ 243 | "## Попугай для ДЗ" 244 | ], 245 | "metadata": { 246 | "id": "iEIZLjBn7VZc" 247 | } 248 | }, 249 | { 250 | "cell_type": "markdown", 251 | "source": [ 252 | "![](https://do-slez.com/uploads/posts/2020-02/1582813051_pesquets-dracula-parrots-birds-new-guinea-1-5e55392f17e1e__700.jpg)" 253 | ], 254 | "metadata": { 255 | "id": "drlXbB4w_RnG" 256 | } 257 | }, 258 | { 259 | "cell_type": "markdown", 260 | "source": [ 261 | "Сегодня у нас очень красивый орлиный попугай (орлиный за счет его клюва) и сходит в семейство щетиноголовых попугаев (видите какая щетина у него прям)" 262 | ], 263 | "metadata": { 264 | "id": "6z3hTgu9_Xk9" 265 | } 266 | }, 267 | { 268 | "cell_type": "markdown", 269 | "source": [ 270 | "![](https://img.theepochtimes.com/assets/uploads/2020/05/14/Dracula-Parrot-i.jpg)" 271 | ], 272 | "metadata": { 273 | "id": "BgnEsrFe_mGd" 274 | } 275 | }, 276 | { 277 | "cell_type": "markdown", 278 | "source": [ 279 | "Исторически живет в Новой Гвинее и его очень редко можно встретить в зоопарках из-за очень прихотливого питания (им обязательно нужны тропические фрукты для ферментации) и требований к содержанию (температура, влажность)\n", 280 | "\n", 281 | "Еще один, к сожалению, вымирающий вид, потому что на них охотятся индейцы за их красные перья (хоть и достаточно распространен в авикультуре, но выращивать его очень сложно)." 282 | ], 283 | "metadata": { 284 | "id": "GW5PDacP_yNY" 285 | } 286 | } 287 | ] 288 | } -------------------------------------------------------------------------------- /Lectures/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Lectures/.DS_Store -------------------------------------------------------------------------------- /Lectures/Lecture_1/Lecture_1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Lectures/Lecture_1/Lecture_1.pdf -------------------------------------------------------------------------------- /Lectures/Lecture_10/Lecture_10_Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "provenance": [], 7 | "collapsed_sections": [ 8 | "EdkG7ge6jCTL", 9 | "4dYJ4RTHT00R", 10 | "_amCJQvKnHQl", 11 | "Dan441XTq0cg", 12 | "X4dRMGGn7oxI", 13 | "n62LWlwT7ufP" 14 | ] 15 | }, 16 | "kernelspec": { 17 | "name": "python3", 18 | "display_name": "Python 3" 19 | }, 20 | "language_info": { 21 | "name": "python" 22 | } 23 | }, 24 | "cells": [ 25 | { 26 | "cell_type": "markdown", 27 | "source": [ 28 | "# Продвинутый Python, лекция 10\n", 29 | "\n", 30 | "**Лектор:** Петров Тимур\n", 31 | "\n", 32 | "**Семинаристы:** Петров Тимур, Коган Александра, Романченко Полина\n", 33 | "\n", 34 | "**Spoiler Alert:** в рамках курса нельзя изучить ни одну из тем от и до досконально (к сожалению, на это требуется больше времени, чем даже 3 часа в неделю). Но мы попробуем рассказать столько, сколько возможно :)\n", 35 | "\n", 36 | "Так как мы проходим веб-разработку, то это явно не та вещь, которая делается через colab. Поэтому здесь написан просто код, который можно воспроизвести локально" 37 | ], 38 | "metadata": { 39 | "id": "Yy3aW8Kwi-A-" 40 | } 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "source": [ 45 | "# Django" 46 | ], 47 | "metadata": { 48 | "id": "EdkG7ge6jCTL" 49 | } 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "source": [ 54 | "## Начала" 55 | ], 56 | "metadata": { 57 | "id": "4dYJ4RTHT00R" 58 | } 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "source": [ 63 | "Как мы уже говорили, если Flask - это буквально сделай все сам (и в этом его плюс и минус), то уже Django - это все сделано за тебя, пользуйся\n", 64 | "\n", 65 | "Давайте смотреть, что сделано и что тогда нам надо делать" 66 | ], 67 | "metadata": { 68 | "id": "4ogcAr5QlPNj" 69 | } 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "source": [ 74 | "По классике вначале просто попробуем установить и создать некоторый проект:" 75 | ], 76 | "metadata": { 77 | "id": "PN4IDZ_ikiPf" 78 | } 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": null, 83 | "metadata": { 84 | "id": "A-ZWK7zli7S_" 85 | }, 86 | "outputs": [], 87 | "source": [ 88 | "!pip install django" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "source": [ 94 | "!django-admin startproject mysite" 95 | ], 96 | "metadata": { 97 | "id": "yDo_a8jMkyFS" 98 | }, 99 | "execution_count": 5, 100 | "outputs": [] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "source": [ 105 | "!cd mysite; ls -la; cd mysite; ls -la" 106 | ], 107 | "metadata": { 108 | "colab": { 109 | "base_uri": "https://localhost:8080/" 110 | }, 111 | "id": "QMUDw82zlGbN", 112 | "outputId": "1ebafdc0-eab5-4d8f-82a2-cdb9c1703fda" 113 | }, 114 | "execution_count": 8, 115 | "outputs": [ 116 | { 117 | "output_type": "stream", 118 | "name": "stdout", 119 | "text": [ 120 | "total 16\n", 121 | "drwxr-xr-x 3 root root 4096 Nov 13 10:41 .\n", 122 | "drwxr-xr-x 1 root root 4096 Nov 13 10:41 ..\n", 123 | "-rwxr-xr-x 1 root root 662 Nov 13 10:41 manage.py\n", 124 | "drwxr-xr-x 2 root root 4096 Nov 13 10:41 mysite\n", 125 | "total 24\n", 126 | "drwxr-xr-x 2 root root 4096 Nov 13 10:41 .\n", 127 | "drwxr-xr-x 3 root root 4096 Nov 13 10:41 ..\n", 128 | "-rw-r--r-- 1 root root 389 Nov 13 10:41 asgi.py\n", 129 | "-rw-r--r-- 1 root root 0 Nov 13 10:41 __init__.py\n", 130 | "-rw-r--r-- 1 root root 3240 Nov 13 10:41 settings.py\n", 131 | "-rw-r--r-- 1 root root 748 Nov 13 10:41 urls.py\n", 132 | "-rw-r--r-- 1 root root 389 Nov 13 10:41 wsgi.py\n" 133 | ] 134 | } 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "source": [ 140 | "Какие файлы видим?\n", 141 | "\n", 142 | "* manage.py - утилита, позволяющая взаимодействовать с проектом различными способами\n", 143 | "\n", 144 | "* __ init __.py\n", 145 | "\n", 146 | "* asgi.py - точка входа для ASGI-совместимых веб-серверов для обслуживания вашего проекта (асинхронная коммуникация между сервером и приложением)\n", 147 | "\n", 148 | "* settings.py - настройки нашего приложения\n", 149 | "\n", 150 | "* urls.py - маппинг между views и страницами (что где выдавать)\n", 151 | "\n", 152 | "* wsgi.py - Точка входа для WSGI совместимых веб-серверов для работы с проектом\n", 153 | "\n", 154 | "Давайте запустим по дефолту и посмотрим!\n", 155 | "\n", 156 | "Видим совсем базовую страничку, что у нас все заработало" 157 | ], 158 | "metadata": { 159 | "id": "b8dHkw8glF9Z" 160 | } 161 | }, 162 | { 163 | "cell_type": "markdown", 164 | "source": [ 165 | "## Время писать что-то свое!" 166 | ], 167 | "metadata": { 168 | "id": "_amCJQvKnHQl" 169 | } 170 | }, 171 | { 172 | "cell_type": "markdown", 173 | "source": [ 174 | "Сделаем простое задание - создать страничку, которая будет отображать текущее время (в UTC). Для этого, как и во Flask, надо написать страничку, которая это будет показывать. Создадим файл views.py и запишем туда:" 175 | ], 176 | "metadata": { 177 | "id": "sIdZSNjJnJ5P" 178 | } 179 | }, 180 | { 181 | "cell_type": "code", 182 | "source": [ 183 | "from django.http import HttpResponse #внутри django зя ответы можно использовать модуль http\n", 184 | "import datetime\n", 185 | "\n", 186 | "def current_datetime(request):\n", 187 | " now = datetime.datetime.now() # Время\n", 188 | " html = \"It is now %s.\" % now # страничка\n", 189 | " return HttpResponse(html) # отправляем страничку через HttpResponse (нашу страницу)" 190 | ], 191 | "metadata": { 192 | "id": "o96rE44Qm4hD" 193 | }, 194 | "execution_count": null, 195 | "outputs": [] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "source": [ 200 | "Мы создали так называемый view - функция, которая принимает запрос и по нему выдает ответ (по аналогии с route внутри Flask). Но только тут непонятно, а что за страница, которая это все будет показывать\n", 201 | "\n", 202 | "В этом смысле есть отличие. Маппинг наших views и непосредственно страниц осуществляется через файл urls.py\n", 203 | "\n", 204 | "Импортируем наши views и добавим:" 205 | ], 206 | "metadata": { 207 | "id": "YyQX3PbVns_K" 208 | } 209 | }, 210 | { 211 | "cell_type": "code", 212 | "source": [ 213 | "from django.contrib import admin\n", 214 | "from django.urls import path\n", 215 | "from djangoProject.views import current_datetime # импортируем функцию\n", 216 | "\n", 217 | "urlpatterns = [\n", 218 | " path(\"admin/\", admin.site.urls), #админка сайта (про нее позже)\n", 219 | " path(\"\", current_datetime) #начальная страница\n", 220 | "]" 221 | ], 222 | "metadata": { 223 | "id": "dQ82gBYlp0ox" 224 | }, 225 | "execution_count": null, 226 | "outputs": [] 227 | }, 228 | { 229 | "cell_type": "markdown", 230 | "source": [ 231 | "Ура, видим то, что надо!" 232 | ], 233 | "metadata": { 234 | "id": "ST0sPXF4qyQi" 235 | } 236 | }, 237 | { 238 | "cell_type": "markdown", 239 | "source": [ 240 | "## Динамические ссылки" 241 | ], 242 | "metadata": { 243 | "id": "Dan441XTq0cg" 244 | } 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "source": [ 249 | "Смотрим на urls.py, и кажется, что для каждого сайта придется прописывать url... На самом деле нет, потому что Django тоже умеет в динамические ссылки. Для демонстрации напишем функцию, которая будет говорить, сколько у нас будет времени через n часов (ну вдруг кому-то надо)" 250 | ], 251 | "metadata": { 252 | "id": "iLL7K876q21h" 253 | } 254 | }, 255 | { 256 | "cell_type": "code", 257 | "source": [ 258 | "def hours_ahead(request, hour):\n", 259 | " dt = datetime.datetime.now() + datetime.timedelta(hours=hour)\n", 260 | " html = \"In %s hour(s), it will be %s.\" % (hour, dt)\n", 261 | " return HttpResponse(html)" 262 | ], 263 | "metadata": { 264 | "id": "ql0ApbB9rYnN" 265 | }, 266 | "execution_count": null, 267 | "outputs": [] 268 | }, 269 | { 270 | "cell_type": "markdown", 271 | "source": [ 272 | "А теперь смапим с urls.py:" 273 | ], 274 | "metadata": { 275 | "id": "UV5uarPfriIb" 276 | } 277 | }, 278 | { 279 | "cell_type": "code", 280 | "source": [ 281 | "from django.contrib import admin\n", 282 | "from django.urls import path\n", 283 | "from djangoProject.views import current_datetime, hours_ahead\n", 284 | "\n", 285 | "urlpatterns = [\n", 286 | " path(\"admin/\", admin.site.urls),\n", 287 | " path(\"\", current_datetime),\n", 288 | " path(\"/\", hours_ahead) #Внутри Django можно осуществлять т.н. захват переменных из URLа\n", 289 | "]" 290 | ], 291 | "metadata": { 292 | "id": "I3RiGhk7rlqb" 293 | }, 294 | "execution_count": null, 295 | "outputs": [] 296 | }, 297 | { 298 | "cell_type": "markdown", 299 | "source": [ 300 | "А можно писать и просто регулярки через re_path (можно указывать регулярки)" 301 | ], 302 | "metadata": { 303 | "id": "h1qFD3w0ub0O" 304 | } 305 | }, 306 | { 307 | "cell_type": "markdown", 308 | "source": [ 309 | "Какие есть динамические захваты по умолчанию?\n", 310 | "\n", 311 | "* str, int - все понятно\n", 312 | "\n", 313 | "* slug - любой набор букв-цифры-подчеркиваний\n", 314 | "\n", 315 | "* path - путь\n", 316 | "\n", 317 | "* uuid - 123e4567-e89b-12d3-a456-426655440000 (канонический вид идентификатора, состоит из 16-ичных числе)" 318 | ], 319 | "metadata": { 320 | "id": "EH4eLFf-tTBm" 321 | } 322 | }, 323 | { 324 | "cell_type": "markdown", 325 | "source": [ 326 | "## Шаблоны" 327 | ], 328 | "metadata": { 329 | "id": "X4dRMGGn7oxI" 330 | } 331 | }, 332 | { 333 | "cell_type": "markdown", 334 | "source": [ 335 | "Смогли сгенерировать ссылки - супер! А теперь под них надо делать страницы (и как обычно, мы не хотим для каждой писать свой собственный шаблон). Внутри Django есть внутренняя шаблонизация\n", 336 | "\n", 337 | "Основана на двух вещах: встроенная шаблонизация Django + Jinja2 (то есть без определенных деталей same из Flask). Давайте отрендерим самый базовый шаблон" 338 | ], 339 | "metadata": { 340 | "id": "HIHDaEwo72Q4" 341 | } 342 | }, 343 | { 344 | "cell_type": "code", 345 | "source": [ 346 | "from django.template import Template\n", 347 | "\n", 348 | "t = Template(\"My name is {{ name }}.\") #строим template и получаем объект типа Template\n", 349 | "print(t)" 350 | ], 351 | "metadata": { 352 | "id": "fDfdRClb9fUq" 353 | }, 354 | "execution_count": null, 355 | "outputs": [] 356 | }, 357 | { 358 | "cell_type": "markdown", 359 | "source": [ 360 | "Как в него запихнуть значение переменной? С помощью словаря контекста" 361 | ], 362 | "metadata": { 363 | "id": "r5uKlWFyANYr" 364 | } 365 | }, 366 | { 367 | "cell_type": "code", 368 | "source": [ 369 | "from django.template import Context, Template\n", 370 | "\n", 371 | "t = Template(\"My name is {{ name }}.\")\n", 372 | "c = {\"name\": \"Oleg\"} # контекст - это по существу словарь с необходимыми данными\n", 373 | "t.render(c) # Получаем то, что нужно" 374 | ], 375 | "metadata": { 376 | "id": "8rcjFP4OARoR" 377 | }, 378 | "execution_count": null, 379 | "outputs": [] 380 | }, 381 | { 382 | "cell_type": "markdown", 383 | "source": [ 384 | "Давайте завернем это в view:" 385 | ], 386 | "metadata": { 387 | "id": "PvcuPjEZAgiX" 388 | } 389 | }, 390 | { 391 | "cell_type": "code", 392 | "source": [ 393 | "def template_func(request):\n", 394 | " t = Template(\"My name is {{ name }}.\")\n", 395 | " return HttpResponse(t.render({\"name\": \"Oleg\"})) # Получаем то, что нужно" 396 | ], 397 | "metadata": { 398 | "id": "RDSD4Yj5BB2d" 399 | }, 400 | "execution_count": null, 401 | "outputs": [] 402 | }, 403 | { 404 | "cell_type": "markdown", 405 | "source": [ 406 | "Урааа, получилось! Суть простая: создаем шаблон, запихиваем внутри него необходимые переменные и живем-живем. Теперь давайте попробуем все сделать с помощью не шаблона из string, в из шаблона в отдельном файле. Для этого есть папочка templates. Запихнем все в layout.html" 407 | ], 408 | "metadata": { 409 | "id": "aWtNgigdBDoR" 410 | } 411 | }, 412 | { 413 | "cell_type": "code", 414 | "source": [ 415 | "from django.template.loader import get_template\n", 416 | "\n", 417 | "def template_func(request):\n", 418 | " t = get_template(\"layout.html\")\n", 419 | " c = {\"name\": \"Oleg\"} # контекст - это по существу словарь с необходимыми данными\n", 420 | " return HttpResponse(t.render(c)) # Получаем то, что нужно" 421 | ], 422 | "metadata": { 423 | "id": "LNeYnjDXCtb0" 424 | }, 425 | "execution_count": null, 426 | "outputs": [] 427 | }, 428 | { 429 | "cell_type": "markdown", 430 | "source": [ 431 | "Но можно еще проще сделать!" 432 | ], 433 | "metadata": { 434 | "id": "8nEYVlfoELTd" 435 | } 436 | }, 437 | { 438 | "cell_type": "code", 439 | "source": [ 440 | "from django.shortcuts import render\n", 441 | "\n", 442 | "def template_func(request):\n", 443 | " return render(request, \"layout.html\", context={\"name\": \"Oleg\"}) # Скажем по-тупому: отрендери все это)" 444 | ], 445 | "metadata": { 446 | "id": "vEYEV_DXEMxT" 447 | }, 448 | "execution_count": null, 449 | "outputs": [] 450 | }, 451 | { 452 | "cell_type": "markdown", 453 | "source": [ 454 | "И еще проще!" 455 | ], 456 | "metadata": { 457 | "id": "vOyjeoW_FSzv" 458 | } 459 | }, 460 | { 461 | "cell_type": "code", 462 | "source": [ 463 | "from django.shortcuts import render\n", 464 | "\n", 465 | "def template_func(request):\n", 466 | " name = \"Oleg\"\n", 467 | " return render(request, \"layout.html\", locals()) # передай все локальные переменные в качестве словаря со значениями" 468 | ], 469 | "metadata": { 470 | "id": "lH7x-S-fFUsI" 471 | }, 472 | "execution_count": null, 473 | "outputs": [] 474 | }, 475 | { 476 | "cell_type": "markdown", 477 | "source": [ 478 | "Чего не хватает для красоты? Правильно, редиректов:\n", 479 | "\n" 480 | ], 481 | "metadata": { 482 | "id": "vOShv4iQFvkd" 483 | } 484 | }, 485 | { 486 | "cell_type": "markdown", 487 | "source": [ 488 | "```\n", 489 | " Go back \n", 490 | "```\n", 491 | "\n", 492 | "Но для этого нужно в urls дать name для ссылки" 493 | ], 494 | "metadata": { 495 | "id": "SV1M6dQcHn18" 496 | } 497 | }, 498 | { 499 | "cell_type": "markdown", 500 | "source": [ 501 | "## Ошибки" 502 | ], 503 | "metadata": { 504 | "id": "n62LWlwT7ufP" 505 | } 506 | }, 507 | { 508 | "cell_type": "markdown", 509 | "source": [ 510 | "Создали шаблоны, urlы, теперь надо бы еще ошибки добавить, верно?\n", 511 | "\n", 512 | "Тут все просто, Django уже постарался, чтобы с этим не приходилось работать. Добавляем в urls вот такие штуки (это наши хэндлеры ошибок)\n", 513 | "\n", 514 | "```\n", 515 | "\n", 516 | "from django.conf.urls import handler400, handler403, handler404, handler500\n", 517 | "\n", 518 | "```\n", 519 | "\n", 520 | "И далее:\n", 521 | "\n", 522 | "```\n", 523 | "handler404 = 'djangoProject.views.handler404'\n", 524 | "handler500 = 'djangoProject.views.handler500' \n", 525 | "```\n", 526 | "\n", 527 | "И далее во views указываем функции и обрабатываем их. И тогда при любой ошибке будет выскакивать именно нужная страница" 528 | ], 529 | "metadata": { 530 | "id": "M871Y8KDNKDr" 531 | } 532 | }, 533 | { 534 | "cell_type": "markdown", 535 | "source": [ 536 | "Но мы хотим еще и вызывать ошибки, а не только их обрабатывать, ведь так? Давайте делать:" 537 | ], 538 | "metadata": { 539 | "id": "xX56eoGfSkFq" 540 | } 541 | }, 542 | { 543 | "cell_type": "code", 544 | "source": [ 545 | "from django.http import Http404\n", 546 | "\n", 547 | "raise(Http404(\"Error\"))" 548 | ], 549 | "metadata": { 550 | "id": "jt64wZPuSq7Q" 551 | }, 552 | "execution_count": null, 553 | "outputs": [] 554 | }, 555 | { 556 | "cell_type": "markdown", 557 | "source": [ 558 | "# SQL" 559 | ], 560 | "metadata": { 561 | "id": "fx84z_dXTX3r" 562 | } 563 | }, 564 | { 565 | "cell_type": "markdown", 566 | "source": [ 567 | "Для того, чтобы двинуться дальше (на семинаре мы поговорим про полную сборку с админкой, БД и так далее), нам необходимо разобраться базово с SQL\n", 568 | "\n", 569 | "Полезный инструмент: https://sql-ex.ru/ (пожалуйста, попрактикуйтесь на нем)\n", 570 | "\n", 571 | "А мы разберем базово этот язык запросов к таблицам (мы говорим про конкретную СУБД, а основы, которые верны везде)" 572 | ], 573 | "metadata": { 574 | "id": "TrjDTjWHTZPe" 575 | } 576 | }, 577 | { 578 | "cell_type": "markdown", 579 | "source": [ 580 | "## Структура" 581 | ], 582 | "metadata": { 583 | "id": "nefwxLF_ikpD" 584 | } 585 | }, 586 | { 587 | "cell_type": "markdown", 588 | "source": [ 589 | "Внутри SQL выделяют следущие группы запросов):\n", 590 | "\n", 591 | "* DDL (Data Definition Language) - для создания/изменения/удаления таблиц\n", 592 | "\n", 593 | "* DML (Data Mannipulation Language) - для манипуляции данными\n", 594 | "\n", 595 | "* DCL (Data Control Language) - для доступов к данным\n", 596 | "\n", 597 | "* TCL (Transaction Control Language) - управление транзакциями\n", 598 | "\n", 599 | "Мы поговорим только про DDL и DML (потому что это самое важное)" 600 | ], 601 | "metadata": { 602 | "id": "SzaOyksMin7P" 603 | } 604 | }, 605 | { 606 | "cell_type": "markdown", 607 | "source": [ 608 | "### DDL" 609 | ], 610 | "metadata": { 611 | "id": "ZHS-Ij87jTpg" 612 | } 613 | }, 614 | { 615 | "cell_type": "markdown", 616 | "source": [ 617 | "Есть 3 команды:\n", 618 | "\n", 619 | "* CREATE - создай таблицу\n", 620 | "\n", 621 | "```\n", 622 | "CREATE TABLE [Name](\n", 623 | " [column_1] ,\n", 624 | " [column_2] ,\n", 625 | " ...\n", 626 | ") - создай таблицу с названием Name с такими колонками\n", 627 | "```\n", 628 | "\n", 629 | "* ALTER - измени таблицу\n", 630 | "\n", 631 | "```\n", 632 | "ALTER TABLE [Name] ALTER COLUMN [column] - поменяй в таблице колонку, поставив нужный тип\n", 633 | "```\n", 634 | "\n", 635 | "* DROP - удали таблицу\n", 636 | "\n", 637 | "```\n", 638 | "DROP TABLE [Name]- удали таблицу Name\n", 639 | "\n", 640 | "```" 641 | ], 642 | "metadata": { 643 | "id": "GC_jQrcSjWSa" 644 | } 645 | }, 646 | { 647 | "cell_type": "markdown", 648 | "source": [ 649 | "### DML" 650 | ], 651 | "metadata": { 652 | "id": "ZZeXoh0zkMdC" 653 | } 654 | }, 655 | { 656 | "cell_type": "markdown", 657 | "source": [ 658 | "Структура запроса:\n", 659 | "\n", 660 | "```\n", 661 | "SELECT \n", 662 | "FROM \n", 663 | "[WHERE]\n", 664 | "[GROUP BY]\n", 665 | "[HAVING]\n", 666 | "[ORDER BY]\n", 667 | "[LIMIT] \n", 668 | "```" 669 | ], 670 | "metadata": { 671 | "id": "3W2Foo4fTxfQ" 672 | } 673 | }, 674 | { 675 | "cell_type": "markdown", 676 | "source": [ 677 | "### Join the Navy!" 678 | ], 679 | "metadata": { 680 | "id": "VRRSwMMaouFQ" 681 | } 682 | }, 683 | { 684 | "cell_type": "markdown", 685 | "source": [ 686 | "```\n", 687 | "select\n", 688 | " a.<>,\n", 689 | " b.<>\n", 690 | "from table_1 as a\n", 691 | "[LEFT|RIGHT|INNER] JOIN table_2 as b\n", 692 | "ON a.<> = b.<>\n", 693 | "```" 694 | ], 695 | "metadata": { 696 | "id": "XDo4wypQoyob" 697 | } 698 | }, 699 | { 700 | "cell_type": "markdown", 701 | "source": [ 702 | "# Птица дня" 703 | ], 704 | "metadata": { 705 | "id": "MDmSHBB3TP2f" 706 | } 707 | }, 708 | { 709 | "cell_type": "markdown", 710 | "source": [ 711 | "![](https://s.mediasalt.ru/images/245/245114/original.jpg)" 712 | ], 713 | "metadata": { 714 | "id": "lI9rp0MDp6-a" 715 | } 716 | }, 717 | { 718 | "cell_type": "markdown", 719 | "source": [ 720 | "Это индийский медоуказчик. По его названию в целом можно понять, что он делает. Правильно, громко кричит для того, чтобы приманить людей или медоедов к ульям. приведя к нужному месту, они начинают летать с дерева на дерево в ожидании, и когда кто-то разграбит улей и выведет всех пчел, она питается сотами (вообще удивительно, очень мало кто ест пчелиные соты)\n", 721 | "\n", 722 | "При этом полностью им доверять не следует: они кричат и приводят не только к пчелам, но и к леопардам, к собакам, к логову и так далее (почему - да непонятно). Как вариант - они надеятся потом насладиться червями от убитой туши. Но вообще считается, что это такой вариант коэволюции с человеком, который пользовался их услугами еще 2млн лет назад\n", 723 | "\n", 724 | "По-латински они зовутся максимально просто - Indicator. А еще они как кукушки - кладут яйца к другим птицам" 725 | ], 726 | "metadata": { 727 | "id": "gCc6AZ9Np-X1" 728 | } 729 | } 730 | ] 731 | } -------------------------------------------------------------------------------- /Lectures/Lecture_2/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Lectures/Lecture_2/archive.zip -------------------------------------------------------------------------------- /Lectures/Lecture_9/Lecture_9_Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "provenance": [], 7 | "collapsed_sections": [ 8 | "bRBavKLsJbw-" 9 | ] 10 | }, 11 | "kernelspec": { 12 | "name": "python3", 13 | "display_name": "Python 3" 14 | }, 15 | "language_info": { 16 | "name": "python" 17 | } 18 | }, 19 | "cells": [ 20 | { 21 | "cell_type": "markdown", 22 | "source": [ 23 | "# Продвинутый Python, лекция 9\n", 24 | "\n", 25 | "**Лектор:** Петров Тимур\n", 26 | "\n", 27 | "**Семинаристы:** Петров Тимур, Коган Александра, Романченко Полина\n", 28 | "\n", 29 | "**Spoiler Alert:** в рамках курса нельзя изучить ни одну из тем от и до досконально (к сожалению, на это требуется больше времени, чем даже 3 часа в неделю). Но мы попробуем рассказать столько, сколько возможно :)\n", 30 | "\n", 31 | "Так как мы проходим веб-разработку, то это явно не та вещь, которая делается через colab. Поэтому здесь написан просто код, который можно воспроизвести локально" 32 | ], 33 | "metadata": { 34 | "id": "bpVB6UyuIipb" 35 | } 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "source": [ 40 | "## Веб-разработка" 41 | ], 42 | "metadata": { 43 | "id": "zDItmkgHInuj" 44 | } 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "source": [ 49 | "Что же, посмотрели, как у других выглядят сайты, что и как они там делают (научились парсить даже все добро), давайте теперь самостоятельно делать! И делать будем, конечно же, с помощью Python\n", 50 | "\n", 51 | "Если вы попробуете вбить в поиск веб-разработка на Python, то увидите, что чаще всего вам предлагают 2 варианта: flask и django.\n", 52 | "\n", 53 | "Ну что же, попробуем освоить эти фреймворки (а также поговорим про Dash к теме визуализации)\n", 54 | "\n", 55 | "А зачем вообще учить оба? Они оба удовлетворяют ваше желание сделать приложение, на разница в том, что:\n", 56 | "\n", 57 | "* Django - очень много решений \"из коробки\", проще промышленная разработка, но за счет таких решений у вас не так много свободы с точки зрения кастомизации (особенно когда все надо менять часто). То есть Django - это т.н. full stack framework\n", 58 | "\n", 59 | "* Flask - в отличие от Django, это уже light-weight framework. Это значит, что в нем нет решений из коробки, он дает максимальную свободу (и ответственность, ведь приходится самому все реализовывать и делать), но за счет это получается большая кастомизация\n", 60 | "\n", 61 | "Поэтому полезно знать оба фреймворка хотя бы на очень общем уровне (да и в целом, если вам нужно какое-то легкое приложение, то стрелять из пушки по воробьям с помощью Django - идея сомнительная)" 62 | ], 63 | "metadata": { 64 | "id": "ok7ugDnEIsLp" 65 | } 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "source": [ 70 | "## Flask" 71 | ], 72 | "metadata": { 73 | "id": "pSgFMDE5JV_a" 74 | } 75 | }, 76 | { 77 | "cell_type": "markdown", 78 | "source": [ 79 | "### Самый простой сайт в мире" 80 | ], 81 | "metadata": { 82 | "id": "Q2Sws09ltn0N" 83 | } 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "source": [ 88 | "Давайте сотворим магию, а потом разберем, что случилось)" 89 | ], 90 | "metadata": { 91 | "id": "lix-Gn1RPETw" 92 | } 93 | }, 94 | { 95 | "cell_type": "code", 96 | "source": [ 97 | "%%writefile app.py\n", 98 | "\n", 99 | "from flask import Flask\n", 100 | "\n", 101 | "app = Flask(__name__)\n", 102 | "\n", 103 | "@app.route(\"/\")\n", 104 | "def hello_world():\n", 105 | " return \"

Hello, World!

\"\n", 106 | "\n", 107 | "if __name__ == '__main__':\n", 108 | " app.run()" 109 | ], 110 | "metadata": { 111 | "colab": { 112 | "base_uri": "https://localhost:8080/" 113 | }, 114 | "id": "6f3HXqDpp7JF", 115 | "outputId": "9c18deea-ed03-4348-96e2-76ab605298cb" 116 | }, 117 | "execution_count": 1, 118 | "outputs": [ 119 | { 120 | "output_type": "stream", 121 | "name": "stdout", 122 | "text": [ 123 | "Writing app.py\n" 124 | ] 125 | } 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "source": [ 131 | "!flask run" 132 | ], 133 | "metadata": { 134 | "id": "Gl7gklPYPagR" 135 | }, 136 | "execution_count": null, 137 | "outputs": [] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "source": [ 142 | "Ура, мы получили самый базовый сайт! Что же мы сделали?\n", 143 | "\n", 144 | "* app - создаем инстанс нашего Web Server Gateway Interface - WSGI (взаимодействие сервера и нашего кода), указываем модуль (если он один, то надо использовать просто __ name __)\n", 145 | "\n", 146 | "* route - декоратор, который\n", 147 | "\n", 148 | "* def hello_world() -> функция, которая будет строить на страничку\n", 149 | "\n", 150 | "* run - запускаем приложение" 151 | ], 152 | "metadata": { 153 | "id": "X3nuYQe8otWP" 154 | } 155 | }, 156 | { 157 | "cell_type": "markdown", 158 | "source": [ 159 | "Запустили код, работает! А теперь хотим изменить код, что делать? Приложение закрыть, исправить, а затем опять запустить. Не нравится, хотим изменять онлайн, ну давайте сделаем!" 160 | ], 161 | "metadata": { 162 | "id": "eqEFLE1ur2zX" 163 | } 164 | }, 165 | { 166 | "cell_type": "code", 167 | "source": [ 168 | "app.run(debug=True)" 169 | ], 170 | "metadata": { 171 | "id": "HQ0jEsF6s2eS" 172 | }, 173 | "execution_count": null, 174 | "outputs": [] 175 | }, 176 | { 177 | "cell_type": "markdown", 178 | "source": [ 179 | "Ура, меняем-обновляем, все работает!" 180 | ], 181 | "metadata": { 182 | "id": "L2IVAqTytXLs" 183 | } 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "source": [ 188 | "### Усложняем и даем запросы" 189 | ], 190 | "metadata": { 191 | "id": "poUxHALItqzX" 192 | } 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "source": [ 197 | "Теперь хотим не одну страничку, а хотим несколько страниц! Давайте делать" 198 | ], 199 | "metadata": { 200 | "id": "PQU03fKlttNY" 201 | } 202 | }, 203 | { 204 | "cell_type": "code", 205 | "source": [ 206 | "@app.route('/hello')\n", 207 | "def hello():\n", 208 | " return 'Hello World'" 209 | ], 210 | "metadata": { 211 | "id": "S4XQg554tyzS" 212 | }, 213 | "execution_count": null, 214 | "outputs": [] 215 | }, 216 | { 217 | "cell_type": "markdown", 218 | "source": [ 219 | "Переходим по http://127.0.0.1:5000/hello и получаем необходимый результат" 220 | ], 221 | "metadata": { 222 | "id": "NIk96RvyuDT4" 223 | } 224 | }, 225 | { 226 | "cell_type": "markdown", 227 | "source": [ 228 | "А еще можем делать все динамически! А зачем? А затем, чтобы давать идентификацию (приходишь, а там страничка чисто для тебя)" 229 | ], 230 | "metadata": { 231 | "id": "VztJkvE0vJmL" 232 | } 233 | }, 234 | { 235 | "cell_type": "code", 236 | "source": [ 237 | "@app.route('/user/') # переменные задаются через <>\n", 238 | "def show_user_profile(username):\n", 239 | " return 'User %s' % username\n", 240 | "\n", 241 | "@app.route('/post/') # отдельно можем задать ограничение на тип (например, здесь указываем int)\n", 242 | "def show_post(post_id):\n", 243 | " return 'Post %d' % post_id" 244 | ], 245 | "metadata": { 246 | "id": "0ggMS7lMwF5F" 247 | }, 248 | "execution_count": null, 249 | "outputs": [] 250 | }, 251 | { 252 | "cell_type": "markdown", 253 | "source": [ 254 | "Ну окей, создали какой-то сайт. А где же те самые запросы POST, GET, о которых мы уже сколько говорили, как там авторизацию делать и всякое такое? Конечно же такое можно задать!" 255 | ], 256 | "metadata": { 257 | "id": "s4LTJrYiltu4" 258 | } 259 | }, 260 | { 261 | "cell_type": "code", 262 | "source": [ 263 | "from flask import request\n", 264 | "@app.route('/login', methods=['GET', 'POST']) #указываем методы, которые обрабатываем\n", 265 | "def login():\n", 266 | " if request.method == 'POST': # если метод POST, то сделай одно\n", 267 | " do_the_login()\n", 268 | " else:\n", 269 | " show_the_login_form() # иначе другое (GET)" 270 | ], 271 | "metadata": { 272 | "id": "cNxErB7_l50x" 273 | }, 274 | "execution_count": null, 275 | "outputs": [] 276 | }, 277 | { 278 | "cell_type": "markdown", 279 | "source": [ 280 | "На все ответы с сервера отвечает request. Что тут есть:\n", 281 | "\n", 282 | "* method - какой запрос был (GET, POST etc)\n", 283 | "\n", 284 | "* form - при заполнении формы etc здесь будут находиться переменные \n", 285 | "\n", 286 | "* args - все наши ключи внутри url-запроса (работает вот так: request.args.get(\"key\", \"\"))\n", 287 | "\n", 288 | "* files - если были загружены файлы\n", 289 | "\n", 290 | "* cookies - ПЕЧЕНЬКИ!\n", 291 | "\n", 292 | "![](https://i.pinimg.com/originals/08/87/d4/0887d45e7bd3d3aaba12638863df8f48.jpg)" 293 | ], 294 | "metadata": { 295 | "id": "f2iBbOTnWtGp" 296 | } 297 | }, 298 | { 299 | "cell_type": "code", 300 | "source": [ 301 | "from flask import request\n", 302 | "\n", 303 | "@app.route('/upload', methods=['GET', 'POST'])\n", 304 | "def upload_file():\n", 305 | " if request.method == 'POST':\n", 306 | " f = request.files['the_file'] # для того, чтобы посмотреть на название файла, можно использовать request.filename\n", 307 | " f.save('/var/www/uploads/uploaded_file.txt')" 308 | ], 309 | "metadata": { 310 | "id": "ZlJTK7MqZSYc" 311 | }, 312 | "execution_count": null, 313 | "outputs": [] 314 | }, 315 | { 316 | "cell_type": "markdown", 317 | "source": [ 318 | "### Генерация шаблонов и статика" 319 | ], 320 | "metadata": { 321 | "id": "5oE2H4-vSebC" 322 | } 323 | }, 324 | { 325 | "cell_type": "markdown", 326 | "source": [ 327 | "Что-то простое разобрали, как просто сделать странички. Но вот есть проблема:\n", 328 | "\n", 329 | "У нас есть сайт, в котором мы хотим иметь единый шаблон (скажем, у нас есть та же вики ФКН, которая на любой странице выглядит одинаково). Прописывать его каждый раз не хочется. Может быть мы можем сделать общий шаблон, по которому все будет генерироваться сразу, а единственное, что нам надо будет делать - это только добавлять новую информацию?\n", 330 | "\n", 331 | "Конечно можем! За шаблоны внутри Flask отвечает библиотека [Jinja2](http://jinja.pocoo.org/docs/templates). Давайте попробуем что-нибудь соорудить уже через шаблонизатор:" 332 | ], 333 | "metadata": { 334 | "id": "XXyLRy4bSsAq" 335 | } 336 | }, 337 | { 338 | "cell_type": "code", 339 | "source": [ 340 | "from flask import render_template\n", 341 | "\n", 342 | "@app.route('/hello/')\n", 343 | "@app.route('/hello/')\n", 344 | "def hello(name=None):\n", 345 | " return render_template('hello.html', name=name)" 346 | ], 347 | "metadata": { 348 | "id": "XxEvVeRuToo3" 349 | }, 350 | "execution_count": null, 351 | "outputs": [] 352 | }, 353 | { 354 | "cell_type": "markdown", 355 | "source": [ 356 | "Опа, словили ошибку! (потому что у нас нет такого шаблона). Надо создать, но где?\n", 357 | "\n", 358 | "Все шаблоны должны находиться в отдельной папке templates. Давайте туда и зафигачим вот такой шаблон:\n", 359 | "\n", 360 | "```\n", 361 | "\n", 362 | "Hello from Flask\n", 363 | "{% if name %}\n", 364 | "

Hello {{ name }}!

\n", 365 | "{% else %}\n", 366 | "

Hello World!

\n", 367 | "{% endif %}\n", 368 | "```" 369 | ], 370 | "metadata": { 371 | "id": "EnroKTEKT78L" 372 | } 373 | }, 374 | { 375 | "cell_type": "markdown", 376 | "source": [ 377 | "Ура, успех! Но как это написано немного непонятно, для этого нужно обратиться к [документации](https://jinja.palletsprojects.com/en/3.1.x/templates/)\n" 378 | ], 379 | "metadata": { 380 | "id": "bX31lOLwUnBD" 381 | } 382 | }, 383 | { 384 | "cell_type": "markdown", 385 | "source": [ 386 | "### Перенаправления" 387 | ], 388 | "metadata": { 389 | "id": "JCW5A3ErZ0RL" 390 | } 391 | }, 392 | { 393 | "cell_type": "markdown", 394 | "source": [ 395 | "Окей, хотим еще уметь перенаправлять (допустим, у нас есть какая-нибудь страница, с которой мы хотим сразу направить куда-то еще) - скажем, с формы оплаты после оплаты возвращать на начальную страницу\n", 396 | "\n", 397 | "И такое мы умеем!" 398 | ], 399 | "metadata": { 400 | "id": "hVlBr5ZRZ5Kg" 401 | } 402 | }, 403 | { 404 | "cell_type": "code", 405 | "source": [ 406 | "from flask import abort, redirect, url_for\n", 407 | "\n", 408 | "@app.route('/')\n", 409 | "def index():\n", 410 | " return redirect(url_for('login')) #сделай редирект на страницу с login\n", 411 | "\n", 412 | "@app.route('/login')\n", 413 | "def login():\n", 414 | " abort(401) # Выдай ошибку 401 " 415 | ], 416 | "metadata": { 417 | "id": "0OMEGDpraHPU" 418 | }, 419 | "execution_count": null, 420 | "outputs": [] 421 | }, 422 | { 423 | "cell_type": "code", 424 | "source": [ 425 | "from flask import render_template\n", 426 | "\n", 427 | "@app.errorhandler(404) #отдельный декоратор, который будет обрабатывать конкретно ошибку 404\n", 428 | "def page_not_found(error):\n", 429 | " return render_template('page_not_found.html'), 404" 430 | ], 431 | "metadata": { 432 | "id": "zVM3YOhEZqk-" 433 | }, 434 | "execution_count": null, 435 | "outputs": [] 436 | }, 437 | { 438 | "cell_type": "markdown", 439 | "source": [ 440 | "### Сессии" 441 | ], 442 | "metadata": { 443 | "id": "oS_BdDm3aUr3" 444 | } 445 | }, 446 | { 447 | "cell_type": "markdown", 448 | "source": [ 449 | "Чего же не хватает?\n", 450 | "\n", 451 | "Вот у нас есть пользователь, который залогинился и хочет что-нибудь смотреть, но при этом мы хотим сохранять информацию о том, что пользователь залогинен. Как это делать?\n", 452 | "\n", 453 | "Для этого есть объект сессии:" 454 | ], 455 | "metadata": { 456 | "id": "NNKQ9TepaWp6" 457 | } 458 | }, 459 | { 460 | "cell_type": "code", 461 | "source": [ 462 | "from flask import Flask, session, redirect, url_for, escape, request\n", 463 | "app = Flask(__name__)\n", 464 | "\n", 465 | "@app.route('/')\n", 466 | "def index():\n", 467 | " if 'username' in session: \n", 468 | " return 'Logged in as %s' % escape(session['username']) #escape заменяет все специсимволы на безопасные (потому что можно же ломать сайты) \n", 469 | " return 'You are not logged in'\n", 470 | "\n", 471 | "@app.route('/login', methods=['GET', 'POST'])\n", 472 | "def login():\n", 473 | " if request.method == 'POST':\n", 474 | " session['username'] = request.form['username']\n", 475 | " return redirect(url_for('index'))\n", 476 | " return '''\n", 477 | "
\n", 478 | "

\n", 479 | "

\n", 480 | " '''\n", 481 | "\n", 482 | "@app.route('/logout')\n", 483 | "def logout():\n", 484 | " # удалить из сессии имя пользователя, если оно там есть\n", 485 | " session.pop('username', None)\n", 486 | " return redirect(url_for('index'))\n", 487 | " \n", 488 | "app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' #секретный ключ (а зачем он? А чтобы пользователь куки не менял)" 489 | ], 490 | "metadata": { 491 | "id": "xyeZU6CaauGi" 492 | }, 493 | "execution_count": null, 494 | "outputs": [] 495 | }, 496 | { 497 | "cell_type": "markdown", 498 | "source": [ 499 | "Как сгенерировать ключ?" 500 | ], 501 | "metadata": { 502 | "id": "blNaNWhxbyO9" 503 | } 504 | }, 505 | { 506 | "cell_type": "code", 507 | "source": [ 508 | "import os\n", 509 | "\n", 510 | "os.urandom(24) #подходяший рандомный ключ, максимально случайный" 511 | ], 512 | "metadata": { 513 | "colab": { 514 | "base_uri": "https://localhost:8080/" 515 | }, 516 | "id": "IpxjuIiqbzi-", 517 | "outputId": "49edcde6-9970-4cd7-91d9-a772eb7380a9" 518 | }, 519 | "execution_count": 1, 520 | "outputs": [ 521 | { 522 | "output_type": "execute_result", 523 | "data": { 524 | "text/plain": [ 525 | "b'@\\x11\\xaex\\xfd5*\\x81\\xfc\\xa3\\x9e\\xe40\\xbc\\xff\\xb1\\xe4\\xa1\\x99\\xa5\\xc0VT\\x98'" 526 | ] 527 | }, 528 | "metadata": {}, 529 | "execution_count": 1 530 | } 531 | ] 532 | }, 533 | { 534 | "cell_type": "markdown", 535 | "source": [ 536 | "## Применяем наши знания на практике!" 537 | ], 538 | "metadata": { 539 | "id": "Gj0HlbGeaa_c" 540 | } 541 | }, 542 | { 543 | "cell_type": "markdown", 544 | "source": [ 545 | "Все это здорово, но пока кажется какими-то мазками без общей структуры и смысла, давайте теперь возьмем и это применим, сделав свой минимальный Твиттер.\n", 546 | "\n", 547 | "Давайте ТЗ:\n", 548 | "\n", 549 | "1. Должна быть аутентификация пользователя (имя-логин)\n", 550 | "\n", 551 | "2. Возможность добавить свой собственный текст в ленту (с названием)\n", 552 | "\n", 553 | "3. Наличие ленты, в которой будут все твиты, упорядоченные по дате\n", 554 | "\n", 555 | "Сейчас мы разберемся с самой сложной частью (а именно с работой БД, а далее на семинаре построим уже отображение с точки зрения веба)" 556 | ], 557 | "metadata": { 558 | "id": "rjSkVPUaafV7" 559 | } 560 | }, 561 | { 562 | "cell_type": "markdown", 563 | "source": [ 564 | "### Структура проекта" 565 | ], 566 | "metadata": { 567 | "id": "E4hsKSzObY8C" 568 | } 569 | }, 570 | { 571 | "cell_type": "markdown", 572 | "source": [ 573 | "Первое, что надо сделать - создать структуру. Выглядеть она будет так:\n", 574 | "\n", 575 | "```\n", 576 | "/deep_twitter\n", 577 | " deep_twitter.py\n", 578 | " /static - статичные файлы (всякие CSS и JS для отрисовок)\n", 579 | " /templates - шаблоны\n", 580 | "```" 581 | ], 582 | "metadata": { 583 | "id": "slXjKTkUbfAx" 584 | } 585 | }, 586 | { 587 | "cell_type": "markdown", 588 | "source": [ 589 | "### Хранение данных" 590 | ], 591 | "metadata": { 592 | "id": "c3qPQqlwcUit" 593 | } 594 | }, 595 | { 596 | "cell_type": "markdown", 597 | "source": [ 598 | "Надо бы где-нибудь данные хранить... А, точно, во Flask так тоже можно сделать! Для этого надо создать схему для таблиц\n", 599 | "\n", 600 | "Создаем файлик ```scheme.sql```, внутри которой напишем:\n", 601 | "\n", 602 | "```\n", 603 | "drop table if exists entries;\n", 604 | "create table entries (\n", 605 | " id integer primary key autoincrement,\n", 606 | " title text not null,\n", 607 | " text text not null\n", 608 | ");\n", 609 | "```\n", 610 | "\n", 611 | "Что тут случилось? Мы создали табличку, которая у нас будет хранить все для ленты: id твита, название твита и сам текст" 612 | ], 613 | "metadata": { 614 | "id": "oi_ldLhecW3U" 615 | } 616 | }, 617 | { 618 | "cell_type": "markdown", 619 | "source": [ 620 | "### Разберемся с БД" 621 | ], 622 | "metadata": { 623 | "id": "-OUC19zcxwnp" 624 | } 625 | }, 626 | { 627 | "cell_type": "markdown", 628 | "source": [ 629 | "Напишем несколько функций, которые будут работать с нашим БД\n", 630 | "\n", 631 | "Будем работать с [sqlite3](https://docs.python.org/3/library/sqlite3.html), которая поддерживается Flaskом (на самом деле можно не только sqlite3 использовать, на с ним максимально просто)" 632 | ], 633 | "metadata": { 634 | "id": "ueAh6mm3yRMA" 635 | } 636 | }, 637 | { 638 | "cell_type": "code", 639 | "source": [ 640 | "import sqlite3\n", 641 | "import os\n", 642 | "from flask import Flask, request, redirect, url_for, abort, render_template, flash\n", 643 | "\n", 644 | "def connect_db():\n", 645 | " \"\"\"Соединяет с указанной базой данных.\"\"\"\n", 646 | " rv = sqlite3.connect(app.config['DATABASE']) # внутри конфигураций надо будет указать БД, в которую мы будем все хранить\n", 647 | " rv.row_factory = sqlite3.Row #инстанс для итерации по строчкам (может брать по строке и выдавать)\n", 648 | " return rv\n", 649 | "\n", 650 | "def get_db():\n", 651 | " \"\"\"Если ещё нет соединения с базой данных, открыть новое - для текущего контекста приложения\"\"\"\n", 652 | " if not hasattr(g, 'sqlite_db'): #g - это наша глобальная переменная, являющасяс объектом отрисовки\n", 653 | " g.sqlite_db = connect_db()\n", 654 | " return g.sqlite_db\n", 655 | "\n", 656 | "@app.teardown_appcontext #декоратор при разрыве connection\n", 657 | "def close_db(error): #закрытие может проходить как нормально, так и с ошибкой, которую можно обрабатывать\n", 658 | " \"\"\"Закрываем БД при разрыве\"\"\"\n", 659 | " if hasattr(g, 'sqlite_db'):\n", 660 | " g.sqlite_db.close()\n", 661 | "\n", 662 | "def init_db():\n", 663 | " \"\"\"Инициализируем наше БД\"\"\"\n", 664 | " with app.app_context(): # внутри app_context app и g связаны\n", 665 | " db = get_db()\n", 666 | " with app.open_resource('schema.sql', mode='r') as f: \n", 667 | " db.cursor().executescript(f.read())\n", 668 | " db.commit()" 669 | ], 670 | "metadata": { 671 | "id": "tBUGFn_9x7GX" 672 | }, 673 | "execution_count": null, 674 | "outputs": [] 675 | }, 676 | { 677 | "cell_type": "markdown", 678 | "source": [ 679 | "Осталось написать функции, которые будут на сайте показывать записи, а также добавлять запись в БД" 680 | ], 681 | "metadata": { 682 | "id": "0QvHm09_zMuN" 683 | } 684 | }, 685 | { 686 | "cell_type": "code", 687 | "source": [ 688 | "@app.route('/')\n", 689 | "def show_entries():\n", 690 | " db = get_db()\n", 691 | " cur = db.execute('select title, text from entries order by id desc')\n", 692 | " entries = cur.fetchall()\n", 693 | " return render_template('show_entries.html', entries=entries)\n", 694 | "\n", 695 | "@app.route('/add', methods=['POST'])\n", 696 | "def add_entry():\n", 697 | " if not session.get('logged_in'):\n", 698 | " abort(401)\n", 699 | " db = get_db()\n", 700 | " db.execute('insert into entries (title, text) values (?, ?)', [request.form['title'], request.form['text']])\n", 701 | " db.commit()\n", 702 | " flash('New entry was successfully posted')\n", 703 | " return redirect(url_for('show_entries'))" 704 | ], 705 | "metadata": { 706 | "id": "VjncKXEyzSxw" 707 | }, 708 | "execution_count": null, 709 | "outputs": [] 710 | }, 711 | { 712 | "cell_type": "markdown", 713 | "source": [ 714 | "## Животное дня" 715 | ], 716 | "metadata": { 717 | "id": "bRBavKLsJbw-" 718 | } 719 | }, 720 | { 721 | "cell_type": "markdown", 722 | "source": [ 723 | "![](https://animaljournal.ru/articles/wild/primati/koshachiy_lemur/detenish_lemura1.jpg)" 724 | ], 725 | "metadata": { 726 | "id": "Po6ms7FNJ3v4" 727 | } 728 | }, 729 | { 730 | "cell_type": "markdown", 731 | "source": [ 732 | "Это кошачий лемур. Их все так или иначе видели (по крайней мере вот в таком виде):" 733 | ], 734 | "metadata": { 735 | "id": "kk6rs4ERJ5YS" 736 | } 737 | }, 738 | { 739 | "cell_type": "markdown", 740 | "source": [ 741 | "![](https://www.meme-arsenal.com/memes/819abc6f23381d803a640e91092ea4a1.jpg)" 742 | ], 743 | "metadata": { 744 | "id": "OSsmBDTgKEce" 745 | } 746 | }, 747 | { 748 | "cell_type": "markdown", 749 | "source": [ 750 | "На Мадагаскаре (где они и обитают) их зовут маки! По размерам как кошка (действительно), при этом хвост может весить примерно половину от всего веса лемура, и это неудивительно - хвост лемура играют важную роль в его жизни.\n", 751 | "\n", 752 | "Помимо самых понятных прикладных функций (с помощью хвоста лемуры удерживают равновесие, будучи на ветках, а также балансируют с его помощью при прыжке), хвост также выполняет социальные функции\n", 753 | "\n", 754 | "С его помощью он более заметен своим сородичам, а также показывают, кто здесь главный (через секрет, которым они этот самый хвост обмазывают)\n", 755 | "\n", 756 | "А еще посмотрите, как они сидят)" 757 | ], 758 | "metadata": { 759 | "id": "GLj3Arp-KMQr" 760 | } 761 | }, 762 | { 763 | "cell_type": "markdown", 764 | "source": [ 765 | "![](https://upload.wikimedia.org/wikipedia/commons/thumb/9/93/Ring.tailed.lemur.situp.arp.jpg/1024px-Ring.tailed.lemur.situp.arp.jpg)" 766 | ], 767 | "metadata": { 768 | "id": "2yPxHCkDLaEC" 769 | } 770 | }, 771 | { 772 | "cell_type": "markdown", 773 | "source": [ 774 | "Лемуры - социальные животные, живут группой по 30 особей (причем у них матриархат), причем у них максимально яркая социальность: будучи одни, они просто с ума сходят, поэтому нормально изучить их когнитивные способности достаточно сложно" 775 | ], 776 | "metadata": { 777 | "id": "Kh7Sr2VHLptu" 778 | } 779 | } 780 | ] 781 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Курс "Продвинутый Python" для 2 курса ПМИ ФКН 2 | -------------------------------------------------------------------------------- /Seminars/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/.DS_Store -------------------------------------------------------------------------------- /Seminars/Seminar_1/test_git/Hello.txt: -------------------------------------------------------------------------------- 1 | It's Wednesday, my dudes 2 | Good luck 3 | There is no point in your existence if you have never meowed back at the kitty. 4 | I love parrots too! 5 | 6 | $$$$$$$$$ $$$ 7 | $$$$$$$$ ( &) $$$$ 8 | $$$$$$$$$$ $$$$ 9 | $$$$$$$$$$$$$$$ § §§§ $ 10 | $$$$$$$$$$$$$$$$$$§§ ?§ §§§ $ 11 | $$$$$$$$$$$$$$$$$$$$$§§ §§§ § $ 12 | $$$$$$$$$$$$$$$$$$ $$$$ § §§ § $ 13 | $$$$$$$$$$$$$ $$$$$$$$ $ § 14 | $$$$$$$$$$$$$$$$$$$$$$$$ $ 15 | $$$$$$$$$ $$$$$$$$$$$$$$$$ $ 16 | $$$$$$$$ §$$$$$$$$$§$$$$$$$$ $ 17 | $$$$$$$ $$§§§$$$§$$§§$$$$§§$ $$$ 18 | $$$$$$ §§§$$§§$$§§§$$§§$$§ ?$ $$$ 19 | $$$$$ $§§§$$$$§§§$$$§§§§§$$ $$$$$ 20 | $$$$$ §§§§§§$$§§§$$§§§§§§ § $$$$$$ 21 | $$$$ §§0§§§§§§§§§§§§§§§ ? $$$$$$$ 22 | $$$ 000§§§00§§§§§000§00§ $$$$$$ 23 | $$ 0000§§0000§§§00§0§ $$$$$$ 24 | $ 0000§§00000§0000 $$$$$$$ 25 | §§00000§000§0000 $$$$$$$$ 26 | §00000§0000000 $$$$$$$$$$ 27 | 000§00000§ §§§§$$$$$$$$$$$$$ ...... 28 | 0000§00§§§§§§$$$$$$$$ $$$$$$$$§§§§§ 29 | 000§0 §§§§§§$$$$$$$ §000 $$$§§§ 000000 0000000000000 30 | 00 §§§§§$$$$$$$ §§00000 $§§§ 00000000 000000000000 31 | §§§§$$$$$$$ §§§ ’’ §§§ ???’ 32 | §§§$$$$$$ 33 | §§$$$$$$$ 34 | $$$$$$$$ 35 | $$$$$$$$ 36 | $$$$$$$$ 37 | $$$$$$$$ 38 | $$$$$$$ 39 | $$$$$$ 40 | $$$$ 41 | 42 | Good news, everyone! -------------------------------------------------------------------------------- /Seminars/Seminar_10/Seminar_10_Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "provenance": [] 7 | }, 8 | "kernelspec": { 9 | "name": "python3", 10 | "display_name": "Python 3" 11 | }, 12 | "language_info": { 13 | "name": "python" 14 | } 15 | }, 16 | "cells": [ 17 | { 18 | "cell_type": "markdown", 19 | "source": [ 20 | "# Продвинутый Python, семинар 10\n", 21 | "\n", 22 | "**Лектор:** Петров Тимур\n", 23 | "\n", 24 | "**Семинаристы:** Петров Тимур, Коган Александра, Романченко Полина\n", 25 | "\n", 26 | "**Spoiler Alert:** в рамках курса нельзя изучить ни одну из тем от и до досконально (к сожалению, на это требуется больше времени, чем даже 3 часа в неделю). Но мы попробуем рассказать столько, сколько возможно :)\n", 27 | "\n", 28 | "Так как мы проходим веб-разработку, то это явно не та вещь, которая делается через colab. Поэтому здесь написан просто код, который можно воспроизвести локально" 29 | ], 30 | "metadata": { 31 | "id": "FDlN_UHuL_nK" 32 | } 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "source": [ 37 | "## Базы данных в Django" 38 | ], 39 | "metadata": { 40 | "id": "Lf5tssZUMHOh" 41 | } 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "source": [ 46 | "Итак, теперь для того, чтобы сделать что-то разумное, нам необходимо иметь базу даннных, внутри которой мы будем что-либо хранить. Понятное дело, что это все добро нужно постоянно, поэтому Django и здесь позаботилось о том, чтобы можно было все сделать легко и из под коробки.\n", 47 | "\n", 48 | "Для начала идем в settings.py и посмотрим, что там лежит:\n", 49 | "\n", 50 | "```\n", 51 | "# Database\n", 52 | "# https://docs.djangoproject.com/en/4.1/ref/settings/#databases\n", 53 | "\n", 54 | "DATABASES = {\n", 55 | " \"default\": {\n", 56 | " \"ENGINE\": \"django.db.backends.sqlite3\",\n", 57 | " \"NAME\": BASE_DIR / \"db.sqlite3\",\n", 58 | " }\n", 59 | "}\n", 60 | "```" 61 | ], 62 | "metadata": { 63 | "id": "YVjN1N2gwqv2" 64 | } 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "source": [ 69 | "Что здесь может быть:\n", 70 | "\n", 71 | "* ENGINE - какой диалект SQL используем (PostrgreSQL, MySQL, SQLite3, Oracle)\n", 72 | " \n", 73 | "* NAME - Название\n", 74 | "\n", 75 | "* USER - Логин\n", 76 | "\n", 77 | "* PASSWORD - Пароль\n", 78 | "\n", 79 | "* HOST - на каком хосте лежит" 80 | ], 81 | "metadata": { 82 | "id": "sKSzH-t2xgvy" 83 | } 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "source": [ 88 | "Базово (для каких-то локальных вещей) нам подходит простой SQLite3 (он не требует ни указания логина/пароля, единственное, что требудется - указать в Name расположение. Про коннекторы мы поговорим через лекцию)\n", 89 | "\n", 90 | "Что же, на чем строить добро у нас есть, теперь давайте заводить таблички!\n", 91 | "\n", 92 | "Сделаем вначале:\n", 93 | "\n", 94 | "```\n", 95 | "python manage.py startapp posts\n", 96 | "```\n", 97 | "\n", 98 | "Что делает данная команда? Создает необходимые компоненты для взаимодействия (у нас получилась папка posts, где мы с вами будем что-нибудь хранить).\n", 99 | "\n", 100 | "Первое, что мы хотим сделать - это проиницилизировать нужные таблицы. Идем в models.py и создаем все, что нам нужно. Пойдем по логике, что опять делаем Твиттер:\n", 101 | "\n", 102 | "* Пользователи\n", 103 | "\n", 104 | "* Твиты\n", 105 | "\n", 106 | "* Реплаи (давайте не считать их отдельными постами)\n", 107 | "\n", 108 | "Внутри Django DB строится через модуль ```django.db```, внутри которого есть ```models```\n", 109 | "\n", 110 | "[Документация](https://docs.djangoproject.com/en/4.1/topics/db/models/)\n", 111 | "\n", 112 | "В чем прелесть Models? Базово: мы создаем таблицу без использования SQL (то есть он самостоятельно скомпилирует это в SQL и выполнит за вас), наличие сразу id (самостоятельно указывать не надо, сам все сделает, умница какой), поддерживает каскадность удаления etc (если пользователь удаляется, например, то надо удалить все его посты и все реплаи)\n", 113 | "\n", 114 | "Но давайте с простого и просто поговорим про создание таблиц и полей:\n", 115 | "\n", 116 | "* CharField - строчки\n", 117 | "\n", 118 | "* DataField (DateTimeField) - поле с датой\n", 119 | "\n", 120 | "* FileField, ImageField - поле с файлом/картинкой\n", 121 | "\n", 122 | "* URLField - поле с ссылкой\n", 123 | "\n", 124 | "* IntegerField - число\n", 125 | "\n", 126 | "Давайте для примера сделаем таблицу для юзеров:" 127 | ], 128 | "metadata": { 129 | "id": "ceoeiRk-0T9z" 130 | } 131 | }, 132 | { 133 | "cell_type": "code", 134 | "source": [ 135 | "from django.db import models\n", 136 | "\n", 137 | "class User(models.Model):\n", 138 | " name = models.CharField(maxlength=30)\n", 139 | " username = models.CharField(maxlength=30)\n", 140 | " city = models.CharField(maxlength=60)\n", 141 | " country = models.CharField(maxlength=30)\n", 142 | " bio = models.CharField(maxlength=300)\n", 143 | " email = models.EmailField()" 144 | ], 145 | "metadata": { 146 | "id": "nJE7zYwl2Z0B" 147 | }, 148 | "execution_count": null, 149 | "outputs": [] 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "source": [ 154 | "### Задание 1." 155 | ], 156 | "metadata": { 157 | "id": "jn159VIr_xLf" 158 | } 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "source": [ 163 | "Составить такие же таблицы для Твитов (Tweets) и ответов (Replies)" 164 | ], 165 | "metadata": { 166 | "id": "ErvUBaUq_zQm" 167 | } 168 | }, 169 | { 170 | "cell_type": "code", 171 | "source": [], 172 | "metadata": { 173 | "id": "hoxrs6Dz_6Se" 174 | }, 175 | "execution_count": null, 176 | "outputs": [] 177 | }, 178 | { 179 | "cell_type": "markdown", 180 | "source": [ 181 | "*Вот тут надо еще проговорить про models.ForeignKey(class, on_delete=models.CASCADE), а также про default параметр*" 182 | ], 183 | "metadata": { 184 | "id": "XC6yeD6EAXxT" 185 | } 186 | }, 187 | { 188 | "cell_type": "markdown", 189 | "source": [ 190 | "Окей, составили что-то, давайте попробуем сделать так, чтобы мы поняли, что оно работает. Идем в настройки и меняем:\n", 191 | "\n", 192 | "```\n", 193 | "INSTALLED_APPS = [\n", 194 | " \"django.contrib.admin\",\n", 195 | " \"django.contrib.auth\",\n", 196 | " \"django.contrib.contenttypes\",\n", 197 | " \"django.contrib.sessions\",\n", 198 | " \"django.contrib.messages\",\n", 199 | " \"django.contrib.staticfiles\",\n", 200 | " \"posts\"\n", 201 | "]\n", 202 | "```\n", 203 | "\n", 204 | "\n", 205 | "А теперь проверим модели:\n", 206 | "\n", 207 | "```\n", 208 | "python manage.py makemigrations\n", 209 | "```" 210 | ], 211 | "metadata": { 212 | "id": "gep_5AUt_61x" 213 | } 214 | }, 215 | { 216 | "cell_type": "markdown", 217 | "source": [ 218 | "Ура, все получилось!" 219 | ], 220 | "metadata": { 221 | "id": "dRHeAMoVDORN" 222 | } 223 | }, 224 | { 225 | "cell_type": "markdown", 226 | "source": [ 227 | "## Заводим данные!" 228 | ], 229 | "metadata": { 230 | "id": "EEsVIhEMDTn-" 231 | } 232 | }, 233 | { 234 | "cell_type": "markdown", 235 | "source": [ 236 | "Начнем с простого: как сделать insert?\n", 237 | "\n", 238 | "По сути models.Model есть ни что иное, как наш любимый датакласс, а поэтому создать инстанс просто:\n", 239 | "\n", 240 | "```\n", 241 | "p = User(name=\"Timur\", username=\"Palladain\",\n", 242 | " city=\"Moscow\", country=\"Russia\", bio=\"Hah\", email=\"example@example.com\")\n", 243 | "```\n", 244 | "\n", 245 | "А записать - это простое ```p.save()```, он превратит ваш инстанс в INSERT и сделает все за вас. UPDATE работает также - что-то меняете в словаре, делаете save() и все поменяется" 246 | ], 247 | "metadata": { 248 | "id": "pR29r-E_DgQP" 249 | } 250 | }, 251 | { 252 | "cell_type": "markdown", 253 | "source": [ 254 | "А как удалить? ```p.delete()```. Подытожим:\n", 255 | "\n", 256 | "* Создание строки - как в датаклассе\n", 257 | "\n", 258 | "* Сохранение/изменение - через save()\n", 259 | "\n", 260 | "* Удаление - через delete()" 261 | ], 262 | "metadata": { 263 | "id": "uPim1xhlE4sk" 264 | } 265 | }, 266 | { 267 | "cell_type": "markdown", 268 | "source": [ 269 | "## Делаем select!" 270 | ], 271 | "metadata": { 272 | "id": "NP-0qYIhGPCJ" 273 | } 274 | }, 275 | { 276 | "cell_type": "markdown", 277 | "source": [ 278 | "Давайте сделаем все и сразу, а потом будем разбираться\n", 279 | "\n", 280 | "```\n", 281 | "SELECT count(*)\n", 282 | "FROM Tweets\n", 283 | "WHERE country=\"Russia\"\n", 284 | "```\n", 285 | "\n", 286 | "```\n", 287 | "SELECT id\n", 288 | "FROM Tweets\n", 289 | "WHERE city='Moscow'\n", 290 | "ORDER BY id DESC\n", 291 | "LIMIT 10\n", 292 | "```" 293 | ], 294 | "metadata": { 295 | "id": "DJo2jcS-GSYP" 296 | } 297 | }, 298 | { 299 | "cell_type": "markdown", 300 | "source": [ 301 | "Первый запрос:" 302 | ], 303 | "metadata": { 304 | "id": "pam3r45ZG5HA" 305 | } 306 | }, 307 | { 308 | "cell_type": "code", 309 | "source": [ 310 | "from django.db.models import Count\n", 311 | "\n", 312 | "Tweets.objects.filter(country=\"Russia\").aggregate(Count(\"id\"))" 313 | ], 314 | "metadata": { 315 | "id": "ZVwyomDwG7G3" 316 | }, 317 | "execution_count": null, 318 | "outputs": [] 319 | }, 320 | { 321 | "cell_type": "markdown", 322 | "source": [ 323 | "Второй запрос:" 324 | ], 325 | "metadata": { 326 | "id": "g_W3lYrqHSUD" 327 | } 328 | }, 329 | { 330 | "cell_type": "code", 331 | "source": [ 332 | "Tweets.objects.filter(city=\"Moscow\").order_by(\"-id\")[:10]" 333 | ], 334 | "metadata": { 335 | "id": "dKf_4K1pHUun" 336 | }, 337 | "execution_count": null, 338 | "outputs": [] 339 | }, 340 | { 341 | "cell_type": "markdown", 342 | "source": [ 343 | "А если нам нужен только 1 объект (потому что все запросы выдают список), то можно сделать вот так:" 344 | ], 345 | "metadata": { 346 | "id": "3IOUIimsHoSO" 347 | } 348 | }, 349 | { 350 | "cell_type": "code", 351 | "source": [ 352 | "Tweets.objects.get(username=\"Palladain\") #можно использовать только тогда, когда вы уверены, что такой объект 1, иначе ошибка" 353 | ], 354 | "metadata": { 355 | "id": "J8FSwFcPHtPq" 356 | }, 357 | "execution_count": null, 358 | "outputs": [] 359 | }, 360 | { 361 | "cell_type": "markdown", 362 | "source": [ 363 | "И еще чуть-чуть посложнее:\n", 364 | "\n", 365 | "```\n", 366 | "select city, count(id)\n", 367 | "from Tweets\n", 368 | "group by city\n", 369 | "```" 370 | ], 371 | "metadata": { 372 | "id": "r7G6Ze3TIVtu" 373 | } 374 | }, 375 | { 376 | "cell_type": "code", 377 | "source": [ 378 | "Tweets.objects.values('city').annotate(dcount=Count('id'))" 379 | ], 380 | "metadata": { 381 | "id": "56o0HdYaITnJ" 382 | }, 383 | "execution_count": null, 384 | "outputs": [] 385 | }, 386 | { 387 | "cell_type": "markdown", 388 | "source": [ 389 | "## DDL" 390 | ], 391 | "metadata": { 392 | "id": "eFvCYTcDJSdR" 393 | } 394 | }, 395 | { 396 | "cell_type": "markdown", 397 | "source": [ 398 | "А вот тут есть небольшая проблема: также нативно сделать ALTER и DROP уже не получится :с (придется уже копаться внутри БД)" 399 | ], 400 | "metadata": { 401 | "id": "pv5bYH9SJVjh" 402 | } 403 | }, 404 | { 405 | "cell_type": "markdown", 406 | "source": [ 407 | "## Админка" 408 | ], 409 | "metadata": { 410 | "id": "vQGzxCBAJuSH" 411 | } 412 | }, 413 | { 414 | "cell_type": "markdown", 415 | "source": [ 416 | "Одна из крутых фич Django - это наличие админки, с помощью которой можно всем управлять!\n", 417 | "\n", 418 | "Создадим вначале админа:\n", 419 | "\n", 420 | "```\n", 421 | "python manage.py createsuperuser\n", 422 | "\n", 423 | "```\n", 424 | "\n", 425 | "Он попросит логин-почту (можно указать любую) и пароль. После того, как мы это все ввели, мы можем ввести все эти credentials, чтобы зайти в админку\n", 426 | "\n", 427 | "Иииии... А где все таблицы, которые мы создали? А для них надо еще зарегистрировать их в админке, для этого идем в admin.py:\n", 428 | "\n", 429 | "```\n", 430 | "from django.contrib import admin\n", 431 | "from posts.models import User, Tweets, Replies\n", 432 | "\n", 433 | "admin.site.register(User) - зарегистрировали, что admin имеет доступ к User\n", 434 | "admin.site.register(Tweets)\n", 435 | "admin.site.register(Replies)\n", 436 | "```\n", 437 | "\n", 438 | "С админкой можно также играть, менять ее конфигурации, что отображается etc ([что можно сделать](https://django.fun/ru/docs/django/4.1/ref/contrib/admin/))" 439 | ], 440 | "metadata": { 441 | "id": "6aU1U67TJvuq" 442 | } 443 | }, 444 | { 445 | "cell_type": "markdown", 446 | "source": [ 447 | "## Формы" 448 | ], 449 | "metadata": { 450 | "id": "_fwmSvTHXIYB" 451 | } 452 | }, 453 | { 454 | "cell_type": "markdown", 455 | "source": [ 456 | "Чего не хватает для того, чтобы вся наша модель в итоге нормально заработала? Правильно, не хватает форм, как же их заполнять?\n", 457 | "\n", 458 | "Для этого внутри Django тоже есть удобные средства)\n", 459 | "\n", 460 | "Какие у нас есть формы?\n", 461 | "\n", 462 | "* Для поиска (которые должны возвращать некоторый GET)\n", 463 | "\n", 464 | "* Для постинга (которые должны возврашать некоторый POST)\n", 465 | "\n", 466 | "Для GET все достаточно просто: мы получаем наш запрос (через requets.get) и далее производим все манипуляции. А что же для POST? Для них есть отдельный класс, которая называется Form" 467 | ], 468 | "metadata": { 469 | "id": "PatAlQMmbjW9" 470 | } 471 | }, 472 | { 473 | "cell_type": "markdown", 474 | "source": [ 475 | "```\n", 476 | "from django import forms\n", 477 | "\n", 478 | "class NameForm(forms.Form):\n", 479 | " your_name = forms.CharField(label='Your name', max_length=100)\n", 480 | "```\n", 481 | "\n", 482 | "Казалось бы, зачем нам отдельная форма еще? Дело в простоте, ведь теперь можно сделать вот так:\n", 483 | "\n", 484 | "```\n", 485 | "def get_name(request):\n", 486 | " if request.method == 'POST':\n", 487 | " form = NameForm(request.POST)\n", 488 | " return render(request, 'name.html', {'form': form})\n", 489 | "```\n", 490 | "\n", 491 | "То есть можно взять и запихнуть все это добро сразу же в необходимый результат, который потом можно записать в ту же таблицу etc\n", 492 | "\n", 493 | "Осталось только одно: а как он поймет, где какое поле? Ответ достаточно прост - из разметки HTML, как уже обсуждалось) (через name в качестве аргумента в тэге HTML)" 494 | ], 495 | "metadata": { 496 | "id": "3pE4gvSmdt_Z" 497 | } 498 | }, 499 | { 500 | "cell_type": "markdown", 501 | "source": [ 502 | "## Что будем в ДЗ?\n", 503 | "\n", 504 | "А в ДЗ вам будет предложено два варианта:\n", 505 | "\n", 506 | "* Собрать это все в общий красивый сайт (либо на flask, либо на django)\n", 507 | "\n", 508 | "* Или же собрать красивые дашборды на Dash или Streamlit (о них на следующей лекции и семинаре)\n", 509 | "\n", 510 | "Как задеплоить добро и референс к ДЗ будет в понедельник, stay tuned :з" 511 | ], 512 | "metadata": { 513 | "id": "f6nDAgqrPMw0" 514 | } 515 | }, 516 | { 517 | "cell_type": "markdown", 518 | "source": [ 519 | "## Животное дня" 520 | ], 521 | "metadata": { 522 | "id": "SKe2hypHfG0q" 523 | } 524 | }, 525 | { 526 | "cell_type": "markdown", 527 | "source": [ 528 | "![](https://images.techinsider.ru/upload/img_cache/e8e/e8eb6b3287f8460a27457aac6395a812_cropped_1332x888.jpg)" 529 | ], 530 | "metadata": { 531 | "id": "2KS3Zy2Vg730" 532 | } 533 | }, 534 | { 535 | "cell_type": "markdown", 536 | "source": [ 537 | "Сегодня у нас будет самое мелкое животное из представленных - это тихоходка (или водяной медведь). Их более 750 видов, и их можно увидеть под микроскопом (что и увидели ученые еще в XVIII веке). Выглядят очень мило (особенно как они пермещаются)\n", 538 | "\n", 539 | "Но что в них более удивительно, так это их способность выживать в самых экстремальных условиях: давление, космическое излучение (их отправляли в космос и под сильным космическим излучением выжило более 50% тихоходок), температуры, близкие к абсолютному нулю, сухость. Вот кто реально выживет после ядерной войны\n", 540 | "\n", 541 | "Секрет их выносливости - это состояние анабиоза, в которых они впадают. Содержание воды уменьшается до 1%, а метаболизм падает практически до нуля (и они сворачиваются в такой клубочек). Причем в состоянии анабиоза они могут находиться более 100 лет, при этом оживая достаточно быстро. Так что если кто-то и поселится первым на Марсе или на другой планете, то это явно будут тихоходки, а не люди)" 542 | ], 543 | "metadata": { 544 | "id": "zYTicYschBWM" 545 | } 546 | } 547 | ] 548 | } -------------------------------------------------------------------------------- /Seminars/Seminar_11/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminar_11/.DS_Store -------------------------------------------------------------------------------- /Seminars/Seminar_11/Dash/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminar_11/Dash/.DS_Store -------------------------------------------------------------------------------- /Seminars/Seminar_11/Dash/main.py: -------------------------------------------------------------------------------- 1 | from dash import Dash, html, dcc, Input, Output 2 | import pandas as pd 3 | from urllib.request import urlopen 4 | import json 5 | import plotly.express as px 6 | import dash 7 | 8 | # Подготовка 9 | 10 | external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] 11 | 12 | app = Dash(__name__, external_stylesheets=external_stylesheets, use_pages=True) 13 | 14 | app.layout = html.Div(children=[ 15 | html.H2(children="US elections"), 16 | html.Div( 17 | [ 18 | html.Div( 19 | dcc.Link( 20 | f"{page['name']}", href=page["relative_path"] 21 | ) 22 | ) 23 | for page in dash.page_registry.values() 24 | ] 25 | ), 26 | 27 | dash.page_container, 28 | ]) 29 | 30 | if __name__ == '__main__': 31 | app.run_server(debug=True) -------------------------------------------------------------------------------- /Seminars/Seminar_11/Dash/pages/__pycache__/compare.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminar_11/Dash/pages/__pycache__/compare.cpython-310.pyc -------------------------------------------------------------------------------- /Seminars/Seminar_11/Dash/pages/__pycache__/home.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminar_11/Dash/pages/__pycache__/home.cpython-310.pyc -------------------------------------------------------------------------------- /Seminars/Seminar_11/Dash/pages/__pycache__/main_dash.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminar_11/Dash/pages/__pycache__/main_dash.cpython-310.pyc -------------------------------------------------------------------------------- /Seminars/Seminar_11/Dash/pages/compare.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import Dash, html, dcc, Input, Output, callback 3 | import pandas as pd 4 | from urllib.request import urlopen 5 | import json 6 | import plotly.express as px 7 | 8 | dash.register_page(__name__) 9 | 10 | df = pd.read_csv("county_statistics.csv") 11 | df = df[~pd.isnull(df.lat)] 12 | df = df[~pd.isnull(df.percentage16_Donald_Trump)] 13 | 14 | df_sample = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/laucnty16.csv') 15 | df_sample['State FIPS Code'] = df_sample['State FIPS Code'].apply(lambda x: str(x).zfill(2)) 16 | df_sample['County FIPS Code'] = df_sample['County FIPS Code'].apply(lambda x: str(x).zfill(3)) 17 | df_sample['FIPS'] = df_sample['State FIPS Code'] + df_sample['County FIPS Code'] 18 | 19 | def replacing_words(x): 20 | x = x.split(",")[0] 21 | samples = [" County", "/town", " Parish", "/city"] 22 | for s in samples: 23 | x = x.replace(s, "") 24 | return x 25 | 26 | def changing_state(x): 27 | x = x.split(", ")[-1] 28 | if x == 'District of Columbia': 29 | x = "DC" 30 | return x 31 | 32 | df_sample["county"] = df_sample["County Name/State Abbreviation"].apply(lambda x: replacing_words(x)) 33 | df_sample["state"] = df_sample["County Name/State Abbreviation"].apply(lambda x: changing_state(x)) 34 | df_fips = pd.merge(df, df_sample[["county", "state", "FIPS"]], on=["county", "state"], how='left') 35 | 36 | perc_data = pd.melt(df_fips[["county", "state", "White", "Black", "Hispanic", "Asian", "Pacific", "Native"]], id_vars=["county", "state"], var_name="Race", value_name="Perc") 37 | 38 | fig_1 = px.pie(data_frame=perc_data, values="Perc", names="Race") 39 | fig_1.update_layout(margin={"r":0,"t":0,"l":0,"b":0}) 40 | fig_2 = px.pie(data_frame=perc_data, values="Perc", names="Race") 41 | fig_2.update_layout(margin={"r":0,"t":0,"l":0,"b":0}) 42 | 43 | 44 | layout = html.Div(children=[ 45 | html.Div([ 46 | html.Div([ 47 | dcc.Markdown('Select State'), 48 | dcc.Dropdown(df_fips.state.unique(), df_fips.state[0], id='state_1_dropdown'), 49 | dcc.Markdown('Select County'), 50 | dcc.Dropdown(df_fips.county.unique(), df_fips.county[0], id='county_1_dropdown'), 51 | ]), 52 | html.Div([ 53 | dcc.Markdown('Select State'), 54 | dcc.Dropdown(df_fips.state.unique(), df_fips.state[0], id='state_2_dropdown'), 55 | dcc.Markdown('Select County'), 56 | dcc.Dropdown(df_fips.county.unique(), df_fips.county[0], id='county_2_dropdown'), 57 | ]), 58 | ], style={'columnCount': 2, 'align': 'center'}), 59 | 60 | html.Div([ 61 | html.Div([ 62 | dcc.Markdown('Race distribution'), 63 | dcc.Graph( 64 | id='Race_1_pie', 65 | figure=fig_1, 66 | ), 67 | ]), 68 | html.Div([ 69 | dcc.Markdown('Race distribution'), 70 | dcc.Graph( 71 | id='Race_2_pie', 72 | figure=fig_2, 73 | ), 74 | ]) 75 | ], style={'columnCount': 2, 'align': 'center'}) 76 | ]) 77 | 78 | @callback( 79 | Output("county_1_dropdown", 'options'), 80 | Input("state_1_dropdown", "value") 81 | ) 82 | def update_counties_1(val): 83 | res = df_fips[df_fips.state == val].county.unique() 84 | return res 85 | 86 | @callback( 87 | Output("county_2_dropdown", 'options'), 88 | Input("state_2_dropdown", "value") 89 | ) 90 | def update_counties_2(val): 91 | res = df_fips[df_fips.state == val].county.unique() 92 | return res 93 | 94 | @callback( 95 | Output("Race_1_pie", 'figure'), 96 | Input("county_1_dropdown", "value"), 97 | Input("state_1_dropdown", "value") 98 | ) 99 | def update_pue_1(county, state): 100 | fig_1 = px.pie(data_frame=perc_data[(perc_data.county == county) & (perc_data.state == state)], values="Perc", names="Race") 101 | fig_1.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0}) 102 | return fig_1 103 | 104 | @callback( 105 | Output("Race_2_pie", 'figure'), 106 | Input("county_2_dropdown", "value"), 107 | Input("state_2_dropdown", "value") 108 | ) 109 | def update_pue_2(county, state): 110 | fig_2 = px.pie(data_frame=perc_data[(perc_data.county == county) & (perc_data.state == state)], values="Perc", names="Race") 111 | fig_2.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0}) 112 | return fig_2 -------------------------------------------------------------------------------- /Seminars/Seminar_11/Dash/pages/home.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html, dcc 3 | 4 | dash.register_page(__name__, path='/') 5 | 6 | layout = html.Div(children=[ 7 | ]) -------------------------------------------------------------------------------- /Seminars/Seminar_11/Dash/pages/main_dash.py: -------------------------------------------------------------------------------- 1 | from dash import Dash, html, dcc, Input, Output, callback 2 | import pandas as pd 3 | from urllib.request import urlopen 4 | import json 5 | import plotly.express as px 6 | import dash 7 | 8 | dash.register_page(__name__) 9 | 10 | df = pd.read_csv("county_statistics.csv") 11 | df = df[~pd.isnull(df.lat)] 12 | df = df[~pd.isnull(df.percentage16_Donald_Trump)] 13 | 14 | df_sample = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/laucnty16.csv') 15 | df_sample['State FIPS Code'] = df_sample['State FIPS Code'].apply(lambda x: str(x).zfill(2)) 16 | df_sample['County FIPS Code'] = df_sample['County FIPS Code'].apply(lambda x: str(x).zfill(3)) 17 | df_sample['FIPS'] = df_sample['State FIPS Code'] + df_sample['County FIPS Code'] 18 | 19 | def replacing_words(x): 20 | x = x.split(",")[0] 21 | samples = [" County", "/town", " Parish", "/city"] 22 | for s in samples: 23 | x = x.replace(s, "") 24 | return x 25 | 26 | def changing_state(x): 27 | x = x.split(", ")[-1] 28 | if x == 'District of Columbia': 29 | x = "DC" 30 | return x 31 | 32 | df_sample["county"] = df_sample["County Name/State Abbreviation"].apply(lambda x: replacing_words(x)) 33 | df_sample["state"] = df_sample["County Name/State Abbreviation"].apply(lambda x: changing_state(x)) 34 | df_fips = pd.merge(df, df_sample[["county", "state", "FIPS"]], on=["county", "state"], how='left') 35 | 36 | with urlopen('https://raw.githubusercontent.com/plotly/datasets/master/geojson-counties-fips.json') as response: 37 | counties = json.load(response) 38 | 39 | # Отрисовка изначальных карт 40 | 41 | fig = px.choropleth(df_fips, geojson=counties, locations='FIPS', color='White', 42 | color_continuous_scale="Viridis", 43 | range_color=(0, 100), 44 | scope="usa", 45 | fitbounds='locations', 46 | hover_data=["state", "county", "percentage16_Donald_Trump"] 47 | ) 48 | fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0}) 49 | 50 | perc_data = pd.melt(df_fips[["county", "state", "White", "Black", "Hispanic", "Asian", "Pacific", "Native"]], id_vars=["county", "state"], var_name="Race", value_name="Perc") 51 | walk_data = pd.melt(df_fips[["county", "state", 'Drive', 'Carpool', 'Transit', 'Walk', 'OtherTransp', 'WorkAtHome']], id_vars=["county", "state"], var_name="Walk", value_name="Perc") 52 | work_data = pd.melt(df_fips[["county", "state", 'PrivateWork', 'PublicWork', 'SelfEmployed', 'FamilyWork', 'Unemployment']], id_vars=["county", "state"], var_name="Work", value_name="Perc") 53 | 54 | fig_1 = px.pie(data_frame=perc_data, values="Perc", names="Race") 55 | fig_1.update_layout(margin={"r":0,"t":0,"l":0,"b":0}) 56 | fig_2 = px.pie(data_frame=walk_data, values="Perc", names="Walk") 57 | fig_2.update_layout(margin={"r":0,"t":0,"l":0,"b":0}) 58 | fig_3 = px.pie(data_frame=work_data, values="Perc", names="Work") 59 | fig_3.update_layout(margin={"r":0,"t":0,"l":0,"b":0}) 60 | 61 | layout = html.Div(children=[ 62 | html.Div([ 63 | dcc.Markdown('Select State'), 64 | dcc.Dropdown(df_fips.state.unique(), df_fips.state[0], id='state_dropdown'), 65 | ]), 66 | 67 | dcc.Graph( 68 | id='US_map', 69 | figure=fig, 70 | clickData={'points': [{'customdata': ['SC', 'Horry']}]} 71 | ), 72 | 73 | html.Div([ 74 | html.Div([ 75 | dcc.Markdown('Race distribution'), 76 | dcc.Graph( 77 | id='Race_pie', 78 | figure=fig_1, 79 | ), 80 | ]), 81 | html.Div([ 82 | dcc.Markdown('Transport distribution'), 83 | dcc.Graph( 84 | id='Walk_pie', 85 | figure=fig_2, 86 | ), 87 | ]), 88 | html.Div([ 89 | dcc.Markdown('Work distribution'), 90 | dcc.Graph( 91 | id='Work_pie', 92 | figure=fig_3, 93 | ), 94 | ]) 95 | ], style={'columnCount': 3, 'align': 'center'}) 96 | ]) 97 | 98 | 99 | @callback( 100 | Output('US_map', 'figure'), 101 | Input('state_dropdown', 'value') 102 | ) 103 | def update_map(state): 104 | fig = px.choropleth(df_fips[df_fips.state == state], geojson=counties, locations='FIPS', color='percentage16_Donald_Trump', 105 | color_continuous_scale="Viridis", 106 | range_color=(0, 1), 107 | scope="usa", 108 | fitbounds='locations', 109 | hover_data=["state", "county", "percentage16_Donald_Trump"] 110 | ) 111 | fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0}) 112 | return fig 113 | 114 | 115 | @callback( 116 | Output('Race_pie', 'figure'), 117 | Input('US_map', 'clickData') 118 | ) 119 | def display_click_data_race(clickData): 120 | state = clickData["points"][0]["customdata"][0] 121 | county = clickData["points"][0]["customdata"][1] 122 | fig_1 = px.pie(data_frame=perc_data[(perc_data.county == county) & (perc_data.state == state)], values="Perc", names="Race") 123 | fig_1.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0}) 124 | return fig_1 125 | 126 | @callback( 127 | Output('Walk_pie', 'figure'), 128 | Input('US_map', 'clickData') 129 | ) 130 | def display_click_data_walk(clickData): 131 | state = clickData["points"][0]["customdata"][0] 132 | county = clickData["points"][0]["customdata"][1] 133 | fig_2 = px.pie(data_frame=walk_data[(walk_data.county == county) & (walk_data.state == state)], values="Perc", names="Walk") 134 | fig_2.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0}) 135 | return fig_2 136 | 137 | @callback( 138 | Output('Work_pie', 'figure'), 139 | Input('US_map', 'clickData') 140 | ) 141 | def display_click_data_work(clickData): 142 | state = clickData["points"][0]["customdata"][0] 143 | county = clickData["points"][0]["customdata"][1] 144 | fig_3 = px.pie(data_frame=work_data[(work_data.county == county) & (work_data.state == state)], values="Perc", names="Work") 145 | fig_3.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0}) 146 | return fig_3 -------------------------------------------------------------------------------- /Seminars/Seminar_12/Seminar_12_Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "provenance": [] 7 | }, 8 | "kernelspec": { 9 | "name": "python3", 10 | "display_name": "Python 3" 11 | }, 12 | "language_info": { 13 | "name": "python" 14 | } 15 | }, 16 | "cells": [ 17 | { 18 | "cell_type": "markdown", 19 | "source": [ 20 | "# Продвинутый Python, семинар 12\n", 21 | "\n", 22 | "**Лектор:** Петров Тимур\n", 23 | "\n", 24 | "**Семинаристы:** Петров Тимур, Коган Александра, Романченко Полина\n", 25 | "\n", 26 | "**Spoiler Alert:** в рамках курса нельзя изучить ни одну из тем от и до досконально (к сожалению, на это требуется больше времени, чем даже 3 часа в неделю). Но мы попробуем рассказать столько, сколько возможно :)" 27 | ], 28 | "metadata": { 29 | "id": "FHy_xV2gTlRj" 30 | } 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "source": [ 35 | "## Комбинируем потоки и корутины" 36 | ], 37 | "metadata": { 38 | "id": "owJSkKaNUjHW" 39 | } 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "source": [ 44 | "Зачем это нужно?\n", 45 | "\n", 46 | "Допустим, что вы настраиваете работу системы (вспоминаем ООП из ДЗ-2). Вам что необходимо? Вы ждете заказов от пользователя, но пока вы их ждете, то у вас же все не простаивает на месте: заказы собираются, курьеры возят. И все это синхронно. Давайте осуществлять мечту" 47 | ], 48 | "metadata": { 49 | "id": "SqjDFA9PUoHw" 50 | } 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": { 56 | "id": "3CjD2eVDTejn" 57 | }, 58 | "outputs": [], 59 | "source": [ 60 | "import time\n", 61 | "import asyncio\n", 62 | "\n", 63 | "\n", 64 | "def blocking_io():\n", 65 | " print(f\"{time.strftime('%X')} block IO\")\n", 66 | " time.sleep(5) # снимает GIL\n", 67 | " print(f\"{time.strftime('%X')} unblock IO\")\n", 68 | "\n", 69 | "non_block = asyncio.to_thread(blocking_io) # без скобочек!!! аргументы просто через пробел, версия 3.9+\n", 70 | "\n", 71 | "async def main():\n", 72 | " print(f\"{time.strftime('%X')} start async\")\n", 73 | " _ = await asyncio.gather(non_block, asyncio.sleep(5))\n", 74 | " print(f\"{time.strftime('%X')} finish async\")\n", 75 | "\n", 76 | "asyncio.run(main())" 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "source": [ 82 | "Аналогично с теми же самими факториалами (опять-таки, это нам не даст ускорения по сравнению с просто корутинами, потому что слишком малые рассчеты) и опять-таки - внутри Python паралеллить вычисления - дело такое себе" 83 | ], 84 | "metadata": { 85 | "id": "QllSst9HaPte" 86 | } 87 | }, 88 | { 89 | "cell_type": "code", 90 | "source": [ 91 | "import time\n", 92 | "import asyncio\n", 93 | "\n", 94 | "def factorial(name, number):\n", 95 | " f = 1\n", 96 | " for i in range(2, number + 1):\n", 97 | " print(f\"Task {name}: Compute factorial({i})...\")\n", 98 | " time.sleep(0.1)\n", 99 | " f *= i\n", 100 | " print(f\"Task {name}: factorial({number}) = {f}\")\n", 101 | " return f\n", 102 | "\n", 103 | "\n", 104 | "fact_1 = asyncio.to_thread(factorial, \"A\", 2)\n", 105 | "fact_2 = asyncio.to_thread(factorial, \"B\", 3)\n", 106 | "fact_3 = asyncio.to_thread(factorial, \"C\", 4)\n", 107 | "\n", 108 | "async def main():\n", 109 | " print(f\"{time.time()} beg\")\n", 110 | " res = await asyncio.gather(\n", 111 | " fact_1,\n", 112 | " fact_2,\n", 113 | " fact_3\n", 114 | " )\n", 115 | " print(f\"{time.time()} end\")\n", 116 | "\n", 117 | "asyncio.run(main())\n" 118 | ], 119 | "metadata": { 120 | "id": "3X6EjqUAawqS" 121 | }, 122 | "execution_count": null, 123 | "outputs": [] 124 | }, 125 | { 126 | "cell_type": "markdown", 127 | "source": [ 128 | "## А теперь к примеру использования" 129 | ], 130 | "metadata": { 131 | "id": "E2Wd-1YEdGjH" 132 | } 133 | }, 134 | { 135 | "cell_type": "markdown", 136 | "source": [ 137 | "Сегодня мы поговорим с вами про пример асинхронности в виде все того же любимого Телеграм-бота, а в следующий раз будем говорить про асинхронность для БД (потому что как мы уже говорили, это позволяет использовать потоки)\n", 138 | "\n", 139 | "Зачем в Телеграм боте нужна асинхронность, спросите вы? Ответ прост: это гораздо быстрее работает (особенно чем больше нагрузка, тем больше вам нужна асинхронность), ну а также это позволяет более эффективно использовать параллельность (которая, сама собой, и так есть в телеграм-боте)\n", 140 | "\n", 141 | "В чем разница? По существу, вместо requests используется [aiohttp](https://docs.aiohttp.org/en/stable/) - асинхронная версия requests, если так можно высказаться" 142 | ], 143 | "metadata": { 144 | "id": "5r7CmuOpdJx3" 145 | } 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "source": [ 150 | "Сделаем сразу вещь, которую нельзя сделать без асинхронности: шедулер! В этом нам поможет бибилиотека [aioschedule](https://github.com/ibrb/python-aioschedule) - асинхронный шедулер. Работает также, как и шедулер (про который мы говорили на ООП), но только асинхронный" 151 | ], 152 | "metadata": { 153 | "id": "WKXB-RyEjgwC" 154 | } 155 | }, 156 | { 157 | "cell_type": "code", 158 | "source": [ 159 | "import asyncio\n", 160 | "import aioschedule\n", 161 | "from telebot.async_telebot import AsyncTeleBot\n", 162 | "\n", 163 | "API_TOKEN = ''\n", 164 | "bot = AsyncTeleBot(API_TOKEN)\n", 165 | "\n", 166 | "\n", 167 | "async def beep(chat_id) -> None:\n", 168 | " \"\"\"Send the beep message.\"\"\"\n", 169 | " await bot.send_message(chat_id, text='Beep!')\n", 170 | " aioschedule.clear(chat_id)\n", 171 | "\n", 172 | "\n", 173 | "@bot.message_handler(commands=['help', 'start'])\n", 174 | "async def send_welcome(message):\n", 175 | " await bot.reply_to(message, \"Hi! Use /set to set a timer\")\n", 176 | "\n", 177 | "\n", 178 | "@bot.message_handler(commands=['set'])\n", 179 | "async def set_timer(message):\n", 180 | " args = message.text.split()\n", 181 | " if len(args) > 1 and args[1].isdigit():\n", 182 | " sec = int(args[1])\n", 183 | " aioschedule.every(sec).seconds.do(beep, message.chat.id).tag(message.chat.id)\n", 184 | " else:\n", 185 | " await bot.reply_to(message, 'Usage: /set ')\n", 186 | "\n", 187 | "\n", 188 | "@bot.message_handler(commands=['unset'])\n", 189 | "def unset_timer(message):\n", 190 | " aioschedule.clean(message.chat.id)\n", 191 | "\n", 192 | "\n", 193 | "async def scheduler():\n", 194 | " while True:\n", 195 | " await aioschedule.run_pending()\n", 196 | " await asyncio.sleep(1)\n", 197 | "\n", 198 | "\n", 199 | "async def main():\n", 200 | " await asyncio.gather(bot.infinity_polling(), scheduler())\n", 201 | "\n", 202 | "\n", 203 | "if __name__ == '__main__':\n", 204 | " asyncio.run(main())" 205 | ], 206 | "metadata": { 207 | "id": "4N3_uv7rj3GA" 208 | }, 209 | "execution_count": null, 210 | "outputs": [] 211 | }, 212 | { 213 | "cell_type": "markdown", 214 | "source": [ 215 | "Зачем еще нужна асинхронность? Опять-таки для подключения к БД. Допустим, что вы храните информацию по юзерам: как их зовут и так далее (та самая проблема, которую вы получали на ДЗ для обработки бана юзера и так далее). Так вот, для создания более сложных ботов (например, магазина), вам нужно подключаться к БД, делать асинхронного бота, и таким образом, запрос еще ждать. А остальные ждут и ждут...\n", 216 | "\n", 217 | "В целом это и будет темой для последнего ДЗ)" 218 | ], 219 | "metadata": { 220 | "id": "TDUINjIfmyQD" 221 | } 222 | }, 223 | { 224 | "cell_type": "markdown", 225 | "source": [ 226 | "## Животное дня" 227 | ], 228 | "metadata": { 229 | "id": "Vt295navnN5Z" 230 | } 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "source": [ 235 | "Итак, сегодня у нас ламы! На самом деле ламы - это 4 отдельных вида, которых стоит различать. Какие есть виды?\n", 236 | "\n", 237 | "* Гуанако\n", 238 | "\n", 239 | "* Викунья\n", 240 | "\n", 241 | "* Лама\n", 242 | "\n", 243 | "* Альпака" 244 | ], 245 | "metadata": { 246 | "id": "one_AAvXnSZ2" 247 | } 248 | }, 249 | { 250 | "cell_type": "markdown", 251 | "source": [ 252 | "![](https://funart.pro/uploads/posts/2021-07/1626722411_27-funart-pro-p-vikunya-zhivotnoe-zhivotnie-krasivo-foto-30.jpg)" 253 | ], 254 | "metadata": { 255 | "id": "Y-FHNstwtimQ" 256 | } 257 | }, 258 | { 259 | "cell_type": "markdown", 260 | "source": [ 261 | "Это гуанако. Они дикие и прыгучие, а одомашненный вид - это лама\n", 262 | "\n", 263 | "![](https://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/Black_Llama.jpg/1920px-Black_Llama.jpg)" 264 | ], 265 | "metadata": { 266 | "id": "bIlMhQOntqzJ" 267 | } 268 | }, 269 | { 270 | "cell_type": "markdown", 271 | "source": [ 272 | "Викунья - дикая, меньше и стройнее, чем гуанако (различить сложно)\n", 273 | "\n", 274 | "![](https://i.artfile.ru/2048x1365_851478_[www.ArtFile.ru].jpg)" 275 | ], 276 | "metadata": { 277 | "id": "DKJgR8ZeuC85" 278 | } 279 | }, 280 | { 281 | "cell_type": "markdown", 282 | "source": [ 283 | "А одомашненный вид - это альпака. Она пышная и пушистая!\n", 284 | "\n", 285 | "![](https://static.fjcdn.com/large/pictures/6d/7d/6d7d59_5574469.jpg)" 286 | ], 287 | "metadata": { 288 | "id": "OSz2XJRTuMut" 289 | } 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "source": [ 294 | "Лам используют в качестве вьючных животных (но они очень гордые, положи на них больше - они пошлют)\n", 295 | "\n", 296 | "А альпак - для того, чтобы их стричь и из шерсти делать одежду и так далее\n", 297 | "\n", 298 | "Одомашнили примерно 5000 тыс лет назад предками инков и прочих народов Анд" 299 | ], 300 | "metadata": { 301 | "id": "2WMmp862uciW" 302 | } 303 | } 304 | ] 305 | } -------------------------------------------------------------------------------- /Seminars/Seminar_2/archive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminar_2/archive.zip -------------------------------------------------------------------------------- /Seminars/Seminar_6/Seminar_6_Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "provenance": [], 7 | "collapsed_sections": [] 8 | }, 9 | "kernelspec": { 10 | "name": "python3", 11 | "display_name": "Python 3" 12 | }, 13 | "language_info": { 14 | "name": "python" 15 | } 16 | }, 17 | "cells": [ 18 | { 19 | "cell_type": "markdown", 20 | "source": [ 21 | "# Продвинутый Python, семинар 6\n", 22 | "\n", 23 | "**Лектор:** Петров Тимур\n", 24 | "\n", 25 | "**Семинаристы:** Петров Тимур, Коган Александра, Романченко Полина\n", 26 | "\n", 27 | "**Spoiler Alert:** в рамках курса нельзя изучить ни одну из тем от и до досконально (к сожалению, на это требуется больше времени, чем даже 3 часа в неделю). Но мы попробуем рассказать столько, сколько возможно :)" 28 | ], 29 | "metadata": { 30 | "id": "AbZhZkEGkvxx" 31 | } 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "source": [ 36 | "## Telegram Bot API" 37 | ], 38 | "metadata": { 39 | "id": "WaGTHbgnk7r5" 40 | } 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "source": [ 45 | "![](https://www.rogovskoe.org/files/rogovskoe/news/2022/03.18/tg.png)" 46 | ], 47 | "metadata": { 48 | "id": "D-ak5aXzlWfC" 49 | } 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "source": [ 54 | "Итак, мы обсудили API, что это вообще такое. Давайте теперь на практике попрактикуемся с API бота в Telegram (достаточно многим нужен кастомный бот, но не умеют в него, хех)\n", 55 | "\n", 56 | "Конечно же мы не будем делать все это через GET реквесты etc, для этого тоже есть красивая библиотека [pytelegrambot](https://pytba.readthedocs.io/en/latest/index.html)\n", 57 | "\n", 58 | "\n", 59 | "Отдельно еще [API](https://core.telegram.org/api#getting-started) " 60 | ], 61 | "metadata": { 62 | "id": "CGW2yB38lAHA" 63 | } 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "metadata": { 69 | "colab": { 70 | "base_uri": "https://localhost:8080/" 71 | }, 72 | "id": "VA28aB7XksQZ", 73 | "outputId": "6fbf05a1-006e-4193-bcb8-dbc42c4fe802" 74 | }, 75 | "outputs": [ 76 | { 77 | "output_type": "stream", 78 | "name": "stdout", 79 | "text": [ 80 | "Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n", 81 | "Collecting pytelegrambotapi\n", 82 | " Downloading pyTelegramBotAPI-4.7.0.tar.gz (210 kB)\n", 83 | "\u001b[K |████████████████████████████████| 210 kB 4.3 MB/s \n", 84 | "\u001b[?25hRequirement already satisfied: requests in /usr/local/lib/python3.7/dist-packages (from pytelegrambotapi) (2.23.0)\n", 85 | "Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from requests->pytelegrambotapi) (1.24.3)\n", 86 | "Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests->pytelegrambotapi) (3.0.4)\n", 87 | "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests->pytelegrambotapi) (2022.9.24)\n", 88 | "Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests->pytelegrambotapi) (2.10)\n", 89 | "Building wheels for collected packages: pytelegrambotapi\n", 90 | " Building wheel for pytelegrambotapi (setup.py) ... \u001b[?25l\u001b[?25hdone\n", 91 | " Created wheel for pytelegrambotapi: filename=pyTelegramBotAPI-4.7.0-py3-none-any.whl size=192826 sha256=e5226c5058f88c092ff01614129b3601544d628b534621884ca4dc8d627b07ca\n", 92 | " Stored in directory: /root/.cache/pip/wheels/ba/13/da/8abf941f7cf9f993cde6118a56a7c24e12ed791507acd8ea39\n", 93 | "Successfully built pytelegrambotapi\n", 94 | "Installing collected packages: pytelegrambotapi\n", 95 | "Successfully installed pytelegrambotapi-4.7.0\n" 96 | ] 97 | } 98 | ], 99 | "source": [ 100 | "!pip install pytelegrambotapi" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "source": [ 106 | "import telebot\n", 107 | "from telebot import types\n", 108 | "\n", 109 | "TOKEN = '5674479560:AAHI0lWyLHZQUa91Di-6NmNqdWbE7lL_6H8' # указываем токен нашего бота (для этого надо создать бота в @BotFather)\n", 110 | "# Создайте собственного бота, чтобы наши наработки друг друга не перебивали\n", 111 | "\n", 112 | "bot = telebot.TeleBot(TOKEN) # инициализируем нашего бота" 113 | ], 114 | "metadata": { 115 | "id": "iCMjtHHpl5Se" 116 | }, 117 | "execution_count": null, 118 | "outputs": [] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "source": [ 123 | "Отлично, подключились к нашему боту! Теперь надо задавать его поведение, что делать, обработку сценариев\n", 124 | "\n", 125 | "Для начала приветствие, то есть то, что происходит при нажатии на кнопочку /start:" 126 | ], 127 | "metadata": { 128 | "id": "pYWzxPQynP6b" 129 | } 130 | }, 131 | { 132 | "cell_type": "code", 133 | "source": [ 134 | "@bot.message_handler(commands=['start'])\n", 135 | "def hello_message(message):\n", 136 | " bot.send_message(message.chat.id,\"Привет ✌️, тренируемся создавать ботов, давай с нами\")" 137 | ], 138 | "metadata": { 139 | "id": "9ZUeWXUAnQqV" 140 | }, 141 | "execution_count": null, 142 | "outputs": [] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "source": [ 147 | "bot.polling(none_stop=True, interval=0) #запускаем нашего бота" 148 | ], 149 | "metadata": { 150 | "id": "K5T8FQVdnfzu" 151 | }, 152 | "execution_count": null, 153 | "outputs": [] 154 | }, 155 | { 156 | "cell_type": "markdown", 157 | "source": [ 158 | "Какие есть базовые функции у бота для коммуникации?\n", 159 | "\n", 160 | "* send_message - отправь сообщение\n", 161 | "\n", 162 | "* send_photo - отправь фотку\n", 163 | "\n", 164 | "* send_voice - отправь войс\n", 165 | "\n", 166 | "* send_poll - отправь голосовалку\n", 167 | "\n", 168 | "* send_sticker - отправь стикер" 169 | ], 170 | "metadata": { 171 | "id": "cPVzodCG0p2t" 172 | } 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "source": [ 177 | "Давайте теперь добавим кнопочку, на которую можно будет тыкать.\n", 178 | "\n", 179 | "Так как API у ботов достаточное богатое, то оно разбито по [частям](https://pytba.readthedocs.io/en/latest/types.html), каждая часть лежит в модуле types\n", 180 | "\n", 181 | "В данном случае нам понадобится 2 модуля: KeyBoardButton и ReplyKeyBoardMarkup (первый - это кнопочка, вторая - расположить кнопочку)" 182 | ], 183 | "metadata": { 184 | "id": "FR0apJPWy0SQ" 185 | } 186 | }, 187 | { 188 | "cell_type": "code", 189 | "source": [ 190 | "@bot.message_handler(commands=['button'])\n", 191 | "def button_message(message):\n", 192 | " markup = types.ReplyKeyboardMarkup(resize_keyboard=True)\n", 193 | " item_1 = types.KeyboardButton(\"Тык\")\n", 194 | " markup.add(item_1)\n", 195 | " bot.send_message(message.chat.id,'Выберите что вам надо',reply_markup=markup)" 196 | ], 197 | "metadata": { 198 | "id": "iHzLVCwszWGX" 199 | }, 200 | "execution_count": null, 201 | "outputs": [] 202 | }, 203 | { 204 | "cell_type": "markdown", 205 | "source": [ 206 | "Отлично, вот мы с вами сделали кнопку, она отправляет текст и... ничего не делает! Почему? Потому что мы не настроили сценарий. Давайте настроим" 207 | ], 208 | "metadata": { 209 | "id": "F9bKD9_G117h" 210 | } 211 | }, 212 | { 213 | "cell_type": "code", 214 | "source": [ 215 | "@bot.message_handler(content_types='text')\n", 216 | "def message_reply(message):\n", 217 | " if message.text==\"Тык\":\n", 218 | " bot.send_message(message.chat.id,\"Ссылка на наш github: https://github.com/Palladain/Deep_Python\")" 219 | ], 220 | "metadata": { 221 | "id": "C7c8Up8u1_Hc" 222 | }, 223 | "execution_count": null, 224 | "outputs": [] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "source": [ 229 | "bot.polling(none_stop=True, interval=0) #запускаем нашего бота" 230 | ], 231 | "metadata": { 232 | "id": "9JcvGTZ4zoos" 233 | }, 234 | "execution_count": null, 235 | "outputs": [] 236 | }, 237 | { 238 | "cell_type": "markdown", 239 | "source": [ 240 | "Итак, магия работает, но откуда магия работает? Правильно, все дело в декораторах, которые мы указываем\n", 241 | "\n", 242 | "Пока мы с вами использовали только 1 такой декоратор:\n", 243 | "\n", 244 | "* bot.message_handler() - обработка сообщений\n", 245 | "\n", 246 | "В качестве аргумента мы передаем функцию, которая обрабатывает message, отправленный пользователем. Message в данном случае - это будет объект типа [Message](https://pytba.readthedocs.io/en/latest/types.html#telebot.types.Message), у которого есть несколько аргументов (самый базовый - text, то, что написал пользователь). Но в качестве аргументв можно передавать почти все, что угодно\n", 247 | "\n", 248 | "Например, кубик!" 249 | ], 250 | "metadata": { 251 | "id": "-yHrINDr2dgf" 252 | } 253 | }, 254 | { 255 | "cell_type": "code", 256 | "source": [ 257 | "@bot.message_handler(content_types='dice')\n", 258 | "def dice_reply(message):\n", 259 | " bot.send_message(message.dice.value)" 260 | ], 261 | "metadata": { 262 | "id": "8-GgtNhA5oZC" 263 | }, 264 | "execution_count": null, 265 | "outputs": [] 266 | }, 267 | { 268 | "cell_type": "code", 269 | "source": [ 270 | "bot.polling(none_stop=True, interval=0) #запускаем нашего бота" 271 | ], 272 | "metadata": { 273 | "id": "xvxXfJPD5pc8" 274 | }, 275 | "execution_count": null, 276 | "outputs": [] 277 | }, 278 | { 279 | "cell_type": "markdown", 280 | "source": [ 281 | "Давайте реализуем теперь нашего бота следующим образом:" 282 | ], 283 | "metadata": { 284 | "id": "aGcEEqHC5-_d" 285 | } 286 | }, 287 | { 288 | "cell_type": "markdown", 289 | "source": [ 290 | "### Задание 1\n", 291 | "\n", 292 | "Сделайте бот, который будет на старте выдавать две кнопки: одна с вашим любимым попугаем, вторая - с подкидыванием кубика. В зависимости от кубика мы тоже выводим рандомного попугая!" 293 | ], 294 | "metadata": { 295 | "id": "xnfPe5346DCW" 296 | } 297 | }, 298 | { 299 | "cell_type": "code", 300 | "source": [ 301 | "TOKEN = '5674479560:AAHI0lWyLHZQUa91Di-6NmNqdWbE7lL_6H8' # указываем токен нашего бота (для этого надо создать бота в @BotFather)\n", 302 | "# Создайте собственного бота, чтобы наши наработки друг друга не перебивали\n", 303 | "\n", 304 | "bot = telebot.TeleBot(TOKEN) # инициализируем нашего бота\n", 305 | "\n", 306 | "parrots = {1: 'https://cindygurmann.files.wordpress.com/2018/06/ea2530ad-e913-4d5b-8036-762b5b227c04.jpeg',\n", 307 | " 2: 'https://cherepah.ru/wp-content/uploads/2/2/8/228937ec782b8755993a3241e1d6c039.jpeg',\n", 308 | " 3: 'https://kotsobaka.com/wp-content/uploads/2018/08/2748131046_8a253489b5_b.jpg',\n", 309 | " 4: 'https://bestpopugai.ru/wp-content/uploads/2022/05/1-5.jpg',\n", 310 | " 5: 'https://i.artfile.ru/1920x1200_952300_[www.ArtFile.ru].jpg',\n", 311 | " 6: 'https://s1.1zoom.ru/big0/612/Parrots_Birds_Ara_(genus)_Hyacinth_macaw_Blue_570002_1280x853.jpg'}\n", 312 | "\n", 313 | "favourite_parrot = 'https://pet7.ru/wp-content/uploads/2017/09/Popugaj-zhako-osobennosti-vida.jpg'" 314 | ], 315 | "metadata": { 316 | "id": "FK11QstZ6dkw" 317 | }, 318 | "execution_count": null, 319 | "outputs": [] 320 | }, 321 | { 322 | "cell_type": "markdown", 323 | "source": [ 324 | "Окей, выглядит прикольно! Уже умеем как-то отвечать на запросы. Но что можно улучшить? Например, кнопочки (можно сделать в одной строке). Давайте сделаем" 325 | ], 326 | "metadata": { 327 | "id": "4RGMVqwv-Wno" 328 | } 329 | }, 330 | { 331 | "cell_type": "code", 332 | "source": [ 333 | "@bot.message_handler(commands=['start'])\n", 334 | "def hello_message(message):\n", 335 | " markup = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) # указываем, сколько кнопок может быть в строке\n", 336 | " item_1 = types.KeyboardButton(\"Любимый попугай\")\n", 337 | " item_2 = types.KeyboardButton(\"🎲\")\n", 338 | " markup.add(item_1, item_2) #добавляем\n", 339 | " bot.send_message(message.chat.id, \"Привет, тут будут попугаи!\", reply_markup=markup)\n", 340 | "\n", 341 | "bot.polling(none_stop=True, interval=0) #запускаем нашего бота" 342 | ], 343 | "metadata": { 344 | "id": "-UrvYZtL-tfE" 345 | }, 346 | "execution_count": null, 347 | "outputs": [] 348 | }, 349 | { 350 | "cell_type": "markdown", 351 | "source": [ 352 | "Чего не хватает? Правильно, дальнейших кнопок!\n", 353 | "\n", 354 | "Давайте добавим так, чтобы при выводе любимого попугая можно было отослать гифку с попугаем" 355 | ], 356 | "metadata": { 357 | "id": "8eLyoqKJBdbB" 358 | } 359 | }, 360 | { 361 | "cell_type": "code", 362 | "source": [ 363 | "@bot.message_handler(content_types=['text', 'emoji'])\n", 364 | "def message_reply(message):\n", 365 | " if message.text==\"Любимый попугай\":\n", 366 | " markup = types.ReplyKeyboardMarkup()\n", 367 | " item = types.KeyboardButton(text='Хочу GIF-ку!')\n", 368 | " markup.add(item)\n", 369 | " bot.send_photo(message.chat.id, favourite_parrot, reply_markup = markup)\n", 370 | " elif message.text == \"🎲\":\n", 371 | " r = bot.send_dice(message.chat.id)\n", 372 | " bot.send_photo(message.chat.id, parrots[r.dice.value])\n", 373 | " elif message.text == 'Хочу GIF-ку!':\n", 374 | " bot.send_animation(message.chat.id, parrot_gif)" 375 | ], 376 | "metadata": { 377 | "id": "wVvj7dwwCG9N" 378 | }, 379 | "execution_count": null, 380 | "outputs": [] 381 | }, 382 | { 383 | "cell_type": "markdown", 384 | "source": [ 385 | "Что еще можем добавить? Ссылку на саму гифку внутри сообщения!\n", 386 | "\n", 387 | "Есть такая вещь, как InlineKeyboardButton - чтобы вывести кнопку в самом сообщении, другое дело, что она работает по-другому:\n", 388 | "\n", 389 | "* types.InlineKeyboardButton(text, url=None, callback_data=None, web_app=None, switch_inline_query=None, switch_inline_query_current_chat=None, callback_game=None, pay=None, login_url=None)\n", 390 | "\n", 391 | "Один из параметров (и только один) должен быть:\n", 392 | "\n", 393 | "* url - ссылка\n", 394 | "\n", 395 | "* callback_data - то, что отправляется назад при нажатии на кнопку\n", 396 | "\n", 397 | "* web_app - запуск app\n", 398 | "\n", 399 | "* switch_inline_query - вызов для выбора чатика, куда вставляется адрес бота и сообщение \n", 400 | "\n", 401 | "* switch_inline_query_current_chat - вызов внутри чата (скажем, полезно для встроенных ботов в чате)\n", 402 | "\n", 403 | "* callback_game - запуск игры\n", 404 | "\n", 405 | "* pay - кнопка оплаты (работает только для кнопок Invoice)" 406 | ], 407 | "metadata": { 408 | "id": "T1rI5B_xEam7" 409 | } 410 | }, 411 | { 412 | "cell_type": "code", 413 | "source": [ 414 | "TOKEN = '5674479560:AAHI0lWyLHZQUa91Di-6NmNqdWbE7lL_6H8' # указываем токен нашего бота (для этого надо создать бота в @BotFather)\n", 415 | "# Создайте собственного бота, чтобы наши наработки друг друга не перебивали\n", 416 | "\n", 417 | "bot = telebot.TeleBot(TOKEN) # инициализируем нашего бота\n", 418 | "\n", 419 | "parrots = {1: 'https://cindygurmann.files.wordpress.com/2018/06/ea2530ad-e913-4d5b-8036-762b5b227c04.jpeg',\n", 420 | " 2: 'https://cherepah.ru/wp-content/uploads/2/2/8/228937ec782b8755993a3241e1d6c039.jpeg',\n", 421 | " 3: 'https://kotsobaka.com/wp-content/uploads/2018/08/2748131046_8a253489b5_b.jpg',\n", 422 | " 4: 'https://bestpopugai.ru/wp-content/uploads/2022/05/1-5.jpg',\n", 423 | " 5: 'https://i.artfile.ru/1920x1200_952300_[www.ArtFile.ru].jpg',\n", 424 | " 6: 'https://s1.1zoom.ru/big0/612/Parrots_Birds_Ara_(genus)_Hyacinth_macaw_Blue_570002_1280x853.jpg'}\n", 425 | "\n", 426 | "favourite_parrot = 'https://pet7.ru/wp-content/uploads/2017/09/Popugaj-zhako-osobennosti-vida.jpg'\n", 427 | "\n", 428 | "parrot_gif = 'https://i1.wp.com/cdn.dribbble.com/users/104127/screenshots/2589080/parrots.gif'\n", 429 | "\n", 430 | "@bot.message_handler(commands=['start'])\n", 431 | "def hello_message(message):\n", 432 | " markup = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) # указываем, сколько кнопок может быть в строке\n", 433 | " item_1 = types.KeyboardButton(\"Любимый попугай\")\n", 434 | " item_2 = types.KeyboardButton(\"🎲\")\n", 435 | " markup.add(item_1, item_2) #добавляем\n", 436 | " bot.send_message(message.chat.id, \"Привет, тут будут попугаи!\", reply_markup=markup)\n", 437 | "\n", 438 | "@bot.message_handler(content_types=['text', 'emoji'])\n", 439 | "def message_reply(message):\n", 440 | " if message.text==\"Любимый попугай\":\n", 441 | " markup = types.ReplyKeyboardMarkup()\n", 442 | " item = types.KeyboardButton(text='Хочу GIF-ку!')\n", 443 | " markup.add(item)\n", 444 | " bot.send_photo(message.chat.id, favourite_parrot, reply_markup = markup)\n", 445 | " elif message.text == \"🎲\":\n", 446 | " r = bot.send_dice(message.chat.id)\n", 447 | " bot.send_photo(message.chat.id, parrots[r.dice.value])\n", 448 | " elif message.text == 'Хочу GIF-ку!':\n", 449 | " markup = types.InlineKeyboardMarkup()\n", 450 | " item = types.InlineKeyboardButton(text='Ссылка', url=parrot_gif)\n", 451 | " markup.add(item)\n", 452 | " bot.send_animation(message.chat.id, parrot_gif, reply_markup = markup)\n", 453 | "\n", 454 | "bot.polling(none_stop=True, interval=0) #запускаем нашего бота" 455 | ], 456 | "metadata": { 457 | "id": "FRnY6R62CyV4" 458 | }, 459 | "execution_count": null, 460 | "outputs": [] 461 | }, 462 | { 463 | "cell_type": "markdown", 464 | "source": [ 465 | "Давайте добавим еще кнопку, с помощью которой можно автоматически переслать ссылку на попуга!" 466 | ], 467 | "metadata": { 468 | "id": "4RcuS62DHJKs" 469 | } 470 | }, 471 | { 472 | "cell_type": "code", 473 | "source": [ 474 | "TOKEN = '5674479560:AAHI0lWyLHZQUa91Di-6NmNqdWbE7lL_6H8' # указываем токен нашего бота (для этого надо создать бота в @BotFather)\n", 475 | "# Создайте собственного бота, чтобы наши наработки друг друга не перебивали\n", 476 | "\n", 477 | "bot = telebot.TeleBot(TOKEN) # инициализируем нашего бота\n", 478 | "\n", 479 | "parrots = {1: 'https://cindygurmann.files.wordpress.com/2018/06/ea2530ad-e913-4d5b-8036-762b5b227c04.jpeg',\n", 480 | " 2: 'https://cherepah.ru/wp-content/uploads/2/2/8/228937ec782b8755993a3241e1d6c039.jpeg',\n", 481 | " 3: 'https://kotsobaka.com/wp-content/uploads/2018/08/2748131046_8a253489b5_b.jpg',\n", 482 | " 4: 'https://bestpopugai.ru/wp-content/uploads/2022/05/1-5.jpg',\n", 483 | " 5: 'https://i.artfile.ru/1920x1200_952300_[www.ArtFile.ru].jpg',\n", 484 | " 6: 'https://kipmu.ru/wp-content/uploads/pchppgks.jpg'}\n", 485 | "\n", 486 | "favourite_parrot = 'https://pet7.ru/wp-content/uploads/2017/09/Popugaj-zhako-osobennosti-vida.jpg'\n", 487 | "\n", 488 | "parrot_gif = 'https://i1.wp.com/cdn.dribbble.com/users/104127/screenshots/2589080/parrots.gif'\n", 489 | "\n", 490 | "@bot.message_handler(commands=['start'])\n", 491 | "def hello_message(message):\n", 492 | " markup = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) # указываем, сколько кнопок может быть в строке\n", 493 | " item_1 = types.KeyboardButton(\"Любимый попугай\")\n", 494 | " item_2 = types.KeyboardButton(\"🎲\")\n", 495 | " markup.add(item_1, item_2) #добавляем\n", 496 | " bot.send_message(message.chat.id, \"Привет, тут будут попугаи!\", reply_markup=markup)\n", 497 | "\n", 498 | "@bot.message_handler(content_types=['text', 'emoji'])\n", 499 | "def message_reply(message):\n", 500 | " if message.text==\"Любимый попугай\":\n", 501 | " markup = types.ReplyKeyboardMarkup()\n", 502 | " item = types.KeyboardButton(text='Хочу GIF-ку!')\n", 503 | " markup.add(item)\n", 504 | " bot.send_photo(message.chat.id, favourite_parrot, reply_markup = markup)\n", 505 | " elif message.text == \"🎲\":\n", 506 | " r = bot.send_dice(message.chat.id)\n", 507 | " bot.send_photo(message.chat.id, parrots[r.dice.value])\n", 508 | " elif message.text == 'Хочу GIF-ку!':\n", 509 | " markup = types.InlineKeyboardMarkup()\n", 510 | " item = types.InlineKeyboardButton(text='Ссылка', url=parrot_gif)\n", 511 | " item_1 = types.InlineKeyboardButton(text='Переслать', switch_inline_query=parrot_gif)\n", 512 | " markup.add(item, item_1)\n", 513 | " bot.send_animation(message.chat.id, parrot_gif, reply_markup = markup)\n", 514 | " markup = types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2) # указываем, сколько кнопок может быть в строке\n", 515 | " item_1 = types.KeyboardButton(\"Любимый попугай\")\n", 516 | " item_2 = types.KeyboardButton(\"🎲\")\n", 517 | " markup.add(item_1, item_2) #добавляем\n", 518 | " bot.send_message(message.chat.id, \"Еще попуги!\", reply_markup = markup)\n", 519 | "\n", 520 | "\n", 521 | "bot.polling(none_stop=True, interval=0) #запускаем нашего бота" 522 | ], 523 | "metadata": { 524 | "id": "V8Eznr-EHNDn" 525 | }, 526 | "execution_count": null, 527 | "outputs": [] 528 | }, 529 | { 530 | "cell_type": "markdown", 531 | "source": [ 532 | "Давайте добавим еще функциональности, чтобы его можно было вызывать из строки!" 533 | ], 534 | "metadata": { 535 | "id": "BNJDhuOHvbpI" 536 | } 537 | }, 538 | { 539 | "cell_type": "code", 540 | "source": [ 541 | "from random import seed, randrange\n", 542 | "from time import time\n", 543 | "\n", 544 | "@bot.inline_handler(func=lambda query: len(query.query) > 0)\n", 545 | "def query_text(query):\n", 546 | " try:\n", 547 | " seed(int(time()))\n", 548 | " size = randrange(1, 100)\n", 549 | " print(size)\n", 550 | " r_sum = types.InlineQueryResultArticle(\n", 551 | " id='1', title=\"Папуг!\",\n", 552 | " input_message_content = types.InputTextMessageContent(message_text=\"Ваш папуг \" + str(size) + \" размера\" )\n", 553 | " )\n", 554 | " bot.answer_inline_query(query.id, [r_sum])\n", 555 | " except Exception as e:\n", 556 | " print(\"{!s}\\n{!s}\".format(type(e), str(e)))" 557 | ], 558 | "metadata": { 559 | "id": "df3rW8rdvfsU" 560 | }, 561 | "execution_count": null, 562 | "outputs": [] 563 | }, 564 | { 565 | "cell_type": "markdown", 566 | "source": [ 567 | "Ну хорошо, умеем отвечать, делать кнопочки, даже открывать ссылки и что-то пересылать. Что еще нужно для всей красоты?\n", 568 | "\n", 569 | "Ну вот как видите, у нас бот работает только тогда, когда мы тут запускаем всю работу, а больше мы ничего не можем делать :с\n", 570 | "\n", 571 | "Давайте его закинем в контейнер dockerа, чтобы можно было его запустить на фоне и жить счастливо!\n" 572 | ], 573 | "metadata": { 574 | "id": "y-g48t5xH5ii" 575 | } 576 | }, 577 | { 578 | "cell_type": "markdown", 579 | "source": [ 580 | "### Делаем бота сильным и независимым" 581 | ], 582 | "metadata": { 583 | "id": "DUV2mVoEJkGk" 584 | } 585 | }, 586 | { 587 | "cell_type": "code", 588 | "source": [ 589 | "%%writefile bot.py" 590 | ], 591 | "metadata": { 592 | "colab": { 593 | "base_uri": "https://localhost:8080/" 594 | }, 595 | "id": "BwpoeQWFJlje", 596 | "outputId": "4997b1c1-67e2-4f50-a300-14f85aeb37d6" 597 | }, 598 | "execution_count": null, 599 | "outputs": [ 600 | { 601 | "output_type": "stream", 602 | "name": "stdout", 603 | "text": [ 604 | "Writing bot.py\n" 605 | ] 606 | } 607 | ] 608 | }, 609 | { 610 | "cell_type": "markdown", 611 | "source": [ 612 | "Вспоминаем, как работать с докером:" 613 | ], 614 | "metadata": { 615 | "id": "Vo4Sv_JdMXHG" 616 | } 617 | }, 618 | { 619 | "cell_type": "code", 620 | "source": [ 621 | "docker pull python:3-onbuild\n", 622 | "\n", 623 | "vim Dockerfile" 624 | ], 625 | "metadata": { 626 | "id": "mZ8FSyy_MaDo" 627 | }, 628 | "execution_count": null, 629 | "outputs": [] 630 | }, 631 | { 632 | "cell_type": "code", 633 | "source": [ 634 | "FROM python:3-onbuild #указываем образ, который надо использовать\n", 635 | "\n", 636 | "#благодаря тому, что у нас onbuild, нам не надо копировать файлы и устанавливать зависимости здесь\n", 637 | "\n", 638 | "EXPOSE 8888 #говорим, на какой порт это все отправлять\n", 639 | "\n", 640 | "CMD [\"python\", \"bot.py\"] # команды для запуска (то есть что надо сделать)" 641 | ], 642 | "metadata": { 643 | "id": "93MA-dsKNZYn" 644 | }, 645 | "execution_count": null, 646 | "outputs": [] 647 | }, 648 | { 649 | "cell_type": "code", 650 | "source": [ 651 | "docker build -t palladain7/catgif . #здесь надо зарегаться на Docker Hub (это быстро) и в качестве user ввести свой ник\n", 652 | "docker run -d -P -p 8888:8888 palladain/catgif #собрали-запустили\n", 653 | "docker images #проверяем образ" 654 | ], 655 | "metadata": { 656 | "id": "akWgl2w3NcmW" 657 | }, 658 | "execution_count": null, 659 | "outputs": [] 660 | } 661 | ] 662 | } -------------------------------------------------------------------------------- /Seminars/Seminar_9/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminar_9/.DS_Store -------------------------------------------------------------------------------- /Seminars/Seminar_9/Project/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminar_9/Project/.DS_Store -------------------------------------------------------------------------------- /Seminars/Seminar_9/Project/app.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import os 3 | from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash 4 | 5 | # конфигурация 6 | 7 | DATABASE = '/tmp/flaskr.db' 8 | DEBUG = True 9 | SECRET_KEY = 'development key' 10 | USERNAME = 'admin' 11 | PASSWORD = 'default' 12 | 13 | app = Flask(__name__) 14 | app.config.from_object(__name__) 15 | 16 | app.config.update(dict( 17 | DATABASE=os.path.join(app.root_path, 'flaskr.db'), 18 | DEBUG=True, 19 | SECRET_KEY='development key', 20 | USERNAME='admin', 21 | PASSWORD='default')) 22 | 23 | app.config.from_envvar('FLASKR_SETTINGS', silent=True) 24 | 25 | def connect_db(): 26 | """Соединяет с указанной базой данных.""" 27 | rv = sqlite3.connect(app.config['DATABASE']) # внутри конфигураций надо будет указать БД, в которую мы будем все хранить 28 | rv.row_factory = sqlite3.Row #инстанс для итерации по строчкам (может брать по строке и выдавать) 29 | return rv 30 | 31 | def get_db(): 32 | """Если ещё нет соединения с базой данных, открыть новое - для текущего контекста приложения""" 33 | if not hasattr(g, 'sqlite_db'): #g - это наша глобальная переменная, являющасяс объектом отрисовки 34 | g.sqlite_db = connect_db() 35 | return g.sqlite_db 36 | 37 | @app.teardown_appcontext #декоратор при разрыве connection 38 | def close_db(error): #закрытие может проходить как нормально, так и с ошибкой, которую можно обрабатывать 39 | """Закрываем БД при разрыве""" 40 | if hasattr(g, 'sqlite_db'): 41 | g.sqlite_db.close() 42 | 43 | def init_db(): 44 | """Инициализируем наше БД""" 45 | with app.app_context(): # внутри app_context app и g связаны 46 | db = get_db() 47 | with app.open_resource('schema.sql', mode='r') as f: 48 | db.cursor().executescript(f.read()) 49 | db.commit() 50 | 51 | @app.route('/') 52 | def hello_page(): 53 | return render_template("layout.html") 54 | 55 | @app.route('/posts') 56 | def show_entries(): 57 | db = get_db() 58 | cur = db.execute('select title, text from entries order by id desc') 59 | entries = cur.fetchall() 60 | return render_template('show_entries.html', entries=entries) 61 | 62 | @app.route('/add', methods=['POST']) 63 | def add_entry(): 64 | if not session.get('logged_in'): 65 | abort(401) 66 | db = get_db() 67 | db.execute('insert into entries (title, text) values (?, ?)', [request.form['title'], request.form['text']]) 68 | db.commit() 69 | flash('New entry was successfully posted') 70 | return redirect(url_for('show_entries')) 71 | 72 | @app.route('/login', methods=['GET', 'POST']) 73 | def login(): 74 | error = None 75 | if request.method == 'POST': 76 | if request.form['username'] != app.config['USERNAME']: 77 | error = 'Invalid username' 78 | elif request.form['password'] != app.config['PASSWORD']: 79 | error = 'Invalid password' 80 | else: 81 | session['logged_in'] = True 82 | flash('You were logged in') 83 | return redirect(url_for('show_entries')) 84 | return render_template('login.html', error=error) 85 | 86 | @app.route('/logout') 87 | def logout(): 88 | session.pop('logged_in', None) 89 | flash('You were logged out, wow') 90 | return redirect(url_for('hello_page')) 91 | 92 | 93 | if __name__ == '__main__': 94 | init_db() 95 | app.run() 96 | -------------------------------------------------------------------------------- /Seminars/Seminar_9/Project/flaskr.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminar_9/Project/flaskr.db -------------------------------------------------------------------------------- /Seminars/Seminar_9/Project/schema.sql: -------------------------------------------------------------------------------- 1 | drop table if exists entries; 2 | create table entries ( 3 | id integer primary key autoincrement, 4 | title text, 5 | text text not null 6 | ); -------------------------------------------------------------------------------- /Seminars/Seminar_9/Project/static/meme.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminar_9/Project/static/meme.jpg -------------------------------------------------------------------------------- /Seminars/Seminar_9/Project/static/style.css: -------------------------------------------------------------------------------- 1 | body { font-family: sans-serif; background: #eee; } 2 | a, h1, h2 { color: #377ba8; } 3 | h1, h2 { font-family: 'Georgia', serif; margin: 0; } 4 | h1 { border-bottom: 2px solid #eee; } 5 | h2 { font-size: 1.2em; } 6 | img {width: 35em;} 7 | 8 | .page { margin: 2em auto; width: 35em; border: 5px solid #ccc; padding: 0.8em; background: white; } 9 | .entries { list-style: none; margin: 0; padding: 0; } 10 | .entries li {margin: 0.8em 1.2em} 11 | .entries li h2 { margin-left: -1em; } 12 | .add_entry { font-size: 0.9em; border-bottom: 1px solid #ccc; } 13 | .add_entry dl { font-weight: bold; } 14 | .metanav { text-align: right; font-size: 0.8em; padding: 0.3em; margin-bottom: 1em; background: #fafafa;} 15 | .flash { background: #cee5F5; padding: 0.5em; border: 1px solid #aacbe2; } 16 | .border { background: #f0d6d6; padding: 0.5em; } 17 | -------------------------------------------------------------------------------- /Seminars/Seminar_9/Project/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | Twitter 3 | 4 |

5 |

Twitter

6 |
7 | {% if not session.logged_in %} 8 | log in {% else %} 9 | log out {% endif %} 10 |
11 | 12 | {% for message in get_flashed_messages() %} 13 |
{{ message }}
{% endfor %} 14 | {% block body %}{% endblock %} 15 |
-------------------------------------------------------------------------------- /Seminars/Seminar_9/Project/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block body %} 2 |

Login

3 | {% if error %}

Error: {{ error }}{% endif %} 4 |
5 |

6 |
Username: 7 |
Password: 8 |
9 |
10 | {% endblock %} -------------------------------------------------------------------------------- /Seminars/Seminar_9/Project/templates/show_entries.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} {% block body %} 2 | {% if session.logged_in %} 3 |
4 |
Title: 5 |
6 |
Text: 7 |
8 |
{% endif %} 9 |
    10 | {% for entry in entries %} 11 |
  • {{ entry.title }}

    {{ entry.text|safe }} {% else %} 12 |
  • Unbelievable. No entries here so far {% endfor %} 13 |
14 | {% endblock %} -------------------------------------------------------------------------------- /Seminars/Seminar_9/Seminar_9_Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "provenance": [], 7 | "collapsed_sections": [] 8 | }, 9 | "kernelspec": { 10 | "name": "python3", 11 | "display_name": "Python 3" 12 | }, 13 | "language_info": { 14 | "name": "python" 15 | } 16 | }, 17 | "cells": [ 18 | { 19 | "cell_type": "markdown", 20 | "source": [ 21 | "# Продвинутый Python, семинар 9\n", 22 | "\n", 23 | "**Лектор:** Петров Тимур\n", 24 | "\n", 25 | "**Семинаристы:** Петров Тимур, Коган Александра, Романченко Полина\n", 26 | "\n", 27 | "**Spoiler Alert:** в рамках курса нельзя изучить ни одну из тем от и до досконально (к сожалению, на это требуется больше времени, чем даже 3 часа в неделю). Но мы попробуем рассказать столько, сколько возможно :)\n", 28 | "\n", 29 | "Так как мы проходим веб-разработку, то это явно не та вещь, которая делается через colab. Поэтому здесь написан просто код, который можно воспроизвести локально" 30 | ], 31 | "metadata": { 32 | "id": "30axvO7utujN" 33 | } 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "source": [ 38 | "## Jinja2" 39 | ], 40 | "metadata": { 41 | "id": "YX3Oa4fst0_I" 42 | } 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "source": [ 47 | "В прошлый раз мы разобрались с самым непонятный: сделали локальную БД, в которой будут все данные храниться. Теперь давайте к части с самим вебом!" 48 | ], 49 | "metadata": { 50 | "id": "IO3uHP50t6eg" 51 | } 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "source": [ 56 | "Давайте для начала нарисуем наш сайт, как он должен выглядеть. Для этого надо немного больше погрузиться в разметку Jinja2. Что основного необходимо знать про это?\n", 57 | "\n", 58 | "* {{ }} - через двойные скобки указываются переменные (которые мы можем указывать внутри функции render_template\n", 59 | "\n", 60 | "* {% %} - в таких скобках указываются всякие условия (с помощью которых можно кастомизировать ваш шаблон\n", 61 | "\n", 62 | "* {# #} - в таких скобках можно указывать комментарии\n", 63 | "\n", 64 | "Помимо того, чтобы передавать сами переменные, также можно передавать функции!\n", 65 | "Например, как сделать переадресацию без полного указания необходимой странички:" 66 | ], 67 | "metadata": { 68 | "id": "rVvGgS5PJOmM" 69 | } 70 | }, 71 | { 72 | "cell_type": "markdown", 73 | "source": [ 74 | "```\n", 75 | "{{ url_for('login') }} - здесь мы передаем функцию url_for, генерируя ссылку куда нам надо\n", 76 | "```" 77 | ], 78 | "metadata": { 79 | "id": "WeePeAt9KHv0" 80 | } 81 | }, 82 | { 83 | "cell_type": "markdown", 84 | "source": [ 85 | "Давайте сразу же разберем условия:\n", 86 | "\n", 87 | "```\n", 88 | "{% if not session.logged_in %} - если выполняется условие\n", 89 | "(в данном случае параметр logged_in)\n", 90 | "\n", 91 | " - покажи вот это\n", 92 | "\n", 93 | "{% else %} - Иначе\n", 94 | "\n", 95 | " - покажи это \n", 96 | "\n", 97 | "{% endif %} - заканчиваем\n", 98 | "```\n", 99 | "\n", 100 | "Помимо классического if-else есть и elif (само собой)\n", 101 | "\n", 102 | "Можно не только условия делать, но можно и так же делать итерацию через for!\n", 103 | "\n", 104 | "```\n", 105 | "{% for value in values %}\n", 106 | "

value

- вывести все value в отдельных абзацах\n", 107 | "{% endfor %}\n", 108 | "```" 109 | ], 110 | "metadata": { 111 | "id": "ZiirCiCuKtfZ" 112 | } 113 | }, 114 | { 115 | "cell_type": "markdown", 116 | "source": [ 117 | "### Наследование" 118 | ], 119 | "metadata": { 120 | "id": "5F3PViN4PZZp" 121 | } 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "source": [ 126 | "Отдельный пункт - это наследование. Что это значит?\n", 127 | "\n", 128 | "Опять-таки у вас есть страничка Вики ФКН. Вы видите, что у вас есть общие части (например header etc). Но мы же не хотим это прописывать для каждого сайта, верно?\n", 129 | "\n", 130 | "Ровно поэтому есть наследование внутри Jinja2. Вы можете отдельно написать общую часть (которая отвечает отдельно за неизменяемые части, чаще всего это называется layout), а после просто добавлять другие блоки. Как это сделать?\n", 131 | "\n", 132 | "Внутри родителя:\n", 133 | "\n", 134 | "```\n", 135 | "{% block body %}{% endblock %} - \"резервируем\" часть, которую может менять наследник\n", 136 | "```\n", 137 | "\n", 138 | "Внутри наследника:\n", 139 | "\n", 140 | "```\n", 141 | "{% extends \"base.html\" %} - указываем родителя, от чего наследуемся\n", 142 | "{% block body %} - указываем часть, которую меняем\n", 143 | "

Index

\n", 144 | "

\n", 145 | " Welcome to my awesome homepage.\n", 146 | "

\n", 147 | "{% endblock %}\n", 148 | "```" 149 | ], 150 | "metadata": { 151 | "id": "NyHByKUyPdjl" 152 | } 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "source": [ 157 | "А еще поддерживается подобное наследование:\n", 158 | "\n", 159 | "parent -> child -> grandchild\n", 160 | "\n", 161 | "И если вам внутри child и grandchild вы меняете один и тот же блок (и хотите их оба вызвать), то тогда можно обращаться на уровень выше с помощью:\n", 162 | "\n", 163 | "```\n", 164 | "{{ super() }} - выведи то, что было на уровень выше\n", 165 | "\n", 166 | "{{ super.super() }} - выведи то, что было на 2 уровня выше\n", 167 | "```" 168 | ], 169 | "metadata": { 170 | "id": "QIIrsrVARqVk" 171 | } 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "source": [ 176 | "### Фильтры" 177 | ], 178 | "metadata": { 179 | "id": "Al1BLnpzSfWs" 180 | } 181 | }, 182 | { 183 | "cell_type": "markdown", 184 | "source": [ 185 | "И последнее, о чем важно сказать для базового понимания - это фильтры. Скажем, что у вас есть переменная name, внутри которой строка \"hi there\"\n", 186 | "\n", 187 | "А нам не нравится, что все с маленькой буквы (ну не дело это). А в коде забыли поменять (или не могли предугадать). Что же делать? Для этого внутри Jinja2 есть [фильтры](https://jinja.palletsprojects.com/en/3.1.x/templates/#builtin-filters):\n", 188 | "\n", 189 | "```\n", 190 | "{{ \"%s, %s!\"|format(greeting, name) }} - фильтр format, который работает как format в питоне\n", 191 | "\n", 192 | "{{name | upper }} - сделать все КАПСОМ!\n", 193 | "```" 194 | ], 195 | "metadata": { 196 | "id": "NpbRpqyQShAr" 197 | } 198 | }, 199 | { 200 | "cell_type": "markdown", 201 | "source": [ 202 | "## Напишем странички!" 203 | ], 204 | "metadata": { 205 | "id": "4nhJhwoWTmv1" 206 | } 207 | }, 208 | { 209 | "cell_type": "markdown", 210 | "source": [ 211 | "Мы с вами напишем 3 страницы:\n", 212 | "\n", 213 | "* layout.html - общая выкладка, которая общая для всех (где можно залогиниться)\n", 214 | "\n", 215 | "* show_entries.html - показать твиты наши\n", 216 | "\n", 217 | "* login.html - страница для того, чтобы залогиниться" 218 | ], 219 | "metadata": { 220 | "id": "b9YtKalrTst2" 221 | } 222 | }, 223 | { 224 | "cell_type": "markdown", 225 | "source": [ 226 | "### Шаг 1. layout.html" 227 | ], 228 | "metadata": { 229 | "id": "zmVgCOTRWORB" 230 | } 231 | }, 232 | { 233 | "cell_type": "markdown", 234 | "source": [ 235 | "```\n", 236 | "\n", 237 | "\n", 238 | "Twitter\n", 239 | "\n", 240 | "
\n", 241 | "

Twitter

\n", 242 | "\n", 243 | "
\n", 244 | "\n", 245 | "{% if not session.logged_in %}\n", 246 | " log in {% else %}\n", 247 | " log out {% endif %}\n", 248 | "\n", 249 | "
\n", 250 | "\n", 251 | "\n", 252 | "\n", 253 | "{% for message in get_flashed_messages() %}\n", 254 | "
{{ message }}
\n", 255 | "{% endfor %}\n", 256 | "\n", 257 | "{% block body %}{% endblock %}\n", 258 | "\n", 259 | "
\n", 260 | "```" 261 | ], 262 | "metadata": { 263 | "id": "eMeXk54DWUAc" 264 | } 265 | }, 266 | { 267 | "cell_type": "markdown", 268 | "source": [ 269 | "### Шаг 2. login.html" 270 | ], 271 | "metadata": { 272 | "id": "wr0C6ALRWoIu" 273 | } 274 | }, 275 | { 276 | "cell_type": "markdown", 277 | "source": [ 278 | "```\n", 279 | "{% extends \"layout.html\" %}\n", 280 | "{% block body %}\n", 281 | "\n", 282 | "

Login

\n", 283 | "\n", 284 | "{% if error %}\n", 285 | "

Error: {{ error }}\n", 286 | "{% endif %}\n", 287 | "\n", 288 | "
\n", 289 | "

\n", 290 | "
Username:\n", 291 | "
\n", 292 | "
Password:\n", 293 | "
\n", 294 | "
\n", 295 | "
\n", 296 | "\n", 297 | "\n", 298 | "{% endblock %}\n", 299 | "```" 300 | ], 301 | "metadata": { 302 | "id": "oFTpMhYKWtxg" 303 | } 304 | }, 305 | { 306 | "cell_type": "markdown", 307 | "source": [ 308 | "### Шаг 3. show_entries.html" 309 | ], 310 | "metadata": { 311 | "id": "G9AW4G0bW-Md" 312 | } 313 | }, 314 | { 315 | "cell_type": "markdown", 316 | "source": [ 317 | "```\n", 318 | "{% extends \"layout.html\" %} {% block body %}\n", 319 | "\n", 320 | "{% if session.logged_in %}\n", 321 | "\n", 322 | "
\n", 323 | "
\n", 324 | "
Title:\n", 325 | "
\n", 326 | "
Text:\n", 327 | "
\n", 328 | "
\n", 329 | " \n", 330 | "{% endif %}\n", 331 | "\n", 332 | "
    \n", 333 | "\n", 334 | "{% for entry in entries %}\n", 335 | "
  • {{ entry.title }}

    {{ entry.text|safe }}\n", 336 | "{% else %}\n", 337 | "
  • Unbelievable. No entries here so far\n", 338 | "{% endfor %}\n", 339 | "\n", 340 | "
\n", 341 | "\n", 342 | "{% endblock %}\n", 343 | "```" 344 | ], 345 | "metadata": { 346 | "id": "1UoKcdxWXB0_" 347 | } 348 | }, 349 | { 350 | "cell_type": "markdown", 351 | "source": [ 352 | "## А теперь опять к коду!" 353 | ], 354 | "metadata": { 355 | "id": "GFkgdLEAXX0S" 356 | } 357 | }, 358 | { 359 | "cell_type": "markdown", 360 | "source": [ 361 | "Какие методы у нас есть?\n", 362 | "\n", 363 | "* Зайти на начальную страницу\n", 364 | "\n", 365 | "* Логирование/разлогирование\n", 366 | "\n", 367 | "* Показать все посты (это мы уже сделали)\n", 368 | "\n", 369 | "* Сделать свой собственный пост (это сделали)" 370 | ], 371 | "metadata": { 372 | "id": "SiobfgARXllf" 373 | } 374 | }, 375 | { 376 | "cell_type": "markdown", 377 | "source": [ 378 | "### Задание 1" 379 | ], 380 | "metadata": { 381 | "id": "HO7uGrryYHIp" 382 | } 383 | }, 384 | { 385 | "cell_type": "markdown", 386 | "source": [ 387 | "\n", 388 | "Напишите функцию, которая приведет на главную страницу" 389 | ], 390 | "metadata": { 391 | "id": "u4AtpxwdYODD" 392 | } 393 | }, 394 | { 395 | "cell_type": "code", 396 | "source": [ 397 | "from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash\n", 398 | "\n", 399 | "@app.route('/')\n", 400 | "def hello_page():\n", 401 | " return render_template(\"layout.html\")" 402 | ], 403 | "metadata": { 404 | "id": "zD3K78YXYPJq" 405 | }, 406 | "execution_count": null, 407 | "outputs": [] 408 | }, 409 | { 410 | "cell_type": "markdown", 411 | "source": [ 412 | "### Задание 2" 413 | ], 414 | "metadata": { 415 | "id": "SzJiQNlAYQVL" 416 | } 417 | }, 418 | { 419 | "cell_type": "markdown", 420 | "source": [ 421 | "Напишите код для разлогирования (после разлогирования мы должны вывести плашку, что мы вышли, и также перевести на главный экран)" 422 | ], 423 | "metadata": { 424 | "id": "GqZF73FMYXU8" 425 | } 426 | }, 427 | { 428 | "cell_type": "code", 429 | "source": [ 430 | "@app.route('/logout')\n", 431 | "def logout():\n", 432 | " session.pop('logged_in', None)\n", 433 | " flash('You were logged out, wow')\n", 434 | " return redirect(url_for('hello_page'))" 435 | ], 436 | "metadata": { 437 | "id": "4U0FLc3yYnqq" 438 | }, 439 | "execution_count": null, 440 | "outputs": [] 441 | }, 442 | { 443 | "cell_type": "markdown", 444 | "source": [ 445 | "### Задание 3" 446 | ], 447 | "metadata": { 448 | "id": "X4PKXfJTYsZI" 449 | } 450 | }, 451 | { 452 | "cell_type": "markdown", 453 | "source": [ 454 | "Напишите функцию, которая будет производить логирование" 455 | ], 456 | "metadata": { 457 | "id": "5VLIn-_FY6W4" 458 | } 459 | }, 460 | { 461 | "cell_type": "markdown", 462 | "source": [ 463 | "Что здесь важно?\n", 464 | "\n", 465 | "Во-первых, у нас есть ошибка, которую надо будет отправлять для странички (что же не так, если пользователь ошибся)\n", 466 | "\n", 467 | "Во-вторых, а как определить, что пользователь ввел то, что нужно, у нас же не было никакой регистрации? Для этого мы должны добавить конфигурации (добавим админа, у нас же тут пока сырой проект):" 468 | ], 469 | "metadata": { 470 | "id": "k7wz0KxtY_cS" 471 | } 472 | }, 473 | { 474 | "cell_type": "code", 475 | "source": [ 476 | "import sqlite3\n", 477 | "\n", 478 | "DATABASE = '/tmp/flaskr.db' \n", 479 | "DEBUG = True\n", 480 | "SECRET_KEY = 'development key'\n", 481 | "USERNAME = 'admin' \n", 482 | "PASSWORD = 'default'\n", 483 | "\n", 484 | "app = Flask(__name__)\n", 485 | "app.config.from_object(__name__)\n", 486 | "\n", 487 | "app.config.update(dict(\n", 488 | " DATABASE=os.path.join(app.root_path, 'flaskr.db'),\n", 489 | " DEBUG=True,\n", 490 | " SECRET_KEY='development key',\n", 491 | " USERNAME='admin',\n", 492 | " PASSWORD='default'))\n", 493 | "\n", 494 | "app.config.from_envvar('FLASKR_SETTINGS', silent=True)" 495 | ], 496 | "metadata": { 497 | "id": "fY32P2S_ZYyo" 498 | }, 499 | "execution_count": null, 500 | "outputs": [] 501 | }, 502 | { 503 | "cell_type": "code", 504 | "source": [ 505 | "@app.route('/login', methods=['GET', 'POST'])\n", 506 | "def login():\n", 507 | " error = None\n", 508 | " if request.method == 'POST':\n", 509 | " if request.form['username'] != app.config['USERNAME']:\n", 510 | " error = 'Invalid username'\n", 511 | " elif request.form['password'] != app.config['PASSWORD']:\n", 512 | " error = 'Invalid password'\n", 513 | " else:\n", 514 | " session['logged_in'] = True\n", 515 | " flash('You were logged in')\n", 516 | " return redirect(url_for('show_entries'))\n", 517 | " return render_template('login.html', error=error)" 518 | ], 519 | "metadata": { 520 | "id": "ZqM7iZRKXl5C" 521 | }, 522 | "execution_count": null, 523 | "outputs": [] 524 | }, 525 | { 526 | "cell_type": "markdown", 527 | "source": [ 528 | "## Соединяем и запускаем!" 529 | ], 530 | "metadata": { 531 | "id": "EcAQu6a4ZzcL" 532 | } 533 | }, 534 | { 535 | "cell_type": "markdown", 536 | "source": [ 537 | "Странная фигня получилась, верно?..\n", 538 | "\n", 539 | "Не хватает верстки (сделать бы шрифт другой, цвет, сделать resize картинки)..\n", 540 | "\n", 541 | "Вот для этого всего есть CSS!" 542 | ], 543 | "metadata": { 544 | "id": "TJTdlm1HaBjk" 545 | } 546 | }, 547 | { 548 | "cell_type": "markdown", 549 | "source": [ 550 | "### CSS" 551 | ], 552 | "metadata": { 553 | "id": "mPoxx1-8aQ2w" 554 | } 555 | }, 556 | { 557 | "cell_type": "markdown", 558 | "source": [ 559 | "CSS (Cascading Style Sheets) - это правила для внешнего вида нашего HTML-документа (которые мы видели абсолютно везде, на всех сайтах), без этого не было бы никакой красоты" 560 | ], 561 | "metadata": { 562 | "id": "ino5mJqHajUh" 563 | } 564 | }, 565 | { 566 | "cell_type": "markdown", 567 | "source": [ 568 | "Как можно задавать стиль? Структура любого CSS-документа выглядит как:\n", 569 | "\n", 570 | "<для чего применить правило> - { <свойсто:значение; свойство:значение> }\n", 571 | "\n", 572 | "Свойств достаточно [много](https://html5book.ru/css-spravochnik.html), давайте на примере разберемся, что внутри CSS документа:\n", 573 | "\n" 574 | ], 575 | "metadata": { 576 | "id": "3hfweYVybUSY" 577 | } 578 | }, 579 | { 580 | "cell_type": "markdown", 581 | "source": [ 582 | "```\n", 583 | "Часть I - накидываем свойства на тэги\n", 584 | "\n", 585 | "body { font-family: sans-serif; background: #eee; } \n", 586 | "a, h1, h2 { color: #377ba8; }\n", 587 | "h1, h2 { font-family: 'Georgia', serif; margin: 0; }\n", 588 | "h1 { border-bottom: 2px solid #eee; }\n", 589 | "h2 { font-size: 1.2em; }\n", 590 | "img {width: 35em;}\n", 591 | "\n", 592 | "Часть II - накидываем свойства на классы (всегда с точкой)\n", 593 | "\n", 594 | ".page { margin: 2em auto; width: 35em; border: 5px solid #ccc; padding: 0.8em; background: white; }\n", 595 | ".entries { list-style: none; margin: 0; padding: 0; }\n", 596 | ".entries li {margin: 0.8em 1.2em}\n", 597 | "\n", 598 | "Отдельный случай - здесь указывается форматирования для всех li внутри класса .entries\n", 599 | "\n", 600 | "\n", 601 | ".entries li h2 { margin-left: -1em; }\n", 602 | ".add_entry { font-size: 0.9em; border-bottom: 1px solid #ccc; }\n", 603 | ".add_entry dl { font-weight: bold; }\n", 604 | ".metanav { text-align: right; font-size: 0.8em; padding: 0.3em; margin-bottom: 1em; background: #fafafa;}\n", 605 | ".flash { background: #cee5F5; padding: 0.5em; border: 1px solid #aacbe2; }\n", 606 | ".border { background: #f0d6d6; padding: 0.5em; }\n", 607 | "\n", 608 | "Если хотим для тэга с классом - то будет li.entries (как вариант)\n", 609 | "```" 610 | ], 611 | "metadata": { 612 | "id": "QkaOdaQmaV4Q" 613 | } 614 | }, 615 | { 616 | "cell_type": "markdown", 617 | "source": [ 618 | "А как решаются конфликты?\n", 619 | "\n", 620 | "Допустим, что вы в одном месте указали, что текст должен быть красным, а в другом синим, кто победит?\n", 621 | "\n", 622 | "Ответ: что позже идет в документе, то и выигрывает (на то оно и называется каскадированным)" 623 | ], 624 | "metadata": { 625 | "id": "a_ktnPOsc-Yd" 626 | } 627 | }, 628 | { 629 | "cell_type": "markdown", 630 | "source": [ 631 | "И вот теперь соединив все это, вы получаете нужный результат! Победа!" 632 | ], 633 | "metadata": { 634 | "id": "lHcXMQNmdMCH" 635 | } 636 | }, 637 | { 638 | "cell_type": "markdown", 639 | "source": [ 640 | "## Животное дня" 641 | ], 642 | "metadata": { 643 | "id": "3wChAx32dV0R" 644 | } 645 | }, 646 | { 647 | "cell_type": "markdown", 648 | "source": [ 649 | "![](https://upload.wikimedia.org/wikipedia/commons/thumb/1/1e/Ambystoma_mexicanum_%286337857516%29.jpg/1920px-Ambystoma_mexicanum_%286337857516%29.jpg)" 650 | ], 651 | "metadata": { 652 | "id": "-pMxQzn7eaD0" 653 | } 654 | }, 655 | { 656 | "cell_type": "markdown", 657 | "source": [ 658 | "Сегодня у нас известное всем животное - амбистома (и аксолотль)\n", 659 | "\n", 660 | "В чем разница? Аксолотль - это амбистомы до тех пор, пока они не станут взрослыми (то есть это лишь этап). Половозрелые амбистомы выглядят вот так:" 661 | ], 662 | "metadata": { 663 | "id": "tGO2MZSNec7J" 664 | } 665 | }, 666 | { 667 | "cell_type": "markdown", 668 | "source": [ 669 | "![](https://upload.wikimedia.org/wikipedia/commons/0/00/Axolotl_ganz.jpg)" 670 | ], 671 | "metadata": { 672 | "id": "fRoncUujftA4" 673 | } 674 | }, 675 | { 676 | "cell_type": "markdown", 677 | "source": [ 678 | "Аксолотль не может самостоятельно стать взрослой, потому что для взросления ей необходим тироксин, которые они сами вообще не вырабатывают (то есть взрослыми они становятся за счет внешних причин, например, засухи)\n", 679 | "\n", 680 | "И для амбистом оставаться аксолотлем, видимо, максимально выгодно!\n", 681 | "\n", 682 | "Аксолотли обладают фантастической способоностью к регенерации - они даже могут мозг регенирировать (не все части, но все-таки), не оставляя на себе никаких шрамов. Поэтому ученые их используют для изучения как идеальную модель\n", 683 | "\n", 684 | "Но к сожалению они вымирают, потому что раньше ацтеки их ели, а потом пришли конкистадоры, естественно, испортили их среду обитания, да и сейчас ситуация еще хуже (из-за загрязнения воды в Мексике)\n" 685 | ], 686 | "metadata": { 687 | "id": "3g_BggGDfvn0" 688 | } 689 | } 690 | ] 691 | } -------------------------------------------------------------------------------- /Seminars/Seminars_8/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminars_8/.DS_Store -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminars_8/Aliexpress.zip -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminars_8/Aliexpress/.DS_Store -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminars_8/Aliexpress/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/__pycache__/items.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminars_8/Aliexpress/__pycache__/items.cpython-310.pyc -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/__pycache__/pipelines.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminars_8/Aliexpress/__pycache__/pipelines.cpython-310.pyc -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/__pycache__/settings.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminars_8/Aliexpress/__pycache__/settings.cpython-310.pyc -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/items.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/items.py: -------------------------------------------------------------------------------- 1 | # Define here the models for your scraped items 2 | # 3 | # See documentation in: 4 | # https://docs.scrapy.org/en/latest/topics/items.html 5 | 6 | import scrapy 7 | 8 | 9 | class AliexpressItem(scrapy.Item): 10 | name = scrapy.Field() 11 | category = scrapy.Field() 12 | price = scrapy.Field() 13 | sale_price = scrapy.Field() 14 | delivery = scrapy.Field() 15 | rating = scrapy.Field() 16 | -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/middlewares.py: -------------------------------------------------------------------------------- 1 | # Define here the models for your spider middleware 2 | # 3 | # See documentation in: 4 | # https://docs.scrapy.org/en/latest/topics/spider-middleware.html 5 | 6 | from scrapy import signals 7 | 8 | # useful for handling different item types with a single interface 9 | from itemadapter import is_item, ItemAdapter 10 | 11 | 12 | class AliexpressSpiderMiddleware: 13 | # Not all methods need to be defined. If a method is not defined, 14 | # scrapy acts as if the spider middleware does not modify the 15 | # passed objects. 16 | 17 | @classmethod 18 | def from_crawler(cls, crawler): 19 | # This method is used by Scrapy to create your spiders. 20 | s = cls() 21 | crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) 22 | return s 23 | 24 | def process_spider_input(self, response, spider): 25 | # Called for each response that goes through the spider 26 | # middleware and into the spider. 27 | 28 | # Should return None or raise an exception. 29 | return None 30 | 31 | def process_spider_output(self, response, result, spider): 32 | # Called with the results returned from the Spider, after 33 | # it has processed the response. 34 | 35 | # Must return an iterable of Request, or item objects. 36 | for i in result: 37 | yield i 38 | 39 | def process_spider_exception(self, response, exception, spider): 40 | # Called when a spider or process_spider_input() method 41 | # (from other spider middleware) raises an exception. 42 | 43 | # Should return either None or an iterable of Request or item objects. 44 | pass 45 | 46 | def process_start_requests(self, start_requests, spider): 47 | # Called with the start requests of the spider, and works 48 | # similarly to the process_spider_output() method, except 49 | # that it doesn’t have a response associated. 50 | 51 | # Must return only requests (not items). 52 | for r in start_requests: 53 | yield r 54 | 55 | def spider_opened(self, spider): 56 | spider.logger.info('Spider opened: %s' % spider.name) 57 | 58 | 59 | class AliexpressDownloaderMiddleware: 60 | # Not all methods need to be defined. If a method is not defined, 61 | # scrapy acts as if the downloader middleware does not modify the 62 | # passed objects. 63 | 64 | @classmethod 65 | def from_crawler(cls, crawler): 66 | # This method is used by Scrapy to create your spiders. 67 | s = cls() 68 | crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) 69 | return s 70 | 71 | def process_request(self, request, spider): 72 | # Called for each request that goes through the downloader 73 | # middleware. 74 | 75 | # Must either: 76 | # - return None: continue processing this request 77 | # - or return a Response object 78 | # - or return a Request object 79 | # - or raise IgnoreRequest: process_exception() methods of 80 | # installed downloader middleware will be called 81 | return None 82 | 83 | def process_response(self, request, response, spider): 84 | # Called with the response returned from the downloader. 85 | 86 | # Must either; 87 | # - return a Response object 88 | # - return a Request object 89 | # - or raise IgnoreRequest 90 | return response 91 | 92 | def process_exception(self, request, exception, spider): 93 | # Called when a download handler or a process_request() 94 | # (from other downloader middleware) raises an exception. 95 | 96 | # Must either: 97 | # - return None: continue processing this exception 98 | # - return a Response object: stops process_exception() chain 99 | # - return a Request object: stops process_exception() chain 100 | pass 101 | 102 | def spider_opened(self, spider): 103 | spider.logger.info('Spider opened: %s' % spider.name) 104 | -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/pipelines.py: -------------------------------------------------------------------------------- 1 | # Define your item pipelines here 2 | # 3 | # Don't forget to add your pipeline to the ITEM_PIPELINES setting 4 | # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html 5 | 6 | 7 | # useful for handling different item types with a single interface 8 | from itemadapter import ItemAdapter 9 | import json 10 | 11 | 12 | class AliexpressPipeline: 13 | 14 | def open_spider(self, spider): 15 | self.file = open("items.json", "w") 16 | 17 | def close_spider(self, spider): 18 | self.file.close() 19 | 20 | def process_item(self, item, spider): 21 | if item["rating"] >= 4.7: 22 | line = json.dumps(ItemAdapter(item).asdict()) + '\n' 23 | self.file.write(line) 24 | return item 25 | -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/settings.py: -------------------------------------------------------------------------------- 1 | # Scrapy settings for Aliexpress project 2 | # 3 | # For simplicity, this file contains only settings considered important or 4 | # commonly used. You can find more settings consulting the documentation: 5 | # 6 | # https://docs.scrapy.org/en/latest/topics/settings.html 7 | # https://docs.scrapy.org/en/latest/topics/downloader-middleware.html 8 | # https://docs.scrapy.org/en/latest/topics/spider-middleware.html 9 | 10 | BOT_NAME = 'Aliexpress' 11 | 12 | SPIDER_MODULES = ['Aliexpress.spiders'] 13 | NEWSPIDER_MODULE = 'Aliexpress.spiders' 14 | 15 | 16 | # Crawl responsibly by identifying yourself (and your website) on the user-agent 17 | #USER_AGENT = 'Aliexpress (+http://www.yourdomain.com)' 18 | 19 | # Obey robots.txt rules 20 | ROBOTSTXT_OBEY = True 21 | 22 | # Configure maximum concurrent requests performed by Scrapy (default: 16) 23 | #CONCURRENT_REQUESTS = 32 24 | 25 | # Configure a delay for requests for the same website (default: 0) 26 | # See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay 27 | # See also autothrottle settings and docs 28 | #DOWNLOAD_DELAY = 3 29 | # The download delay setting will honor only one of: 30 | #CONCURRENT_REQUESTS_PER_DOMAIN = 16 31 | #CONCURRENT_REQUESTS_PER_IP = 16 32 | 33 | # Disable cookies (enabled by default) 34 | #COOKIES_ENABLED = False 35 | 36 | # Disable Telnet Console (enabled by default) 37 | #TELNETCONSOLE_ENABLED = False 38 | 39 | # Override the default request headers: 40 | #DEFAULT_REQUEST_HEADERS = { 41 | # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 42 | # 'Accept-Language': 'en', 43 | #} 44 | 45 | # Enable or disable spider middlewares 46 | # See https://docs.scrapy.org/en/latest/topics/spider-middleware.html 47 | #SPIDER_MIDDLEWARES = { 48 | # 'Aliexpress.middlewares.AliexpressSpiderMiddleware': 543, 49 | #} 50 | 51 | # Enable or disable downloader middlewares 52 | # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html 53 | #DOWNLOADER_MIDDLEWARES = { 54 | # 'Aliexpress.middlewares.AliexpressDownloaderMiddleware': 543, 55 | #} 56 | 57 | # Enable or disable extensions 58 | # See https://docs.scrapy.org/en/latest/topics/extensions.html 59 | #EXTENSIONS = { 60 | # 'scrapy.extensions.telnet.TelnetConsole': None, 61 | #} 62 | 63 | # Configure item pipelines 64 | # See https://docs.scrapy.org/en/latest/topics/item-pipeline.html 65 | ITEM_PIPELINES = { 66 | 'Aliexpress.pipelines.AliexpressPipeline': 300, 67 | } 68 | 69 | # Enable and configure the AutoThrottle extension (disabled by default) 70 | # See https://docs.scrapy.org/en/latest/topics/autothrottle.html 71 | #AUTOTHROTTLE_ENABLED = True 72 | # The initial download delay 73 | #AUTOTHROTTLE_START_DELAY = 5 74 | # The maximum download delay to be set in case of high latencies 75 | #AUTOTHROTTLE_MAX_DELAY = 60 76 | # The average number of requests Scrapy should be sending in parallel to 77 | # each remote server 78 | #AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 79 | # Enable showing throttling stats for every response received: 80 | #AUTOTHROTTLE_DEBUG = False 81 | 82 | # Enable and configure HTTP caching (disabled by default) 83 | # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings 84 | #HTTPCACHE_ENABLED = True 85 | #HTTPCACHE_EXPIRATION_SECS = 0 86 | #HTTPCACHE_DIR = 'httpcache' 87 | #HTTPCACHE_IGNORE_HTTP_CODES = [] 88 | #HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage' 89 | 90 | # Set settings whose default value is deprecated to a future-proof value 91 | REQUEST_FINGERPRINTER_IMPLEMENTATION = '2.7' 92 | TWISTED_REACTOR = 'twisted.internet.asyncioreactor.AsyncioSelectorReactor' 93 | -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/spiders/AliexpressSpider.py: -------------------------------------------------------------------------------- 1 | import scrapy 2 | from urllib.parse import urlencode 3 | from urllib.parse import urlparse 4 | from urllib.parse import urljoin 5 | import re 6 | import json 7 | from Aliexpress.items import AliexpressItem 8 | 9 | queries = ['Видеокарта'] 10 | API = '' 11 | 12 | 13 | def get_url(url): 14 | payload = {'api_key': API, 'url': url} 15 | proxy_url = 'http://api.scraperapi.com/?' + urlencode(payload) 16 | return proxy_url 17 | 18 | 19 | class AliexpressSpider(scrapy.Spider): 20 | name = 'AliexpressSpider' 21 | page = 1 22 | 23 | def start_requests(self): 24 | for query in queries: 25 | url = 'https://aliexpress.ru/wholesale?' + urlencode({'g': 'n', 'SearchText': query, 'page': str(self.page)}) 26 | yield scrapy.Request(url=get_url(url), callback=self.parse_keyword_response) 27 | 28 | def parse_keyword_response(self, response): 29 | products = set() 30 | for res in response.xpath('//a[contains(@target, _target)]/@href').extract(): 31 | if 'sku_id' in res: 32 | products.add(res) 33 | 34 | for product in products: 35 | product_url = 'https://aliexpress.ru' + product 36 | yield scrapy.Request(url=get_url(product_url), callback=self.parse_product_page) 37 | 38 | self.page += 1 39 | if self.page <= 1: 40 | url = urlparse(response.url[-1]) + str(self.page) 41 | yield scrapy.Request(url=get_url(url), callback=self.parse_keyword_response) 42 | 43 | def parse_product_page(self, response): 44 | item = AliexpressItem() 45 | title = response.xpath('//h1/text()').extract() 46 | category = response.xpath('//ol[contains(@class, SnowBreadcrumbs_SnowBreadcrumbs__list__1xzrg)]/li/a/text()').extract() 47 | price = response.xpath('//div[contains(@class, snow-price_SnowPrice__secondPrice__18x8np)][1]/text()').extract() 48 | sale_price = response.xpath('//div[contains(@class, snow-price_SnowPrice__mainM__18x8np)][1]/text()').extract() 49 | delivery = response.xpath('//div[contains(@class, SnowProductDelivery_SnowProductDelivery__item__y5v67)[0]/span[1]/text()').extract() 50 | rating = response.xpath('//p[contains(@class, SnowReviews_ProductRating__ratingAverage__17pz0)[0]/text()').extract() 51 | item["title"] = ''.join(title).strip() 52 | item["category"] = '/'.join(category).strip() 53 | item["price"] = ''.join(price).strip() 54 | item["sale_price"] = ''.join(sale_price).strip() 55 | item["delivery"] = ''.join(delivery).strip() 56 | item["rating"] = ''.join(rating).strip() 57 | yield item 58 | 59 | -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/spiders/__init__.py: -------------------------------------------------------------------------------- 1 | # This package will contain the spiders of your Scrapy project 2 | # 3 | # Please refer to the documentation for information on how to create and manage 4 | # your spiders. 5 | -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/spiders/__pycache__/AliexpressSpider.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminars_8/Aliexpress/spiders/__pycache__/AliexpressSpider.cpython-310.pyc -------------------------------------------------------------------------------- /Seminars/Seminars_8/Aliexpress/spiders/__pycache__/__init__.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Palladain/Deep_Python/HEAD/Seminars/Seminars_8/Aliexpress/spiders/__pycache__/__init__.cpython-310.pyc -------------------------------------------------------------------------------- /Seminars/Seminars_8/Seminar_8_Python.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "provenance": [], 7 | "collapsed_sections": [] 8 | }, 9 | "kernelspec": { 10 | "name": "python3", 11 | "display_name": "Python 3" 12 | }, 13 | "language_info": { 14 | "name": "python" 15 | } 16 | }, 17 | "cells": [ 18 | { 19 | "cell_type": "markdown", 20 | "source": [ 21 | "# Продвинутый Python, семинар 8\n", 22 | "\n", 23 | "**Лектор:** Петров Тимур\n", 24 | "\n", 25 | "**Семинаристы:** Петров Тимур, Коган Александра, Романченко Полина\n", 26 | "\n", 27 | "**Spoiler Alert:** в рамках курса нельзя изучить ни одну из тем от и до досконально (к сожалению, на это требуется больше времени, чем даже 3 часа в неделю). Но мы попробуем рассказать столько, сколько возможно :)" 28 | ], 29 | "metadata": { 30 | "id": "5EWo4vpdHvHb" 31 | } 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "source": [ 36 | "## Углубляемся в парсинг" 37 | ], 38 | "metadata": { 39 | "id": "VuWlixt0Hxn9" 40 | } 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "source": [ 45 | "Научились с вами парсить странички, получать товар и даже обходить блокировку роботов, которым ничего нельзя. Что же, давайте добавим динамики\n", 46 | "\n", 47 | "Если в прошлый раз мы с вами просто брали и получали результат по заданным страничкам, то теперь хотим брать страницу, из нее забирать ссылки, ходить по ним и также получать информацию о товаре. Таким образом получим реального паука" 48 | ], 49 | "metadata": { 50 | "id": "KnV-0Z1cH0Wt" 51 | } 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": { 57 | "id": "sGvu0jrRHr8_" 58 | }, 59 | "outputs": [], 60 | "source": [ 61 | "!pip install scrapy" 62 | ] 63 | }, 64 | { 65 | "cell_type": "markdown", 66 | "source": [ 67 | "Сегодня в качестве нашей жертвы выберем другой маркетплейс, а именно [Алиэкспресс](https://aliexpress.ru)\n", 68 | "\n", 69 | "И сформулируем задачу так:\n", 70 | "\n", 71 | "Хотим взять все заказы по запросу \"Видеокарта\" (вот такие мы маленькие любители помайнить, видимо)" 72 | ], 73 | "metadata": { 74 | "id": "40R2-LA_IT8S" 75 | } 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "source": [ 80 | "Перезходим по ссылке: https://aliexpress.ru/wholesale?SearchText=Видеокарта&g=n&page=1\n", 81 | "\n", 82 | "Ииии... тут нет никаких страниц! Есть кнопка загрузить еще и он не делает новую страницу, он просто догружает, что же делать?\n", 83 | "\n", 84 | "Ответ: внимательнее посмотреть на url-ссылку!" 85 | ], 86 | "metadata": { 87 | "id": "heh_WoC5I5Ze" 88 | } 89 | }, 90 | { 91 | "cell_type": "markdown", 92 | "source": [ 93 | "## URL-ссылки и как они работают" 94 | ], 95 | "metadata": { 96 | "id": "j1TXOKiYJFuf" 97 | } 98 | }, 99 | { 100 | "cell_type": "markdown", 101 | "source": [ 102 | "URL (Uniform Resource Locator) - это адрес всему, что выложено в интернетах. \n", 103 | "\n", 104 | "Сам по себе URL состоит из нескольких частей:\n", 105 | "\n", 106 | "* Протокол - какой протокол должен использовать запрос. Самый частый пример: ```https//:```, но бывают и другие, например, ```mailto:```, открывающий почтовый клиент. В нашем случае все банально и просто\n", 107 | "\n", 108 | "* Domain - полное доменное имя (пример: ```example.com```), то есть к какому веб ресурсу необходимо подключиться\n", 109 | "\n", 110 | "Обратите внимание: ```vk.com``` и ```m.vk.com``` - в чем разница? В уровнях домена (m - это дополнительный уровень, который вас отсылает именно к мобильной версии сайта, а тот же com - это доменная зона)\n", 111 | "\n", 112 | "* Port - технический параметр (порт для доступа к веб-ресурсу, пример: ```:443```), обычно никто ничего не ставит, потому что он устанваливается по дефолту (80 для HTTP и 443 для HTTPS)\n", 113 | "\n", 114 | "* Parameters - параметры для передачи ресурсу. Каждый сервер их обрабатывает по-своему, в зависимости от того, как владелец это обрабатывает. Как они выглядят:\n", 115 | "\n", 116 | "```\n", 117 | "?key1=value1&key_2=value_2 - ? - начинаются параметры, через & идет перечисление\n", 118 | "```\n", 119 | "\n", 120 | "* Anchor - якорь, который вас отсылает к какой-то конкретной части (например, к какой-то плашке уже на странице etc), пример ```#anchor```\n", 121 | "\n", 122 | "* Path - может быть просто путь к файлу (```a/b/c```)" 123 | ], 124 | "metadata": { 125 | "id": "tKcrkJVNKG9o" 126 | } 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "source": [ 131 | "Имея вот такие знания, давайте обратимся к адресу:\n", 132 | "\n", 133 | "```\n", 134 | "https://aliexpress.ru/wholesale?SearchText=Видеокарта&g=n&page=1\n", 135 | "\n", 136 | "Параметры:\n", 137 | "\n", 138 | "SearchText=Видеокарта - наш поисковый запрос\n", 139 | "g = n - ???\n", 140 | "page = 1 - страница\n", 141 | "```\n", 142 | "\n", 143 | "Давайте тестировать, что за g. Посмотрели и ничего не увидели, ну ладно\n", 144 | "\n", 145 | "Но что увидели? Что изменение page показывает только нужную страницу, а не только с самого начала, ура, значит можно парсить через изменение данного параметра" 146 | ], 147 | "metadata": { 148 | "id": "wx-lQw3COAiL" 149 | } 150 | }, 151 | { 152 | "cell_type": "markdown", 153 | "source": [ 154 | "## Смотрим, как перейти и как вытащить информацию" 155 | ], 156 | "metadata": { 157 | "id": "3znUqGGRPZ1T" 158 | } 159 | }, 160 | { 161 | "cell_type": "markdown", 162 | "source": [ 163 | "Открываем и видмм более красивую и приятную картинку, чем в Amazon, кто-то постарался на славу" 164 | ], 165 | "metadata": { 166 | "id": "tiM6uWsZPeHv" 167 | } 168 | }, 169 | { 170 | "cell_type": "markdown", 171 | "source": [ 172 | "### Задание 1\n", 173 | "\n", 174 | "Имея страничку поиска (Aliexpress.txt) или с помощью навигации по сайту вычлените все ссылки на на карточки товаров" 175 | ], 176 | "metadata": { 177 | "id": "8_J622-qQsVQ" 178 | } 179 | }, 180 | { 181 | "cell_type": "code", 182 | "source": [ 183 | "from bs4 import BeautifulSoup\n", 184 | "\n", 185 | "s = \"\"\n", 186 | "with open(\"Aliexpress.txt\", 'r') as f:\n", 187 | " s = f.read()\n", 188 | "soup = BeautifulSoup(s, 'html.parser') # указываем парсер\n", 189 | "print(soup.prettify()) # выглядит уже более структурно" 190 | ], 191 | "metadata": { 192 | "id": "onmj9SLXQ1_Z" 193 | }, 194 | "execution_count": null, 195 | "outputs": [] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "source": [ 200 | "### Задание 2\n", 201 | "\n", 202 | "Имея страницу (Aliexpress_item.txt) вычлените следующую информацию о товаре:\n", 203 | "\n", 204 | "* Название\n", 205 | "\n", 206 | "* Категорию\n", 207 | "\n", 208 | "* Цену\n", 209 | "\n", 210 | "* Цену без скидки (если есть скидка)\n", 211 | "\n", 212 | "* Стоимость доставки\n", 213 | "\n", 214 | "* Средний отзыв" 215 | ], 216 | "metadata": { 217 | "id": "RbGzgh-rSxJO" 218 | } 219 | }, 220 | { 221 | "cell_type": "code", 222 | "source": [ 223 | "from bs4 import BeautifulSoup\n", 224 | "\n", 225 | "s = \"\"\n", 226 | "with open(\"Aliexpress_item.txt\", 'r') as f:\n", 227 | " s = f.read()\n", 228 | "soup = BeautifulSoup(s, 'html.parser') # указываем парсер\n", 229 | "print(soup.prettify()) # выглядит уже более структурно" 230 | ], 231 | "metadata": { 232 | "id": "mK1jsmh6TRpD" 233 | }, 234 | "execution_count": null, 235 | "outputs": [] 236 | }, 237 | { 238 | "cell_type": "markdown", 239 | "source": [ 240 | "Название:" 241 | ], 242 | "metadata": { 243 | "id": "VH2O8Q9RVjb-" 244 | } 245 | }, 246 | { 247 | "cell_type": "code", 248 | "source": [], 249 | "metadata": { 250 | "id": "NIjcVTIqVQOn" 251 | }, 252 | "execution_count": null, 253 | "outputs": [] 254 | }, 255 | { 256 | "cell_type": "markdown", 257 | "source": [ 258 | "Категория:" 259 | ], 260 | "metadata": { 261 | "id": "KuiCJf4sVk7r" 262 | } 263 | }, 264 | { 265 | "cell_type": "code", 266 | "source": [], 267 | "metadata": { 268 | "id": "0kvibbIrVntI" 269 | }, 270 | "execution_count": null, 271 | "outputs": [] 272 | }, 273 | { 274 | "cell_type": "markdown", 275 | "source": [ 276 | "Цена:" 277 | ], 278 | "metadata": { 279 | "id": "aNUZHfVzWIAP" 280 | } 281 | }, 282 | { 283 | "cell_type": "code", 284 | "source": [], 285 | "metadata": { 286 | "id": "beUfrtLOWKqM" 287 | }, 288 | "execution_count": null, 289 | "outputs": [] 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "source": [ 294 | "Цена без скидки:" 295 | ], 296 | "metadata": { 297 | "id": "kk05CEl0WhXm" 298 | } 299 | }, 300 | { 301 | "cell_type": "code", 302 | "source": [], 303 | "metadata": { 304 | "id": "A-81kKALWkjZ" 305 | }, 306 | "execution_count": null, 307 | "outputs": [] 308 | }, 309 | { 310 | "cell_type": "markdown", 311 | "source": [ 312 | "Стоимость доставки:" 313 | ], 314 | "metadata": { 315 | "id": "Sq59ASpFWwfZ" 316 | } 317 | }, 318 | { 319 | "cell_type": "code", 320 | "source": [], 321 | "metadata": { 322 | "id": "l156_dgdW0PK" 323 | }, 324 | "execution_count": null, 325 | "outputs": [] 326 | }, 327 | { 328 | "cell_type": "markdown", 329 | "source": [ 330 | "Средний отзыв (посмотрите по коду, почему ничего не получилось):" 331 | ], 332 | "metadata": { 333 | "id": "WTOXiiTMWyQ7" 334 | } 335 | }, 336 | { 337 | "cell_type": "code", 338 | "source": [], 339 | "metadata": { 340 | "id": "AbDa8Y1MW0oL" 341 | }, 342 | "execution_count": null, 343 | "outputs": [] 344 | }, 345 | { 346 | "cell_type": "markdown", 347 | "source": [ 348 | "### Задание 3" 349 | ], 350 | "metadata": { 351 | "id": "LB0szNstY_lh" 352 | } 353 | }, 354 | { 355 | "cell_type": "markdown", 356 | "source": [ 357 | "Выведите все данные с помощью XPath" 358 | ], 359 | "metadata": { 360 | "id": "Di9CYTQ1ZBtw" 361 | } 362 | }, 363 | { 364 | "cell_type": "code", 365 | "source": [ 366 | "from lxml import etree\n", 367 | "\n", 368 | "dom = etree.HTML(str(soup))" 369 | ], 370 | "metadata": { 371 | "id": "q92LUkAIZGB8" 372 | }, 373 | "execution_count": null, 374 | "outputs": [] 375 | }, 376 | { 377 | "cell_type": "code", 378 | "source": [], 379 | "metadata": { 380 | "id": "tP4TLnkGZNQC" 381 | }, 382 | "execution_count": null, 383 | "outputs": [] 384 | }, 385 | { 386 | "cell_type": "markdown", 387 | "source": [ 388 | "Отлично, мы знаем как парсить! Дело осталось за малым - подготовить нашего паука и научить его ходить по ссылкам" 389 | ], 390 | "metadata": { 391 | "id": "RwMKiMdTTSU9" 392 | } 393 | }, 394 | { 395 | "cell_type": "markdown", 396 | "source": [ 397 | "## Готовим паука" 398 | ], 399 | "metadata": { 400 | "id": "T3FYkiZ7Tajl" 401 | } 402 | }, 403 | { 404 | "cell_type": "markdown", 405 | "source": [ 406 | "На самом деле структура для такого не слишком сложная:\n", 407 | "\n", 408 | "(1) Пишем код для начала парсинга (ввести нужный запрос и пройти по нему) - первая функция\n", 409 | "\n", 410 | "(1) Пишем код для парсинга страницы поиска - вторая функция\n", 411 | "\n", 412 | "(2) Получив ссылки, пишем код для парсинга товара, который будет возвращать ответы - третья функция\n", 413 | "\n", 414 | "Ну в целом все..." 415 | ], 416 | "metadata": { 417 | "id": "VNwjYS25l415" 418 | } 419 | }, 420 | { 421 | "cell_type": "markdown", 422 | "source": [ 423 | "А теперь давайте писать павука!" 424 | ], 425 | "metadata": { 426 | "id": "glE0renQmk_A" 427 | } 428 | }, 429 | { 430 | "cell_type": "markdown", 431 | "source": [ 432 | "### Задание 3" 433 | ], 434 | "metadata": { 435 | "id": "a9GkOBsumm-5" 436 | } 437 | }, 438 | { 439 | "cell_type": "markdown", 440 | "source": [ 441 | "Запишите в items.py класс для товаров" 442 | ], 443 | "metadata": { 444 | "id": "CnrkZ4oOmtoo" 445 | } 446 | }, 447 | { 448 | "cell_type": "markdown", 449 | "source": [ 450 | "### Задание 4" 451 | ], 452 | "metadata": { 453 | "id": "nKJjJVzamrdN" 454 | } 455 | }, 456 | { 457 | "cell_type": "markdown", 458 | "source": [ 459 | "Запишите в pipeline.py функцию по обработке данных:\n", 460 | "\n", 461 | "(1) Все данные сохраняем в csv\n", 462 | "\n", 463 | "(2) Берем только товары с оценкой выше 4.7" 464 | ], 465 | "metadata": { 466 | "id": "0ZEACDJsmynN" 467 | } 468 | }, 469 | { 470 | "cell_type": "markdown", 471 | "source": [ 472 | "### Задание 5" 473 | ], 474 | "metadata": { 475 | "id": "Va_DsHTpnAJN" 476 | } 477 | }, 478 | { 479 | "cell_type": "markdown", 480 | "source": [ 481 | "Поправьте settings, чтобы включить pipeline" 482 | ], 483 | "metadata": { 484 | "id": "JVNOr6S1nJqZ" 485 | } 486 | }, 487 | { 488 | "cell_type": "markdown", 489 | "source": [ 490 | "### Задание 6" 491 | ], 492 | "metadata": { 493 | "id": "KbEdhUOznYLB" 494 | } 495 | }, 496 | { 497 | "cell_type": "markdown", 498 | "source": [ 499 | "Напишите паука, который будет автоматически ходить по запросу в товары и забирать оттуда данные" 500 | ], 501 | "metadata": { 502 | "id": "g52TV5NwnbPT" 503 | } 504 | }, 505 | { 506 | "cell_type": "code", 507 | "source": [ 508 | "import scrapy\n", 509 | "from urllib.parse import urlencode\n", 510 | "from urllib.parse import urljoin\n", 511 | "import re\n", 512 | "import json\n", 513 | "queries = ['Видеокарта'] ##Запросы (что ищем)\n", 514 | "API = '' ##API ключ для ScraperAPI\n", 515 | "\n", 516 | "def get_url(url):\n", 517 | " payload = {'api_key': API, 'url': url, 'country_code': 'us'}\n", 518 | " proxy_url = 'http://api.scraperapi.com/?' + urlencode(payload)\n", 519 | " return proxy_url\n", 520 | "\n", 521 | "class AliexpressSpider(scrapy.Spider):\n", 522 | " name = 'Aliexpress'\n", 523 | "\n", 524 | " def start_requests(self):\n", 525 | " for query in queries:\n", 526 | " url = 'https://aliexpress.ru/wholesale?' + urlencode({'g': 'n', 'SearchText': query, 'page': '1'})\n", 527 | " yield scrapy.Request(url=get_url(url), callback=self.parse_keyword_response)\n", 528 | "\n", 529 | " def parse_keyword_response(self, response):\n", 530 | " products = #вставить поиск ссылкок\n", 531 | "\n", 532 | " for product in products:\n", 533 | " product_url = #строим ссылку для продукта\n", 534 | " yield scrapy.Request(url=get_url(product_url), callback=self.parse_product_page)\n", 535 | " \n", 536 | " next_page = #ссылка для следующей страницы\n", 537 | " if next_page:\n", 538 | " url = #делаем ссылку\n", 539 | " yield scrapy.Request(url=get_url(url), callback=self.parse_keyword_response)\n", 540 | "\n", 541 | " def parse_product_page(self, response):\n", 542 | " #парсим продукт" 543 | ], 544 | "metadata": { 545 | "id": "CXBPYL_vbN00" 546 | }, 547 | "execution_count": null, 548 | "outputs": [] 549 | }, 550 | { 551 | "cell_type": "markdown", 552 | "source": [ 553 | "## Паук дня" 554 | ], 555 | "metadata": { 556 | "id": "uT-JIRE0ng26" 557 | } 558 | }, 559 | { 560 | "cell_type": "markdown", 561 | "source": [ 562 | "![](https://i.pinimg.com/originals/fc/3d/a4/fc3da4b4e143c69fab9a3877d1ea6ab4.jpg)" 563 | ], 564 | "metadata": { 565 | "id": "5j8TkInooEX9" 566 | } 567 | }, 568 | { 569 | "cell_type": "markdown", 570 | "source": [ 571 | "Это паук Maratus Volans, живут они в Австралии (неядовитые) и просто очень красивые!\n", 572 | "\n", 573 | "Как известно, разноцетный окрас не особо хорош для скрытия, но хорош для другого: для привлечения самок! Поэтому они такие цветные\n", 574 | "\n", 575 | "Одни из немногих пауков, которые исполняют брачный танец, чтобы привлечь самку (но если самке не понравится, то она его съест). На картинке - поза паука при танце\n", 576 | "\n", 577 | "А также они видят сильно больше цветов, чем человек (например, ультрафиолет)" 578 | ], 579 | "metadata": { 580 | "id": "Z4Cm55mtoGJQ" 581 | } 582 | }, 583 | { 584 | "cell_type": "markdown", 585 | "source": [ 586 | "![](https://i.pinimg.com/originals/f7/a3/b2/f7a3b267312001a4d17a0cea530211dd.jpg)" 587 | ], 588 | "metadata": { 589 | "id": "A9sHIppTpGvf" 590 | } 591 | } 592 | ] 593 | } --------------------------------------------------------------------------------