Share This
Связаться со мной
Крути в низ
Categories
//Делаем запросы к API с помощью Python

Делаем запросы к API с помощью Python

21.12.2021Category : Python

API (Application Programming Interface – интерфейс прикладного программирования) – это сервер, который позволяет извлекать и отправлять данные с помощью кода. В основном мы используем API для получения данных. Именно эта тема и будет в фокусе нашего руководства для начинающих. Итак, приступим!

Когда мы хотим получать данные из API, нам нужно сделать запрос. Запросы используются по всему Интернету. Например, когда вы зашли на этот сайт, ваш браузер сделал запрос на веб-сервер Pythonist, который ответил и выдал вам содержание данной веб-страницы.

delaem zaprosy k api s pomoshhju python ed78797 - Делаем запросы к API с помощью Python

Запросы API работают точно так же. Вы запрашиваете данные у сервера API, и он отвечает на ваш запрос. Сервер может вам вернуть какие-то данные или код, который будет в дальнейшем интерпретирован вашей машиной.

Например, когда возвращается HTML-код страницы, для пользователя она преображается именно в страницу с различными надписями, картинками и т.д., а не остается просто в виде кода. Конечно, всегда может что-то пойти не по плану 🙃, но в остальных случаях это должно работать именно так.

Различные методы HTTP и коды ответов

Для REST API есть различные HTTP-методы. Они сообщают API, какие операции необходимо выполнить с данными. Хотя HTTP-методов довольно много, с REST API чаще всего используются следующие пять:

HTTP-метод Описание
GET Получить существующие данные
POST Добавить новые данные
PUT Обновить существующие данные
PATCH Частично обновить существующие данные
DELETE Удалить данные

Как только REST API получает и обрабатывает HTTP-запрос, он возвращает ответ с кодом состояния HTTP. Этот код состояния предоставляет информацию об ответе и помогает клиентскому приложению узнать, что это за ответ.

Коды ответов или коды состояния нумеруются в зависимости от категории результата:

Код Категория результата
1хх Информационный ответ
2хх Успешная операция
3хх Перенаправление
4хх Ошибка на стороне клиента
5хх Ошибка на стороне сервера

Вы можете узнать больше о кодах состояния HTTP в веб-документации MDN.

delaem zaprosy k api s pomoshhju python 815795f - Делаем запросы к API с помощью Python

Марк Лутц «Изучаем Python»

Скачивайте книгу у нас в телеграм

Скачать ×

Конечные точки API

Конечные точки API – это общедоступные URL-адреса, предоставляемые сервером, которые клиентское приложение использует для доступа к различным ресурсам и данным.

В этом руководстве мы будем использовать REST API Fake Store. В частности, мы воспользуемся следующими конечными точками:

HTTP-мето Конечная точка API Описание
GET /products Получить список продуктов
GET /products?limit=x Получить только х товаров (к примеру, только 5 товаров)
GET /products/<product_id>          Получить один конкретный продукт
POST /products Создать новый продукт
PUT /products/<product_id>          Обновить товар
PATCH /products/<product_id>          Частично обновить товар
DELETE /products/<product_id>          Удалить товар

Каждая из вышеперечисленных конечных точек выполняет разные действия в зависимости от метода HTTP. Для каждого URL-адреса API базовый URL-адрес это: https://fakestoreapi.com. Мы будем исследовать запросы по очереди.

Однако сначала нам нужно установить внешнюю библиотеку для использования этих API. Большинство разработчиков Python для взаимодействия с веб-сервисами используют библиотеку запросов requests. Вы можете установить эту библиотеку с помощью команды pip следующим образом:

$ pip install requests

Как только библиотека установлена, все готово и можно приступать!

Как сделать запрос GET

Это один из наиболее распространенных методов HTTP-запросов, с которыми вы столкнетесь. Это операция только для чтения, позволяющая получать данные из API.

Давайте попробуем использовать запрос GET на первой конечной точке из упомянутых выше. Она должна вернуть список продуктов.

import requests  BASE_URL = 'https://fakestoreapi.com'  response = requests.get(f"{BASE_URL}/products") print(response.json())

