Автоматический постинг своего Instagram на сайт
Как показала практика, очень удобно размещать фотографии на сайте, импортируя их автоматически из Instagram аккаунта. Сегодня поговорим об этом.
Сначала создадим приложение в нашем Django проекте.
Консоль
(env) ./project $ cd apps/ (env) ./project/apps $ ../manage.py startapp instagram (env) ./project/apps $ mv instagram/apps.py instagram/app.py (env) ./project $ cd ../
Поправим файлы
./apps/instagram/__init__.py
default_app_config = "apps.instagram.app.InstagramConfig"
./apps/instagram/app.py
# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.apps import AppConfig class InstagramConfig(AppConfig): name = 'apps.instagram' verbose_name = u'Instagram фото'
В файле models.py
используется сторонний модуль enum, почитать о котором можно в разделе «Утилиты для облегчения разработки»
./apps/instagram/models.py
# -*- coding:utf-8 -*- from __future__ import unicode_literals from sorl.thumbnail import ImageField, get_thumbnail from django.db import models from django.conf import settings from libs import enum from utils import PathAndRename class InstagramType(enum.Enum): Image = enum.Item('image', u'Фотопост') Video = enum.Item('video', u'Видеопост') class InstagramPost(models.Model): instagram_id = models.CharField(u'Instagram ID', max_length=64) created_time = models.DateTimeField(u'Дата добавления') caption = models.TextField(u'Подпись', null=True, blank=True) comments = models.IntegerField(u'Количество коментариев', default=0) likes = models.IntegerField(u'Количество лайков', default=0) link = models.CharField(u'Ссылка на пост', max_length=255) type = models.CharField(u'Тип поста', max_length=12, choices=InstagramType, default=InstagramType.Image, null=True, blank=True) carousel_media = models.BooleanField(u'Карусель', default=False) import_content = models.BooleanField(u'Контент импортирован', default=False) def thumbnail_400(self): image = self.images.all().first() if image and image.image: return image.size_X(400) else: return None def main_photo(self): image = self.images.all().first() if image and image.image: return image.image else: return None class Meta: verbose_name = u'пост из инстаграмма' verbose_name_plural = u'Пост из инстаграмма' ordering = ['-created_time'] def __unicode__(self): return self.instagram_id class InstagramImage(models.Model): path = PathAndRename("instagram/import/") instagram = models.ForeignKey('InstagramPost', verbose_name=u'Пост', related_name='images') t_link = models.CharField(u'Ссылка на thumbnail фото', max_length=255, null=True, blank=True) s_link = models.CharField(u'Ссылка на маленькое фото', max_length=255, null=True, blank=True) i_link = models.CharField(u'Ссылка на большое фото', max_length=255, null=True, blank=True) image = ImageField(u'Фото', upload_to=path, null=True, blank=True) processed = models.BooleanField(u'Фото скачано на локал', default=False ) def size_150(self): return get_thumbnail(self.image, '150x150', crop='center', quality=70) def size_X(self, size): return get_thumbnail(self.image, '%dx%d' % (size, size), crop='center', quality=70) class Meta: verbose_name = u'фото инстаграм поста' verbose_name_plural = u'Фото инстаграм поста' def __unicode__(self): return u'%s' % self.id class InstagramVideo(models.Model): instagram = models.ForeignKey('InstagramPost', verbose_name=u'Пост', related_name='videos') l_link = models.CharField(u'Ссылка на видео низкого качества', max_length=255, null=True, blank=True) h_link = models.CharField(u'Ссылка на видео высокого качества', max_length=255, null=True, blank=True) class Meta: verbose_name = u'видео инстаграм поста' verbose_name_plural = u'Видео инстаграм поста' def __unicode__(self): return u'%s' % self.id class InstagramTag(models.Model): instagram = models.ForeignKey('InstagramPost', verbose_name=u'Пост', related_name='tags') tag = models.CharField(u'Тэг', max_length=255) class Meta: verbose_name = u'тег инстаграм поста' verbose_name_plural = u'Теги инстаграм поста' ordering = ['tag'] def __unicode__(self): return u'#%s' % self.tag
./apps/instagram/forms.py
# -*- coding:utf-8 -*- from django import forms from django.contrib.admin.widgets import AdminFileWidget from django.utils.safestring import mark_safe from sorl.thumbnail import get_thumbnail from .models import InstagramImage class AdminImageWidget(AdminFileWidget): def render(self, name, value, attrs=None): output = [] if value and getattr(value, "url", None): output.append('' % get_thumbnail(value, '100x100', crop='center', quality=70).url) output.append(super(AdminFileWidget, self).render(name, value, attrs)) return mark_safe(u''.join(output)) class ImageForm(forms.ModelForm): class Meta: model = InstagramImage fields = '__all__' widgets = { 'image' : AdminImageWidget, }
./apps/instagram/admin.py
# -*- coding:utf-8 -*- from __future__ import unicode_literals import nested_admin from django.contrib import admin from apps.instagram.models import InstagramImage, InstagramVideo, InstagramTag, InstagramPost from apps.instagram.forms import ImageForm class InstagramImageInline(nested_admin.NestedStackedInline): form = ImageForm model = InstagramImage extra = 0 fieldsets = ( ( u'', { 'fields' : ( ('image', ), ) }), ) class InstagramVideoInline(admin.TabularInline): model = InstagramVideo extra = 0 class InstagramTagInline(admin.TabularInline): model = InstagramTag extra = 0 class InstagramPostAdmin(admin.ModelAdmin): list_display = ('created_time', 'thumbnail_150', 'import_content', 'caption') inlines = [ InstagramImageInline, InstagramVideoInline, InstagramTagInline, ] admin.site.register(InstagramPost, InstagramPostAdmin)
Добавляем в settings.py
в INSTALLED_APPS
новый модуль
./project/settings.py
... INSTALLED_APPS = [ ... 'apps.instagram', ... ...
Делаем и применяем миграции
Консоль
(env) ./project $ ./manage.py makemigrations (env) ./project $ ./manage.py migrate
Я предлагаю следующую схему работы: сначала получаем последние посты из Instagram, потом парсим вывод и сохраняем себе на сайт. Есть одно ограничение, скачиваются не больше 20 постов.
Для такой работы нам надо получить получить Instagram access token
. Делается это на странице https://www.instagram.com/developer/
Нужно нажать на кнопку Register Your Application
.
Далее нужно заполнить форму, перейти на вкладку Security
и снять галочку с пункта Disable implicit OAuth
и нажать на кнопку Register
Теперь на странице https://www.instagram.com/developer/clients/manage/ должно отобразиться новое приложение.
Получим токен. Нужно в следующей ссылке заменить CLIENT_ID на строку из приложения, и URL на Valid redirect URIs
(в моем примере это «https://omoroot.ru/»):
https://instagram.com/oauth/authorize/?client_id=CLIENT_ID&redirect_uri=URL&response_type=token
После перехода по этой ссылке, должна открыться страница, где нужно подтвердить доступ к новому приложению.
После нажатия на кнопку «Авторизовать» будет редирект на сайт, и в строке с URL будет написан ваш access_token. В нем будет две точки. Строка до первой точки — USER_ID
, весь токен — ACCESS_TOKEN
Теперь нужно написать команды импорта новых постов и импорта содержимого этих постов. Для этого сначала нужно создать структуру каталогов:
Консоль
(env) ./project $ mkdir -p apps/instagram/management/commands/ (env) ./project $ touch apps/instagram/management/__init__.py (env) ./project $ touch apps/instagram/management/commands/__init__.py (env) ./project $ touch apps/instagram/management/commands/import_instagram.py (env) ./project $ touch apps/instagram/management/commands/import_instagram_content.py
Команда получения новых постов
apps/instagram/management/commands/import_instagram.py
# -*- coding: utf-8 -*- from django.core.management.base import BaseCommand, CommandError from requests import get from datetime import datetime import pytz from apps.instagram.models import InstagramType, InstagramPost, InstagramImage, InstagramVideo, InstagramTag USER_ID = '' ACCESS_TOKEN = '' def get_data_from_json(json_text): answer = list() for element in json_text['data']: answer.append(element) return answer def get_media(user_id, access_token): answer = list() data = get('https://api.instagram.com/v1/users/%s/media/recent/?access_token=%s' % (user_id, access_token), verify=True).json() try: if data[u'meta'][u'code'] == 200: if data[u'pagination'] == {}: answer += get_data_from_json(data) else: answer += get_data_from_json(data) while True: if data[u'pagination'] != {}: data = get(data[u'pagination'][u'next_url'], verify=True).json() answer += get_data_from_json(data) else: break else: answer = list() except: answer = list() return answer class Command(BaseCommand): def handle(self, *args, **options): answer = get_media(USER_ID, ACCESS_TOKEN) already_added = InstagramPost.objects.all().values_list('instagram_id', flat=True) for item in answer: instagram_id = item[u'id'] # Если нет такого поста, то добавляем if instagram_id not in already_added: created_time = datetime.fromtimestamp(int(item[u'created_time']), tz=pytz.UTC) type = item[u'type'] if type in [t for (t, d) in InstagramType]: type = InstagramType.by_value(type) carousel_media = False else: type = None carousel_media = True ip = InstagramPost.objects.create( instagram_id = instagram_id, created_time = created_time, comments = int(item[u'comments'][u'count']), likes = int(item[u'likes'][u'count']), link = item[u'link'], type = item[u'type'], carousel_media = carousel_media ) try: ip.caption = item[u'caption'][u'text'] ip.save() except: pass # Добавляем теги try: for tag in item[u'tags']: InstagramTag.objects.create( instagram = ip, tag = tag ) except: pass # Если пост не карусель if not ip.carousel_media: ii = InstagramImage.objects.create( instagram = ip, t_link = item[u'images'][u'thumbnail'][u'url'], s_link = item[u'images'][u'low_resolution'][u'url'], i_link = item[u'images'][u'standard_resolution'][u'url'] ) # Если это видео пост if ip.type == InstagramType.Video: iv = InstagramVideo.objects.create( instagram = ip, l_link = item[u'videos'][u'low_resolution'][u'url'], h_link = item[u'videos'][u'standard_resolution'][u'url'] ) # Пост — карусель else: for carousel_item in item[u'carousel_media']: # Если это фото пост if u'images' in carousel_item: ii = InstagramImage.objects.create( instagram = ip, t_link = carousel_item[u'images'][u'thumbnail'][u'url'], s_link = carousel_item[u'images'][u'low_resolution'][u'url'], i_link = carousel_item[u'images'][u'standard_resolution'][u'url'] ) # Если это видео пост if u'videos' in carousel_item: iv = InstagramVideo.objects.create( instagram = ip, l_link = carousel_item[u'videos'][u'low_resolution'][u'url'], h_link = carousel_item[u'videos'][u'standard_resolution'][u'url'] )
Команда сохранения картинок на локале. Дело в том, что импортированные ссылки на картинки со временем протухают, поэтому нужно их сохранить.
apps/instagram/management/commands/import_instagram_content.py
# -*- coding: utf-8 -*- from django.conf import settings from django.core import files from django.core.management.base import BaseCommand, CommandError import requests import tempfile from apps.instagram.models import InstagramType, InstagramPost, InstagramImage, \ InstagramVideo, InstagramTag def get_media(url, obj): request = requests.get(url, stream=True) if request.status_code != requests.codes.ok: return None filename = url.split('/')[-1] lf = tempfile.NamedTemporaryFile() for block in request.iter_content(1024 * 8): if not block: break lf.write(block) obj.image.save(filename, files.File(lf)) return obj class Command(BaseCommand): def handle(self, *args, **options): posts_for_import = InstagramPost.objects.filter(import_content=False) for post in posts_for_import: for image in post.images.all(): if not image.processed: image.processed = True image.save() get_media(image.i_link, image) post.import_content = True post.save()
Дальше остается только прописать обе команды в крон так, чтобы они выполнялись одна за другой.
Консоль
(env) ./project $ ./manage.py import_instagram (env) ./project $ ./manage.py import_instagram_content