Кросспостинг постов из Instagram в паблик VK на Python +8




Предисловие


Решил выйти на новый рынок сбыта, тем более, целевая аудитория моего интернет-магазина, не имеющая аккаунтов в Instagram, давно интересовалась появлением дубликата в VK. Идея хорошая, но постов на странице сотни, соответственно вручную работать ctrl+c ctrl+v не хотелось, плюс дальнейшие перспективы обезьянней работы не впечатляли.

Уверенный, что в интернете полно бесплатный решений, я начал гуглить. Естественно, первые страницы поисковой выдачи пестрят платными сервисами, с довольно обширными функционалами. Но мне, всего лишь на всего, надо было перенести все посты со страницы Instagram в паблик VK и в дальнейшем синхронно пополнять его.

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

Документация по api Instagram и VK довольно подробная и задачка не кажется сложной. Освободив себе пару вечеров, я приступил к работе. Первым делом необходимо было получить токены как в Instagram, так и в VK. С этим проблем не было, оба были получены за пару минут.

Дальше меня ждал первый подводный камень…

Кросспостинг первых 20 постов


К своему удивлению я обнаружил, что после изменений в политике Instagram, можно было получить json-словарь (ссылки на фото, описание к посту, дату публикации…) лишь к последним 20 постам страницы. Мне это подходило для второй задачи – обновление паблика новыми постами время от времени. Потому что новые публикации у меня появляются не так часто и 20 постов — вполне удобно. Было решено сначала взяться за эту задачку.

Получаем сессию VK и объявляем необходимые переменные:

session = vk.Session(
    access_token='123abc') # вместо 123abc свой токен
vk_api = vk.API(session, v='5.85') 
groupID = '12345678'  #id паблика vk

upload_url = vk_api.photos.getWallUploadServer(group_id=groupID)['upload_url']  #получаем сервер vk для загрузки
data = []                                           
photo_id = 0                                        
attachments = []
direct = 'C:/Users/jo/PycharmProjects/repost/foto'  # директория для хранения фото
d = date(2019, 3, 21)                               # дата, с которой начинать публиковать посты
data_parsing = int(time.mktime(d.timetuple()))      # перевод её в unix Формат

Далее пишем основную функцию. Для начала получим массив данных, с которым и будем работать:

answer = get(
    'https://api.instagram.com/v1/users/[id_inst]/media/recent?access_token=[access_token]',
    verify=True).json()

В запросе необходимо вставить свои данные вместо квадратных скобок. Найти id страницы по username можно, например, здесь.

Если нам что-то вернули идём дальше:

if answer:
    for x in answer['data']:  # для каждого поста из json словаря
        global photo_id         # обнуляем массивы и переменные
        photo_id = 0
        global attachments
        attachments = []
        global data
        data = []
        date_create = x['created_time']  # date_create дата создания в Unix
        if int(date_create) > data_parsing:
            n = 0  # название фото по порядку
            if not os.path.isdir(direct + '/' + date_create):  # если нет папки создаем
                os.makedirs(direct + '/' + date_create)
                if 'carousel_media' in x:  # если несколько фото в посте
                    for a in x['carousel_media']:
                        li = list(a.keys())  # все ключи словаря в массив
                        if li.count('videos') == 0:  # если это не видео, мне просто они не нужны
                            req.urlretrieve(a['images']['standard_resolution']['url'],
                                            "foto/" + date_create + "/" + str(n) + ".jpg")  # загружаем фото к себе в папку
                            if x['caption'] is not None:  # описание поста
                                text = str(x['caption']['text'])
                            else:
                                text = ' '
                            n = n + 1
                    post_foto(date_create, text)
                else:       # если в посте одно фото
                    req.urlretrieve(x['images']['standard_resolution']['url'], "foto/" + date_create + "/0.jpg")
                    if x['caption'] is not None:  # описание поста
                        text = str(x['caption']['text'])
                    else:
                        text = ' '
                    post_foto(date_create, text)