В приведенном выше сценарии используется метод requests.get() для отправки запроса GET в конечную точку API /products. Данный запрос возвращает нам список всех продуктов. Затем мы вызываем метод .json(), чтобы просмотреть полученный ответ JSON. Выглядит он так:

[ { "id": 1, "title": "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops", "price": 109.95, "description": "Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday", "category": "men's clothing", "image": "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg", "rating": { "rate": 3.9, "count": 120 } }, { "id": 2, "title": "Mens Casual Premium Slim Fit T-Shirts ", "price": 22.3, "description": "Slim-fitting style, contrast raglan long sleeve, three-button henley placket, light weight & soft fabric for breathable and comfortable wearing. And Solid stitched shirts with round neck made for durability and a great fit for casual fashion wear and diehard baseball fans. The Henley style round neckline includes a three-button placket.", "category": "men's clothing", "image": "https://fakestoreapi.com/img/71-3HjGNDUL._AC_SY879._SX._UX._SY._UY_.jpg", "rating": { "rate": 4.1, "count": 259 } }, { "id": 3, "title": "Mens Cotton Jacket", "price": 55.99, "description": "great outerwear jackets for Spring/Autumn/Winter, suitable for many occasions, such as working, hiking, camping, mountain/rock climbing, cycling, traveling or other outdoors. Good gift choice for you or your family member. A warm hearted love to Father, husband or son in this thanksgiving or Christmas Day.", "category": "men's clothing", "image": "https://fakestoreapi.com/img/71li-ujtlUL._AC_UX679_.jpg", "rating": { "rate": 4.7, "count": 500 } }, { "id": 4, "title": "Mens Casual Slim Fit", "price": 15.99, "description": "The color could be slightly different between on the screen and in practice. / Please note that body builds vary by person, therefore, detailed size information should be reviewed below on the product description.", "category": "men's clothing", "image": "https://fakestoreapi.com/img/71YXzeOuslL._AC_UY879_.jpg", "rating": { "rate": 2.1, "count": 430 } }, { "id": 5, "title": "John Hardy Women's Legends Naga Gold & Silver Dragon Station Chain Bracelet", "price": 695, "description": "From our Legends Collection, the Naga was inspired by the mythical water dragon that protects the ocean's pearl. Wear facing inward to be bestowed with love and abundance, or outward for protection.", "category": "jewelery", "image": "https://fakestoreapi.com/img/71pWzhdJNwL._AC_UL640_QL65_ML3_.jpg", "rating": { "rate": 4.6, "count": 400 } }, { "id": 6, "title": "Solid Gold Petite Micropave ", "price": 168, "description": "Satisfaction Guaranteed. Return or exchange any order within 30 days.Designed and sold by Hafeez Center in the United States. Satisfaction Guaranteed. Return or exchange any order within 30 days.", "category": "jewelery", "image": "https://fakestoreapi.com/img/61sbMiUnoGL._AC_UL640_QL65_ML3_.jpg", "rating": { "rate": 3.9, "count": 70 } }, { "id": 7, "title": "White Gold Plated Princess", "price": 9.99, "description": "Classic Created Wedding Engagement Solitaire Diamond Promise Ring for Her. Gifts to spoil your love more for Engagement, Wedding, Anniversary, Valentine's Day...", "category": "jewelery", "image": "https://fakestoreapi.com/img/71YAIFU48IL._AC_UL640_QL65_ML3_.jpg", "rating": { "rate": 3, "count": 400 } }, { "id": 8, "title": "Pierced Owl Rose Gold Plated Stainless Steel Double", "price": 10.99, "description": "Rose Gold Plated Double Flared Tunnel Plug Earrings. Made of 316L Stainless Steel", "category": "jewelery", "image": "https://fakestoreapi.com/img/51UDEzMJVpL._AC_UL640_QL65_ML3_.jpg", "rating": { "rate": 1.9, "count": 100 } }, { "id": 9, "title": "WD 2TB Elements Portable External Hard Drive - USB 3.0 ", "price": 64, "description": "USB 3.0 and USB 2.0 Compatibility Fast data transfers Improve PC Performance High Capacity; Compatibility Formatted NTFS for Windows 10, Windows 8.1, Windows 7; Reformatting may be required for other operating systems; Compatibility may vary depending on user’s hardware configuration and operating system", "category": "electronics", "image": "https://fakestoreapi.com/img/61IBBVJvSDL._AC_SY879_.jpg", "rating": { "rate": 3.3, "count": 203 } }, { "id": 10, "title": "SanDisk SSD PLUS 1TB Internal SSD - SATA III 6 Gb/s", "price": 109, "description": "Easy upgrade for faster boot up, shutdown, application load and response (As compared to 5400 RPM SATA 2.5” hard drive; Based on published specifications and internal benchmarking tests using PCMark vantage scores) Boosts burst write performance, making it ideal for typical PC workloads The perfect balance of performance and reliability Read/write speeds of up to 535MB/s/450MB/s (Based on internal testing; Performance may vary depending upon drive capacity, host device, OS and application.)", "category": "electronics", "image": "https://fakestoreapi.com/img/61U7T1koQqL._AC_SX679_.jpg", "rating": { "rate": 2.9, "count": 470 } }, { "id": 11, "title": "Silicon Power 256GB SSD 3D NAND A55 SLC Cache Performance Boost SATA III 2.5", "price": 109, "description": "3D NAND flash are applied to deliver high transfer speeds Remarkable transfer speeds that enable faster bootup and improved overall system performance. The advanced SLC Cache Technology allows performance boost and longer lifespan 7mm slim design suitable for Ultrabooks and Ultra-slim notebooks. Supports TRIM command, Garbage Collection technology, RAID, and ECC (Error Checking & Correction) to provide the optimized performance and enhanced reliability.", "category": "electronics", "image": "https://fakestoreapi.com/img/71kWymZ+c+L._AC_SX679_.jpg", "rating": { "rate": 4.8, "count": 319 } }, { "id": 12, "title": "WD 4TB Gaming Drive Works with Playstation 4 Portable External Hard Drive", "price": 114, "description": "Expand your PS4 gaming experience, Play anywhere Fast and easy, setup Sleek design with high capacity, 3-year manufacturer's limited warranty", "category": "electronics", "image": "https://fakestoreapi.com/img/61mtL65D4cL._AC_SX679_.jpg", "rating": { "rate": 4.8, "count": 400 } }, { "id": 13, "title": "Acer SB220Q bi 21.5 inches Full HD (1920 x 1080) IPS Ultra-Thin", "price": 599, "description": "21. 5 inches Full HD (1920 x 1080) widescreen IPS display And Radeon free Sync technology. No compatibility for VESA Mount Refresh Rate: 75Hz - Using HDMI port Zero-frame design | ultra-thin | 4ms response time | IPS panel Aspect ratio - 16: 9. Color Supported - 16. 7 million colors. Brightness - 250 nit Tilt angle -5 degree to 15 degree. Horizontal viewing angle-178 degree. Vertical viewing angle-178 degree 75 hertz", "category": "electronics", "image": "https://fakestoreapi.com/img/81QpkIctqPL._AC_SX679_.jpg", "rating": { "rate": 2.9, "count": 250 } }, { "id": 14, "title": "Samsung 49-Inch CHG90 144Hz Curved Gaming Monitor (LC49HG90DMNXZA) – Super Ultrawide Screen QLED ", "price": 999.99, "description": "49 INCH SUPER ULTRAWIDE 32:9 CURVED GAMING MONITOR with dual 27 inch screen side by side QUANTUM DOT (QLED) TECHNOLOGY, HDR support and factory calibration provides stunningly realistic and accurate color and contrast 144HZ HIGH REFRESH RATE and 1ms ultra fast response time work to eliminate motion blur, ghosting, and reduce input lag", "category": "electronics", "image": "https://fakestoreapi.com/img/81Zt42ioCgL._AC_SX679_.jpg", "rating": { "rate": 2.2, "count": 140 } }, { "id": 15, "title": "BIYLACLESEN Women's 3-in-1 Snowboard Jacket Winter Coats", "price": 56.99, "description": "Note:The Jackets is US standard size, Please choose size as your usual wear Material: 100% Polyester; Detachable Liner Fabric: Warm Fleece. Detachable Functional Liner: Skin Friendly, Lightweigt and Warm.Stand Collar Liner jacket, keep you warm in cold weather. Zippered Pockets: 2 Zippered Hand Pockets, 2 Zippered Pockets on Chest (enough to keep cards or keys)and 1 Hidden Pocket Inside.Zippered Hand Pockets and Hidden Pocket keep your things secure. Humanized Design: Adjustable and Detachable Hood and Adjustable cuff to prevent the wind and water,for a comfortable fit. 3 in 1 Detachable Design provide more convenience, you can separate the coat and inner as needed, or wear it together. It is suitable for different season and help you adapt to different climates", "category": "women's clothing", "image": "https://fakestoreapi.com/img/51Y5NI-I5jL._AC_UX679_.jpg", "rating": { "rate": 2.6, "count": 235 } }, { "id": 16, "title": "Lock and Love Women's Removable Hooded Faux Leather Moto Biker Jacket", "price": 29.95, "description": "100% POLYURETHANE(shell) 100% POLYESTER(lining) 75% POLYESTER 25% COTTON (SWEATER), Faux leather material for style and comfort / 2 pockets of front, 2-For-One Hooded denim style faux leather jacket, Button detail on waist / Detail stitching at sides, HAND WASH ONLY / DO NOT BLEACH / LINE DRY / DO NOT IRON", "category": "women's clothing", "image": "https://fakestoreapi.com/img/81XH0e8fefL._AC_UY879_.jpg", "rating": { "rate": 2.9, "count": 340 } }, { "id": 17, "title": "Rain Jacket Women Windbreaker Striped Climbing Raincoats", "price": 39.99, "description": "Lightweight perfet for trip or casual wear---Long sleeve with hooded, adjustable drawstring waist design. Button and zipper front closure raincoat, fully stripes Lined and The Raincoat has 2 side pockets are a good size to hold all kinds of things, it covers the hips, and the hood is generous but doesn't overdo it.Attached Cotton Lined Hood with Adjustable Drawstrings give it a real styled look.", "category": "women's clothing", "image": "https://fakestoreapi.com/img/71HblAHs5xL._AC_UY879_-2.jpg", "rating": { "rate": 3.8, "count": 679 } }, { "id": 18, "title": "MBJ Women's Solid Short Sleeve Boat Neck V ", "price": 9.85, "description": "95% RAYON 5% SPANDEX, Made in USA or Imported, Do Not Bleach, Lightweight fabric with great stretch for comfort, Ribbed on sleeves and neckline / Double stitching on bottom hem", "category": "women's clothing", "image": "https://fakestoreapi.com/img/71z3kpMAYsL._AC_UY879_.jpg", "rating": { "rate": 4.7, "count": 130 } }, { "id": 19, "title": "Opna Women's Short Sleeve Moisture", "price": 7.95, "description": "100% Polyester, Machine wash, 100% cationic polyester interlock, Machine Wash & Pre Shrunk for a Great Fit, Lightweight, roomy and highly breathable with moisture wicking fabric which helps to keep moisture away, Soft Lightweight Fabric with comfortable V-neck collar and a slimmer fit, delivers a sleek, more feminine silhouette and Added Comfort", "category": "women's clothing", "image": "https://fakestoreapi.com/img/51eg55uWmdL._AC_UX679_.jpg", "rating": { "rate": 4.5, "count": 146 } }, { "id": 20, "title": "DANVOUY Womens T Shirt Casual Cotton Short", "price": 12.99, "description": "95%Cotton,5%Spandex, Features: Casual, Short Sleeve, Letter Print,V-Neck,Fashion Tees, The fabric is soft and has some stretch., Occasion: Casual/Office/Beach/School/Home/Street. Season: Spring,Summer,Autumn,Winter.", "category": "women's clothing", "image": "https://fakestoreapi.com/img/61pHAEJ4NML._AC_UX679_.jpg", "rating": { "rate": 3.6, "count": 145 } } ]

