Автоматический постинг своего 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


