На днях возникла необходимость сохранить все фотографии из своего фотоальбома ВКонтакте на жесткий диск. Естественно, вариант, с сохранением фотографий по одной, меня не устроил. Тут вспомнилось, что у ВКонтакте есть 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.