Если вы присмотритесь, ответ в виде JSON выглядит как список словарей в Python. JSON – это очень популярный формат обмена данными для REST API.

Вы также можете получить и другие атрибуты, связанные с ответом, например код состояния. Это будет выглядеть следующим образом:

print(response.status_code) # OUTPUT >>> 200

Как известно, код состояния 200 означает, что нам пришел успешный ответ и всё сработало отлично.

Поскольку конечная точка /products возвращает много данных, давайте ограничим эти данные только тремя продуктами.

Для этого у нас есть конечная точка /products?limit=x, где x – это положительное целое число. limit (лимит, ограничение) — параметр запроса. Давайте посмотрим, как мы можем добавить этот параметр в наш запрос:

import requests BASE_URL = 'https://fakestoreapi.com' query_params = { "limit": 3 } response = requests.get(f"{BASE_URL}/products", params=query_params) print(response.json())

Метод requests.get() принимает параметр с именем params, в котором мы можем указать параметры нашего запроса в формате словаря Python. Таким образом, мы создаем словарь с именем query_params и передаем limit в качестве ключа и 3 в качестве значения. Затем мы передаем этот словарь query_params в request.get(). Теперь наш код выглядит следующим образом:

[ { "id": 1, "title": "Fjallraven - Foldsack No. 1 Backpack, Fits 15 Laptops", "price": 109.95, "description": "Your perfect pack for everyday use and walks in the forest. Stash your laptop (up to 15 inches) in the padded sleeve, your everyday", "category": "men's clothing", "image": "https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg", "rating": { "rate": 3.9, "count": 120 } }, { "id": 2, "title": "Mens Casual Premium Slim Fit T-Shirts ", "price": 22.3, "description": "Slim-fitting style, contrast raglan long sleeve, three-button henley placket, light weight & soft fabric for breathable and comfortable wearing. And Solid stitched shirts with round neck made for durability and a great fit for casual fashion wear and diehard baseball fans. The Henley style round neckline includes a three-button placket.", "category": "men's clothing", "image": "https://fakestoreapi.com/img/71-3HjGNDUL._AC_SY879._SX._UX._SY._UY_.jpg", "rating": { "rate": 4.1, "count": 259 } }, { "id": 3, "title": "Mens Cotton Jacket", "price": 55.99, "description": "great outerwear jackets for Spring/Autumn/Winter, suitable for many occasions, such as working, hiking, camping, mountain/rock climbing, cycling, traveling or other outdoors. Good gift choice for you or your family member. A warm hearted love to Father, husband or son in this thanksgiving or Christmas Day.", "category": "men's clothing", "image": "https://fakestoreapi.com/img/71li-ujtlUL._AC_UX679_.jpg", "rating": { "rate": 4.7, "count": 500 } } ]