Каюсь
Знаю, что так работать с глобальными переменными плохо, но размеры скрипта позволяют не вникать в сложности

Итак, если дата публикации поста больше даты которую мы задали, создаём папку, название которой (внимание тавтология, особо впечатлительным пропустить) дата её публикации. Далее идут проверки на количество фото и на видео. Наверняка можно прикреплять и его, мне это просто не нужно. Загружаем фотографии в созданную папку. Берём описание к посту по ключу caption и переходим к функции post_foto:

def post_foto(date_create, text):
    quantity_foto = len([name for name in os.listdir(direct + "/" + date_create) if
                       os.path.isfile(os.path.join(direct + "/" + date_create, name))])  # количество фото в папке
    append_attach(quantity_foto, date_create)
    params = {'attachments': attachments,
              'message': text,
              'owner_id': '-' + groupID,
              'from_group': '1'}
    vk_api.wall.post(**params)  # публикуем пост в VK с заданными параметрами

Определяем количество фото в папке, загружаем их на сервер VK, добавляем в параметры поста и публикуем его в паблике. Добавление в параметры осуществляется с помощью функции append_attach:

def append_attach(x, directory):  # добавление в параметры поста
    for t in range(x):
        upload_foto(t, directory)
        global photo_id
        photo_id = data[t][0]['id']
        global attachments
        attachments.append('photo' + str(data[t][0]['owner_id']) + '_' + str(photo_id))  # добавляем id фото в параметры поста

А непосредственно загрузка фото на сервер VK выполняется функцией upload_foto:

def upload_foto(num_foto, directory):  # загрузка фото на сервер
    request = requests.post(upload_url, files={'photo': open("foto/" + directory + "/" + str(num_foto) + ".jpg", "rb")})
    params = {'server': request.json()['server'],
              'photo': request.json()['photo'],
              'hash': request.json()['hash'],
              'group_id': groupID}
    global data
    data.append(vk_api.photos.saveWallPhoto(**params))

Со второй задачей разобрались. Скрипт можно выполнять как самому, так и поставить по расписанию (например, в cron, один раз в 15 минут). А как теперь перенести все остальные сотни постов?

Перенос всей страницы


Часть скрипта у нас уже готова, та которая отвечала за сами публикации в VK. Осталось найти способ выкачать все фото и описания к ним. Я не стал заморачиваться с парсингом исходных кодов страниц Instagram и взял готовое решение. На самом деле я уверен, что таких программ много, я использовал первую попавшуюся бесплатную (4K Stogram). Интуитивно понятный интерфейс позволяет быстро справиться со скачкой всех фотографий со страницы. В меню также есть экспорт всех описаний к постам. Нам нужен «*.txt» формат. Осталось лишь разложить все фотографии по папкам (один пост – одна папка) и спарсить по регулярному выражению описания к постам из текстовика.

Разложить фото по папкам нам поможет следующий код:

i = 0
for top, dirs, files in os.walk(os.getcwd()+"\\res\\"):
    for nm in files:
        if re.findall(r'\d\d\.\d\d\.\d\d', nm):
            old_file = os.path.join(top, nm)
            frq = re.findall(r'\d\d\d\d-\d\d-\d\d \d\d\.\d\d\.\d\d', str(nm))
            frq = str(frq[0])
            if not os.path.exists(os.getcwd()+"\\res\\" + frq):
                i = 0
                os.makedirs(os.getcwd() + "\\res\\" + frq)
                new_file = os.path.join(os.getcwd() + "\\res\\" + frq, str(i)+'.jpg')
                os.rename(old_file, new_file)
                i = i+1
            else:
                new_file = os.path.join(os.getcwd() + "\\res\\" + frq, str(i)+'.jpg')
                os.rename(old_file, new_file)
                i = i+1

Здесь ключевым моментом является время публикации, которое и объединяет несколько фото в одну папку.

Ну а дальше все просто. Открываем каждую папку, загружаем все фотографии на сервер, прикрепляем описание и публикуем. Не забываем про ограничение VK: не более 50-ти постов в день:

f = open(os.getcwd() + "\input_opis\\gusi.txt", "rt", errors="ignore", encoding='utf-8')  # открываем текстовик с описаниями
data = f.read()  # открываем целиком, чтобы не пропустить переносы строк
result = re.findall(r'"([^\"]*)"', data, re.S)
new_x = [el for el, _ in groupby(result)]  # удаляем дубликаты из списка
dlina = new_x.__len__()
i = 1
f.close()

for top, dirs, files in os.walk(os.getcwd() + "\\res\\"):   #папка с фото
    for nm in dirs:
        attachments = []
        photo_id = 0
        data = []
        DIR = 'C:/Users/jo/PycharmProjects/repost/res/' + nm  #путь к папке с фото
        quantity_foto = len([name for name in os.listdir(DIR) if os.path.isfile(os.path.join(DIR, name))])
        post_foto(quantity_foto, nm)
        attachments.reverse()   # разворачиваем список
        params = {'attachments': attachments,
                  'message': new_x[dlina - i],
                  'owner_id': '-' + groupID,
                  'from_group': '1'}
        vk_api.wall.post(**params)
        i = i + 1

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

Все 3 скрипта целиком:

Кросспостинг первых 20 постов
from requests import get
import urllib.request as req
import vk
import requests
import os
import time
from datetime import date

session = vk.Session(
    access_token='123abc')
vk_api = vk.API(session, v='5.85')
groupID = '12345678'  #id паблика vk

upload_url = vk_api.photos.getWallUploadServer(group_id=groupID)['upload_url']  #получаем сервер vk для загрузки
data = []
photo_id = 0
attachments = []
direct = 'C:/Users/jo/PycharmProjects/repost/foto'  # директория для хранения фото
d = date(2019, 3, 21)                               # дата, с которой начинать публиковать посты
data_parsing = int(time.mktime(d.timetuple()))      # перевод её в unix Формат


def upload_foto(num_foto, directory):  # загрузка фото на сервер
    request = requests.post(upload_url, files={'photo': open("foto/" + directory + "/" + str(num_foto) + ".jpg", "rb")})
    params = {'server': request.json()['server'],
              'photo': request.json()['photo'],
              'hash': request.json()['hash'],
              'group_id': groupID}
    global data
    data.append(vk_api.photos.saveWallPhoto(**params))

def append_attach(x, directory):  # загрузка на сервер каждой фото из папки и добавление в параметры поста
    for t in range(x):
        upload_foto(t, directory)
        global photo_id
        photo_id = data[t][0]['id']
        global attachments
        attachments.append('photo' + str(data[t][0]['owner_id']) + '_' + str(photo_id))  # добавляем id фото в параметры поста


def post_foto(date_create, text):
    quantity_foto = len([name for name in os.listdir(direct + "/" + date_create) if
                       os.path.isfile(os.path.join(direct + "/" + date_create, name))])  # количество фото в папке
    append_attach(quantity_foto, date_create)
    params = {'attachments': attachments,
              'message': text,
              'owner_id': '-' + groupID,
              'from_group': '1'}
    vk_api.wall.post(**params)  # публикуем пост в VK с заданными параметрами


