среда, 31 июля 2013 г.

Использование api vk python : Скачивание стены Вконтакте

Аватарка группы
Евгений Онегин: Глава 11
  


Введение



Добрый день, давно я ничего не писал - много было причин. За это время накопилось достаточно большое количество материала и я постараюсь его сюда выставить как можно полнее. Сегодня  я хочу рассказать о работе с API контакта через Python, а примером послужит - скачивание стены журнала Евгений Онегин. Глава 11. Если вам лень читать как я всё это делал, а охота поскорее скачать свою стену, переходите сразу к разделу "Отчёт и тестирование".




Подготовка

Первое, что нужно сделать - скачать АПИ и зарегистрировать приложение, получив его идентификатор ( если вам не охота регистрировать своё приложения и получать его идентификатор, то просто пропустите этот шаг - по умолчанию программа подставит мой идентификатор приложения) . АПИ доступно по этому адресу https://pypi.python.org/pypi/vkontakte .Там есть инструкция по установке. Если-же не получается по ней, то скачайте файлы, разархивируйте, в терминале выполните (Linux)

[alex@archalex vkontakte]$ sudo python2 ./setup.py install
[alex@archalex vkontakte]$ python2
Python 2.7.5 (default, May 12 2013, 12:00:47) 
[GCC 4.8.0 20130502 (prerelease)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import vkontakte

После установки мы можем пользоваться функционалом этой библиотеки, однако с некоторых пор стало обязательным создание окна браузера, чтоб пользователь там ввёл свои логин и пароль, и только потом мы получили token_id для работы с API питона. Чтоб не лезть в эти дебри можно прикинуться окном броузера, фиктивно ввести туда валидные логин и пароль и получить свой законный токен. В этой статье рассказано более подробно про этот маленький хак. В результате статьи был создан класс авторизации, в который сообщается логин, пароль, идентификатор приложения, запрашиваемые права. Я немного подредактировал этот файл, так как он не работал. Скачать мой вариант можно тут.

Первые шаги

Для первого примера давайте сделаем простую авторизацию с неким запросом.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import vk_auth
import vkontakte

def main():
 (token,user_id) = vk_auth.auth('$EMAIL', '$PASSWD', '$ID_APP', '$SOPE')
 vk = vkontakte.API(token=token)
 print "Hello vk API , server time is ",vk.getServerTime()
 return 0

if __name__ == '__main__':
 main()

Ничего сложного -  импортируем класс авторизации, импортируем библиотеку c API, получаем токен и идентификатор, используем АПИ-шные функции.

Пример

 Теперь пришло время к массивному, серьёзному, а главное практичному примеру - скачивание стены Вконтакте. Идея скачивания стены зародилась из острой необходимости. Паблик Вконтакте организован очень удобно, за одним исключением - старую информацию на стене невозможно найти. Родилась идея - скачать всю стену из паблика и разбить её на несколько HTML фалов для лучшей скорости доступа.
Перед началом работы нужно поставить грамотное техническое задание.

Техническое задание

Скрипт или набор скриптов для скачивания стены из социальной сети Вконтакте.
Входные данные:
  • Имя пользователя
  • Пароль
  • Идентификатор группы\паблика\страницы
  • Количество постов для скачивания
  • Начиная с какого сообщения скачивать
  • На сколько HTML блоков разбивать
Приложение должно скачать всю текстовую и графическую информацию со стены для удобного оффлайн просмотра. Если на стене есть видео или ссылки, то приложение должно сообщить их название и дать ссылку  в интернет. Ещё приложение должно скачивать все комментарии к постам и оформлять их должным образом(аналогично постам) .

Реализация

Обобщённый
алгоритм работы программы
Определимся с каркасом программы и входящими параметрами.
Набросаем пока такой код. Я использовал библиотеку OptionParser для парсинга параметров - она идёт из коробки и о-о-очень удобная. Следующий шаг - организация разбивки на тома. Чуть изменив старый код, мы получаем алгоритм, который разобьёт информацию на необходимое количество томов. Вот код. Далее необходимо использовать vk API и загружать посты из Контакта. Покурив документацию, я быстро нашел необходимую функцию . Апи контакта позволяет отправлять JSON запросы и получать JSON ответы. Используемой нами vkAPI инкапсулирует JSON запросы в список переменных. Не могу сказать, что это очень удобно, но я не нашел другого способа. Для запроса постов со стены определённой группы нам необходимо выполнить следующий код


posts = vk.get('wall.get', owner_id=owner_id,offset=offset,count=count)

После выполнения этого кода в posts запишется count постов со смещением offset. Стоит обратить внимание, что на количество скачиваемых постов есть ограничение и, если я не ошибаюсь, оно равно 100. Я взял значение 50, как оптимальное количество скачиваемых постов за 1 итерацию цикла. В прошлом листинге я выделил 2 функции: скачивание постов get_posts() и форматирование в HTML posts_to_html(), и поставил на них заглушки. Теперь я заполняю get_posts() кодом, который привёл выше. В техническом задании ещё требовалось скачать все комментарии к коду. Я разделил функцию скачивания постов и оформления их в html, а значит скачивание комментариев должно производиться вместе со скачиванием постов и записываться в одну переменную. Благо, JSON - формат динамический и его несложно дополнить одним полем, в котором будут храниться комментарии.
В документации находим функцию скачивания комментариев и реализуем. Функцию скачивания поста с комментариями разобьём на 2 функции - скачивание поста и скачивание комментариев к нему. Получаем вот такой код:


def get_comments(vk,owner_id,post_id,count):
 size = 50
 comments = []
 if(count<size):
  com  = vk.get('wall.getComments', owner_id=owner_id,post_id=post_id,count=count)
  comments += com[1:]
 else:
  offset = 0
  while(offset<count):
   com = vk.get('wall.getComments', owner_id=owner_id,post_id=post_id,count=size,offset=offset)
   comments += com[1:]
   offset+=size
 return  comments
 
def get_posts(vk,owner_id,offset,count):
 posts = vk.get('wall.get', owner_id=owner_id,offset=offset,count=count)
 for i in range(1,len(posts)):
  cur = posts[i]
  coments_count = posts[i]['comments']['count']
  if(coments_count>0):
   post_id = posts[i]['id']
   comments = get_comments(vk,owner_id,post_id,coments_count)
   posts[i]['comments']['data']=comments
   print len(comments),coments_count
 return posts

Теперь функция get_posts вернёт посты, в которых в поле comments.data будут храниться комментарии к ним.
На этом работа со скачиванием данных закончена.Необходимо ещё из  этой всей канители сформировать красивые HTML странички. Скажу честно - для меня это самый адский труд. Но других путей нет - вперёд.

Формирование HTML страниц.

Для формирования я написал целый ряд шаблонов, в которые потом буду вставлять данные из JSON. Я не буду их всех здесь приводить. Просто дам ссылку на готовый проект. Функция, формирования HTML вышла огромная и некрасивая.Если будут люди - профи в HTML, буду им благодарен за более благоприятный дизайн, который более схож с Vk.

Скачивание Данных.

Одним из пунктов ТЗ был удобный локальный просмотр. В данный момент все ссылки на картинки и аудиозаписи - абсолютные (то есть ведут в интернет) а нужно чтоб они лежали рядом и при отсутствии интернета можно было и картинку посмотреть и музычку прослушать. Можно это сделать питоном через жуткую Караганду. Но я предпочёл вариант поэлегантней - wget.  Эта стандартная линуксовская интрнето-качалка и она умеет скачивать web-страницу с информацией на ней.Чтобы скормить HTML-ки нужно ещё почитать man wget и найти несколько необходимых ключей.
Получился вот такой скрипт:

$wget -B -k -r -l0 --force-html -i name-*.html -P ./data

Одно только НО - wget скачает всю информацию, но не преобразует ссылки.Может, конечно, он это и умеет делать, но я не нашел как. Поэтому в питоне мы напишем преобразование от любого контактовского адреса хранения фото\аудио в ссылку на рядом лежащую папку {ИмяГруппы}_{НомерТома}data. Я умышленно разделил имя группы и номер тома, чтобы можно было переместить за собой только один том и не тащить за собой несчётное количество информации, которая не используется.

def convert_lincs(txt,folder,recurce = 0): 
 out = re.sub("http://",folder,txt)
 return out

Как видите, благодаря мощи регулярных выражений эту задачу мы решили в 1 строку.
Ещё я столкнулся с проблемой постоянно прерывающийся связи (у меня плохой интернет) и отказа со стороны Контакта из-за высокой частоты запросов. Эти 2 фактора периодически валили программу, поэтому я сделал отказоустойчивые обёртки для всех методов обращения в интернет, которые использовал. Получилось вот так:

def wall_getComments(vk,owner_id,post_id,count,recurce = 0):
 if (recurce ==20):
  return []
 try:
  com  = vk.get('wall.getComments', owner_id=owner_id,post_id=post_id,count=count)
 except:
  time.sleep(1)
  print "Error wall_getComments try ",recurce
  com = wall_getComments(vk,owner_id,post_id,count,recurce = recurce+1)
 return com
def wall_get(vk,owner_id,offset,count,recurce = 0):
 if (recurce ==20):
  return []
 try:
  res = vk.get('wall.get', owner_id=owner_id,offset=offset,count=count)
 except:
  time.sleep(1)
  print "Error wall_get try ",recurce
  res = wall_get(vk,owner_id,offset,count,recurce = recurce +1)
 return res
def users_get(vk,uids,fields,recurce = 0):
 if (recurce ==20):
  return []
 try:
  res = vk.get('users.get',uids=uids,fields="photo")
 except:
  time.sleep(1)
  print "Error users_get try ",recurce
  res = users_get(vk,uids,fields,recurce = recurce +1)
 return res
def groups_getById(vk,group_ids,recurce = 0):
 if (recurce ==20):
  return []
 try:
  res = vk.get('groups.getById', group_ids=group_ids)
 except:
  time.sleep(1)
  print "Error groups_getById try ",recurce
  res = groups_getById(vk,group_ids,recurce = recurce +1)
 return res

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

Отчёт и тестирование

Результат работы я публикую на Github. Так , что вы можете присоединиться  к проекту, взять мои наработки, добавить свои, переделать под свои нужды  и просто развивать проект.
И вот, наконец, тестирование. Обратите внимание на то, что я буду использовать Python2.X и не ругайтесь заранее, увидев что-то вроде этого при попытке запустить программу:

[alex@archalex WallCopy]$ python ./WallCopy.py 
  File "./WallCopy.py", line 40
    print "%s Failure"%"wall_getComments" 
                     ^
SyntaxError: invalid syntax

Программа выдаёт справку на аглицком если запустить её с ключём -h. Для ленивых перевожу на русский:
  •  -e EMAIL, --email=EMAIL Имеил для авторизации   
  • -p PASSWD, --passwd=PASSWD Пароль для авторизации, если вы его не задали в явном виде, программа попросит вас ввести его вовремя выполнения.Конечно пароль будет невидим. 
  • -i GID, --groupid=GID Идентификатор группы, стену которой необходимо скачать
  • -c COUNT, --count=COUNT - количество постов, которые необходимо скачать. Если не задано - выкачивает все записи со стены. 
  • -f OFFSET, --offset=OFFSET - смещение, с которого необходимо начать скачивание, если не задано , то 0. 
  • -s SPLIT_NUM, --split=SPLIT_NUM  - количество блоков, на которые разбить скачанные посты, если не задано, то 1
  • -a APP_ID, --app_id=APP_ID - идентификатор приложения, которое вы получили зарегестрировавшись ( если не указано, то используется мой идентификатор приложения)
  • -d DOWNLOAD, --download_all=DOWNLOAD - ключ, который определяет, следует ли загружать мультимедиа. Возможные значения:
    • 0 - Не загружать ( по умолчанию)
    • 1 - загружать после того, как основная информация загрузится
   
Типичный пример использования:

python2 ./WallCopy.py -e EMAIL -i GROUP_ID

При этом загрузятся все записи со стены с нулевым смещением, но ссылки останутся абсолютными. Пароль программа спросит в интерактивном режиме, а остальные опции примут значения по умолчанию.

Расширенный пример использования:


python2 ./WallCopy.py -e EMAIL -p PASSWORD -i GROUP_ID -c 1000 -f 500 -s 8 -a APP_ID -d 1


При этом загрузится 1000 постов со смещением на 500 и разобьётся на 8 томов, и картинки с музыкой загрузятся на компьютер.

На данном этапе есть много вещей, которые программа не поддерживает, а хотелось бы:
  1. Загрузка только фото и игнорирование музыки;
  2. Загрузка документов;
  3. Загрузка репостов;
  4. Более похожий дизайн на Vk;
  5. Превью Видео;
  6. Требование Linux ( wget )
  7. Поддержка только Python 2.X
  8. Использование ООП
На этом, пожалуй, всё.  Буду очень рад обратной связи и талантливых HTML верстальщиков для лучшего дизайна.



13 комментариев:

  1. Спасибо за полезный материал!

    Скажите, насколько сложно запустить данные скрипты из-под Windows?

    ОтветитьУдалить
    Ответы
    1. Конечно ! Необходимо скачать Питон с официального сайта и скачать vkontakte от сюда. https://pypi.python.org/pypi/vkontakte

      Удалить
    2. Спасибо за ответ. Правильно ли я понимаю, что в строке:
      (token,user_id) = vk_auth.auth('$EMAIL', '$PASSWD', '$ID_APP', '$SOPE')
      надо подставить свой e-mail, пароль, id приложения и защищённый ключ приложения?

      Удалить
    3. Да. Если вы беспокоитесь, что ваши данные утекут, то можете более подробно посмотреть в код модуля vk_auth. Ничего такого там нет. Он занимается эмуляцией веб-страницы авторизации Вконтакте, в которую вводит значения, что вы в него передали.

      Удалить
    4. сори , если я правильно понял , он каждый пост целиком сохраняет (текст с картинкой)?

      Удалить
  2. Прошу прощения, но, для меня это дико сложно, а стену очень необходимо сохранить. Могли бы вы подсказать, скачал я питон http://www.python.org/ftp/python/2.7.6/python-2.7.6.msi
    Разархивировал vkontakte от сюда. https://pypi.python.org/pypi/vkontakte
    А что дальше?

    ОтветитьУдалить
  3. а приложение качает все записи со стены или только те, которые запостили админы группы?

    ОтветитьУдалить
  4. Не совсем понятно, что именно и как именно сохраняет приложение.
    Можно пример?

    ОтветитьУдалить
  5. Программа загружает всё это в HTML файлы( или файл, если ключ s указан в 1)

    ОтветитьУдалить
  6. Прорекламирую свой модуль доступа к API vk.com: https://github.com/dimka665/vk

    Реализована авторизация. Работает на Python2.7, Python3.3

    ОтветитьУдалить
  7. Не АПИ, а SDK для API

    ОтветитьУдалить
  8. Хотел бы так же поделиться своими наработками для vk:
    https://github.com/prawn-cake/vk-requests - это заметно улучшенный fork проекта dimka665/vk c множеством баг и дизайн фиксов

    ОтветитьУдалить