Таким образом, у нас есть данные об ответах, ограниченные всего тремя продуктами. Попробуем получить только один товар, id которого равен 18.

import requests BASE_URL = 'https://fakestoreapi.com' response = requests.get(f"{BASE_URL}/products/18") print(response)

Поскольку у нас есть конечная точка /products/<product_id>, мы можем передать идентификатор 18 в URL-адресе API и сделать для него запрос GET. Ответ будет выглядеть следующим образом:

{ "id": 18, "title": "MBJ Women's Solid Short Sleeve Boat Neck V ", "price": 9.85, "description": "95% RAYON 5% SPANDEX, Made in USA or Imported, Do Not Bleach, Lightweight fabric with great stretch for comfort, Ribbed on sleeves and neckline / Double stitching on bottom hem", "category": "women's clothing", "image": "https://fakestoreapi.com/img/71z3kpMAYsL._AC_UY879_.jpg", "rating": { "rate": 4.7, "count": 130 } }

Как сделать POST-запрос

Мы используем запрос POST для добавления новых данных в REST API. Данные отправляются на сервер в формате JSON, который выглядит как словарь Python. Согласно документации Fake Store API, у продукта есть следующие атрибуты: title (название), price (цена), description (описание), image (изображение) и category (категория). Итак, наша новинка выглядит так:

