Автоматический постинг своего Instagram на сайт

Автоматическое размещение фотографий из instagram на сайт

Как показала практика, очень удобно размещать фотографии на сайте, импортируя их автоматически из Instagram аккаунта. Сегодня поговорим об этом.

Фото из 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
Читать дальше

Похожие записи

Весь блог →
· Разработка

Apple Silicon

Настройка компьютера на новой архитектуре

· Разработка

Telegram bot на локале

При разработке любых приложений их приходится тестировать, а значит, инструмент должен быть рабочим. В современных реалиях просто необходимо, чтобы в продакшене находился …

· Разработка

Ajax captcha на django сайт

Капча нужна для того, чтобы формой обратной связи пользовались только люди, а не спам-боты. Алгоритм работы встраиваемой капчи такой: В форму отправки …