{"id":460130,"date":"2025-05-19T09:00:52","date_gmt":"2025-05-19T09:00:52","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=460130"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=460130","title":{"rendered":"<span>\u0420\u0443\u043a\u043e\u043f\u0438\u0441\u043d\u044b\u0439 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440 \u043d\u0430 Python: \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u0442\u0435\u0445, \u043a\u0442\u043e \u0445\u043e\u0447\u0435\u0442 \u00ab\u0440\u0438\u0441\u043e\u0432\u0430\u0442\u044c\u00bb \u043a\u043e\u0434<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/wy\/lf\/0b\/wylf0bwb-niny9swj09z5cfgszo.jpeg\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/wy\/lf\/0b\/wylf0bwb-niny9swj09z5cfgszo.jpeg 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/wy\/lf\/0b\/wylf0bwb-niny9swj09z5cfgszo.jpeg 781w\" loading=\"lazy\" decode=\"async\"\/><\/div>\n<p> \u041f\u0440\u0438\u0432\u0435\u0442, \u043c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u041b\u0451\u043d\u044f! \u042f \u0430\u0432\u0442\u043e\u0440 <a href=\"https:\/\/youtube.com\/@eleday\">YouTube\u2011\u043a\u0430\u043d\u0430\u043b\u0430 eleday<\/a> \u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u043d\u0430 Python. \u041d\u0435\u0434\u0430\u0432\u043d\u043e \u0432 \u0448\u043a\u043e\u043b\u0435 \u0431\u044b\u043b\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043e\u0447\u043d\u0430\u044f \u0440\u0430\u0431\u043e\u0442\u0430 \u0438 \u043c\u043d\u0435 \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u043f\u0438\u0441\u0430\u0442\u044c \u043a\u043e\u0434 \u043d\u0430 \u0431\u0443\u043c\u0430\u0433\u0435. \u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043f\u043e\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u0441\u0442\u0440\u0430\u043d\u043d\u044b\u043c: \u0432\u0441\u0435-\u0442\u0430\u043a\u0438 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0435 \u0438 \u043b\u043e\u0433\u0438\u0447\u043d\u043e \u043d\u0430\u0431\u0438\u0440\u0430\u0442\u044c \u0435\u0435 \u0442\u0430\u043c \u0436\u0435. \u041f\u043e\u0434\u043e\u0431\u043d\u0430\u044f \u0446\u0435\u043f\u043e\u0447\u043a\u0430 \u0440\u0430\u0441\u0441\u0443\u0436\u0434\u0435\u043d\u0438\u0439 \u043f\u0440\u0438\u0432\u0435\u043b\u0430 \u043a \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0439 \u0438\u0434\u0435\u0435 \u2014 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0443 \u0440\u0443\u043a\u043e\u043f\u0438\u0441\u043d\u043e\u0433\u043e \u0432\u0432\u043e\u0434\u0430. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e \u0437\u0430\u0434\u0443\u043c\u043a\u0435 \u0438 \u0434\u0435\u0442\u0430\u043b\u044f\u0445 \u0435\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u043b\u0438\u0441\u0442, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0431\u0440\u043e\u0441\u0430\u0442\u044c \u043a\u043e\u0434 \u043e\u0442 \u0440\u0443\u043a\u0438 \u2014 \u0438 \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f!<a name=\"habracut\"><\/a><\/p>\n<p> <b>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043e\u0433\u043b\u0430\u0432\u043b\u0435\u043d\u0438\u0435, \u0435\u0441\u043b\u0438 \u043d\u0435 \u0445\u043e\u0442\u0438\u0442\u0435 \u0447\u0438\u0442\u0430\u0442\u044c \u0442\u0435\u043a\u0441\u0442 \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e:<\/b><\/p>\n<p> \u2192 <a href=\"#1\">\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f<\/a><br \/> \u2192 <a href=\"#2\">\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043f\u043e\u043b\u044f \u0434\u043b\u044f \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f<\/a><br \/> \u2192 <a href=\"#3\">\u0423\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430<\/a><br \/> \u2192 <a href=\"#4\">\u0421\u0435\u0440\u0432\u0435\u0440\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c<\/a><br \/> \u2192 <a href=\"#5\">\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440<\/a><br \/> \u2192 <a href=\"#6\">\u0418\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043a\u043e\u0434\u0430<\/a><br \/> \u2192 <a href=\"#7\">\u0414\u0435\u043f\u043b\u043e\u0439<\/a><\/p>\n<p> <a name=\"1\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0438\u0434\u0435\u044f<\/h2>\n<p><\/font><br \/> \u041a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044f \u043f\u0440\u043e\u0441\u0442\u0430: \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u043e\u043b\u0435 \u0434\u043b\u044f \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f, \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0435\u043c \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u0441 \u0443\u0447\u0435\u0442\u043e\u043c \u043e\u0442\u0441\u0442\u0443\u043f\u043e\u0432 \u0438 \u043f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u0435\u0433\u043e \u00ab\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c\u00bb. \u0421 \u0442\u043e\u0447\u043a\u0438 \u0437\u0440\u0435\u043d\u0438\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u044b \u043f\u0440\u043e\u0435\u043a\u0442 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435. \u0424\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u2014 JavaScript \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u00ab\u043f\u0435\u0440\u0430\u00bb, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u0434\u0430 \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435. \u0411\u044d\u043a\u0435\u043d\u0434 \u2014 Python \u0434\u043b\u044f \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u044f \u0440\u0443\u043a\u043e\u043f\u0438\u0441\u043d\u043e\u0433\u043e \u0432\u0432\u043e\u0434\u0430.<\/p>\n<p> \u0417\u0430\u043a\u043e\u043d\u0447\u0438\u0432 \u0441 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0435\u0439 \u0438 \u043e\u0442\u043b\u0430\u0434\u043a\u043e\u0439, \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442 <a href=\"https:\/\/selectel.ru\/services\/cloud\/servers\/?utm_source=habr.com&amp;utm_medium=referral&amp;utm_campaign=cloud_article_handcode_190525_content\">\u043d\u0430 \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0435<\/a>, \u0447\u0442\u043e\u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0435\u0433\u043e \u043b\u0435\u0433\u043a\u043e\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u043c \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432.<\/p>\n<p> <a name=\"2\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u043f\u043e\u043b\u044f \u0434\u043b\u044f \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f<\/h2>\n<p><\/font><br \/> \u041f\u0435\u0440\u0432\u044b\u043c \u0448\u0430\u0433\u043e\u043c \u0441\u0442\u0430\u043b\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u0435\u0431-\u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430. \u0414\u043b\u044f \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u044f \u0441\u043e\u0437\u0434\u0430\u043b <code>index.html<\/code>, \u0433\u0434\u0435 \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432.<\/p>\n<p> <b>\u041a\u043d\u043e\u043f\u043a\u0438 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u0438\u0441\u0442\u044c\u044e<\/b><\/p>\n<pre> &lt;div class=\"brushControls\"&gt; &lt;div class=\"sliderOuter\"&gt; &lt;input type=\"range\" min=\"2\" max=\"100\" step=\"1\" value=\"4\" id=\"brushSize\"&gt; &lt;\/div&gt; &lt;span class=\"material-symbols-rounded active notranslate\" id=\"brushBtn\"&gt;brush&lt;\/span&gt; &lt;span class=\"material-symbols-rounded notranslate\" id=\"eraserBtn\"&gt;ink_eraser&lt;\/span&gt; &lt;\/div&gt; <\/pre>\n<p> <b>\u041a\u043d\u043e\u043f\u043a\u0438 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043a\u043e\u0434\u0430 \u0438 \u043e\u0447\u0438\u0441\u0442\u043a\u0438 \u044d\u043a\u0440\u0430\u043d\u0430<\/b><\/p>\n<pre> &lt;div class=\"controls\"&gt;   &lt;span class=\"material-symbols-rounded notranslate\" id=\"runBtn\"&gt;play_arrow&lt;\/span&gt;    &lt;span class=\"material-symbols-rounded notranslate\" id=\"clearScreenBtn\"&gt;delete&lt;\/span&gt; &lt;\/div&gt; <\/pre>\n<p> <b>\u041f\u043e\u043b\u0435 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u043d\u043d\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u0438 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430 \u0435\u0433\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f<\/b><\/p>\n<pre> &lt;div class=\"codePreviewOuter\"&gt;     &lt;span class=\"material-symbols-rounded notranslate\" id=\"hideBtn\"&gt;arrow_back_ios_new&lt;\/span&gt;     &lt;div&gt;         &lt;textarea name=\"codePreview notranslate\" id=\"codePreview\" readonly&gt;\u043a\u043e\u0434&lt;\/textarea&gt;         &lt;textarea name=\"codeOutput notranslate\" id=\"codeOutput\" readonly&gt;\u0432\u044b\u0432\u043e\u0434&lt;\/textarea&gt;     &lt;\/div&gt; &lt;\/div&gt; <\/pre>\n<p> <b>\u0418, \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435, \u0433\u043b\u0430\u0432\u043d\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f \u2014 \u0445\u043e\u043b\u0441\u0442<\/b><\/p>\n<pre> &lt;canvas oncontextmenu=\"return false;\"&gt;&lt;\/canvas&gt; <\/pre>\n<p> <\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/7t\/np\/gb\/7tnpgbj81jxo9jeeeocbwxiekce.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/7t\/np\/gb\/7tnpgbj81jxo9jeeeocbwxiekce.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/7t\/np\/gb\/7tnpgbj81jxo9jeeeocbwxiekce.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/div>\n<p> \u0417\u0430\u0442\u0435\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u0441\u0442\u0438\u043b\u0438, \u0447\u0442\u043e\u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043f\u0440\u0438\u044f\u0442\u043d\u044b\u043c, \u0438 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043b <code>drawing.js<\/code>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u043b\u043e\u0433\u0438\u043a\u0443 \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/ma\/kz\/tm\/makztm7z9xa2ejcdf_lw-7yadpc.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/ma\/kz\/tm\/makztm7z9xa2ejcdf_lw-7yadpc.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/ma\/kz\/tm\/makztm7z9xa2ejcdf_lw-7yadpc.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/div>\n<p> <b>\u041a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u00ab\u0445\u043e\u043b\u0441\u0442\u00bb<\/b><\/p>\n<p> \u041a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043a\u0430\u0441\u0430\u0435\u0442\u0441\u044f \u044d\u043a\u0440\u0430\u043d\u0430, \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f: \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439 <code>isDrawing<\/code> \u043f\u0440\u0438\u0441\u0432\u0430\u0438\u0432\u0430\u0435\u0442\u0441\u044f <code>true<\/code>, \u0430 \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442\u0441\u044f. \u041f\u0440\u0438 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0438 \u043f\u043e \u044d\u043a\u0440\u0430\u043d\u0443 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0441\u043e\u0435\u0434\u0438\u043d\u044f\u044e\u0442\u0441\u044f \u0441 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u043b\u0438\u043d\u0438\u0435\u0439. \u041a\u043e\u0433\u0434\u0430 \u043f\u0430\u043b\u0435\u0446 \u043e\u0442\u0445\u043e\u0434\u0438\u0442 \u043e\u0442 \u044d\u043a\u0440\u0430\u043d\u0430 (\u0438\u043b\u0438 \u043e\u0442\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043a\u043d\u043e\u043f\u043a\u0430 \u043c\u044b\u0448\u0438), <code>isDrawing<\/code> \u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u0441\u044f <code>false<\/code>, \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u044f \u043f\u0440\u043e\u0446\u0435\u0441\u0441.<\/p>\n<pre> \/\/ \u041e\u0431\u044a\u044f\u0432\u043b\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 var canvas = document.querySelector('canvas'); var sendBtn = document.querySelector('.sendBtn'); var codePreview = document.querySelector('#codePreview'); var loading = document.querySelector('.loading');  var ctx = canvas.getContext('2d');  var isDrawing = false; var lastX = 0; var lastY = 0;  var brushSize = 2; var color = '#fff'  \/\/ \u0420\u0430\u0437\u0432\u043e\u0440\u0430\u0447\u0438\u0432\u0430\u0435\u043c \u0445\u043e\u043b\u0441\u0442 \u043d\u0430 \u0432\u0435\u0441\u044c \u044d\u043a\u0440\u0430\u043d canvas.width = window.innerWidth; canvas.height = window.innerHeight;  \/\/ \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f function startDrawing(e) { isDrawing = true; [lastX, lastY] = [e.offsetX, e.offsetY]; }  \/\/ \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f function draw(e) { if (!isDrawing) return;  \/\/ \u0417\u0430\u0434\u0430\u0435\u043c \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043a\u0438\u0441\u0442\u0438 ctx.strokeStyle = color;  ctx.lineWidth = brushSize; ctx.lineCap = 'round'; ctx.lineJoin = 'round';  \/\/ \u0421\u043e\u0435\u0434\u0438\u043d\u044f\u0435\u043c \u043b\u0438\u043d\u0438\u0435\u0439 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0438 \u0442\u0435\u043a\u0443\u0449\u0438\u0435 ctx.beginPath(); ctx.moveTo(lastX, lastY); ctx.lineTo(e.offsetX, e.offsetY); ctx.stroke();  \/\/ \u041e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u043c \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b [lastX, lastY] = [e.offsetX, e.offsetY]; }  \/\/ \u0424\u0443\u043d\u043a\u0446\u0438\u044f \u043e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u044f \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u044f function stopDrawing(e) { if (!isDrawing) return;  isDrawing = false; ctx.closePath(); }  \/\/ \u041f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u043c \u0432\u044b\u0448\u0435\u043e\u043f\u0438\u0441\u0430\u043d\u043d\u044b\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043a \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f canvas.addEventListener('mousedown', startDrawing); canvas.addEventListener('mousemove', draw); canvas.addEventListener('mouseup', stopDrawing); canvas.addEventListener('mouseout', stopDrawing); <\/pre>\n<p> \u0420\u0438\u0441\u043e\u0432\u0430\u0442\u044c \u0443\u0436\u0435 \u043c\u043e\u0436\u043d\u043e, \u043d\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043f\u043e\u043a\u0430 \u043d\u0435\u043b\u044c\u0437\u044f \u043d\u0430\u0437\u0432\u0430\u0442\u044c \u0443\u0434\u043e\u0431\u043d\u044b\u043c.<\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/hj\/ei\/h2\/hjeih2coqx5ctu8lmx39j3bsele.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/hj\/ei\/h2\/hjeih2coqx5ctu8lmx39j3bsele.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/hj\/ei\/h2\/hjeih2coqx5ctu8lmx39j3bsele.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/div>\n<p> <a href=\"https:\/\/promo.selectel.ru\/infra-meetup#5?utm_source=habr.com&amp;utm_medium=referral&amp;utm_campaign=events_article_handcode_190525_banner_081_02_ord\"><\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/so\/zw\/k4\/sozwk4sguczr1uculk2khxkwe-4.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/so\/zw\/k4\/sozwk4sguczr1uculk2khxkwe-4.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/so\/zw\/k4\/sozwk4sguczr1uculk2khxkwe-4.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/div>\n<p><\/a><br \/> <a name=\"3\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u0423\u043b\u0443\u0447\u0448\u0435\u043d\u0438\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430<\/h2>\n<p><\/font><br \/> \u0427\u0442\u043e\u0431\u044b \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0431\u044b\u043b\u043e \u0443\u0434\u043e\u0431\u043d\u0435\u0435, \u0432 \u043c\u043e\u0434\u0443\u043b\u0435 <code>ui.js<\/code> \u044f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0435\u0439.<\/p>\n<p> <b>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0442\u043e\u043b\u0449\u0438\u043d\u044b \u043a\u0438\u0441\u0442\u0438 \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u0437\u0443\u043d\u043e\u043a<\/b><\/p>\n<pre> var slicer = document.getElementById('brushSize');  \/\/ \u0423\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u0437\u0443\u043d\u043a\u0430 \u043f\u0440\u0438 \u043d\u0430\u0432\u0435\u0434\u0435\u043d\u0438\u0438 \u043c\u044b\u0448\u0438 slicer.addEventListener('mouseover', () =&gt; { document.documentElement.style.setProperty('--thumb-size', `25px`); document.documentElement.style.setProperty('--brush-size', `${brushSize}px`); brushPreview.style.opacity = 1; cursor.style.opacity = 0; });  \/\/ \u0423\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u0438\u0435 \u043f\u043e\u043b\u0437\u0443\u043d\u043a\u0430, \u043a\u043e\u0433\u0434\u0430 \u043c\u044b\u0448\u044c \u0441\u0434\u0432\u0438\u043d\u0443\u043b\u0438 slicer.addEventListener('mouseout', () =&gt; { document.documentElement.style.setProperty('--thumb-size', `15px`); brushPreview.style.opacity = 0; });  \/\/ \u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u043a\u0438\u0441\u0442\u0438 \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u0438 \u043f\u043e\u043b\u0437\u0443\u043d\u043a\u0430 slicer.addEventListener('input', () =&gt; { brushSize = slicer.value; document.documentElement.style.setProperty('--brush-size', `${brushSize}px`); }); <\/pre>\n<p> <b>\u0421\u043c\u0435\u043d\u0430 \u043a\u0438\u0441\u0442\u0438 \u0438 \u043b\u0430\u0441\u0442\u0438\u043a\u0430<\/b><\/p>\n<pre> var brushBtn = document.getElementById('brushBtn'); var eraserBtn = document.getElementById('eraserBtn');  \/\/ \u041f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u043a\u043d\u043e\u043f\u043a\u0438 \u043a\u0438\u0441\u0442\u0438 \u0446\u0432\u0435\u0442 \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u0431\u0435\u043b\u044b\u0439 brushBtn.addEventListener('click', () =&gt; { color = '#fff'; document.documentElement.style.setProperty('--cursor-color', '#fff'); brushSize = 2; document.documentElement.style.setProperty('--brush-size', `2px`); slicer.value = 2; brushBtn.classList.add('active'); eraserBtn.classList.remove('active'); });  \/\/ \u041f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 \u043a\u043d\u043e\u043f\u043a\u0438 \u043b\u0430\u0441\u0442\u0438\u043a\u0430 \u0446\u0432\u0435\u0442 \u043c\u0435\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u0447\u0435\u0440\u043d\u044b\u0439 eraserBtn.addEventListener('click', () =&gt; { color = '#000'; brushSize = 32; document.documentElement.style.setProperty('--brush-size', `32px`); document.documentElement.style.setProperty('--cursor-color', '#101010'); slicer.value = 32; brushBtn.classList.remove('active'); eraserBtn.classList.add('active'); }); <\/pre>\n<p> <b>\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u0433\u043e\u0440\u044f\u0447\u0438\u0445 \u043a\u043b\u0430\u0432\u0438\u0448<\/b><\/p>\n<p> \u041a\u043b\u0430\u0432\u0438\u0448\u0438 <b>[<\/b> \u0438 <b>]<\/b> \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0434\u043b\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u043a\u0438\u0441\u0442\u0438, <b>P<\/b> \u2014 \u0432\u044b\u0431\u043e\u0440\u0430 \u043a\u0438\u0441\u0442\u0438, <b>E<\/b> \u2014 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043b\u0430\u0441\u0442\u0438\u043a\u0430. <\/p>\n<pre> window.addEventListener('keydown', (e) =&gt; { \/\/ \u0423\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u043a\u0438\u0441\u0442\u0438 if (e.key == ']' || e.key == '}' || e.key == '\u044a' || e.key == '\u042a') {     let step = 1;     if (e.shiftKey) step = 10;     brushSize = Math.min(Number(slicer.max), brushSize + step);     slicer.value = brushSize;     document.documentElement.style.setProperty('--brush-size', `${brushSize}px`); } \/\/ \u0423\u043c\u0435\u043d\u044c\u0448\u0435\u043d\u0438\u0435 \u0440\u0430\u0437\u043c\u0435\u0440\u0430 \u043a\u0438\u0441\u0442\u0438 if (e.key == '[' || e.key == '{' || e.key == '\u0445' || e.key == '\u0425') {     let step = 1;     if (e.shiftKey) step = 10;     brushSize = Math.max(Number(slicer.min), brushSize - step);     slicer.value = brushSize;     document.documentElement.style.setProperty('--brush-size', `${brushSize}px`); } \/\/ \u0412\u044b\u0431\u043e\u0440 \u043a\u0438\u0441\u0442\u0438 if (e.key == 'p' || e.key == '\u0437') {     color = '#fff';     document.documentElement.style.setProperty('--cursor-color', '#fff');     brushSize = 2;     document.documentElement.style.setProperty('--brush-size', `2px`);     slicer.value = 2;     brushBtn.classList.add('active');     eraserBtn.classList.remove('active'); } \/\/ \u0412\u044b\u0431\u043e\u0440 \u043b\u0430\u0441\u0442\u0438\u043a\u0430 if (e.key == 'e' || e.key == '\u0443') {     color = '#000';     document.documentElement.style.setProperty('--cursor-color', '#101010');     brushSize = 32;     document.documentElement.style.setProperty('--brush-size', `32px`);     slicer.value = 32;     brushBtn.classList.remove('active');     eraserBtn.classList.add('active'); } }); <\/pre>\n<p> \u0422\u0435\u043f\u0435\u0440\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u043b\u043e \u0443\u0434\u043e\u0431\u043d\u044b\u043c. \u041f\u043e\u0440\u0430 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u0442\u044c \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u043d\u043e\u0439 \u0447\u0430\u0441\u0442\u0438.<\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/ru\/zq\/br\/ruzqbro9a9bk-vxbro41wce7vzm.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/ru\/zq\/br\/ruzqbro9a9bk-vxbro41wce7vzm.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/ru\/zq\/br\/ruzqbro9a9bk-vxbro41wce7vzm.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/div>\n<p> <a name=\"4\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u0421\u0435\u0440\u0432\u0435\u0440\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c<\/h2>\n<p><\/font><br \/> \u0421\u0435\u0440\u0432\u0435\u0440\u043d\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u2014 Python\u2011\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430, \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043d\u0430\u044f \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043c\u0438\u043a\u0440\u043e\u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u0430 Flask.<br \/> \u042f \u0441\u043e\u0437\u0434\u0430\u043b \u043f\u0430\u043f\u043a\u0443 <code>app<\/code>, \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043d\u0430\u0445\u043e\u0434\u044f\u0442\u0441\u044f:<\/p>\n<ul>\n<li><code>__init__.py<\/code> \u2014 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f Flask-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f,<\/li>\n<li><code>routes.py<\/code> \u2014 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b,<\/li>\n<li><code>image_utils.py<\/code> \u2014 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439.<\/li>\n<\/ul>\n<p>\u0414\u043b\u044f \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u044f \u0442\u0435\u043a\u0441\u0442\u0430 \u044f \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u043e\u043f\u0440\u043e\u0431\u043e\u0432\u0430\u043b <code>pytesseract<\/code>. \u041e\u0434\u043d\u0430\u043a\u043e \u0432\u044b\u044f\u0441\u043d\u0438\u043b\u043e\u0441\u044c, \u0447\u0442\u043e \u044d\u0442\u0430 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430 \u043f\u043b\u043e\u0445\u043e \u0441\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0441 \u0440\u0443\u043a\u043e\u043f\u0438\u0441\u043d\u044b\u043c \u0432\u0432\u043e\u0434\u043e\u043c. \u041e\u043a\u043e\u043d\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0432\u044b\u0431\u043e\u0440 \u043f\u0430\u043b \u043d\u0430 <code>easyocr<\/code> \u2014 \u043e\u043d\u0430 \u0445\u043e\u0442\u044c \u0438 \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u0435\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442, \u0437\u0430\u0442\u043e \u0442\u043e\u0447\u043d\u0435\u0435.<\/p>\n<h3>\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0439<\/h3>\n<p> \u0412 <code>image_utils.py<\/code> \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0445 \u0434\u043b\u044f \u0432\u043e\u0441\u043f\u0440\u0438\u044f\u0442\u0438\u044f \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p> <b>\u0414\u0435\u043a\u043e\u0434\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438 \u0438\u0437 base64<\/b><\/p>\n<pre> def base64_to_image(base64_string: str) -&gt; np.ndarray: image = base64.b64decode(base64_string.split(',')[1]) image = np.frombuffer(image, np.uint8) image = cv2.imdecode(image, cv2.IMREAD_GRAYSCALE) return image <\/pre>\n<p> <b>\u0418\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0446\u0432\u0435\u0442\u043e\u0432 \u0438 \u0443\u0432\u0435\u043b\u0438\u0447\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0440\u0430\u0441\u0442\u043d\u043e\u0441\u0442\u0438<\/b><\/p>\n<pre> def prepare_image(image: np.ndarray) -&gt; str: image = cv2.bitwise_not(image) _, image = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)  files = list(map(lambda x: int(x.split('.')[0]), os.listdir('app\/static\/user_images'))) i = max(files) + 1 if files else 0  cv2.imwrite(f'app\/static\/user_images\/{i}.png', image) return f'app\/static\/user_images\/{i}.png' <\/pre>\n<p> <b>\u0420\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u0435 \u0442\u0435\u043a\u0441\u0442\u0430 \u0441 \u0443\u0447\u0435\u0442\u043e\u043c \u043e\u0442\u0441\u0442\u0443\u043f\u043e\u0432<\/b> (\u043f\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0443 \u043f\u0440\u043e\u0431\u0435\u043b\u043e\u0432 \u043f\u0435\u0440\u0435\u0434 \u0441\u0442\u0440\u043e\u043a\u043e\u0439)<\/p>\n<pre> def image_to_code(image: str) -&gt; str: # \u0420\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u0435 \u0431\u043b\u043e\u043a\u043e\u0432 \u0442\u0435\u043a\u0441\u0442\u0430 \u043d\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0435 blocks = reader.readtext(image) blocks = sorted(blocks, key=lambda x: x[0][0][1])  # \u0422\u043e\u043b\u0435\u0440\u0430\u043d\u0442\u043d\u043e\u0441\u0442\u044c \u043a \u0432\u044b\u0441\u043e\u0442\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u0432 \u043f\u0438\u043a\u0441\u0435\u043b\u044f\u0445. \u0427\u0435\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 - \u0442\u0435\u043c \u0431\u043e\u043b\u0435\u0435 \u0434\u0430\u043b\u044c\u043d\u0438\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u043f\u043e \u0432\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u0438 \u0431\u0443\u0434\u0443\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u043e\u0434\u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0430 tolerance = 20 # \u0421\u043f\u0438\u0441\u043e\u043a \u0438\u0437 \u0441\u0440\u0435\u0434\u043d\u0438\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0448\u0438\u0440\u0438\u043d\u044b \u0434\u043b\u044f \u0441\u0438\u043c\u0432\u043e\u043b\u043e\u0432 \u0432 \u0431\u043b\u043e\u043a\u0430\u0445 symbol_widths = [(block[0][2][0] - block[0][0][0]) \/ len(block[1]) for block in blocks]  # \u0420\u0430\u0437\u0431\u0438\u0435\u043d\u0438\u0435 \u043d\u0430 \u0441\u0442\u0440\u043e\u043a\u0438 last_y = None block_lines = [] for block in blocks:     if last_y is not None and abs(block[0][0][1] - last_y) &lt;= tolerance:         block_lines[-1].append(block)     else:         block_lines.append([block])     last_y = block[0][0][1]  block_lines = [sorted(e, key=lambda x: x[0][0][0]) for e in block_lines] lines = [[line[0][0][:2], ' '.join([e[1] for e in line])] for line in block_lines]  # \u0412\u044b\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435 \u0441\u0440\u0435\u0434\u043d\u0435\u0439 \u0448\u0438\u0440\u0438\u043d\u044b \u0441\u0438\u043c\u0432\u043e\u043b\u0430 av_symbol_widths = float(sum(symbol_widths) \/ len(symbol_widths)) if symbol_widths else 0  for i, line in enumerate(lines[1:], 1): # \u043f\u043e\u0438\u0441\u043a \u0447\u0435\u0433\u043e-\u0442\u043e \u043f\u043e\u0445\u043e\u0436\u0435\u0433\u043e \u043d\u0430 \u043e\u0442\u0441\u0442\u0443\u043f \u0438 \u0437\u0430\u043c\u0435\u043d\u0430 \u043d\u0430 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0439 \u043e\u0442\u0441\u0442\u0443\u043f     tabs = (float(line[0][0][0]) - float(lines[0][0][0][0])) \/\/ (av_symbol_widths * 3)     lines[i][1] = ' ' * (4 * int(tabs)) + line[1]  lines = [e[1] for e in lines]  return ' '.join(lines) <\/pre>\n<p> \u0422\u0435\u043f\u0435\u0440\u044c \u0441\u0435\u0440\u0432\u0435\u0440 \u043c\u043e\u0436\u0435\u0442 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u044b\u0432\u0430\u0442\u044c \u0440\u0443\u043a\u043e\u043f\u0438\u0441\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442 \u0432 Python-\u043a\u043e\u0434 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0435\u0433\u043e \u043e\u0431\u0440\u0430\u0442\u043d\u043e \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443.<\/p>\n<p> <a name=\"5\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u041e\u0442\u043f\u0440\u0430\u0432\u043a\u0430 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440<\/h2>\n<p><\/font><br \/> \u0412 <code>drawing.js<\/code> \u044f \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u0444\u0443\u043d\u043a\u0446\u0438\u044e, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440, \u0435\u0441\u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043f\u0440\u0435\u043a\u0440\u0430\u0442\u0438\u043b \u0440\u0438\u0441\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u043f\u0440\u043e\u0448\u043b\u043e \u043f\u043e\u043b\u0441\u0435\u043a\u0443\u043d\u0434\u044b. \u0422\u0430\u043a\u0430\u044f \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0430\u044f \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u0441\u043d\u0438\u0436\u0430\u0435\u0442 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0438 \u043f\u0440\u0435\u0434\u043e\u0442\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0438\u0437\u0431\u044b\u0442\u043e\u0447\u043d\u044b\u0445 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432. <\/p>\n<pre> function sendImage() { if (waitingForServer) return;  waitingForServer = true; loading.style.opacity = 1; \/\/ \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u0432 \u0432\u0438\u0434\u0435 base64 \u0441\u0442\u0440\u043e\u043a\u0438 const dataURL = canvas.toDataURL('image\/png');  \/\/ \u0414\u0435\u043b\u0430\u0435\u043c \u0437\u0430\u043f\u0440\u043e\u0441 \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443, \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044f \u0441\u0442\u0440\u043e\u043a\u0443 fetch('\/image', {     method: 'POST',     headers: {         'Content-Type': 'application\/json'     },     body: JSON.stringify({ image: dataURL }) }) .then((response) =&gt; response.json()) .then((data) =&gt; { \/\/ \u0412 \u043e\u0442\u0432\u0435\u0442 \u0441\u0435\u0440\u0432\u0435\u0440 \u043e\u0442\u0434\u0430\u0435\u0442 \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u043d\u043d\u044b\u0439 \u0442\u0435\u043a\u0441\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432 \u043e\u043a\u043d\u043e \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f     console.log(data.text);     codePreview.textContent = data.text; }) .catch((error) =&gt; {     console.error(error); }) .finally(() =&gt; {     loading.style.opacity = 0;     waitingForServer = false;     if (needToUpdate) {         needToUpdate = false;         serverAskTimeout = setTimeout(sendImage, 500);     } }); } <\/pre>\n<p> <a name=\"6\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u0418\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u043a\u043e\u0434\u0430<\/h2>\n<p><\/font><br \/> \u0414\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u0434\u0430 \u043f\u0440\u044f\u043c\u043e \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b <code>pyodide<\/code>.<\/p>\n<p> \u0412 <code>codeEval.js<\/code> \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043d\u0430 \u043f\u0430\u0440\u0443 \u0441\u0435\u043a\u0443\u043d\u0434. \u0427\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438 \u043d\u0435 \u0438\u0441\u043f\u044b\u0442\u044b\u0432\u0430\u043b\u0438 \u043d\u0435\u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u043e\u0442 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f, \u044f \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u044d\u043a\u0440\u0430\u043d \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438. <\/p>\n<pre> async function load() { let pyodide = await loadPyodide(); pyodide.setStdout({batched: (str) =&gt; {     if (outputBlock.innerHTML != '') outputBlock.innerHTML += ' ' + str;     else outputBlock.innerHTML = str; }});  document.querySelector('.loading_block').remove();  return pyodide; }; let pyodideReadyPromise = load(); <\/pre>\n<p> \u0424\u0443\u043d\u043a\u0446\u0438\u044f <code>evaluatePython<\/code> \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \u043a\u043e\u0434 \u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435.<\/p>\n<pre> async function evaluatePython(code) { if (code == '' || code == '\u043a\u043e\u0434') {     outputBlock.innerHTML = '\u041d\u0443 \u0445\u043e\u0442\u044c \u0447\u0442\u043e-\u043d\u0438\u0431\u0443\u0434\u044c \u043d\u0430\u043f\u0438\u0448\u0438';     return; } outputBlock.innerHTML = ''; let pyodide = await pyodideReadyPromise; try {     outputBlock.style.color = 'white';     let output = await pyodide.runPythonAsync(code);     console.log(output); } catch (err) {     console.log(err);     outputBlock.innerHTML = err;     outputBlock.style.color = 'red'; } } <\/pre>\n<p> <\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/zr\/k9\/_5\/zrk9_5ypuqxmzhoz1vpuwomfjea.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/zr\/k9\/_5\/zrk9_5ypuqxmzhoz1vpuwomfjea.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/zr\/k9\/_5\/zrk9_5ypuqxmzhoz1vpuwomfjea.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/div>\n<p> <a name=\"7\"><\/a><font color=\"#EB4247\"><\/p>\n<h2>\u0414\u0435\u043f\u043b\u043e\u0439<\/h2>\n<p><\/font><br \/> \u041a\u043e\u0433\u0434\u0430 \u043f\u0440\u043e\u0435\u043a\u0442 \u0431\u044b\u043b \u0433\u043e\u0442\u043e\u0432, \u044f \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u043b \u0435\u0433\u043e \u043d\u0430 \u043e\u0431\u043b\u0430\u0447\u043d\u043e\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0435. \u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0435\u0441\u043b\u043e\u0436\u0435\u043d \u0438 \u0432\u043a\u043b\u044e\u0447\u0430\u043b \u0432 \u0441\u0435\u0431\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0448\u0430\u0433\u043e\u0432.<\/p>\n<p> <b>\u0428\u0430\u0433 1<\/b>. \u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u0432 \u043f\u0430\u043d\u0435\u043b\u0438 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f <a href=\"https:\/\/my.selectel.ru\/?utm_source=habr.com&amp;utm_medium=referral&amp;utm_campaign=myselectel_article_handcode_190525_content\">my.selectel.ru<\/a>. \u0417\u0430\u0445\u043e\u0434\u0438\u043c \u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u0438\u043b\u0438 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0439, \u0435\u0441\u043b\u0438 \u0435\u0433\u043e \u0435\u0449\u0435 \u043d\u0435\u0442.<\/p>\n<p> <b>\u0428\u0430\u0433 2<\/b>. \u041d\u0430\u0436\u0438\u043c\u0430\u0435\u043c \u043d\u0430 \u0440\u0430\u0437\u0434\u0435\u043b <b>\u041f\u0440\u043e\u0434\u0443\u043a\u0442\u044b<\/b> \u0438 \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u0432\u043a\u043b\u0430\u0434\u043a\u0443 <b>\u041e\u0431\u043b\u0430\u0447\u043d\u044b\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u044b<\/b>.<\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/ni\/ud\/d6\/niudd6pbeohyt_kdr6ufk7imeem.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/ni\/ud\/d6\/niudd6pbeohyt_kdr6ufk7imeem.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/ni\/ud\/d6\/niudd6pbeohyt_kdr6ufk7imeem.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/div>\n<p> \u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0447\u043a\u0443 <b>\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440<\/b>, \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u043f\u043e\u0434\u0445\u043e\u0434\u044f\u0449\u0443\u044e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e, <i><a href=\"https:\/\/selectel.ru\/blog\/ssh-authentication\/\">selectel.ru\/blog\/ssh-authentication<\/a> \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c SSH-\u043a\u043b\u044e\u0447<\/i> \u0438 \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u043c \u043a\u043d\u043e\u043f\u043a\u0443 <b>\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440<\/b>.<\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/9d\/yq\/z0\/9dyqz0b7apqjyyaes0wgtp-k930.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/9d\/yq\/z0\/9dyqz0b7apqjyyaes0wgtp-k930.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/9d\/yq\/z0\/9dyqz0b7apqjyyaes0wgtp-k930.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/div>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/dh\/yf\/sk\/dhyfskg-eqvywxilu5hd5ukkbso.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/dh\/yf\/sk\/dhyfskg-eqvywxilu5hd5ukkbso.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/dh\/yf\/sk\/dhyfskg-eqvywxilu5hd5ukkbso.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/div>\n<p> \u0414\u043e\u0436\u0438\u0434\u0430\u0435\u043c\u0441\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430. \u0421\u0442\u0430\u0442\u0443\u0441 \u043c\u043e\u0436\u043d\u043e \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435, \u043d\u0430\u043f\u0440\u043e\u0442\u0438\u0432 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0441\u0435\u0440\u0432\u0435\u0440\u0430.<\/p>\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/v-\/r_\/b5\/v-r_b50q-fbz6k5-qe8msulmyaq.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/v-\/r_\/b5\/v-r_b50q-fbz6k5-qe8msulmyaq.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/v-\/r_\/b5\/v-r_b50q-fbz6k5-qe8msulmyaq.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/div>\n<p> <b>\u0428\u0430\u0433 3<\/b>. \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 \u043f\u043e SSH \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b:<\/p>\n<pre> ssh root@[ip \u0441\u0435\u0440\u0432\u0435\u0440\u0430] (ssh root@31.128.50.164 \u0434\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0432\u044b\u0448\u0435) sudo apt update sudo apt install git gunicorn ufw python3.12-venv certbot <\/pre>\n<p> <b>\u0428\u0430\u0433 4<\/b>. \u041a\u043b\u043e\u043d\u0438\u0440\u0443\u0435\u043c Git-\u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439:<\/p>\n<pre> git clone https:\/\/github.com\/eledays\/handCode <\/pre>\n<p> \u041f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u0432 \u043f\u0430\u043f\u043a\u0443 <code>handCode<\/code>, \u043f\u043e\u044f\u0432\u0438\u0432\u0448\u0443\u044e\u0441\u044f \u0432 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0435 \u043a\u043b\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f:<\/p>\n<pre> cd handCode <\/pre>\n<p> <b>\u0428\u0430\u0433 5<\/b>. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435 \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438:<\/p>\n<pre> python3 -m venv .venv source .venv\/bin\/activate pip install -r requirements.txt <\/pre>\n<p> \u041e\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043b\u044f\u0435\u043c \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a, \u0447\u0442\u043e\u0431\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0441\u0435\u0440\u0432\u0435\u0440:<\/p>\n<pre> flask run <\/pre>\n<p> <b>\u0428\u0430\u0433 6<\/b>. \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e gunicorn:<\/p>\n<pre> \/home\/handCode\/.venv\/bin\/python3 -m gunicorn --bind 0.0.0.0:5000 main:app <\/pre>\n<p> <b>\u0428\u0430\u0433 7<\/b>. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438 \u0433\u0440\u0443\u043f\u043f\u0443 handcodeuser, \u043d\u043e \u0431\u0435\u0437 \u0434\u043e\u043c\u0430\u0448\u043d\u0435\u0439 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 \u0438 \u043f\u0440\u0430\u0432\u0430 \u043d\u0430 \u0437\u0430\u043f\u0443\u0441\u043a \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0441\u0435\u0430\u043d\u0441\u0430:<\/p>\n<pre> sudo useradd -r -s \/sbin\/nologin -M -c \"\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f handCode\" handcodeuser <\/pre>\n<p> \u0414\u0435\u043b\u0430\u0435\u043c \u0435\u0433\u043e \u0432\u043b\u0430\u0434\u0435\u043b\u044c\u0446\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0430:<\/p>\n<pre> sudo chown -R handcodeuser:handcodeuser \/home\/handCode <\/pre>\n<p> \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u0435\u0431\u044f \u0432 \u0433\u0440\u0443\u043f\u043f\u0443, \u0447\u0442\u043e\u0431\u044b \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0444\u0430\u0439\u043b\u044b:<\/p>\n<pre> sudo usermod -aG handcodeuser &lt;\u0438\u043c\u044f \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f&gt; <\/pre>\n<p> <b>\u0428\u0430\u0433 8<\/b>. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u0434\u0433\u043e\u0442\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0444\u0430\u0439\u043b:<\/p>\n<pre> sudo nano \/etc\/systemd\/system\/handCode.service <\/pre>\n<p> \u0421\u043e\u0434\u0435\u0440\u0436\u0438\u043c\u043e\u0435 \u0444\u0430\u0439\u043b\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<pre> [Unit] Description=gunicorn daemon After=network.target  [Service] User=handcodeuser Group=handcodeuser WorkingDirectory=\/home\/handCode Environment=\"PATH=\/home\/handCode\/.venv\/bin\" ExecStart=\/home\/handCode\/.venv\/bin\/gunicorn --workers 3 --bind 0.0.0.0:80 main:app  [Install] WantedBy=multi-user.target <\/pre>\n<p> \u0412 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0435 nano \u0434\u043b\u044f \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u043c <b>Ctrl<\/b>+<b>X<\/b>, \u0430 \u0437\u0430\u0442\u0435\u043c <b>Y<\/b>.<br \/> \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0438\u0441: <\/p>\n<pre> sudo systemctl start handCode sudo systemctl enable handCode <\/pre>\n<p> \u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0441\u0442\u0430\u0442\u0443\u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043c\u043e\u0436\u043d\u043e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u043e\u0439: <\/p>\n<pre> sudo systemctl status handCode <\/pre>\n<p> <b>\u0428\u0430\u0433 9<\/b>. \u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u043c\u0435\u0436\u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u044d\u043a\u0440\u0430\u043d \u2014 \u043e\u043d \u0434\u043e\u043b\u0436\u0435\u043d \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043f\u043e 80\u2011\u043c\u0443 \u043f\u043e\u0440\u0442\u0443.<\/p>\n<pre> ufw allow 80 <\/pre>\n<p> <b>\u0428\u0430\u0433 10<\/b>. \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430. \u0414\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043d\u0430\u0431\u0440\u0430\u0442\u044c \u0432 \u0430\u0434\u0440\u0435\u0441\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 IP\u2011\u0430\u0434\u0440\u0435\u0441 \u043d\u0430\u0448\u0435\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430. \u041c\u043e\u0436\u043d\u043e \u043f\u0440\u0438\u043e\u0431\u0440\u0435\u0441\u0442\u0438 \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0438 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u0435\u0433\u043e \u043a IP\u2011\u0430\u0434\u0440\u0435\u0441\u0443.<\/p>\n<pre> http:\/\/&lt;IP\u2011\u0430\u0434\u0440\u0435\u0441 \u0438\u043b\u0438 \u0434\u043e\u043c\u0435\u043d \u0441\u0435\u0440\u0432\u0435\u0440\u0430&gt; <\/pre>\n<p> \u0413\u043e\u0442\u043e\u0432\u043e! \u0414\u0435\u043b\u0438\u0442\u0435\u0441\u044c \u0441\u0432\u043e\u0438\u043c\u0438 \u0432\u0430\u0440\u0438\u0430\u043d\u0442\u0430\u043c\u0438, \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0443\u043b\u0443\u0447\u0448\u0438\u0442\u044c \u043f\u0440\u043e\u0435\u043a\u0442. \u041c\u043d\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u0443\u0441\u043b\u044b\u0448\u0430\u0442\u044c \u0432\u0430\u0448\u0435 \u043c\u043d\u0435\u043d\u0438\u0435. \u0410 \u0442\u0430\u043a\u0436\u0435 \u0437\u0430\u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0449\u0438\u0435 \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u2014 \u0441 \u0443\u0434\u043e\u0432\u043e\u043b\u044c\u0441\u0442\u0432\u0438\u0435\u043c \u043e\u0442\u0432\u0435\u0447\u0443 \u043d\u0430 \u043d\u0438\u0445 \u0432 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f\u0445.<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/910602\/\"> https:\/\/habr.com\/ru\/articles\/910602\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<div style=\"text-align:center;\"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/wy\/lf\/0b\/wylf0bwb-niny9swj09z5cfgszo.jpeg\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/wy\/lf\/0b\/wylf0bwb-niny9swj09z5cfgszo.jpeg 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/wy\/lf\/0b\/wylf0bwb-niny9swj09z5cfgszo.jpeg 781w\" loading=\"lazy\" decode=\"async\"\/><\/div>\n<p> \u041f\u0440\u0438\u0432\u0435\u0442, \u043c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u041b\u0451\u043d\u044f! \u042f \u0430\u0432\u0442\u043e\u0440 <a href=\"https:\/\/youtube.com\/@eleday\">YouTube\u2011\u043a\u0430\u043d\u0430\u043b\u0430 eleday<\/a> \u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u043d\u0430 Python. \u041d\u0435\u0434\u0430\u0432\u043d\u043e \u0432 \u0448\u043a\u043e\u043b\u0435 \u0431\u044b\u043b\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043e\u0447\u043d\u0430\u044f \u0440\u0430\u0431\u043e\u0442\u0430 \u0438 \u043c\u043d\u0435 \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u043f\u0438\u0441\u0430\u0442\u044c \u043a\u043e\u0434 \u043d\u0430 \u0431\u0443\u043c\u0430\u0433\u0435. \u0422\u0430\u043a\u043e\u0439 \u043f\u043e\u0434\u0445\u043e\u0434 \u043f\u043e\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u0441\u0442\u0440\u0430\u043d\u043d\u044b\u043c: \u0432\u0441\u0435-\u0442\u0430\u043a\u0438 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430 \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u0435 \u0438 \u043b\u043e\u0433\u0438\u0447\u043d\u043e \u043d\u0430\u0431\u0438\u0440\u0430\u0442\u044c \u0435\u0435 \u0442\u0430\u043c \u0436\u0435. \u041f\u043e\u0434\u043e\u0431\u043d\u0430\u044f \u0446\u0435\u043f\u043e\u0447\u043a\u0430 \u0440\u0430\u0441\u0441\u0443\u0436\u0434\u0435\u043d\u0438\u0439 \u043f\u0440\u0438\u0432\u0435\u043b\u0430 \u043a \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0439 \u0438\u0434\u0435\u0435 \u2014 \u0440\u0435\u0434\u0430\u043a\u0442\u043e\u0440\u0443 \u0440\u0443\u043a\u043e\u043f\u0438\u0441\u043d\u043e\u0433\u043e \u0432\u0432\u043e\u0434\u0430. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e \u0437\u0430\u0434\u0443\u043c\u043a\u0435 \u0438 \u0434\u0435\u0442\u0430\u043b\u044f\u0445 \u0435\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438. \u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u043b\u0438\u0441\u0442, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0431\u0440\u043e\u0441\u0430\u0442\u044c \u043a\u043e\u0434 \u043e\u0442 \u0440\u0443\u043a\u0438 \u2014 \u0438 \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f!<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-460130","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/460130","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=460130"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/460130\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=460130"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=460130"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=460130"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}