new_product = { "title": 'test product', "price": 13.5, "description": 'lorem ipsum set', "image": 'https://i.pravatar.cc', "category": 'electronic' }

Мы можем отправить запрос POST с помощью метода requests.post() следующим образом:

import requests BASE_URL = 'https://fakestoreapi.com' new_product = { "title": 'test product', "price": 13.5, "description": 'lorem ipsum set', "image": 'https://i.pravatar.cc', "category": 'electronic' } response = requests.post(f"{BASE_URL}/products", json=new_product) print(response.json())

В методе request.post() мы можем передавать данные JSON с помощью аргумента json. Использование аргумента json автоматически устанавливает в Content-Type значение Application/JSON в заголовке запроса.

Как только мы сделаем запрос POST в конечной точке /products, мы получим объект продукта с идентификатором в ответе. Этот ответ выглядит так:

{ "_id": "61b45067e087f30012c45a45", "id": 21, "title": "test product", "price": 13.5, "description": "lorem ipsum set", "image": "https://i.pravatar.cc", "category": "electronic" }

Если мы не используем аргумент json, мы должны сделать запрос POST следующим образом:

import requests import json BASE_URL = 'https://fakestoreapi.com' new_product = { "title": 'test product', "price": 13.5, "description": 'lorem ipsum set', "image": 'https://i.pravatar.cc', "category": 'electronic' } headers = { "Content-Type": "application/json" } response = requests.post(f"{BASE_URL}/products", data=json.dumps(new_product), headers=headers) print(response.json())

