Вступление
Лирическое отступление
Здравствуй, публика Хабрахабра. Я уже некоторое время наблюдаю за Вами, читаю Ваши работы. Честно признаться, меня завораживает тонкий и одновременно смелый мир Хабра. Поэтому, немного поразмыслив, я решил влиться в Вашу компанию. Это, как Вы уже, наверное, заметили, мой первый пост, но я не призываю Вас быть помягче ко мне. Скорее, я хочу испытать все те чувства, которые пережили или переживают бывалые пользователи. Давно хотел это написать здесь — добро пожаловать под кат.
Что, как и, главное, зачем?
Все просто: скрипт, написанный на Python 3.3, позволяющий загрузить указанную композицию с небезызвестного vk.com. Я использовал только стандартные модули: urllib и html.parser. Выражу общее мнение, если скажу, что это тот еще велосипед.
Код
Собственно, код разделен на три условные части: класс для парсинга формы, класс для парсинга списка композиций и главная часть.
Парсинг формы входа
Как я уже упоминал, парсить мы будем при помощи html.parser.
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")
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 </form>")
self.in_form = False
self.form_parsed = True
Класс занимается тем, что находит тег form, input и прилежащие к ним атрибуты, вроде method, action и так далее.
Парсинг списка композиций
Код имеет похожую структуру и приведен ниже.
class TrackSearch(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):
if self.url == None or len(self.url) < 10:
tag = tag.lower()
if tag == "input":
attrs = dict((name.lower(), value) for name, value in attrs)
if attrs["type"] == "hidden":
self.url = attrs["value"]
Думаю, ничего сложного здесь нет. Класс парсит список в поисках первой композиции в начале списка — самый релевантный вариант — и сохраняет ссылку на неё в self.url. Проверка len(self.url) < 10 нужна лишь для того, чтобы отсеять первые два input, необходимые для функционирования поиска VK.
Интерфейс и вызовы
Конечно, классов, указанных выше, недостаточно. Чтобы успешно авторизоваться, необходимо правильно обработать cookie. На помощь приходит urllib.request и http.cookiejar. Создаем обработчик.
opener = urllib.request.build_opener(
urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()),
urllib.request.HTTPRedirectHandler())
Теперь, когда других препятствий нет, мы можем начать.
#Сбор необходимой информации
print("VK Music Downloader")
login = input("Телефон или email > ")
password = input("Пароль > ")
track = input("Исполнитель и название композиции > ")
filename = track + ".mp3"
track = urllib.parse.quote(track)
#Авторизация
print("Авторизация...")
parser = FormParser()
response = str(opener.open("http://m.vk.com/").read())
parser.feed(response)
parser.params["email"] = login
parser.params["pass"] = password
response = opener.open(parser.url, urllib.parse.urlencode(parser.params).encode())
parser.close()
#Поиск
print("Поиск композиции...")
response = str(opener.open("http://m.vk.com/audio?act=search&q=%s" % track).read())
seacher = TrackSearch()
seacher.feed(response)
#Загрузка
print("Загрузка > %s" % filename)
with urllib.request.urlopen(seacher.url) as data,
open(filename, 'wb') as fout:
fout.write(data.read())
seacher.close()
input("Загрузка завершена")
Заключение
Пример работы скрипта.
На этом мой первый пост заканчивается. Спасибо тем, кто уделил время, чтобы просмотреть его. Если у Вас появились вопросы, я с радостью отвечу. Всего доброго.
Автор: Merlen_Gross