def get_all():
    answer = get(
        'https://api.instagram.com/v1/users/12345678/media/recent?access_token=123abc',
        verify=True).json()     # получаем большой массив из instagram
    if answer:
        for x in answer['data']:  # для каждого поста из json словаря
            global photo_id         # обнуляем массивы и переменные
            photo_id = 0
            global attachments
            attachments = []
            global data
            data = []
            date_create = x['created_time']  # date_create дата создания в Unix
            if int(date_create) > data_parsing:
                n = 0  # название фото по порядку
                if not os.path.isdir(direct + '/' + date_create):  # если нет папки создаем
                    os.makedirs(direct + '/' + date_create)
                    if 'carousel_media' in x:  # если несколько фото в посте
                        for a in x['carousel_media']:
                            li = list(a.keys())  # все ключи словаря в массив
                            if li.count('videos') == 0:  # если это не видео, мне просто они не нужны
                                req.urlretrieve(a['images']['standard_resolution']['url'],
                                                "foto/" + date_create + "/" + str(n) + ".jpg")  # загружаем фото к себе в папку
                                if x['caption'] is not None:  # описание поста
                                    text = str(x['caption']['text'])
                                else:
                                    text = ' '
                                n = n + 1
                        post_foto(date_create, text)
                    else:       # если в посте одно фото
                        req.urlretrieve(x['images']['standard_resolution']['url'], "foto/" + date_create + "/0.jpg")
                        if x['caption'] is not None:  # описание поста
                            text = str(x['caption']['text'])
                        else:
                            text = ' '
                        post_foto(date_create, text)


get_all()


Сортировка фото по папкам
import re
import os

i = 0
for top, dirs, files in os.walk(os.getcwd()+"\\res\\"):
    for nm in files:
        if re.findall(r'\d\d\.\d\d\.\d\d', nm):
            old_file = os.path.join(top, nm)
            frq = re.findall(r'\d\d\d\d-\d\d-\d\d \d\d\.\d\d\.\d\d', str(nm))
            frq = str(frq[0])
            if not os.path.exists(os.getcwd()+"\\res\\" + frq):
                i = 0
                os.makedirs(os.getcwd() + "\\res\\" + frq)
                new_file = os.path.join(os.getcwd() + "\\res\\" + frq, str(i)+'.jpg')
                os.rename(old_file, new_file)
                i = i+1
            else:
                new_file = os.path.join(os.getcwd() + "\\res\\" + frq, str(i)+'.jpg')
                os.rename(old_file, new_file)
                i = i+1


Постинг в VK всех публикаций
import os
import vk
import requests
import re
from itertools import groupby

session = vk.Session(
    access_token='123abc')
vk_api = vk.API(session, v='5.85')
groupID = '12345678'

upload_url = vk_api.photos.getWallUploadServer(group_id=groupID)['upload_url']
data = []
photo_id = 0
attachments = []


def upload_foto(num_foto, direc):
    request = requests.post(upload_url, files={'photo': open('res/' + direc + "/" + str(num_foto) + ".jpg", "rb")})
    params = {'server': request.json()['server'],
              'photo': request.json()['photo'],
              'hash': request.json()['hash'],
              'group_id': groupID}
    global data
    data.append(vk_api.photos.saveWallPhoto(**params))


def post_foto(x, direc):
    for i in range(x):
        upload_foto(i, direc)
        global photo_id
        photo_id = data[i][0]['id']
        global attachments
        attachments.append('photo' + str(data[i][0]['owner_id']) + '_' + str(photo_id))

f = open(os.getcwd() + "\input_opis\\gusi.txt", "rt", errors="ignore", encoding='utf-8')  # открываем текстовик с описаниями
data = f.read()  # открываем целиком, чтобы не пропустить переносы строк
result = re.findall(r'"([^\"]*)"', data, re.S)
new_x = [el for el, _ in groupby(result)]  # удаляем дубликаты из списка
dlina = new_x.__len__()
i = 1
f.close()

for top, dirs, files in os.walk(os.getcwd() + "\\res\\"):   #папка с фото
    for nm in dirs:
        attachments = []
        photo_id = 0
        data = []
        DIR = 'C:/Users/jo/PycharmProjects/repost/res/' + nm  #путь к папке с фото
        quantity_foto = len([name for name in os.listdir(DIR) if os.path.isfile(os.path.join(DIR, name))])
        post_foto(quantity_foto, nm)
        attachments.reverse()   # разворачиваем список
        params = {'attachments': attachments,
                  'message': new_x[dlina - i],
                  'owner_id': '-' + groupID,
                  'from_group': '1'}
        vk_api.wall.post(**params)
        i = i + 1




К сожалению, не доступен сервер mySQL