В этом случае, когда мы используем аргумент data вместо json, нам нужно явно установить Content-Type на application/json в заголовке. Когда мы используем аргумент json, нам не нужно сериализовать данные, но в данном случае это необходимо и делается с помощью json.dumps().

Как сделать запрос PUT

Нам часто требуется обновить существующие данные в API. Используя запрос PUT, мы можем обновить данные полностью. Это означает, что, когда мы делаем запрос PUT, он заменяет все старые данные новыми.

В запросе POST мы создали новый продукт с идентификатором 21. Давайте обновим старый продукт на новый, сделав запрос PUT к конечной точке products/<product_id>. Напишем следующий код:

import requests BASE_URL = 'https://fakestoreapi.com' updated_product = { "title": 'updated_product', "category": 'clothing' } response = requests.put(f"{BASE_URL}/products/21", json=updated_product) print(response.json())

Когда мы делаем запрос PUT с updated_product с помощью метода requests.put(), он возвращает нам следующие данные JSON:

{ "id": "21", "title": "updated_product", "category": "clothing" }

Обратите внимание, что старый продукт был полностью заменен обновленным продуктом.

Как сделать PATCH-запрос

Иногда нам не нужно полностью заменять старые данные. Скорее мы хотим изменить только определенные поля. В этом случае мы используем запрос PATCH.

Давайте обновим категорию (category) продукта обратно с clothing (одежды) на electronic (электронику), сделав запрос PATCH к конечной точке products/<product_id>. Наш код будет выглядеть так:

import requests BASE_URL = 'https://fakestoreapi.com' updated_product = { "category": 'electronic' } response = requests.patch(f"{BASE_URL}/products/21", json=updated_product) print(response.json())

В этом случае мы используем метод requests.patch(), который возвращает такой ответ:

{ "id": "21", "title": "updated_product", "category": "electronic" }

Обратите внимание, что на этот раз все данные не изменились – обновилось только поле category (категория).

Как сделать запрос DELETE

Как следует из названия, если вы хотите удалить ресурс из API, вы можете использовать запрос DELETE. Удалим товар с идентификатором, равным 21:

import requests BASE_URL = 'https://fakestoreapi.com' response = requests.delete(f"{BASE_URL}/products/21") print(response.json())

Метод requests.delete() помогает нам сделать запрос DELETE к конечной точке /products/<product_id>.

Заключение

В этом руководстве мы рассмотрели, как взаимодействовать с веб-сервисами с помощью Python. Мы рассказали о пяти разных запросах: GET, POST, PUT, PATCH и DELETE. Более того, мы разобрали на примерах их синтаксис и принцип работы. Также мы обсудили, какие есть коды ответов или коды состояния и что они значат.

Надеемся, вам понравилось данное руководство – спасибо, что прочитали! Успехов в написании кода!

Перевод статьи Python Requests – «How to Interact with Web Services using Python».

  • 8 views
  • 0 Comment

Leave a Reply

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.

Свежие комментарии

    Рубрики

    About Author 01.

    blank
    Roman Spiridonov

    Моя специальность - Back-end Developer, Software Engineer Python. Мне 39 лет, я работаю в области информационных технологий более 5 лет. Опыт программирования на Python более 3 лет. На Django более 2 лет.

    Categories 05.

    © Speccy 2022 / All rights reserved

    Связаться со мной
    Close