На днях возникла необходимость сохранить все фотографии из своего фотоальбома ВКонтакте на жесткий диск. Естественно, вариант, с сохранением фотографий по одной, меня не устроил. Тут вспомнилось, что у ВКонтакте есть API. Пять минут чтения мануалов, и все нужные функции найдены. Единственная проблема – не существует нормального способа, для получения доступа к API. В документации сказано следующее:
На первый взгляд, набросать простенький портабельный скрипт не получится. Хотя, что мешает нам притвориться браузером?
Для достижения наших целей, будем использовать только стандартные модули Python:
Для того, чтобы прикинутся настоящим браузером, просто загружать нужные страницы недостаточно. Как минимум, нужно корректно обрабатывать cookies и редиректы. Для этого создаем opener, который будет делать всю работу за нас:
Подробно про параметры обращения можно прочитать в документации. Здесь следует отметить, что мы будем использовать параметр
Посмотрим на код странички авторизации. Больше всего нас будет интересовать следующий участок:
Для последующей отправки формы необходимо распарсить все input-ы (ы том числе и hidden), а также url, на который форма сабмитится. Пишем простенький парсер на основе
Подставляем в параметры запроса email и пароль пользователя и отправляем форму:
Следующим этапом, если пользователь этого еще не делал, нам надо дать приложению те права, которые мы запрашивали в параметре scope. Для этого нам будет предложена страничка с формой, на которой будут кнопки Allow и Deny. Парсим и отправляем форму методом, описаным выше.
Если мы все сделали правильно, то нас в итоге перекинет на страничку с url вида
откуда несложными манипуляциями мы можем достать нужные нам
Собственно, ради чего все и затевалось – скрипт для загрузки альбома:
и как оно выглядит в работе:

Конечно этот модуль не подходит для использования в серьезных проектах, но для личных целей – вполне.
Ссылка на GitHub.
Процесс авторизации приложения состоит из 3-х шагов:
- Открытие окна браузера для аутентификации пользователя на сайте ВКонтакте.
- Разрешение пользователем доступа к своим данным.
- Передача в приложение ключа access_token для доступа к API.
На первый взгляд, набросать простенький портабельный скрипт не получится. Хотя, что мешает нам притвориться браузером?
Для достижения наших целей, будем использовать только стандартные модули Python:
Создаем opener
Для того, чтобы прикинутся настоящим браузером, просто загружать нужные страницы недостаточно. Как минимум, нужно корректно обрабатывать cookies и редиректы. Для этого создаем opener, который будет делать всю работу за нас:
opener = urllib2.build_opener( urllib2.HTTPCookieProcessor(cookielib.CookieJar()), urllib2.HTTPRedirectHandler())
Обращаемся к странице авторизации
response = opener.open( "http://oauth.vk.com/oauth/authorize?" + \ "redirect_uri=http://oauth.vk.com/blank.html&response_type=token&" + \ "client_id=%s&scope=%s&display=wap" % (client_id, ",".join(scope)) )
Подробно про параметры обращения можно прочитать в документации. Здесь следует отметить, что мы будем использовать параметр
display со значением wap, т.к. в этом варианте странички практически отсутствует javascript. scope представляет собой список названий прав, к которым мы хотим получить доступ.Парсим ответ
Посмотрим на код странички авторизации. Больше всего нас будет интересовать следующий участок:
<form method="POST" action="https://login.vk.com/?act=login&soft=1&utf8=1"> <input type="hidden" name="q" value="1"> <input type="hidden" name="from_host" value="oauth.vk.com"> <input type="hidden" name="from_protocol" value="http"> <input type="hidden" name="ip_h" value="df5a3639f3cb32ecc1" /> <input type="hidden" name="to" value="aHR0cDovL29hdXRoLnZrLmNvbS9vYXV0aC9hdXRob3JpemU/Y2xpZW50X2lkPTI5NTE4NTcmcmVkaXJlY3RfdXJpPWJsYW5rLmh0bWwmcmVzcG9uc2VfdHlwZT10b2tlbiZzY29wZT00JnN0YXRlPSZkaXNwbGF5PXdhcA--"> <span class="label">Телефон или e-mail:</span><br /> <input type="text" name="email"><br /> <span class="label">Пароль:</span><br /> <input type="password" name="pass"> <div style="padding: 8px 0px 5px 0px"> <div class="button_yes"> <input type="submit" value="Войти" /> </div> <a class="button_no" href="https://oauth.vk.com/grant_access?hash=95a8fc64a19d011436&client_id=2951857&settings=4&redirect_uri=blank.html&cancel=1&state=&token_type=0"> <div> Отмена </div> </a> </form>
Для последующей отправки формы необходимо распарсить все input-ы (ы том числе и hidden), а также url, на который форма сабмитится. Пишем простенький парсер на основе
HTMLParser:class FormParser(HTMLParser): def __init__(self): HTMLParser.__init__(self) self.url = None self.params = {} self.in_form = False self.form_parsed = False self.method = "GET" def handle_starttag(self, tag, attrs): tag = tag.lower() if tag == "form": if self.form_parsed: raise RuntimeError("Second form on page") if self.in_form: raise RuntimeError("Already in form") self.in_form = True if not self.in_form: return attrs = dict((name.lower(), value) for name, value in attrs) if tag == "form": self.url = attrs["action"] if "method" in attrs: self.method = attrs["method"] elif tag == "input" and "type" in attrs and "name" in attrs: if attrs["type"] in ["hidden", "text", "password"]: self.params[attrs["name"]] = attrs["value"] if "value" in attrs else "" def handle_endtag(self, tag): tag = tag.lower() if tag == "form": if not self.in_form: raise RuntimeError("Unexpected end of <form>") self.in_form = False self.form_parsed = True
Авторизируемся
Подставляем в параметры запроса email и пароль пользователя и отправляем форму:
parser.params["email"] = email parser.params["pass"] = password response = opener.open(parser.url, urllib.urlencode(parser.params))
Разрешаем доступ
Следующим этапом, если пользователь этого еще не делал, нам надо дать приложению те права, которые мы запрашивали в параметре scope. Для этого нам будет предложена страничка с формой, на которой будут кнопки Allow и Deny. Парсим и отправляем форму методом, описаным выше.
Получаем token и user_id
Если мы все сделали правильно, то нас в итоге перекинет на страничку с url вида
http://oauth.vk.com/blank.html#access_token= 533bacf01e11f55b536a565b57531ad114461ae8736d6506a3&expires_in=86400&user_id=8492
откуда несложными манипуляциями мы можем достать нужные нам
access_token и user_id.Пример использования модуля
Собственно, ради чего все и затевалось – скрипт для загрузки альбома:
import vk_auth import json import urllib2 from urllib import urlencode import json import os import os.path import getpass import sys def call_api(method, params, token): if isinstance(params, list): params_list = [kv for kv in params] elif isinstance(params, dict): params_list = params.items() else: params_list = [params] params_list.append(("access_token", token)) url = "https://api.vk.com/method/%s?%s" % (method, urlencode(params_list)) return json.loads(urllib2.urlopen(url).read())["response"] def get_albums(user_id, token): return call_api("photos.getAlbums", ("uid", user_id), token) def get_photos_urls(user_id, album_id, token): photos_list = call_api("photos.get", [("uid", user_id), ("aid", album_id)], token) result = [] for photo in photos_list: #Choose photo with largest resolution if "src_xxbig" in photo: url = photo["src_xxbig"] elif "src_xbig" in photo: url = photo["src_xbig"] else: url = photo["src_big"] result.append(url) return result def save_photos(urls, directory): if not os.path.exists(directory): os.mkdir(directory) names_pattern = "%%0%dd.jpg" % len(str(len(urls))) for num, url in enumerate(urls): filename = os.path.join(directory, names_pattern % (num + 1)) print "Downloading %s" % filename open(filename, "w").write(urllib2.urlopen(url).read()) if len(sys.argv) != 2: print "Usage: %s destination" % sys.argv[0] sys.exit(1) directory = sys.argv[1] email = raw_input("Email: ") password = getpass.getpass() token, user_id = vk_auth.auth(email, password, "2951857", "photos") albums = get_albums(user_id, token) print "\n".join("%d. %s" % (num + 1, album["title"]) for num, album in enumerate(albums)) choise = -1 while choise not in xrange(len(albums)): choise = int(raw_input("Choose album number: ")) - 1 photos_urls = get_photos_urls(user_id, albums[choise]["aid"], token) save_photos(photos_urls, directory)
и как оно выглядит в работе:

Заключение
Конечно этот модуль не подходит для использования в серьезных проектах, но для личных целей – вполне.
Ссылка на GitHub.
