{"id":368299,"date":"2024-05-21T03:39:47","date_gmt":"2024-05-21T03:39:47","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=368299"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=368299","title":{"rendered":"<span>Python, MSA, Kafka<\/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-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430, \u0447\u0442\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f &#171;\u043d\u0430 \u0445\u0430\u0439\u043f\u0435&#187;. \u042f \u043f\u0435\u0440\u0435\u0447\u0438\u0442\u0430\u043b \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043c\u043d\u043e\u0433\u043e \u0441\u0442\u0430\u0442\u0435\u0439 \u043f\u043e \u0434\u0430\u043d\u043d\u043e\u0439 \u0442\u0435\u043c\u0430\u0442\u0438\u043a\u0435, \u043d\u043e \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u043b, \u0447\u0442\u043e \u0441\u0440\u0435\u0434\u0438 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u043e\u0447\u0435\u0433\u043e, \u043d\u0435 \u0442\u0430\u043a \u043c\u043d\u043e\u0433\u043e \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0439, \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u0443\u044e \u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044e \u043d\u0430 <strong>\u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c<\/strong> \u043f\u0440\u0438\u043c\u0435\u0440\u0435 (\u043c\u043e\u0436\u0435\u0442, \u043f\u043b\u043e\u0445\u043e \u0438\u0441\u043a\u0430\u043b). \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u044f \u0431\u044b \u0445\u043e\u0442\u0435\u043b \u043f\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0440\u044f\u0434\u044b \u0430\u0432\u0442\u043e\u0440\u043e\u0432 \u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u044e \u043f\u0435\u0440\u0432\u0443\u044e \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044e, <s>\u043d\u0435<\/s> \u0441\u0443\u0434\u0438\u0442\u0435 \u0441\u0442\u0440\u043e\u0433\u043e!<\/p>\n<h3>\u041e\u0433\u043b\u0430\u0432\u043b\u0435\u043d\u0438\u0435<\/h3>\n<ol>\n<li>\n<p><a href=\"#1\" rel=\"noopener noreferrer nofollow\">\u0414\u043b\u044f\u00a0\u043a\u043e\u0433\u043e \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f?<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#2\" rel=\"noopener noreferrer nofollow\">\u041a\u0440\u0430\u0442\u043a\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#3\" rel=\"noopener noreferrer nofollow\">\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#4\" rel=\"noopener noreferrer nofollow\">FASTApi \u0441\u0435\u0440\u0432\u0438\u0441 (web)<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#5\" rel=\"noopener noreferrer nofollow\">Currency \u0441\u0435\u0440\u0432\u0438\u0441 (Scrapy)<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#6\" rel=\"noopener noreferrer nofollow\">Notify \u0441\u0435\u0440\u0432\u0438\u0441 (\u0431\u043e\u0442)<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#7\" rel=\"noopener noreferrer nofollow\">\u0418\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#8\" rel=\"noopener noreferrer nofollow\">\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/a><\/p>\n<\/li>\n<\/ol>\n<p><a class=\"anchor\" name=\"1\" id=\"1\"><\/a><\/p>\n<h2>\u0414\u043b\u044f \u043a\u043e\u0433\u043e \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f?<\/h2>\n<p>\u042f \u043d\u0430\u0434\u0435\u044e\u0441\u044c, \u0447\u0442\u043e \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u0441\u0442\u0430\u043d\u0435\u0442 \u043e\u0442\u043b\u0438\u0447\u043d\u044b\u043c \u0438 \u043f\u043e\u043d\u044f\u0442\u043d\u044b\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u043c \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430, \u0442\u043e\u043b\u044c\u043a\u043e <strong>\u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0435\u0433\u043e<\/strong> \u0441\u0432\u043e\u0439 \u043f\u0443\u0442\u044c \u0432 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445. \u041c\u043d\u043e\u0433\u0438\u0435 \u0430\u0441\u043f\u0435\u043a\u0442\u044b \u0434\u0430\u043d\u043d\u043e\u0439 \u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u0438 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u0441\u0431\u043e\u0440\u0430 \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0438\u0437 \u0440\u0430\u0437\u043d\u044b\u0445 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u0432. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043f\u043e\u043f\u0443\u0442\u043d\u043e \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u044e\u0449\u0438\u0435 \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u0447\u0430\u0441\u0442\u043e \u0441\u0431\u0438\u0432\u0430\u044e\u0442 \u0441 \u0442\u043e\u043b\u043a\u0443 \u0438 \u043e\u0442\u0432\u043b\u0435\u043a\u0430\u044e\u0442 \u043e\u0442 <strong>\u043f\u0435\u0440\u0432\u043e\u0439 \u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0439 \u0446\u0435\u043b\u0438<\/strong> &#8212; &#171;\u043f\u043e\u0449\u0443\u043f\u0430\u0442\u044c&#187; \u0441\u0432\u043e\u0438\u043c\u0438 \u0440\u0443\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u0440\u0430\u0431\u043e\u0442\u0443. \u041a\u043e\u0433\u0434\u0430 \u043d\u0435\u043a\u043e\u0435 \u044f\u0434\u0440\u043e \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0431\u0443\u0434\u0435\u0442 \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043e, \u043f\u043e\u043b\u0430\u0433\u0430\u044e, <strong>\u0432\u044b \u0432\u0435\u0440\u043d\u0435\u0442\u0435\u0441\u044c \u043a \u0431\u043e\u043b\u0435\u0435 \u0443\u0437\u043a\u0438\u043c \u0438 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u0432\u043e\u043f\u0440\u043e\u0441\u0430\u043c \u0432 \u044d\u0442\u043e\u0439 \u0442\u0435\u043c\u0435, \u0438 \u043f\u043e\u043d\u044f\u0442\u044c \u0438\u0445 \u0432\u0430\u043c \u0441\u0442\u0430\u043d\u0435\u0442 \u043b\u0435\u0433\u0447\u0435.<\/strong> <\/p>\n<p><a class=\"anchor\" name=\"2\" id=\"2\"><\/a><\/p>\n<h2>\u041a\u0440\u0430\u0442\u043a\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435<\/h2>\n<p>\u0412\u0430\u0448\u0435\u043c\u0443 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u044e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0430, \u0441\u043e\u0441\u0442\u043e\u044f\u0449\u0430\u044f \u0438\u0437 3-\u0435\u0445 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432:<\/p>\n<ul>\n<li>\n<p>\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 <strong>FASTApi<\/strong>;<\/p>\n<\/li>\n<li>\n<p>\u0412\u0435\u0431-\u043f\u0430\u0440\u0441\u0435\u0440 \u043d\u0430 <strong>Scrapy<\/strong>;<\/p>\n<\/li>\n<li>\n<p>\u0422\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c \u0431\u043e\u0442 \u043d\u0430 <strong>Aiogram<\/strong>;<\/p>\n<\/li>\n<\/ul>\n<p>\u0427\u0442\u043e \u044d\u0442\u0430 &#171;\u0441\u0438\u0441\u0442\u0435\u043c\u0430&#187; \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u0434\u0435\u043b\u0430\u0442\u044c? \u0412\u0441\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e &#8212; \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c end-point, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0432 \u0441\u0435\u0431\u044f \u0434\u0432\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 &#8212; <strong>\u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c ID<\/strong> \u0438 <strong>\u043a\u043e\u0434 \u0432\u0430\u043b\u044e\u0442\u044b<\/strong>. \u0414\u0430\u043b\u0435\u0435 \u044d\u0442\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u044f\u0442\u0441\u044f \u0432 \u043f\u0430\u0440\u0441\u0435\u0440 (Scrapy), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u043c\u0443 \u043a\u043e\u0434\u0443 \u0432\u0430\u043b\u044e\u0442\u044b, \u0443\u0437\u043d\u0430\u0435\u0442 \u0435\u0435 \u043a\u0443\u0440\u0441 \u0432 \u0440\u0443\u0431\u043b\u044f\u0445. \u041d\u0430\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u043a, \u0441\u043e\u0431\u0440\u0430\u043d\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043f\u0440\u0438\u043c\u0435\u0442 \u0431\u043e\u0442 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e. \u0427\u0442\u043e\u0431\u044b \u0441\u0442\u0430\u043b\u043e \u0435\u0449\u0435 \u043f\u043e\u043d\u044f\u0442\u043d\u0435\u0435, \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u0440\u0438\u0441. 1: <\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/5cc\/d9f\/724\/5ccd9f724e9f1407039d90295d926b63.png\" alt=\"\u0420\u0438\u0441. 1. \u0421\u0445\u0435\u043c\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432\" title=\"\u0420\u0438\u0441. 1. \u0421\u0445\u0435\u043c\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432\" width=\"1350\" height=\"492\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/5cc\/d9f\/724\/5ccd9f724e9f1407039d90295d926b63.png\"\/><\/p>\n<div><figcaption>\u0420\u0438\u0441. 1. \u0421\u0445\u0435\u043c\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432<\/figcaption><\/div>\n<\/figure>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u044f\u0437\u044b\u043a\u0430 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Python, \u0442\u0430\u043a\u0436\u0435 \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f Redis, Kafka \u0438 Zookeper. \u0427\u0442\u043e\u0431\u044b &#171;\u043f\u043e\u0434\u043d\u044f\u0442\u044c&#187; \u0432\u0441\u0435 \u044d\u0442\u043e <s>\u0437\u043b\u043e<\/s> \u0434\u043e\u0431\u0440\u043e, \u043f\u0440\u0438\u0431\u0435\u0433\u043d\u0435\u043c \u043a Docker-\u0443. <\/p>\n<p><a class=\"anchor\" name=\"3\" id=\"3\"><\/a><\/p>\n<h2>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430<\/h2>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u044c \u043d\u0435 &#171;\u0437\u0430\u0431\u0443\u043a\u0441\u043e\u0432\u0430\u043b&#187; \u0432 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u043d\u044f\u0442\u044c \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0442\u043e\u0433\u043e \u0438\u043b\u0438 \u0438\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0438\u043b\u0438 \u043a\u0443\u0441\u043a\u0430 \u043a\u043e\u0434\u0430, \u0441\u0447\u0438\u0442\u0430\u044e \u043d\u0443\u0436\u043d\u044b\u043c \u043f\u043e\u044f\u0441\u043d\u0438\u0442\u044c \u043a\u0430\u043a \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c\u0441\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u043f\u0430\u043f\u043e\u043a:<\/p>\n<p>MSA\/<\/p>\n<p>    docker\u2011compose.yml;<\/p>\n<p>    .env;<\/p>\n<p>    services\/ (\u0432 \u043d\u0435\u0439 \u0431\u0443\u0434\u0443\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432\u0441\u0435 \u043d\u0430\u0448\u0438 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u044b);<\/p>\n<p>        currency\/ (\u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 Scrapy, \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0437\u0430 \u043f\u0430\u0440\u0441\u0438\u043d\u0433 \u0432\u0430\u043b\u044e\u0442);<\/p>\n<p>        web\/ (\u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 FASTApi, \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0437\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0432\u0445\u043e\u0434\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445);<\/p>\n<p>        notify\/ (\u0441\u0435\u0440\u0432\u0438\u0441 \u0441 \u0431\u043e\u0442\u043e\u043c, \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0437\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e);<\/p>\n<p>\u041a\u043e\u043d\u0435\u0447\u043d\u043e, \u0443 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0441\u0432\u043e\u0439 .env \u0444\u0430\u0439\u043b. \u041e\u0434\u043d\u0430\u043a\u043e \u0437\u0434\u0435\u0441\u044c \u043c\u044b \u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u043e <s>\u0441\u043e\u0433\u0440\u0435\u0448\u0438\u043c<\/s> \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u043c \u0438 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043e\u0434\u0438\u043d \u043e\u0431\u0449\u0438\u0439 \u0434\u043b\u044f \u0432\u0441\u0435\u0445. <strong>\u0422\u0430\u043a\u0436\u0435 \u043d\u0430 \u043f\u0435\u0440\u0432\u043e\u0439 \u0441\u0442\u0440\u043e\u0447\u043a\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0431\u043b\u043e\u043a\u0430 \u043a\u043e\u0434\u0430, \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c\u0441\u044f \u043f\u0443\u0442\u044c \u043a \u044d\u0442\u043e\u043c\u0443 \u0444\u0430\u0439\u043b\u0443. <\/strong><\/p>\n<details class=\"spoiler\">\n<summary>Hidden text<\/summary>\n<div class=\"spoiler__content\">\n<p>\u0412\u0435\u0441\u044c \u043a\u043e\u0434 \u0434\u043b\u044f \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u044f\/\u043a\u043b\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0432 <a href=\"https:\/\/github.com\/youngwishes\/MSA\" rel=\"noopener noreferrer nofollow\">\u044d\u0442\u043e\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438.<\/a> \u041e\u0434\u043d\u0430\u043a\u043e \u044f \u043d\u0430\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e <strong>\u043f\u0440\u043e\u043f\u0438\u0441\u0430\u0442\u044c \u0432\u0441\u0435 \u0441\u0432\u043e\u0438\u043c\u0438 \u0440\u0443\u043a\u0430\u043c\u0438.<\/strong><\/p>\n<\/div>\n<\/details>\n<p><a class=\"anchor\" name=\"4\" id=\"4\"><\/a><\/p>\n<h2>FASTApi \u0441\u0435\u0440\u0432\u0438\u0441 (web)<\/h2>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u0433\u043b\u0430\u0432\u043d\u043e\u0433\u043e &#8212; \u0442\u043e\u0447\u043a\u0438 \u0432\u0445\u043e\u0434\u0430 \u0432 \u043d\u0430\u0448\u0443 \u0441\u0438\u0441\u0442\u0435\u043c\u0443. \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438, \u0438\u0445, \u043a\u0441\u0442\u0430\u0442\u0438, \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432 <strong>poetry. <\/strong>\u0412 <strong>web <\/strong>\u0441\u0435\u0440\u0432\u0438\u0441\u0435 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435.<\/p>\n<pre><code class=\"bash\">cd services\/web &amp;&amp; poetry init<\/code><\/pre>\n<p>\u0418 \u0441\u0440\u0430\u0437\u0443 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438<\/p>\n<pre><code class=\"bash\">poetry add fastapi uvicorn aiokafka<\/code><\/pre>\n<p>\u041a\u0430\u043a \u0443\u0436\u0435 \u0433\u043e\u0432\u043e\u0440\u0438\u043b\u043e\u0441\u044c \u0440\u0430\u043d\u043d\u0435\u0435 &#8212; \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Apache Kafka \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0431\u0440\u043e\u043a\u0435\u0440\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439. \u0412 Python \u0435\u0441\u0442\u044c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u043f\u0430\u043a\u0435\u0442 &#8212; <strong>aiokafka<\/strong> &#8212; \u0438\u043c \u043c\u044b \u0438 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f. <\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c &#171;\u043a\u0443\u0447\u0438-\u043c\u0430\u043b\u044b&#187; \u0438 \u043d\u0435 \u043c\u0435\u0448\u0430\u0442\u044c <code>.py<\/code> \u0444\u0430\u0439\u043b\u044b \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 &#8212; \u0432\u043d\u0443\u0442\u0440\u0438 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <strong>web <\/strong>\u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0435\u0449\u0435 \u043e\u0434\u043d\u0443 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e <strong>app <\/strong>&#8212; \u0438 \u0432 \u043d\u0435\u0439 \u0443\u0436\u0435 \u0431\u0443\u0434\u0435\u043c \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c \u0432\u0435\u0441\u044c \u043a\u043e\u0434.<\/p>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0441\u0445\u0435\u043c, \u0437\u0434\u0435\u0441\u044c \u043d\u0438\u0447\u0435\u0433\u043e \u0441\u043b\u043e\u0436\u043d\u043e\u0433\u043e, \u043c\u044b \u043e\u0436\u0438\u0434\u0430\u0435\u043c \u0432\u0441\u0435\u0433\u043e \u0434\u0432\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430:<\/p>\n<pre><code class=\"python\"># services\/web\/app\/schemas.py from pydantic import BaseModel   class Message(BaseModel):     currency_char_code: str     telegram_id: int<\/code><\/pre>\n<p>\u041e\u043f\u0438\u0448\u0435\u043c \u043d\u0443\u0436\u043d\u044b\u0439 \u043d\u0430\u043c end-point:<\/p>\n<pre><code class=\"python\"># services\/web\/app\/main.py from fastapi import FastAPI from web.schemas import Message   app = FastAPI()   @app.post(\"\/currency-info\") async def send(message: Message):     return message.model_dump()<\/code><\/pre>\n<p>\u041f\u043e\u043a\u0430 \u0447\u0442\u043e \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0442\u043e, \u0447\u0442\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u043b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c, \u0430 \u043b\u043e\u0433\u0438\u043a\u0443 \u0441 \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u043e\u0437\u0436\u0435. \u0414\u0430\u043b\u0435\u0435 \u0443\u043a\u0430\u0436\u0435\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u043c:<\/p>\n<pre><code class=\"python\"># services\/web\/app\/settings.py import os  KAFKA_BOOTSTRAP_SERVERS = os.getenv(\"KAFKA_BOOTSTRAP_SERVERS\")  PRODUCE_TOPIC = os.getenv(\"WEB_TOPIC\")<\/code><\/pre>\n<pre><code class=\"bash\"># .env KAFKA_BOOTSTRAP_SERVERS=kafka:9092  WEB_TOPIC=web<\/code><\/pre>\n<p><strong>WEB_TOPIC<\/strong> &#8212; \u0442\u043e\u043f\u0438\u043a, \u043a\u0443\u0434\u0430 \u043d\u0430\u0448 web-\u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u0443\u0434\u0435\u0442 <strong>\u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c<\/strong> \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f. \u0412 \u0431\u0443\u0434\u0443\u0449\u0435\u043c Scrapy \u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u0443\u0434\u0435\u0442 \u044d\u0442\u043e\u0442 \u0442\u043e\u043f\u0438\u043a <strong>\u0441\u043b\u0443\u0448\u0430\u0442\u044c.<\/strong><\/p>\n<p><strong>KAFKA_BOOTSTRAP_SERVERS<\/strong> &#8212; \u0430\u0434\u0440\u0435\u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0431\u0440\u043e\u043a\u0435\u0440\u0430 (\u0438\u0445 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e, \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u0441\u0435\u0433\u043e \u043e\u0434\u0438\u043d).<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044e \u043a\u043b\u0430\u0441\u0441\u0430-\u043e\u0431\u043e\u043b\u043e\u0447\u043a\u0438 \u043d\u0430\u0434 \u043d\u0430\u0448\u0438\u043c &#171;\u043f\u0440\u043e\u0434\u044c\u044e\u0441\u0435\u0440\u043e\u043c&#187;:<\/p>\n<pre><code class=\"python\"># services\/web\/app\/producer.py from aiokafka import AIOKafkaProducer from web.app import settings import asyncio  event_loop = asyncio.get_event_loop()   class AIOWebProducer(object):     def __init__(self):         self.__producer = AIOKafkaProducer(             bootstrap_servers=settings.KAFKA_BOOTSTRAP_SERVERS,             loop=event_loop,         )         self.__produce_topic = settings.PRODUCE_TOPIC      async def start(self) -> None:         await self.__producer.start()      async def stop(self) -> None:         await self.__producer.stop()      async def send(self, value: bytes) -> None:         await self.start()         try:             await self.__producer.send(                 topic=self.__produce_topic,                 value=value,             )         finally:             await self.stop()   def get_producer() -> AIOWebProducer:     return AIOWebProducer()<\/code><\/pre>\n<p>\u041a\u0442\u043e \u0442\u0430\u043a\u043e\u0439 &#171;\u043f\u0440\u043e\u0434\u044c\u044e\u0441\u0435\u0440&#187;? \u0413\u043e\u0432\u043e\u0440\u044f \u043f\u0440\u043e\u0441\u0442\u044b\u043c \u044f\u0437\u044b\u043a\u043e\u043c &#8212; \u044d\u0442\u043e \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u0435\u0442 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0441 \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e \u043d\u0443\u0436\u043d\u043e\u043c\u0443 \u0442\u043e\u043f\u0438\u043a\u0443.<\/p>\n<p>\u041c\u044b \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u043a\u043b\u0430\u0441\u0441\u043e\u043c <code>AIOKafkaProducer<\/code><em> <\/em>\u0438\u0437 \u043c\u043e\u0434\u0443\u043b\u044f <code>aiokafka<\/code>. \u041e\u043d \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0437 \u0441\u0435\u0431\u044f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u043c \u043e\u0442 \u043b\u0438\u0446\u0430 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044f (\u043f\u0440\u043e\u0434\u044c\u044e\u0441\u0435\u0440\u0430) \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043c\u044b \u0443\u043a\u0430\u0437\u0430\u043b\u0438 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0446\u0438\u043a\u043b \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0438 <code>KAFKA_BOOTSTRAP_SERVERS<\/code>.<\/p>\n<p>\u0412 \u043d\u0430\u0448\u0435\u043c \u043a\u043b\u0430\u0441\u0441\u0435-\u043e\u0431\u0435\u0440\u0442\u043a\u0435 \u043d\u0438\u0447\u0435\u0433\u043e \u0441\u043b\u043e\u0436\u043d\u043e\u0433\u043e \u043d\u0435\u0442. \u041c\u0435\u0442\u043e\u0434\u044b <code>start<\/code> \u0438 <code>stop<\/code> \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0431\u043e\u043b\u043e\u0447\u043a\u0438 \u043d\u0430\u0434 \u043e\u0434\u043d\u043e\u0438\u043c\u0435\u043d\u043d\u044b\u043c\u0438 \u043c\u0435\u0442\u043e\u0434\u0430\u043c\u0438 \u043a\u043b\u0430\u0441\u0441\u0430 <code>AIOKafkaProducer<\/code><em>.<\/em> <code>start<\/code> &#8212; \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043e\u043c Kafka, <code>stop<\/code> &#8212; \u043e\u0447\u0438\u0449\u0430\u0435\u0442 \u0432\u0441\u0435 \u043e\u0436\u0438\u0434\u0430\u044e\u0449\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435.<\/p>\n<p>\u041c\u0435\u0442\u043e\u0434 <code>send<\/code> &#8212; \u0442\u0430\u043a\u0430\u044f \u0436\u0435 \u043d\u0430\u0434\u0441\u0442\u0440\u043e\u0439\u043a\u0430, \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u043d\u0430 \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u0438\u0440\u0443\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0435\u0434 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u043e\u0439 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e \u043d\u0443\u0436\u043d\u043e\u043c\u0443 \u0442\u043e\u043f\u0438\u043a\u0443. \u0422\u0430\u043a\u0436\u0435 \u0441\u0442\u043e\u0438\u0442 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0432 \u0432\u0438\u0434\u0435 \u0431\u0430\u0439\u0442\u043e\u0432. \u042d\u0442\u043e \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0438\u0437 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u043f\u0430\u043a\u0435\u0442\u0430 \u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441\u0430\u043c\u043e\u0439 Kafka.<\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f <code>get_producer<\/code> \u043d\u0443\u0436\u043d\u0430 \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u044a\u0435\u043a\u0446\u0438\u0438 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u0430 \u0432 \u043d\u0430\u0448 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 end-point. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043a\u0430\u043a \u0440\u0430\u0437 \u044d\u0442\u0438\u043c \u0438 \u0437\u0430\u0439\u043c\u0435\u043c\u0441\u044f:<\/p>\n<pre><code class=\"python\"># web\/app\/main.py from __future__ import annotations from typing import TYPE_CHECKING from fastapi import FastAPI, Depends from web.app.schemas import Message from web.app.producer import get_producer import json  if TYPE_CHECKING:     from web.app.producer import AIOWebProducer   app = FastAPI()   @app.post(\"\/currency-info\") async def send(message: Message, producer: AIOWebProducer = Depends(get_producer)) -> None:     message_to_produce = json.dumps(message.model_dump()).encode(encoding=\"utf-8\")     await producer.send(value=message_to_produce)<\/code><\/pre>\n<p>\u0412 \u0446\u0435\u043b\u043e\u043c, \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c, \u043a\u043e\u0434\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0435\u043c\u043d\u043e\u0433\u043e (2 \u0441\u0442\u0440\u043e\u0447\u043a\u0438). \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u044b \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f (\u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u043c \u0435\u0433\u043e \u0432 \u0431\u0430\u0439\u0442\u044b), \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 \u0431\u0430\u0439\u0442\u043e\u0432 \u0432 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044b\u0439 \u0442\u043e\u043f\u0438\u043a \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043c\u0435\u0442\u043e\u0434\u0430 send. <strong>\u0412\u0441\u0435.<\/strong><\/p>\n<p>\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435, \u0447\u0442\u043e \u043d\u0430\u043c \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430 &#8212; \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0434\u043b\u044f \u043d\u0435\u0433\u043e Dockerfile:<\/p>\n<pre><code class=\"python\"># services\/web\/Dockerfile  FROM python:3.11-slim-buster  WORKDIR \/usr\/src\/web  ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 ENV PYTHONPATH=\/usr\/src  COPY poetry.lock pyproject.toml .\/  RUN pip install poetry &amp;&amp; poetry config virtualenvs.create false &amp;&amp; poetry install --no-root  COPY app app\/  WORKDIR .\/app<\/code><\/pre>\n<p><a class=\"anchor\" name=\"5\" id=\"5\"><\/a><\/p>\n<h2>Currency \u0441\u0435\u0440\u0432\u0438\u0441 (Scrapy)<\/h2>\n<p>\u041a\u0430\u043a \u0438 \u0434\u043e \u044d\u0442\u043e\u0433\u043e \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435 \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438:<\/p>\n<pre><code class=\"bash\">cd services\/currency &amp;&amp; poetry init poetry add scrapy redis aiokafka<\/code><\/pre>\n<p>\u0412 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u043c\u044b <strong>\u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c<\/strong> \u043f\u0430\u043f\u043a\u0443 app, \u0442\u0430\u043a \u043a\u0430\u043a <strong>Scrapy \u0441\u0434\u0435\u043b\u0430\u0435\u0442 \u044d\u0442\u043e \u0437\u0430 \u043d\u0430\u0441 \u0438 \u0434\u0430\u0436\u0435 \u043b\u0443\u0447\u0448\u0435.<\/strong> \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442 \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u044b:<\/p>\n<pre><code class=\"bash\">scrapy startproject currency<\/code><\/pre>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0438\u0437\u0431\u0430\u0432\u0438\u043c\u0441\u044f \u043e\u0442 \u043b\u0438\u0448\u043d\u0435\u0439 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u043f\u0430\u043f\u043e\u043a currency, \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0440\u0430\u0441\u043a\u0440\u043e\u0435\u043c \u043c\u0430\u0442\u0440\u0435\u0448\u043a\u0443 \u0438 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043c \u0435\u0435 \u043a \u0442\u0430\u043a\u043e\u043c\u0443 \u0432\u0438\u0434\u0443:<\/p>\n<figure class=\"\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/a15\/78e\/c4c\/a1578ec4cf27155a3caa80039823d56d.png\" alt=\"\u0420\u0438\u0441. 2. \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 Scrapy \u0441\u0435\u0440\u0432\u0438\u0441\u0430\" title=\"\u0420\u0438\u0441. 2. \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 Scrapy \u0441\u0435\u0440\u0432\u0438\u0441\u0430\" width=\"245\" height=\"317\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/a15\/78e\/c4c\/a1578ec4cf27155a3caa80039823d56d.png\"\/><\/p>\n<div><figcaption>\u0420\u0438\u0441. 2. \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 Scrapy \u0441\u0435\u0440\u0432\u0438\u0441\u0430<\/figcaption><\/div>\n<\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0431\u0443\u0434\u0435\u0442 \u043f\u0430\u0440\u0441\u0438\u0442\u044c\u0441\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0438 \u043a\u0443\u0434\u0430 \u043e\u043d\u0430 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043f\u0430\u0434\u0430\u0442\u044c. \u0418\u0442\u0430\u043a, \u0432 Scrapy \u0435\u0441\u0442\u044c &#171;\u043f\u0430\u0443\u043a\u0438&#187;, \u0441\u0443\u0442\u044c \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043a\u0430\u043a \u0440\u0430\u0437 \u043f\u0430\u0440\u0441\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u0447\u0435\u0433\u043e-\u043b\u0438\u0431\u043e. \u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u0434\u0430\u043d\u043d\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u044b, \u043c\u044b \u043f\u043e\u043b\u043e\u0436\u0438\u043c \u0438\u0445 \u0432 Redis, \u0447\u0442\u043e\u0431\u044b \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0445\u043e\u0434\u0438\u0442\u044c \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442. \u0421\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0438\u0440\u0443\u0435\u043c \u0431\u0430\u0437\u043e\u0432\u0443\u044e \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0442\u0430\u0446\u0438\u044e &#171;\u043f\u0430\u0443\u043a\u0430&#187;, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u0432\u0435\u0434\u0435\u043c:<\/p>\n<pre><code class=\"bash\">scrapy genspider currency_v1 https:\/\/www.cbr.ru\/scripts\/xml_daily.asp<\/code><\/pre>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0432 \u043f\u0430\u043f\u043a\u0435 spiders \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u043b \u043f\u043e\u044f\u0432\u0438\u0442\u044c\u0441\u044f \u043a\u0430\u0440\u043a\u0430\u0441 \u043d\u0430\u0448\u0435\u0433\u043e \u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e &#171;\u043f\u0430\u0443\u043a\u0430&#187;. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u0435\u0433\u043e \u043a\u043e\u0434:<\/p>\n<pre><code class=\"python\"># services\/currency\/currency\/spiders\/currency_v1.py import scrapy   class CurrencyV1Spider(scrapy.Spider):     name = \"currency_v1\"     allowed_domains = [\"www.cbr.ru\"]     start_urls = [\"https:\/\/www.cbr.ru\/scripts\/xml_daily.asp\"]      def parse(self, response, **kwargs) -> None:         currencies = response.xpath(\"\/\/ValCurs\/\/Valute\")         for currency in currencies:             print(currency)<\/code><\/pre>\n<p>\u0418\u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e &#171;\u043f\u0430\u0443\u043a&#187; \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0447\u0442\u043e-\u043b\u0438\u0431\u043e \u043f\u0430\u0440\u0441\u0438\u0442, \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0435\u0433\u043e \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u0438\u0448\u0435\u043c \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c:<\/p>\n<pre><code class=\"bash\">scrapy crawl currency_v1 <\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0432 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u0435 \u043f\u043e\u0441\u044b\u043f\u0430\u043b\u043e\u0441\u044c \u043a\u0443\u0447\u0443 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0432\u0438\u0434\u0430 <code>&lt;Selector query='\/\/ValCurs\/\/Valute'...<\/code><em> <\/em>&#8212; \u043e\u0442\u043b\u0438\u0447\u043d\u043e, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c Redis, \u0447\u0442\u043e\u0431\u044b \u044d\u0442\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0433\u0434\u0435-\u0442\u043e \u0445\u0440\u0430\u043d\u0438\u043b\u0430\u0441\u044c. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e, \u0432\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u043e\u0431\u043d\u043e\u0432\u0438\u043c .env:<\/p>\n<pre><code class=\"bash\">KAFKA_BOOTSTRAP_SERVERS=kafka:9092  WEB_TOPIC=web  REDIS_HOST=redis  REDIS_PORT=6379<\/code><\/pre>\n<p>\u0410, \u0432\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0448 \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u043b\u0441\u044f \u043a Redis:<\/p>\n<pre><code class=\"python\"># services\/currency\/currency\/settings.py import os  BOT_NAME = \"currency\" SPIDER_MODULES = [\"currency.spiders\"] NEWSPIDER_MODULE = \"currency.spiders\" ROBOTSTXT_OBEY = True REQUEST_FINGERPRINTER_IMPLEMENTATION = \"2.7\" TWISTED_REACTOR = \"twisted.internet.asyncioreactor.AsyncioSelectorReactor\" FEED_EXPORT_ENCODING = \"utf-8\"  # --- REDIS SETTINGS  CURRENCY_REDIS_CACHE_TIME_IN_SECONDS = 60  REDIS_HOST = os.getenv(\"REDIS_HOST\", \"redis\") REDIS_PORT = os.getenv(\"REDIS_PORT\", \"6379\") REDIS_URL = f\"redis:\/\/{REDIS_HOST}:{REDIS_PORT}\"<\/code><\/pre>\n<p>\u042f \u0443\u0434\u0430\u043b\u0438\u043b \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u043e \u0432\u0435\u0441\u044c \u0437\u0430\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u0441\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u043c \u043a\u043e\u0434 \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043b \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0434\u043b\u044f \u043d\u0430\u0448\u0435\u0433\u043e NoSQL \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430. \u0422\u0435\u043f\u0435\u0440\u044c \u0432 \u043c\u0435\u0442\u043e\u0434\u0435 <code>parse<\/code> \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0430\u0443\u043a\u0430 \u0431\u0443\u0434\u0435\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u043f\u043e\u0441\u0442\u0443\u043f\u0438\u0432\u0448\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u0432 Redis:<\/p>\n<pre><code class=\"python\"># services\/currency\/currency\/spiders\/currency_v1.py from currency.currency import settings import scrapy import redis import json   class CurrencyV1Spider(scrapy.Spider):     name = \"currency_v1\"     allowed_domains = [\"www.cbr.ru\"]     start_urls = [\"https:\/\/www.cbr.ru\/scripts\/xml_daily.asp\"]      def parse(self, response, **kwargs) -> None:         currencies = response.xpath(\"\/\/ValCurs\/\/Valute\")         with redis.from_url(url=settings.REDIS_URL) as redis_client:             for currency in currencies:                 redis_client.set(                     name=self.get_currency_redis_key(currency),                     value=self.get_currency_redis_value(currency),                     ex=settings.CURRENCY_REDIS_CACHE_TIME_IN_SECONDS,                 )      @staticmethod     def get_currency_redis_key(selector) -> str:         return selector.xpath(\".\/\/CharCode\/\/text()\").get()      @staticmethod     def get_currency_redis_value(selector) -> bytes:         return json.dumps(             {                 \"currency_value\": selector.xpath(\".\/\/Value\/\/text()\").get()             }         ).encode(\"utf-8\")<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043a\u0430\u043a\u043e\u0439-\u043d\u0438\u0431\u0443\u0434\u044c \u043a\u043b\u0430\u0441\u0441-\u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043c\u044b \u0441\u043c\u043e\u0436\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043a\u0443\u0440\u0441 \u0432\u0430\u043b\u044e\u0442\u044b \u043f\u043e \u0435\u0435 \u043a\u043e\u0434\u0443:<\/p>\n<pre><code class=\"python\"># services\/currency\/currency\/controller.py from multiprocessing import Process, Queue from scrapy.crawler import CrawlerProcess from currency.currency import settings from currency.currency.spiders.currency_v1 import CurrencyV1Spider from redis import asyncio as aioredis import json   class CurrencyController(object):      def update_redis_cache(self) -> None:         queue = Queue()         process = Process(target=self._crawl_currency, args=(queue,))         process.start()         none_or_exception = queue.get()         process.join()         if none_or_exception is not None:             raise none_or_exception      @staticmethod     def _crawl_currency(queue: Queue) -> None:         try:             process = CrawlerProcess()             process.crawl(CurrencyV1Spider)             process.start()             queue.put(None)         except Exception as exc:             queue.put(exc)      async def get_currency_info(self, currency_char_code: str) -> dict:         if currency_info := await self.get_currency_from_redis(currency_char_code):             return currency_info         self.update_redis_cache()         return await self.get_currency_from_redis(currency_char_code)          @staticmethod     async def get_currency_from_redis(char_code: str) -> dict | None:         async with aioredis.from_url(settings.REDIS_URL) as redis_client:             if currency_info := await redis_client.get(name=char_code):                 return json.loads(currency_info)<\/code><\/pre>\n<p>\u041c\u043e\u0436\u043d\u043e \u043d\u0435 \u043e\u0431\u0440\u0430\u0449\u0430\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0442\u0430\u043d\u0446\u044b \u0441 \u0431\u0443\u0431\u043d\u043e\u043c \u0432\u043e\u043a\u0440\u0443\u0433 <code>multiprocessing<\/code>, \u043f\u0440\u043e\u0441\u0442\u043e Scrapy \u043d\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043f\u0430\u0443\u043a \u0432 \u0442\u043e\u043c \u0436\u0435 \u0441\u0430\u043c\u043e\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043d\u043e\u0432\u044b\u0439. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e (\u0438 \u0441\u043a\u043e\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043e) \u044d\u0442\u043e \u043d\u0435 \u0441\u0430\u043c\u043e\u0435 \u043b\u0443\u0447\u0448\u0435\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435, \u043e\u0434\u043d\u0430\u043a\u043e \u044f \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u044e \u0435\u0433\u043e \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0438 \u0434\u043b\u044f \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u0438\u044f. \u0418\u0442\u0430\u043a, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u043c\u0441\u044f \u043a \u0431\u0440\u043e\u043a\u0435\u0440\u0443.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0435 \u043d\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c \u043a\u0430\u043a \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c, \u0442\u0430\u043a \u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044c. \u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044c \u0431\u0443\u0434\u0435\u0442 \u0441\u043b\u0443\u0448\u0430\u0442\u044c \u043d\u0430 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0442 <strong>web <\/strong>\u0441\u0435\u0440\u0432\u0438\u0441\u0430, \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c <strong>notify <\/strong>\u0441\u0435\u0440\u0432\u0438\u0441\u0443. \u0414\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438:<\/p>\n<pre><code class=\"python\"># services\/currency\/currency\/settings.py # --- SCRAPY SETTINGS ...  # --- REDIS SETTINGS ...  # --- KAFKA SETTINGS  PRODUCE_TOPIC = os.getenv(\"CURRENCY_TOPIC\") CONSUME_TOPIC = os.getenv(\"WEB_TOPIC\") KAFKA_BOOTSTRAP_SERVERS = os.getenv(\"KAFKA_BOOTSTRAP_SERVERS\")<\/code><\/pre>\n<p>\u041d\u0435 \u0437\u0430\u0431\u044b\u0432\u0430\u0435\u043c \u043f\u0440\u043e <code>.env:<\/code><\/p>\n<pre><code class=\"bash\">KAFKA_BOOTSTRAP_SERVERS=kafka:9092  WEB_TOPIC=web  REDIS_HOST=redis  REDIS_PORT=6379  CURRENCY_TOPIC=currency<\/code><\/pre>\n<p>\u0418 \u043f\u0438\u0448\u0435\u043c \u043a\u043e\u0434 \u0432 <code>main.py<\/code> \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u043b\u0443\u0448\u0438\u0432\u0430\u043d\u0438\u044f \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u043e\u043c \u0431\u0440\u043e\u043a\u0435\u0440\u0430: <\/p>\n<pre><code class=\"python\"># services\/currency\/currency\/main.py  import json import asyncio  from aiokafka import AIOKafkaConsumer, AIOKafkaProducer from currency.currency import settings from currency.currency.controller import CurrencyController   async def start_consumer() -> AIOKafkaConsumer:     consumer = AIOKafkaConsumer(         settings.CONSUME_TOPIC,         bootstrap_servers=settings.KAFKA_BOOTSTRAP_SERVERS,     )     await consumer.start()     return consumer   async def start_producer() -> AIOKafkaProducer:     producer = AIOKafkaProducer(bootstrap_servers=settings.KAFKA_BOOTSTRAP_SERVERS)     await producer.start()     return producer   async def main() -> None:     controller = CurrencyController()     consumer = await start_consumer()     producer = await start_producer()     try:         async for message in consumer:             decoded_message = json.loads(message.value)             currency_info = await controller.get_currency_info(                 currency_char_code=decoded_message.get(\"currency_char_code\"),             ) or dict()             currency_info[\"telegram_id\"] = decoded_message[\"telegram_id\"]             info_to_send = json.dumps(currency_info).encode(encoding=\"utf-8\")             await producer.send(topic=settings.PRODUCE_TOPIC, value=info_to_send)     finally:         await consumer.stop()         await producer.stop()   if __name__ == '__main__':     asyncio.run(main()) <\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044f \u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u044f, \u0441\u043b\u0443\u0448\u0430\u0435\u043c \u043d\u0430 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f, \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u043d\u043e \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f &#8212; \u043f\u0430\u0440\u0441\u0438\u043c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u0432\u0430\u043b\u044e\u0442\u0435 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u0443\u0436\u0435 \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c \u0431\u043e\u0442\u0443. \u0415\u0449\u0435 \u0440\u0430\u0437: <strong>\u0441\u043b\u0443\u0448\u0430\u0435\u043c WEB_TOPIC, <\/strong>\u0430 <strong>\u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u043c \u0432 CURRENCY_TOPIC<\/strong> .<\/p>\n<p>\u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c <strong>Dockerfile<\/strong>:<\/p>\n<pre><code># services\/currency\/Dockerfile FROM python:3.11-slim-buster  WORKDIR \/usr\/src\/currency\/  ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 ENV PYTHONPATH=\/usr\/src  COPY poetry.lock pyproject.toml .\/  RUN pip install poetry &amp;&amp; poetry config virtualenvs.create false &amp;&amp; poetry install --no-root  COPY currency currency\/  WORKDIR .\/currency<\/code><\/pre>\n<p><a class=\"anchor\" name=\"6\" id=\"6\"><\/a><\/p>\n<h2>Notify \u0441\u0435\u0440\u0432\u0438\u0441 (\u0431\u043e\u0442)<\/h2>\n<p>\u0421\u0447\u0438\u0442\u0430\u044e \u043d\u0435\u0446\u0435\u043b\u0435\u0441\u043e\u043e\u0431\u0440\u0430\u0437\u043d\u043e \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0432 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435, \u043a\u0430\u043a \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0441\u0432\u043e\u0435\u0433\u043e \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c-\u0431\u043e\u0442\u0430. \u0412 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0435 \u043c\u0430\u0441\u0441\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043f\u043e \u044d\u0442\u043e\u0439 \u0442\u0435\u043c\u0435. \u041d\u0430\u0447\u0438\u043d\u0430\u0435\u043c \u043c\u044b \u0441 \u0442\u043e\u0433\u043e, <strong>\u0447\u0442\u043e \u0443 \u0432\u0430\u0441 \u0435\u0441\u0442\u044c \u0442\u043e\u043a\u0435\u043d \u0438 \u0432\u044b \u0437\u043d\u0430\u0435\u0442\u0435 \u0437\u0430\u0447\u0435\u043c \u043e\u043d \u043d\u0443\u0436\u0435\u043d.<\/strong> <\/p>\n<p>\u041a\u0430\u043a \u0438 \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0435 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c poetry:<\/p>\n<pre><code class=\"bash\">cd services\/notify &amp;&amp; poetry init<\/code><\/pre>\n<p>\u0418 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438:<\/p>\n<pre><code class=\"bash\">poetry add aiogram aiokafka<\/code><\/pre>\n<p>\u0421\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0432\u043d\u0443\u0442\u0440\u0438 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 notify \u043d\u043e\u0432\u0443\u044e \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e <code>app<\/code>, \u0430 \u0432 \u043d\u0435\u0439 <code>settings.py<\/code> \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u0442\u0443\u0434\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439 \u043a\u043e\u0434:<\/p>\n<pre><code class=\"python\">import os  BOT_TOKEN = os.getenv(\"BOT_TOKEN\")  CONSUME_TOPIC = os.getenv(\"CURRENCY_TOPIC\")  KAFKA_BOOTSTRAP_SERVERS = os.getenv(\"KAFKA_BOOTSTRAP_SERVERS\")<\/code><\/pre>\n<p>\u0418\u0437 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e\u0433\u043e \u0437\u0434\u0435\u0441\u044c \u0442\u043e, \u0447\u0442\u043e \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u043e\u0441\u043b\u0443\u0448\u0438\u0432\u0430\u0435\u043c\u043e\u0433\u043e \u0442\u043e\u043f\u0438\u043a\u0430, \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <code>CURRENCY_TOPIC<\/code>, \u043a\u0443\u0434\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043d\u0430\u0448 \u043c\u0438\u043a\u0440\u043e-\u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 <strong>Scrapy<\/strong>. \u0422\u0430\u043a\u0436\u0435 \u043d\u0435 \u0437\u0430\u0431\u0443\u0434\u044c\u0442\u0435 \u0432 <code>.env<\/code> \u0444\u0430\u0439\u043b \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e <code>BOT_TOKEN<\/code> \u0438 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0432 \u043d\u0435\u0439 \u0442\u043e\u043a\u0435\u043d \u0432\u0430\u0448\u0435\u0433\u043e \u0431\u043e\u0442\u0430. \u0424\u0430\u0439\u043b \u0441 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c\u0438 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f\u043c\u0438 \u043f\u043e \u0438\u0442\u043e\u0433\u0443 \u0434\u043e\u043b\u0436\u0435\u043d \u0432\u044b\u0433\u043b\u044f\u0434\u0435\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a:<\/p>\n<pre><code class=\"bash\">KAFKA_BOOTSTRAP_SERVERS=kafka:9092  WEB_TOPIC=web  REDIS_HOST=redis  REDIS_PORT=6379  CURRENCY_TOPIC=currency  BOT_TOKEN=#\u0442\u043e\u043a\u0435\u043d<\/code><\/pre>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0440\u044f\u0434\u043e\u043c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c <code>main.py,<\/code> \u0432 \u043d\u0435\u043c \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u043a \u0431\u043e\u0442\u0430 \u0438 \u043f\u0440\u043e\u0441\u043b\u0443\u0448\u0438\u0432\u0430\u043d\u0438\u0435 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439:<\/p>\n<pre><code class=\"python\">from aiogram import Bot from aiogram import Dispatcher from aiokafka import AIOKafkaConsumer from notify.app import settings  import asyncio import json  dispatcher = Dispatcher() BOT = Bot(token=settings.BOT_TOKEN)   async def consume() -> None:     consumer = AIOKafkaConsumer(         settings.CONSUME_TOPIC,         bootstrap_servers=settings.KAFKA_BOOTSTRAP_SERVERS,     )     await consumer.start()     try:         async for msg in consumer:             serialized = json.loads(msg.value)             await BOT.send_message(                 chat_id=serialized.get(\"telegram_id\"),                 text=serialized.get(\"currency_value\") or \"\u0412\u0430\u043b\u044e\u0442\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430\",             )     finally:         await consumer.stop()   async def main() -> None:     polling = asyncio.create_task(dispatcher.start_polling(BOT))     consuming = asyncio.create_task(consume())     await asyncio.gather(polling, consuming)     print(\"Bot has successfully started polling\")   if __name__ == \"__main__\":     asyncio.run(main())<\/code><\/pre>\n<p>\u0411\u0443\u0434\u0435\u043c \u043e\u0442\u0434\u0435\u043b\u044f\u0442\u044c \u043c\u0443\u0445 \u043e\u0442 \u043a\u043e\u0442\u043b\u0435\u0442 \u0438 \u043e\u0431\u0440\u0430\u0449\u0430\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u043a\u043e\u0434, \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0439 \u0441 \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u0435\u043d\u0438\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439. <\/p>\n<ol>\n<li>\n<p>\u041c\u044b \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u043a\u043b\u0430\u0441\u0441\u043e\u043c <code>AIOKafkaConsumer<\/code>, \u043a\u0443\u0434\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u043b\u0438 \u0442\u043e\u043f\u0438\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u0441\u043b\u0443\u0448\u0430\u0435\u043c, \u0438 <code>BOOTSTRAP_SERVERS<\/code>. <\/p>\n<\/li>\n<li>\n<p>\u0410\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f <code>consume<\/code> \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0433\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u043e \u0438\u0437 \u0442\u0440\u0435\u0445 \u0447\u0430\u0441\u0442\u0435\u0439: \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u043c &#8212; <code>await consumer.start()<\/code>, \u0441\u043b\u0443\u0448\u0430\u0435\u043c \u043d\u0430 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f &#8212; <code>async for msg in consumer<\/code>. \u0415\u0441\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0435\u0441\u0442\u044c &#8212; \u0434\u0435\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u0435\u0433\u043e \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0432 \u0435\u0433\u043e \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043c\u0435\u0442\u043e\u0434\u0430 <code>send_message<\/code>.<\/p>\n<\/li>\n<\/ol>\n<p>\u041a\u0430\u043a \u0432\u044b \u043f\u043e\u043d\u044f\u043b\u0438, \u0437\u0434\u0435\u0441\u044c \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u0435\u0442 &#171;\u043f\u0440\u043e\u0434\u044c\u044e\u0441\u0435\u0440&#187;, \u0442\u0430\u043a \u043a\u0430\u043a \u044d\u0442\u043e \u043a\u043e\u043d\u0435\u0447\u043d\u0430\u044f \u0442\u043e\u0447\u043a\u0430 \u043d\u0430\u0448\u0435\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b, \u043e\u0442\u043a\u0443\u0434\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u0441\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u0432 \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c \u0431\u043e\u0442.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043d\u0430\u043f\u0438\u0448\u0435\u043c \u0431\u043e\u0442\u0443 <strong>Dockerfile<\/strong>, \u043e\u043d \u043f\u043e\u0447\u0442\u0438 \u043d\u0438\u0447\u0435\u043c \u043d\u0435 \u043e\u0442\u043b\u0438\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u0433\u043e:<\/p>\n<pre><code class=\"python\"># services\/notify\/Dockerfile FROM python:3.11-slim-buster  WORKDIR \/usr\/src\/notify  ENV PYTHONDONTWRITEBYTECODE 1  ENV PYTHONUNBUFFERED 1  ENV PYTHONPATH=\/usr\/src  COPY poetry.lock pyproject.toml .\/  RUN pip install poetry &amp;&amp; poetry config virtualenvs.create false &amp;&amp; poetry install --no-root  COPY app app\/  WORKDIR .\/app<\/code><\/pre>\n<p><a class=\"anchor\" name=\"7\" id=\"7\"><\/a><\/p>\n<h3>\u0418\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430<\/h3>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0432\u0441\u0435 \u043d\u0430\u0448\u0438 \u0448\u0435\u0441\u0442\u0435\u0440\u0435\u043d\u043a\u0438, \u043e\u043f\u0438\u0448\u0435\u043c <code>docker-compose.yml<\/code>:<\/p>\n<details class=\"spoiler\">\n<summary>Hidden text<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"yaml\">version: '3.3'  services:   web:     restart: on-failure     build:       context: services\/web\/       dockerfile: Dockerfile     container_name: \"web\"     ports:       - \"8000:8000\"     environment:       KAFKA_BOOTSTRAP_SERVERS: $KAFKA_BOOTSTRAP_SERVERS       WEB_TOPIC: $WEB_TOPIC     command: uvicorn main:app --reload --host 0.0.0.0 --port 8000    currency:     restart: on-failure     build:       context: services\/currency\/       dockerfile: Dockerfile     container_name: \"currency\"     environment:       KAFKA_BOOTSTRAP_SERVERS: $KAFKA_BOOTSTRAP_SERVERS       WEB_TOPIC: $WEB_TOPIC       CURRENCY_TOPIC: $CURRENCY_TOPIC       REDIS_HOST: $REDIS_HOST       REDIS_PORT: $REDIS_PORT     command: python main.py    bot:     restart: on-failure     build:       context: services\/notify\/       dockerfile: Dockerfile     container_name: \"notify\"     environment:       KAFKA_BOOTSTRAP_SERVERS: $KAFKA_BOOTSTRAP_SERVERS       CURRENCY_TOPIC: $CURRENCY_TOPIC       BOT_TOKEN: $BOT_TOKEN     command: python main.py    zookeeper:     image: 'bitnami\/zookeeper:3.7.0'     container_name: zookeeper     ports:       - '2181:2181'     environment:       - ALLOW_ANONYMOUS_LOGIN=yes      kafka:     image: 'bitnami\/kafka:2.8.0'     container_name: kafka     ports:       - \"9093:9093\"     environment:       - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181       - ALLOW_PLAINTEXT_LISTENER=yes     depends_on:       - zookeeper    redis:     image: redis     container_name: redis     environment:       REDIS_HOST: REDIS_HOST     ports:       - \"6379:6379\" <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u041c\u043e\u043c\u0435\u043d\u0442 \u0438\u0441\u0442\u0438\u043d\u044b &#8212; <u>\u043f\u043e\u0434\u043d\u0438\u043c\u0430\u0435\u043c<\/u>:<\/p>\n<pre><code class=\"bash\">docker compose up -d --build<\/code><\/pre>\n<p>\u0417\u0430\u0440\u0430\u043d\u0435\u0435 \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0447\u0442\u043e \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 &#171;\u043e\u0442\u0432\u0430\u043b\u0438\u043b\u043e\u0441\u044c&#187;:<\/p>\n<pre><code>docker ps<\/code><\/pre>\n<p>\u0412 \u0432\u044b\u0432\u043e\u0434\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0448\u0435\u0441\u0442\u044c \u0440\u0430\u0431\u043e\u0447\u0438\u0445 \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u043e\u0432. \u0415\u0441\u043b\u0438 \u044d\u0442\u043e \u0442\u0430\u043a &#8212; \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u044b \u043d\u0430\u0447\u0430\u043b\u0438 \u0434\u0438\u0430\u043b\u043e\u0433 \u0441 \u0431\u043e\u0442\u043e\u043c \u0438, \u0435\u0441\u043b\u0438 \u043e\u043d\u043e \u0442\u043e\u0436\u0435 \u0442\u0430\u043a, \u0441\u043c\u0435\u043b\u043e \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043f\u043e <a href=\"http:\/\/127.0.0.1:8000\/docs\" rel=\"noopener noreferrer nofollow\">\u0430\u0434\u0440\u0435\u0441\u0443<\/a> \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c <strong>POST<\/strong>-\u0437\u0430\u043f\u0440\u043e\u0441  \u043d\u0430\u0448\u0435\u043c\u0443 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u043c\u0443 \u0435\u043d\u0434-\u043f\u043e\u0438\u043d\u0442\u0443, \u043a\u0443\u0434\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0441\u0432\u043e\u0439 <strong>\u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c ID<\/strong> \u0438, \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c, <strong>USD<\/strong>. \u041e\u0442\u0432\u0435\u0442\u043e\u043c \u043d\u0430 \u044d\u0442\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u0432 \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c\u0435 \u043e\u0442 \u043d\u0430\u0448\u0435\u0433\u043e \u0431\u043e\u0442\u0430 \u0441 \u043e\u0434\u0438\u043d\u043e\u043a\u0438\u043c <s>\u0438<\/s> <s>\u043d\u0435\u043f\u0440\u0438\u044f\u0442\u043d\u044b\u043c<\/s> \u0447\u0438\u0441\u043b\u043e\u043c.<\/p>\n<p><a class=\"anchor\" name=\"8\" id=\"8\"><\/a><\/p>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>\u041d\u0430\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u043a, \u0445\u043e\u0442\u0435\u043b\u043e\u0441\u044c \u0431\u044b \u0441\u043a\u0430\u0437\u0430\u0442\u044c, \u0447\u0442\u043e \u0432 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u043d\u043e \u043f\u0440\u043e\u043f\u0443\u0449\u0435\u043d\u044b \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b \u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 Kafka, \u0441 \u0435\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0432 <code>docker-compose.yml<\/code>. \u042f \u043d\u0435 \u0445\u043e\u0442\u0435\u043b \u043f\u0435\u0440\u0435\u0433\u0440\u0443\u0436\u0430\u0442\u044c \u0441\u0442\u0430\u0442\u044c\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439, \u0442\u0430\u043a \u043a\u0430\u043a, \u043f\u043e\u043b\u0430\u0433\u0430\u044e, \u0447\u0442\u043e \u0434\u043b\u044f \u0447\u0435\u043b\u043e\u0432\u0435\u043a\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u044d\u0442\u0438\u043c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u043c \u0432\u043f\u0435\u0440\u0432\u044b\u0435, <strong>\u0432\u0430\u0436\u043d\u0435\u0435 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c<\/strong> <strong>\u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043e\u043f\u044b\u0442<\/strong>.<\/p>\n<p>\u042f \u0431\u043b\u0430\u0433\u043e\u0434\u0430\u0440\u0435\u043d, \u0435\u0441\u043b\u0438 \u0432\u044b \u0434\u043e\u0447\u0438\u0442\u0430\u043b\u0438 \u0441\u0442\u0430\u0442\u044c\u044e \u0434\u043e \u044d\u0442\u043e\u0433\u043e \u043c\u043e\u043c\u0435\u043d\u0442\u0430. \u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u044f \u0441\u043c\u043e\u0433 \u0432 \u043f\u043e\u043d\u044f\u0442\u043d\u043e\u0439 \u0444\u043e\u0440\u043c\u0435 \u0440\u0430\u0441\u0441\u043a\u0430\u0437\u0430\u0442\u044c \u0438, \u0447\u0442\u043e \u0433\u043b\u0430\u0432\u043d\u043e\u0435, <strong>\u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c<\/strong>, \u043a\u0430\u043a \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0431\u0440\u043e\u043a\u0435\u0440 \u043d\u0430 \u043f\u043e\u0432\u0435\u0440\u0445\u043d\u043e\u0441\u0442\u043d\u043e\u043c \u0443\u0440\u043e\u0432\u043d\u0435, \u043d\u0435 \u0437\u0430\u043b\u0435\u0437\u0430\u044f \u0432 \u0434\u0435\u0431\u0440\u0438. \u0414\u043e \u043d\u043e\u0432\u044b\u0445 \u0432\u0441\u0442\u0440\u0435\u0447!<\/p>\n<\/p>\n<\/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\/793936\/\"> https:\/\/habr.com\/ru\/articles\/793936\/<\/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-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043d\u0430\u044f \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430, \u0447\u0442\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f &#171;\u043d\u0430 \u0445\u0430\u0439\u043f\u0435&#187;. \u042f \u043f\u0435\u0440\u0435\u0447\u0438\u0442\u0430\u043b \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043c\u043d\u043e\u0433\u043e \u0441\u0442\u0430\u0442\u0435\u0439 \u043f\u043e \u0434\u0430\u043d\u043d\u043e\u0439 \u0442\u0435\u043c\u0430\u0442\u0438\u043a\u0435, \u043d\u043e \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u043b, \u0447\u0442\u043e \u0441\u0440\u0435\u0434\u0438 \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u043e\u0447\u0435\u0433\u043e, \u043d\u0435 \u0442\u0430\u043a \u043c\u043d\u043e\u0433\u043e \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0439, \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u0443\u044e \u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u044e \u043d\u0430 <strong>\u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c<\/strong> \u043f\u0440\u0438\u043c\u0435\u0440\u0435 (\u043c\u043e\u0436\u0435\u0442, \u043f\u043b\u043e\u0445\u043e \u0438\u0441\u043a\u0430\u043b). \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u044f \u0431\u044b \u0445\u043e\u0442\u0435\u043b \u043f\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0440\u044f\u0434\u044b \u0430\u0432\u0442\u043e\u0440\u043e\u0432 \u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0441\u0432\u043e\u044e \u043f\u0435\u0440\u0432\u0443\u044e \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044e, <s>\u043d\u0435<\/s> \u0441\u0443\u0434\u0438\u0442\u0435 \u0441\u0442\u0440\u043e\u0433\u043e!<\/p>\n<h3>\u041e\u0433\u043b\u0430\u0432\u043b\u0435\u043d\u0438\u0435<\/h3>\n<ol>\n<li>\n<p><a href=\"#1\" rel=\"noopener noreferrer nofollow\">\u0414\u043b\u044f\u00a0\u043a\u043e\u0433\u043e \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f?<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#2\" rel=\"noopener noreferrer nofollow\">\u041a\u0440\u0430\u0442\u043a\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#3\" rel=\"noopener noreferrer nofollow\">\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#4\" rel=\"noopener noreferrer nofollow\">FASTApi \u0441\u0435\u0440\u0432\u0438\u0441 (web)<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#5\" rel=\"noopener noreferrer nofollow\">Currency \u0441\u0435\u0440\u0432\u0438\u0441 (Scrapy)<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#6\" rel=\"noopener noreferrer nofollow\">Notify \u0441\u0435\u0440\u0432\u0438\u0441 (\u0431\u043e\u0442)<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#7\" rel=\"noopener noreferrer nofollow\">\u0418\u043d\u0444\u0440\u0430\u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430<\/a><\/p>\n<\/li>\n<li>\n<p><a href=\"#8\" rel=\"noopener noreferrer nofollow\">\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/a><\/p>\n<\/li>\n<\/ol>\n<p><a class=\"anchor\" name=\"1\" id=\"1\"><\/a><\/p>\n<h2>\u0414\u043b\u044f \u043a\u043e\u0433\u043e \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f?<\/h2>\n<p>\u042f \u043d\u0430\u0434\u0435\u044e\u0441\u044c, \u0447\u0442\u043e \u044d\u0442\u0430 \u0441\u0442\u0430\u0442\u044c\u044f \u0441\u0442\u0430\u043d\u0435\u0442 \u043e\u0442\u043b\u0438\u0447\u043d\u044b\u043c \u0438 \u043f\u043e\u043d\u044f\u0442\u043d\u044b\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u043c \u0434\u043b\u044f \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430, \u0442\u043e\u043b\u044c\u043a\u043e <strong>\u043d\u0430\u0447\u0438\u043d\u0430\u044e\u0449\u0435\u0433\u043e<\/strong> \u0441\u0432\u043e\u0439 \u043f\u0443\u0442\u044c \u0432 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430\u0445. \u041c\u043d\u043e\u0433\u0438\u0435 \u0430\u0441\u043f\u0435\u043a\u0442\u044b \u0434\u0430\u043d\u043d\u043e\u0439 \u043a\u043e\u043d\u0446\u0435\u043f\u0446\u0438\u0438 \u0442\u0440\u0435\u0431\u0443\u044e\u0442 \u0441\u0431\u043e\u0440\u0430 \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0438\u0437 \u0440\u0430\u0437\u043d\u044b\u0445 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u0432. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043f\u043e\u043f\u0443\u0442\u043d\u043e \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u044e\u0449\u0438\u0435 \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u0447\u0430\u0441\u0442\u043e \u0441\u0431\u0438\u0432\u0430\u044e\u0442 \u0441 \u0442\u043e\u043b\u043a\u0443 \u0438 \u043e\u0442\u0432\u043b\u0435\u043a\u0430\u044e\u0442 \u043e\u0442 <strong>\u043f\u0435\u0440\u0432\u043e\u0439 \u043a\u043e\u043d\u0435\u0447\u043d\u043e\u0439 \u0446\u0435\u043b\u0438<\/strong> &#8212; &#171;\u043f\u043e\u0449\u0443\u043f\u0430\u0442\u044c&#187; \u0441\u0432\u043e\u0438\u043c\u0438 \u0440\u0443\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b\u0445 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439, \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u043d\u0438\u043c\u0438 \u0440\u0430\u0431\u043e\u0442\u0443. \u041a\u043e\u0433\u0434\u0430 \u043d\u0435\u043a\u043e\u0435 \u044f\u0434\u0440\u043e \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0431\u0443\u0434\u0435\u0442 \u0441\u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u043e, \u043f\u043e\u043b\u0430\u0433\u0430\u044e, <strong>\u0432\u044b \u0432\u0435\u0440\u043d\u0435\u0442\u0435\u0441\u044c \u043a \u0431\u043e\u043b\u0435\u0435 \u0443\u0437\u043a\u0438\u043c \u0438 \u0441\u043f\u0435\u0446\u0438\u0444\u0438\u0447\u0435\u0441\u043a\u0438\u043c \u0432\u043e\u043f\u0440\u043e\u0441\u0430\u043c \u0432 \u044d\u0442\u043e\u0439 \u0442\u0435\u043c\u0435, \u0438 \u043f\u043e\u043d\u044f\u0442\u044c \u0438\u0445 \u0432\u0430\u043c \u0441\u0442\u0430\u043d\u0435\u0442 \u043b\u0435\u0433\u0447\u0435.<\/strong> <\/p>\n<p><a class=\"anchor\" name=\"2\" id=\"2\"><\/a><\/p>\n<h2>\u041a\u0440\u0430\u0442\u043a\u043e\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435<\/h2>\n<p>\u0412\u0430\u0448\u0435\u043c\u0443 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u044e \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u0430, \u0441\u043e\u0441\u0442\u043e\u044f\u0449\u0430\u044f \u0438\u0437 3-\u0435\u0445 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432:<\/p>\n<ul>\n<li>\n<p>\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 <strong>FASTApi<\/strong>;<\/p>\n<\/li>\n<li>\n<p>\u0412\u0435\u0431-\u043f\u0430\u0440\u0441\u0435\u0440 \u043d\u0430 <strong>Scrapy<\/strong>;<\/p>\n<\/li>\n<li>\n<p>\u0422\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c \u0431\u043e\u0442 \u043d\u0430 <strong>Aiogram<\/strong>;<\/p>\n<\/li>\n<\/ul>\n<p>\u0427\u0442\u043e \u044d\u0442\u0430 &#171;\u0441\u0438\u0441\u0442\u0435\u043c\u0430&#187; \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u0434\u0435\u043b\u0430\u0442\u044c? \u0412\u0441\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e &#8212; \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c end-point, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0442\u044c \u0432 \u0441\u0435\u0431\u044f \u0434\u0432\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 &#8212; <strong>\u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c ID<\/strong> \u0438 <strong>\u043a\u043e\u0434 \u0432\u0430\u043b\u044e\u0442\u044b<\/strong>. \u0414\u0430\u043b\u0435\u0435 \u044d\u0442\u0438 \u0434\u0430\u043d\u043d\u044b\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u044f\u0442\u0441\u044f \u0432 \u043f\u0430\u0440\u0441\u0435\u0440 (Scrapy), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u043e\u043c\u0443 \u043a\u043e\u0434\u0443 \u0432\u0430\u043b\u044e\u0442\u044b, \u0443\u0437\u043d\u0430\u0435\u0442 \u0435\u0435 \u043a\u0443\u0440\u0441 \u0432 \u0440\u0443\u0431\u043b\u044f\u0445. \u041d\u0430\u043f\u043e\u0441\u043b\u0435\u0434\u043e\u043a, \u0441\u043e\u0431\u0440\u0430\u043d\u043d\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043f\u0440\u0438\u043c\u0435\u0442 \u0431\u043e\u0442 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e. \u0427\u0442\u043e\u0431\u044b \u0441\u0442\u0430\u043b\u043e \u0435\u0449\u0435 \u043f\u043e\u043d\u044f\u0442\u043d\u0435\u0435, \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u0440\u0438\u0441. 1: <\/p>\n<figure class=\"full-width\">\n<div><figcaption>\u0420\u0438\u0441. 1. \u0421\u0445\u0435\u043c\u0430 \u0440\u0430\u0431\u043e\u0442\u044b \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432<\/figcaption><\/div>\n<\/figure>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u044f\u0437\u044b\u043a\u0430 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Python, \u0442\u0430\u043a\u0436\u0435 \u043d\u0430\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f Redis, Kafka \u0438 Zookeper. \u0427\u0442\u043e\u0431\u044b &#171;\u043f\u043e\u0434\u043d\u044f\u0442\u044c&#187; \u0432\u0441\u0435 \u044d\u0442\u043e <s>\u0437\u043b\u043e<\/s> \u0434\u043e\u0431\u0440\u043e, \u043f\u0440\u0438\u0431\u0435\u0433\u043d\u0435\u043c \u043a Docker-\u0443. <\/p>\n<p><a class=\"anchor\" name=\"3\" id=\"3\"><\/a><\/p>\n<h2>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430<\/h2>\n<p>\u0414\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u0447\u0438\u0442\u0430\u0442\u0435\u043b\u044c \u043d\u0435 &#171;\u0437\u0430\u0431\u0443\u043a\u0441\u043e\u0432\u0430\u043b&#187; \u0432 \u043f\u043e\u043f\u044b\u0442\u043a\u0435 \u043f\u043e\u043d\u044f\u0442\u044c \u0440\u0430\u0441\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0442\u043e\u0433\u043e \u0438\u043b\u0438 \u0438\u043d\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430 \u0438\u043b\u0438 \u043a\u0443\u0441\u043a\u0430 \u043a\u043e\u0434\u0430, \u0441\u0447\u0438\u0442\u0430\u044e \u043d\u0443\u0436\u043d\u044b\u043c \u043f\u043e\u044f\u0441\u043d\u0438\u0442\u044c \u043a\u0430\u043a \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c\u0441\u044f \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u0438 \u043f\u0430\u043f\u043e\u043a:<\/p>\n<p>MSA\/<\/p>\n<p>    docker\u2011compose.yml;<\/p>\n<p>    .env;<\/p>\n<p>    services\/ (\u0432 \u043d\u0435\u0439 \u0431\u0443\u0434\u0443\u0442 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0432\u0441\u0435 \u043d\u0430\u0448\u0438 \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u044b);<\/p>\n<p>        currency\/ (\u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 Scrapy, \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0437\u0430 \u043f\u0430\u0440\u0441\u0438\u043d\u0433 \u0432\u0430\u043b\u044e\u0442);<\/p>\n<p>        web\/ (\u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0430 FASTApi, \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0437\u0430 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0432\u0445\u043e\u0434\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445);<\/p>\n<p>        notify\/ (\u0441\u0435\u0440\u0432\u0438\u0441 \u0441 \u0431\u043e\u0442\u043e\u043c, \u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0437\u0430 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0443 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e);<\/p>\n<p>\u041a\u043e\u043d\u0435\u0447\u043d\u043e, \u0443 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0441\u0432\u043e\u0439 .env \u0444\u0430\u0439\u043b. \u041e\u0434\u043d\u0430\u043a\u043e \u0437\u0434\u0435\u0441\u044c \u043c\u044b \u0443\u043c\u044b\u0448\u043b\u0435\u043d\u043d\u043e <s>\u0441\u043e\u0433\u0440\u0435\u0448\u0438\u043c<\/s> \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u043c \u0438 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043e\u0434\u0438\u043d \u043e\u0431\u0449\u0438\u0439 \u0434\u043b\u044f \u0432\u0441\u0435\u0445. <strong>\u0422\u0430\u043a\u0436\u0435 \u043d\u0430 \u043f\u0435\u0440\u0432\u043e\u0439 \u0441\u0442\u0440\u043e\u0447\u043a\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0431\u043b\u043e\u043a\u0430 \u043a\u043e\u0434\u0430, \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c\u0441\u044f \u043f\u0443\u0442\u044c \u043a \u044d\u0442\u043e\u043c\u0443 \u0444\u0430\u0439\u043b\u0443. <\/strong><\/p>\n<details class=\"spoiler\">\n<summary>Hidden text<\/summary>\n<div class=\"spoiler__content\">\n<p>\u0412\u0435\u0441\u044c \u043a\u043e\u0434 \u0434\u043b\u044f \u0438\u0437\u0443\u0447\u0435\u043d\u0438\u044f\/\u043a\u043b\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u0432 <a href=\"https:\/\/github.com\/youngwishes\/MSA\" rel=\"noopener noreferrer nofollow\">\u044d\u0442\u043e\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438.<\/a> \u041e\u0434\u043d\u0430\u043a\u043e \u044f \u043d\u0430\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e <strong>\u043f\u0440\u043e\u043f\u0438\u0441\u0430\u0442\u044c \u0432\u0441\u0435 \u0441\u0432\u043e\u0438\u043c\u0438 \u0440\u0443\u043a\u0430\u043c\u0438.<\/strong><\/p>\n<\/div>\n<\/details>\n<p><a class=\"anchor\" name=\"4\" id=\"4\"><\/a><\/p>\n<h2>FASTApi \u0441\u0435\u0440\u0432\u0438\u0441 (web)<\/h2>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u0433\u043b\u0430\u0432\u043d\u043e\u0433\u043e &#8212; \u0442\u043e\u0447\u043a\u0438 \u0432\u0445\u043e\u0434\u0430 \u0432 \u043d\u0430\u0448\u0443 \u0441\u0438\u0441\u0442\u0435\u043c\u0443. \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438, \u0438\u0445, \u043a\u0441\u0442\u0430\u0442\u0438, \u0431\u0443\u0434\u0435\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u044c \u0432 <strong>poetry. <\/strong>\u0412 <strong>web <\/strong>\u0441\u0435\u0440\u0432\u0438\u0441\u0435 \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435.<\/p>\n<pre><code class=\"bash\">cd services\/web &amp;&amp; poetry init<\/code><\/pre>\n<p>\u0418 \u0441\u0440\u0430\u0437\u0443 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438<\/p>\n<pre><code class=\"bash\">poetry add fastapi uvicorn aiokafka<\/code><\/pre>\n<p>\u041a\u0430\u043a \u0443\u0436\u0435 \u0433\u043e\u0432\u043e\u0440\u0438\u043b\u043e\u0441\u044c \u0440\u0430\u043d\u043d\u0435\u0435 &#8212; \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Apache Kafka \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0431\u0440\u043e\u043a\u0435\u0440\u0430 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439. \u0412 Python \u0435\u0441\u0442\u044c \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u043f\u0430\u043a\u0435\u0442 &#8212; <strong>aiokafka<\/strong> &#8212; \u0438\u043c \u043c\u044b \u0438 \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f. <\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c &#171;\u043a\u0443\u0447\u0438-\u043c\u0430\u043b\u044b&#187; \u0438 \u043d\u0435 \u043c\u0435\u0448\u0430\u0442\u044c <code>.py<\/code> \u0444\u0430\u0439\u043b\u044b \u0441 \u0434\u0440\u0443\u0433\u0438\u043c\u0438 &#8212; \u0432\u043d\u0443\u0442\u0440\u0438 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0438 <strong>web <\/strong>\u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0435\u0449\u0435 \u043e\u0434\u043d\u0443 \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e <strong>app <\/strong>&#8212; \u0438 \u0432 \u043d\u0435\u0439 \u0443\u0436\u0435 \u0431\u0443\u0434\u0435\u043c \u0440\u0430\u0441\u043f\u043e\u043b\u0430\u0433\u0430\u0442\u044c \u0432\u0435\u0441\u044c \u043a\u043e\u0434.<\/p>\n<p>\u041d\u0430\u0447\u043d\u0435\u043c \u0441 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u0441\u0445\u0435\u043c, \u0437\u0434\u0435\u0441\u044c \u043d\u0438\u0447\u0435\u0433\u043e \u0441\u043b\u043e\u0436\u043d\u043e\u0433\u043e, \u043c\u044b \u043e\u0436\u0438\u0434\u0430\u0435\u043c \u0432\u0441\u0435\u0433\u043e \u0434\u0432\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430:<\/p>\n<pre><code class=\"python\"># services\/web\/app\/schemas.py from pydantic import BaseModel   class Message(BaseModel):     currency_char_code: str     telegram_id: int<\/code><\/pre>\n<p>\u041e\u043f\u0438\u0448\u0435\u043c \u043d\u0443\u0436\u043d\u044b\u0439 \u043d\u0430\u043c end-point:<\/p>\n<pre><code class=\"python\"># services\/web\/app\/main.py from fastapi import FastAPI from web.schemas import Message   app = FastAPI()   @app.post(\"\/currency-info\") async def send(message: Message):     return message.model_dump()<\/code><\/pre>\n<p>\u041f\u043e\u043a\u0430 \u0447\u0442\u043e \u0431\u0443\u0434\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0442\u044c \u0442\u043e, \u0447\u0442\u043e \u043f\u0435\u0440\u0435\u0434\u0430\u043b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c, \u0430 \u043b\u043e\u0433\u0438\u043a\u0443 \u0441 \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u043c \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043f\u043e\u0437\u0436\u0435. \u0414\u0430\u043b\u0435\u0435 \u0443\u043a\u0430\u0436\u0435\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043b\u044f \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u043c:<\/p>\n<pre><code class=\"python\"># services\/web\/app\/settings.py import os  KAFKA_BOOTSTRAP_SERVERS = os.getenv(\"KAFKA_BOOTSTRAP_SERVERS\")  PRODUCE_TOPIC = os.getenv(\"WEB_TOPIC\")<\/code><\/pre>\n<pre><code class=\"bash\"># .env KAFKA_BOOTSTRAP_SERVERS=kafka:9092  WEB_TOPIC=web<\/code><\/pre>\n<p><strong>WEB_TOPIC<\/strong> &#8212; \u0442\u043e\u043f\u0438\u043a, \u043a\u0443\u0434\u0430 \u043d\u0430\u0448 web-\u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u0443\u0434\u0435\u0442 <strong>\u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c<\/strong> \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f. \u0412 \u0431\u0443\u0434\u0443\u0449\u0435\u043c Scrapy \u0441\u0435\u0440\u0432\u0438\u0441 \u0431\u0443\u0434\u0435\u0442 \u044d\u0442\u043e\u0442 \u0442\u043e\u043f\u0438\u043a <strong>\u0441\u043b\u0443\u0448\u0430\u0442\u044c.<\/strong><\/p>\n<p><strong>KAFKA_BOOTSTRAP_SERVERS<\/strong> &#8212; \u0430\u0434\u0440\u0435\u0441 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0431\u0440\u043e\u043a\u0435\u0440\u0430 (\u0438\u0445 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e, \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u0441\u0435\u0433\u043e \u043e\u0434\u0438\u043d).<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044e \u043a\u043b\u0430\u0441\u0441\u0430-\u043e\u0431\u043e\u043b\u043e\u0447\u043a\u0438 \u043d\u0430\u0434 \u043d\u0430\u0448\u0438\u043c &#171;\u043f\u0440\u043e\u0434\u044c\u044e\u0441\u0435\u0440\u043e\u043c&#187;:<\/p>\n<pre><code class=\"python\"># services\/web\/app\/producer.py from aiokafka import AIOKafkaProducer from web.app import settings import asyncio  event_loop = asyncio.get_event_loop()   class AIOWebProducer(object):     def __init__(self):         self.__producer = AIOKafkaProducer(             bootstrap_servers=settings.KAFKA_BOOTSTRAP_SERVERS,             loop=event_loop,         )         self.__produce_topic = settings.PRODUCE_TOPIC      async def start(self) -> None:         await self.__producer.start()      async def stop(self) -> None:         await self.__producer.stop()      async def send(self, value: bytes) -> None:         await self.start()         try:             await self.__producer.send(                 topic=self.__produce_topic,                 value=value,             )         finally:             await self.stop()   def get_producer() -> AIOWebProducer:     return AIOWebProducer()<\/code><\/pre>\n<p>\u041a\u0442\u043e \u0442\u0430\u043a\u043e\u0439 &#171;\u043f\u0440\u043e\u0434\u044c\u044e\u0441\u0435\u0440&#187;? \u0413\u043e\u0432\u043e\u0440\u044f \u043f\u0440\u043e\u0441\u0442\u044b\u043c \u044f\u0437\u044b\u043a\u043e\u043c &#8212; \u044d\u0442\u043e \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u043e\u0436\u0435\u0442 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0441 \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u0438 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e \u043d\u0443\u0436\u043d\u043e\u043c\u0443 \u0442\u043e\u043f\u0438\u043a\u0443.<\/p>\n<p>\u041c\u044b \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b\u0438\u0441\u044c \u043a\u043b\u0430\u0441\u0441\u043e\u043c <code>AIOKafkaProducer<\/code><em> <\/em>\u0438\u0437 \u043c\u043e\u0434\u0443\u043b\u044f <code>aiokafka<\/code>. \u041e\u043d \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0438\u0437 \u0441\u0435\u0431\u044f \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 \u0431\u0440\u043e\u043a\u0435\u0440\u043e\u043c \u043e\u0442 \u043b\u0438\u0446\u0430 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044f (\u043f\u0440\u043e\u0434\u044c\u044e\u0441\u0435\u0440\u0430) \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043c\u044b \u0443\u043a\u0430\u0437\u0430\u043b\u0438 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0446\u0438\u043a\u043b \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0438 <code>KAFKA_BOOTSTRAP_SERVERS<\/code>.<\/p>\n<p>\u0412 \u043d\u0430\u0448\u0435\u043c \u043a\u043b\u0430\u0441\u0441\u0435-\u043e\u0431\u0435\u0440\u0442\u043a\u0435 \u043d\u0438\u0447\u0435\u0433\u043e \u0441\u043b\u043e\u0436\u043d\u043e\u0433\u043e \u043d\u0435\u0442. \u041c\u0435\u0442\u043e\u0434\u044b <code>start<\/code> \u0438 <code>stop<\/code> \u043f\u0440\u043e\u0441\u0442\u043e \u043e\u0431\u043e\u043b\u043e\u0447\u043a\u0438 \u043d\u0430\u0434 \u043e\u0434\u043d\u043e\u0438\u043c\u0435\u043d\u043d\u044b\u043c\u0438 \u043c\u0435\u0442\u043e\u0434\u0430\u043c\u0438 \u043a\u043b\u0430\u0441\u0441\u0430 <code>AIOKafkaProducer<\/code><em>.<\/em> <code>start<\/code> &#8212; \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 \u043a\u043b\u0430\u0441\u0442\u0435\u0440\u043e\u043c Kafka, <code>stop<\/code> &#8212; \u043e\u0447\u0438\u0449\u0430\u0435\u0442 \u0432\u0441\u0435 \u043e\u0436\u0438\u0434\u0430\u044e\u0449\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u0442 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435.<\/p>\n<p>\u041c\u0435\u0442\u043e\u0434 <code>send<\/code> &#8212; \u0442\u0430\u043a\u0430\u044f \u0436\u0435 \u043d\u0430\u0434\u0441\u0442\u0440\u043e\u0439\u043a\u0430, \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u043d\u0430 \u0438\u043d\u043a\u0430\u043f\u0441\u0443\u043b\u0438\u0440\u0443\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0438 \u0437\u0430\u043a\u0440\u044b\u0442\u0438\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u043f\u0435\u0440\u0435\u0434 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u043e\u0439 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043f\u043e \u043d\u0443\u0436\u043d\u043e\u043c\u0443 \u0442\u043e\u043f\u0438\u043a\u0443. \u0422\u0430\u043a\u0436\u0435 \u0441\u0442\u043e\u0438\u0442 \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c, \u0447\u0442\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0432 \u0432\u0438\u0434\u0435 \u0431\u0430\u0439\u0442\u043e\u0432. \u042d\u0442\u043e \u0441\u043b\u0435\u0434\u0443\u0435\u0442 \u0438\u0437 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0438 \u043f\u0430\u043a\u0435\u0442\u0430 \u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441\u0430\u043c\u043e\u0439 Kafka.<\/p>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f <code>get_producer<\/code> \u043d\u0443\u0436\u043d\u0430 \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u043d\u043e\u0439 \u0438\u043d\u044a\u0435\u043a\u0446\u0438\u0438 \u043f\u0440\u043e\u0434\u044e\u0441\u0435\u0440\u0430 \u0432 \u043d\u0430\u0448 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 end-point. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043a\u0430\u043a \u0440\u0430\u0437 \u044d\u0442\u0438\u043c \u0438 \u0437\u0430\u0439\u043c\u0435\u043c\u0441\u044f:<\/p>\n<pre><code class=\"python\"># web\/app\/main.py from __future__ import annotations from typing import TYPE_CHECKING from fastapi import FastAPI, Depends from web.app.schemas import Message from web.app.producer import get_producer import json  if TYPE_CHECKING:     from web.app.producer import AIOWebProducer   app = FastAPI()   @app.post(\"\/currency-info\") async def send(message: Message, producer: AIOWebProducer = Depends(get_producer)) -> None:     message_to_produce = json.dumps(message.model_dump()).encode(encoding=\"utf-8\")     await producer.send(value=message_to_produce)<\/code><\/pre>\n<p>\u0412 \u0446\u0435\u043b\u043e\u043c, \u043f\u0440\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u043e\u0441\u044c, \u043a\u043e\u0434\u0430 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043d\u0435\u043c\u043d\u043e\u0433\u043e (2 \u0441\u0442\u0440\u043e\u0447\u043a\u0438). \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u044b \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043e\u0431\u044a\u0435\u043a\u0442 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f (\u043f\u0435\u0440\u0435\u0432\u043e\u0434\u0438\u043c \u0435\u0433\u043e \u0432 \u0431\u0430\u0439\u0442\u044b), \u043f\u043e\u0441\u043b\u0435 \u0447\u0435\u0433\u043e \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043d\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 \u0431\u0430\u0439\u0442\u043e\u0432 \u0432 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044b\u0439 \u0442\u043e\u043f\u0438\u043a \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043c\u0435\u0442\u043e\u0434\u0430 send. <strong>\u0412\u0441\u0435.<\/strong><\/p>\n<p>\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435, \u0447\u0442\u043e \u043d\u0430\u043c \u043e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0438\u043a\u0440\u043e\u0441\u0435\u0440\u0432\u0438\u0441\u0430 &#8212; \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0434\u043b\u044f \u043d\u0435\u0433\u043e Dockerfile:<\/p>\n<pre><code class=\"python\"># services\/web\/Dockerfile  FROM python:3.11-slim-buster  WORKDIR \/usr\/src\/web  ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 ENV PYTHONPATH=\/usr\/src  COPY poetry.lock pyproject.toml .\/  RUN pip install poetry &amp;&amp; poetry config virtualenvs.create false &amp;&amp; poetry install --no-root  COPY app app\/  WORKDIR .\/app<\/code><\/pre>\n<p><a class=\"anchor\" name=\"5\" id=\"5\"><\/a><\/p>\n<h2>Currency \u0441\u0435\u0440\u0432\u0438\u0441 (Scrapy)<\/h2>\n<p>\u041a\u0430\u043a \u0438 \u0434\u043e \u044d\u0442\u043e\u0433\u043e \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435 \u0438 \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438:<\/p>\n<pre><code class=\"bash\">cd services\/currency &amp;&amp; poetry init poetry add scrapy redis aiokafka<\/code><\/pre>\n<p>\u0412 \u044d\u0442\u043e\u0442 \u0440\u0430\u0437 \u043c\u044b <strong>\u043d\u0435 \u0431\u0443\u0434\u0435\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c<\/strong> \u043f\u0430\u043f\u043a\u0443 app, \u0442\u0430\u043a \u043a\u0430\u043a <strong>Scrapy \u0441\u0434\u0435\u043b\u0430\u0435\u0442 \u044d\u0442\u043e \u0437\u0430 \u043d\u0430\u0441 \u0438 \u0434\u0430\u0436\u0435 \u043b\u0443\u0447\u0448\u0435.<\/strong> \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442 \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u044b:<\/p>\n<pre><code class=\"bash\">scrapy startproject currency<\/code><\/pre>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0438\u0437\u0431\u0430\u0432\u0438\u043c\u0441\u044f \u043e\u0442 \u043b\u0438\u0448\u043d\u0435\u0439 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u043f\u0430\u043f\u043e\u043a currency, \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0440\u0430\u0441\u043a\u0440\u043e\u0435\u043c \u043c\u0430\u0442\u0440\u0435\u0448\u043a\u0443 \u0438 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043c \u0435\u0435 \u043a \u0442\u0430\u043a\u043e\u043c\u0443 \u0432\u0438\u0434\u0443:<\/p>\n<figure class=\"\">\n<div><figcaption>\u0420\u0438\u0441. 2. \u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 Scrapy \u0441\u0435\u0440\u0432\u0438\u0441\u0430<\/figcaption><\/div>\n<\/figure>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0433\u043e\u0432\u043e\u0440\u0438\u043c \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0431\u0443\u0434\u0435\u0442 \u043f\u0430\u0440\u0441\u0438\u0442\u044c\u0441\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0438 \u043a\u0443\u0434\u0430 \u043e\u043d\u0430 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043f\u0430\u0434\u0430\u0442\u044c. \u0418\u0442\u0430\u043a, \u0432 Scrapy \u0435\u0441\u0442\u044c &#171;\u043f\u0430\u0443\u043a\u0438&#187;, \u0441\u0443\u0442\u044c \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043a\u0430\u043a \u0440\u0430\u0437 \u043f\u0430\u0440\u0441\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u0441 \u0447\u0435\u0433\u043e-\u043b\u0438\u0431\u043e. \u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e \u043a\u0430\u043a \u0434\u0430\u043d\u043d\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u044b, \u043c\u044b \u043f\u043e\u043b\u043e\u0436\u0438\u043c \u0438\u0445 \u0432 Redis, \u0447\u0442\u043e\u0431\u044b \u043d\u0430 \u043a\u0430\u0436\u0434\u044b\u0439 \u0437\u0430\u043f\u0440\u043e\u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043d\u0435 \u0445\u043e\u0434\u0438\u0442\u044c \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442. \u0421\u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0438\u0440\u0443\u0435\u043c \u0431\u0430\u0437\u043e\u0432\u0443\u044e \u043a\u043e\u043c\u043f\u043b\u0435\u043a\u0442\u0430\u0446\u0438\u044e &#171;\u043f\u0430\u0443\u043a\u0430&#187;, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0432\u0432\u0435\u0434\u0435\u043c:<\/p>\n<pre><code class=\"bash\">scrapy genspider currency_v1 https:\/\/www.cbr.ru\/scripts\/xml_daily.asp<\/code><\/pre>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0432 \u043f\u0430\u043f\u043a\u0435 spiders \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u043b \u043f\u043e\u044f\u0432\u0438\u0442\u044c\u0441\u044f \u043a\u0430\u0440\u043a\u0430\u0441 \u043d\u0430\u0448\u0435\u0433\u043e \u0431\u0443\u0434\u0443\u0449\u0435\u0433\u043e &#171;\u043f\u0430\u0443\u043a\u0430&#187;. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043f\u043e\u0434\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u0443\u0435\u043c \u0435\u0433\u043e \u043a\u043e\u0434:<\/p>\n<pre><code class=\"python\"># services\/currency\/currency\/spiders\/currency_v1.py import scrapy   class CurrencyV1Spider(scrapy.Spider):     name = \"currency_v1\"     allowed_domains = [\"www.cbr.ru\"]     start_urls = [\"https:\/\/www.cbr.ru\/scripts\/xml_daily.asp\"]      def parse(self, response, **kwargs) -> None:         currencies = response.xpath(\"\/\/ValCurs\/\/Valute\")         for currency in currencies:             print(currency)<\/code><\/pre>\n<p>\u0418\u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0443\u0431\u0435\u0434\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e &#171;\u043f\u0430\u0443\u043a&#187; \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0447\u0442\u043e-\u043b\u0438\u0431\u043e \u043f\u0430\u0440\u0441\u0438\u0442, \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0435\u0433\u043e \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c, \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u0438\u0448\u0435\u043c \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c:<\/p>\n<pre><code class=\"bash\">scrapy crawl currency_v1 <\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0432 \u0442\u0435\u0440\u043c\u0438\u043d\u0430\u043b\u0435 \u043f\u043e\u0441\u044b\u043f\u0430\u043b\u043e\u0441\u044c \u043a\u0443\u0447\u0443 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0432\u0438\u0434\u0430 <code>&lt;Selector query='\/\/ValCurs\/\/Valute'...<\/code><em> <\/em>&#8212; \u043e\u0442\u043b\u0438\u0447\u043d\u043e, \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u043c Redis, \u0447\u0442\u043e\u0431\u044b \u044d\u0442\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0433\u0434\u0435-\u0442\u043e \u0445\u0440\u0430\u043d\u0438\u043b\u0430\u0441\u044c. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e, \u0432\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u043e\u0431\u043d\u043e\u0432\u0438\u043c .env:<\/p>\n<pre><code class=\"bash\">KAFKA_BOOTSTRAP_SERVERS=kafka:9092  WEB_TOPIC=web  REDIS_HOST=redis  REDIS_PORT=6379<\/code><\/pre>\n<p>\u0410, \u0432\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, \u0434\u043e\u0431\u0430\u0432\u0438\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0448 \u0441\u0435\u0440\u0432\u0438\u0441 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u043b\u0441\u044f \u043a Redis:<\/p>\n<pre><code class=\"python\"># services\/currency\/currency\/settings.py import os  BOT_NAME = \"currency\" SPIDER_MODULES = [\"currency.spiders\"] NEWSPIDER_MODULE = \"currency.spiders\" ROBOTSTXT_OBEY = True REQUEST_FINGERPRINTER_IMPLEMENTATION = \"2.7\" TWISTED_REACTOR = \"twisted.internet.asyncioreactor.AsyncioSelectorReactor\" FEED_EXPORT_ENCODING = \"utf-8\"  # --- REDIS SETTINGS  CURRENCY_REDIS_CACHE_TIME_IN_SECONDS = 60<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\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-368299","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/368299","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=368299"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/368299\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=368299"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=368299"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=368299"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}