Задача, которая перед нами стоит — скачивание музыкальных произведений с сайта предоставляющего такую возможность. Использовать будем язык-программирования Python.

Для осуществления этого нам будут необходимы знания о парсинге сайта и работе с медиа файлами.

На рисунке выше изображен общий алгоритм парсинга сатов. Парсинг будем осуществлять с помощью модулей BeautifulSoup и request, а для работы с текстом нам будет достаточно модуля re.

Импорт


import requests                        #осуществляет работу с HTTP-запросами
import urllib.request                  #библиотека HTTP
from bs4 import BeautifulSoup          #осуществляет синтаксический разбор документов HTML
import re                              #осуществляет работу с регулярными выражениями

Объявление переменных и основная процедура


Нам будет необходимо всего два массива и одна переменная для хранения информации:

page_count = []             #массив для хранения страниц сайта, содержащих музыку
perehod = ''                #сайт перенаправляет на новую страницу для скачивания, здесь мы будем хранить эту ссылку
download = []                #массив для поочередного хранения готовых ссылок для скачивания и имени файла

Пишем процедуру, где первым делом считаем все страницы на сайте, содержащие нужные нам песни.

if __name__ == '__main__':         
#условие для запуска процедуры
    u = str(input('Впишите группу для скачивания:\n'))       
#input - ввод данных с клавиатуры в программу. Эта переменная будет содержать название группы исполнителей, которую мы скачивем
    base_url = 'http://go.mail.ru/zaycev?sbmt=1486991736446&q='+u         
#переменная содержит http сайта, который мы парсим
    count=0              
#объявляем переменную и приравниваем ее к нулю для дальнейшего создания счетчика
    page_count = [base_url]
#в массив добавляем ссылку первой страницы, остальные будем помещать в цикле
    print('Поиск станиц. Подождите...')
#print - осуществляем вывод указанного текста
    while True:
#запускаем цикл
        try:
#try обработчик исключительных ситуаций. То есть, программа будет выполнять цикл и когда встретит ошибку, которую мы укажем в условии except, начнет выполнять условие в нем
            page_count = page_count+[get_page_count(get_html(page_count[count]),page_count)]
#к массиву прибавляем переменную, получаемую в функции get_page_count, куда мы также передаем переменную page_count для дальнейшего заполнения. Внутри этой переменной выполняется функция get_html (для получения http) от page_count[count], где count изначально равен нулю. Проще говоря, программа будет брать первый элемент в массиве - первый раз, и на единицу больше - каждый последующий проход цикла
            count = count + 1
#счетчик, позволяющий нам перебирать элементы в массиве
        except TypeError:
#когда закончатся страницы на сайте, возникнет ошибка TypeError. Воспользуемся ею
            break
#оператор break прекращает выполнение цикла и переводит выполнение программы на строку следующую после цикла
    print("Всего страниц найдено - ",len(page_count))
#в желании вывести количество найденных страниц нам поможет оператор len, который считает количество элементов в массиве

Получение HTTP-страниц


Для жизнедеятельности ранее написанного, необходимо написать две функции, первая — будет получать http и передавать этот параметр во вторую, которая в свою очередь будет получать данные с этой ссылки по средству парсинга.

def get_html(url):
#объявление функции и передача в нее переменной url, которая является page_count[count]
    response = urllib.request.urlopen(url)
#это надстройка над «низкоуровневой» библиотекой httplib, то есть, функция обрабатывает переменную для дальнейшего взаимодействия с самим железом
    return response.read()
#возвращаем полученную переменную с заданным параметром read для корректного отображения

Следующая функция будет представлять сам парсинг. Главное, что нам необходимо для получения информации о построении сайта — это просмотреть его html верстку. Для этого заходим на сайт, нажимаем Shift+Ctrl+C и получаем исходный код, где отображены все имена виджетов.

def get_page_count(html,page_count):
#в функцию мы передаем две переменные page_count (о ней мы говорили ранее) и html эта переменная нам также встречалась, просто в более сложном виде: get_html(page_count[count]) 
    soup = BeautifulSoup(html, "html.parser")
#объявляем новую переменную с полным html-кодом страницы
    href = soup.find('a', text = 'Вперед')
#теперь из страницы находим нужный нам виджет - это кнопка с текстом "Вперед". "а" - это блочный элемент, которому принадлежит данная кнопка. Убедиться в этом можно описанным выше способом(Shift+Ctrl+C).
    base_url = 'http://go.mail.ru'
#как видим, это лишь часть урла сайта. Берем лишь часть для дальнейшего соединения с частью, содержащей порядковый номер страницы сайта
    page_count = base_url + href['href']
#теперь крепим недостающую часть. Так как нам нужен лишь адрес из переменной href, а не весь html-код, принадлежащий кнопке, то с помощью функции ['href'] мы получим ссылку следующей страницы (также можно получить и иные части html-кода).
    return page_count
#возвращаем значение переменной в процедуру

Важно! Все данные, получаемые с использованием BeautifulSoup, имеют не строковой тип данных, а отдельный «красивый суп» тип данных.

Очередная задача — получение нового адреса для скачивания при нажатии «Скачать» на каждой из страниц. Заметим, что не станем использовать массив, так как в этом случае нам придется заполнять его полностью и лишь затем начинать скачивание, что сильно замедлит работоспособность программы. Будем брать каждый раз новую ссылку и работать непосредственно с ней. Для этого пишем вторую функцию и добавляем в процедуру:

print('Скачивание')
#выводим надпись 'Скачивание' пока идет скачивание музыки
try:
#запускаем обработчик исключительных ситуаций
    for i in page_count:
#перебираем каждый элемент в массиве
        perehod = parsing1(get_html(i),perehod)
#приравниваем переменную к функции, получающей url кнопки для скачивания. В эту функцию передаем две переменные - саму приравниваемую переменную и каждый раз меняющийся url
except TypeError:
#ошибка, которая нас потревожит TypeError (функция применяется к объекту несовместимого типа)
    print('Скачивание окончено')
#выводим надпись 'Скачивание окончено' по окончанию будущего скачивания

В третьей функции встретимся с использованием re.findall(Шаблон, строка)- осуществляет поиск по заданному шаблону в строковой переменной.

def parsing1(html,perehod):
#объявляем функцию
    soup = BeautifulSoup(html, "html.parser")
#получаем полный html-код страницы
    perehod = []
#необходимо каждый раз обнулять массив, так как данные с предыдущих страниц нам не нужны
    for row in soup.find_all('a'):
#создаем цикл, перебирая каждую найденную кнопку отдельно (на сайте, как мы можем убедится, их примерно 20) для занесения ее в массив
        if re.findall(r'Скачать', str(row)):
#вот нам и пригодился импорт re, мы будем проверять в полученном html-коде, есть ли данные, связанные с кнопкой, так как сейчас переменная row, из-за особенности сайта (блок "a" имеет и иные классы, кроме самих кнопок), содержит много лишнего
            perehod=perehod+[row['href']]
#теперь просто прибавляем к массиву проверенную переменную, предварительно получив из нее лишь свойство тега
    return perehod
#возвращаем переменную

Теперь процедура берет каждый раз новую ссылку с каждой страницы и нам необходимо находить адрес новой страницы, где находится новая кнопка с текстовым полем «Скачать», с конечным адресом для скачивания. В главную процедуру пишем:

for y in perehod:
#цикл с перебором значений в массиве со ссылками на новую страницу
    download = parsing2(get_html(y),download
#download - массив для хранения двух параметров - название песни и ссылка на ее скачивание

Получение HTTP для прямого скачивания


В последней функции мы найдем ссылки для прямого скачивания. Здесь мы будем использовать две новые процедуры:

  1. re.sub(Шаблон, Новым фрагмент, Строка для замены) ищет все совпадения с шаблоном и заменяет их указанным значением. В качестве первого параметра можно указать ссылку на функцию.
  2. text — получает текст из html-кода (только из результата поиска BeautifulSoup, строковой тип данных не подойдет).

def parsing2(html,download):
#объявляем функцию
    soup = BeautifulSoup(html, "html.parser")
#объявляем новую переменную с полным html страницы
    table = soup.find('a', {'id':'audiotrack-download-link'})
#в html страницы ищем блок "a" с его "id". В нем и будут храниться данные - название и ссылка на прямое скачивание
    href=''
#переменная хранения адреса песни
    name=''
#переменная хранения названия песни
    if table != None:
#условие: если данные найдены не были, то выполнить условие else
        row = soup.find('h1', {'class':"block__header block__header_audiotrack"})
#для записи имени находим блок "h1" с его классом и передаем данные в последующую
        name = re.sub(r'\n\t\t\t\t\t\t','',row.text)
#в названии нам будут мешать лишние символы - заменим их на пустую строку. Строковым значением выступит текст от html-кода переменной row
        href=table.get('href')
#в этот раз я использовал .get('href') вместо ['href'] - они идентичны
        download=[href]+[name]
#массив, который мы передавали в функцию, теперь заполняем двумя переменными
        return download
#возвращаем массив
    else:
#условие ответвления не станет вносить изменения в массив download
        return download
#возвращаем пустой массив

Запись файла


Нам остается скачать и записать файл.

  1. Процедура get позволяет отправлять HTTP-запрос, который позже проверим процедурой req.status_code: это список кодов состояния HTTP (список можно найти в Интеренете, статус <200> означает удачный вход).
  2. Процедура open открывает и закрывает файл для записи в двоичном формате, wb — создает файл с именем, если такового не существует.

if download != []:
#условие, проверяющее, не равен ли массив пустому множеству
    req = requests.get(download[0],stream = True)
#переменную приравниваем отправленному запросу от нашей ссылки для скачивания и задаем обязательный параметр stream равного True
    if req.status_code == requests.codes.ok:
#условие проверяет статус http и если он удачен, то продолжить
        with open(download[1]+'.mp3', 'wb') as a:
#открываем файл с присвоением имени
            a.write(req.content)
#запись в файл осуществляется с помощью метода write

Используя всего два модуля BeautifulSoup и request можно достигать решений практически любых поставленных задач, связанных с парсингом сайта. С помощью полученных знаний можно адаптировать программу для скачивания иных данных даже с других сайтов. Желаю удачи в вашей работе!
Поделиться с друзьями
-->

Комментарии (33)


  1. DaneSoul
    24.02.2017 12:10
    +7

    Python — язык программирования, предназначенный для работы с текстом.

    А можно ссылочку, откуда это? Вы точно с Perl не путаете?


    1. estin
      24.02.2017 12:44
      +6

      Лучше бы автор "отжег" так: Python — язык программирования, предназначенный для парсинга сайтов


    1. SomeOneWhoCares
      24.02.2017 12:47
      -7

      Здравствуйте, на сколько мне известно, Perl — язык программирования общего назначения, а Python специализируется на работе с текстом. Точнее ничего не могу сказать, так как, с Perl я не работаю. Если не прав, прошу меня извинить.


      1. alexey-m-ukolov
        24.02.2017 13:01

        А вы статью, на которую дали ссылку в самом начале, не открывали?


      1. fireSparrow
        24.02.2017 13:02
        +2

        Да вы, похоже, и с питоном особо не работали :))

        Вот только некоторые применения питона:
        — Бэк-енд в вебе
        — Написание бизнес-логики при работе с БД
        — Парсинг страниц
        — EML-скрипты
        — «Админские» скрипты
        — Статистическая обработка данных
        — Научные вычисления
        — Простые десктоп-приложения с графическим интерфейсом
        — Мобильные приложения
        — И многое другое!


        1. SomeOneWhoCares
          24.02.2017 13:29
          -2

          Здравствуйте, я догадывался что эта фраза вызовет негатив. И если я не прав в данном изречении, поправить плод нашего дискурса в лучшую сторону, это самый верный вариант. Не стоит насмехаться над кем-то, это никого не красит.


          1. fireSparrow
            24.02.2017 14:18
            +8

            Я не хотел, чтобы это выглядело так, будто я весь такой на негативе злобно насмехаюсь над вами. Но всё-таки вы взялись писать статью о языке, о возможностях и применении которого имеете крайне поверхностное представление. И допускаете ляпы, которые кажутся очень забавными.
            Имхо, в такой ситуации лёгкое подтрунивание в комментах — вполне адекватная реакция. Не надо на это болезненно реагировать.
            Лучше, посмейтесь вместе с нами. Здоровая самоирония — это прекрасно.


        1. fireSparrow
          24.02.2017 14:08

          *ETL-скрипты, конечно же. Спутал аббревиатуры :)


        1. Scorobey
          25.02.2017 12:16
          -1

          Да очень похоже что Вы знакомы с Python только на уровне читающего заголовки. Основным преимуществом Python является библиотека NLTK которая работает с корпусами текстов book, Brown и другими. Кроме того для тематического анализа текстовой информации есть ещё Gensim и BigArtm не говоря уже о модулях re,pyMorfologik, pymorphy2, LDA которые составляют почти половину библиотек Python. Поэтому воздержитесь от суждения о "ляпах" и не читайте только заголовки.


          1. fireSparrow
            25.02.2017 12:30
            +4

            Основным преимуществом Python является библиотека NLTK


            Это можно считать основным преимуществом только для людей, работающих в области лингвистики. Однако же область применения питона не только не ограничивается лингвистикой, но и на самом деле в основном находится за её пределами.

            У питона невероятно большое количество библиотек на все случаи жизни!

            Если следовать вашей логике, то каждый человек мог бы назвать своё основное преимущество питона:
            — веб-программист назвал бы Django, Flask или Pyramid
            — тот, кто работает с базами назвал бы sqlalchemy и alembic
            — учёный назвал бы scipy
            — Data Scintist назвал бы scikit-learn или TensorFlow
            а ведь ещё есть множество библиотек для создания игр, работы с аудио и графикой, фреймворки для мобильных приложений и ещё много чего.

            Так что фраза «Питон — это язык для работы с текстом» — однозначно ляп.


            1. Scorobey
              25.02.2017 13:07

              О чем идёт речь в статье об обработке текстовых текстовых данных. Руководствуясь Вашей же логикой можно утверждать что для этой задачи Pythoon лучший для обработки текстов такого же мнения и думаю известный Вам датчанин. А создатель знает для чего он создавал Python.


              1. fireSparrow
                25.02.2017 13:35
                +2

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

                Создатель языка — голландец, а не датчанин.
                И он никогда не говорил, что питон — специализированный язык для обработке текстов. Если я ошибаюсь, приведите ссылку на цитату.
                Создатели питона делали его как универсальный язык, в него не было заложено никакого специального инструментария для работы с текстами. Все библиотеки, которые вы назвали — сторонние, созданы совсем другими людьми. Кроме модуля «re», который вполне вписывается в концепцию языка универсального назначения.


                1. Scorobey
                  25.02.2017 14:06

                  В таком объёме как Python с корпусами не работает ни один язык программирования — ссылка для ознакомления https://ru.wikipedia.org/wiki/Natural_Language_Toolkit. Вот книга которая стала бестселлером -Steven Bird, Ewan Klein, Edward Loper. Natural Language Processing with Python. — O'Reilly Media, 2009. — ISBN 0-596-51649-5… Вот ссылка из статьи -задача, которая перед нами стоит — скачивание музыкальных произведений с сайта предоставляющего такую возможность. Использовать будем язык-программирования Python.Где здесь утверждение которое Вы назвали "ляпом". Нет здесь никакого "ляпа". Что Вы пытаетесь доказать — что Python имеет много модулей — это так, ознакомитесь http://www.lfd.uci.edu/~gohlke/pythonlibs/#pycurl — Вы перечислили далеко не все. Автор статьи и не утверждал что Python только для анализа текста. Поэтому не надо разбрасываться "ляпами'/ Вашее утверждение — и на самом деле в основном находится за её пределами.лишено основания. А проверку датчанином Вы прошли. Но это единственное с чем можно согласиться. ,


                  1. DaneSoul
                    25.02.2017 14:27

                    NLTK была разработана через 10 лет после релиза самого Python.
                    СЕЙЧАС в статье нет ляпа, там исправленный текст, исходно там была формулировка, процитированная в первом сообщении этой ветки.


                    1. Scorobey
                      25.02.2017 15:15

                      О чем говорит Ваша фраза -NLTK была разработана через 10 лет после релиза самого Python, она говорит о том что Вы не знаете что модуль NLTK постоянно совершенствуется вместе с корпусами совершенствоваться и Python. Например стоп- слова теперь в 3.4,3.5 можно получить
                      from nltk import *
                      from nltk.corpus import brown
                      stop_words= nltk.corpus.stopwords.words('english')
                      Многое изменилось например появился интерфейс PyQt5. PyCharm, И тот факт что релиз появился раньше говорит только о том что Python востребован именно для анализа контента на основе больших моделей, например Big Artm. Но я думаю, что сообществу нужно заняться не бесплодной критикой молодых авторов а подсказкой по улучшению кода. Думаю автор статьи ждёт от Вас именно этого.


                      1. fireSparrow
                        25.02.2017 21:10
                        +3

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

                        Кстати, забавно, что единственные два комментатора, которые сочли автора статьи несправедливо обиженным и кинулись защищать, — Scorobey и po_lli — оба зарегистрировались на хабре сегодня, и пока комментировали только эту статью.


                        1. Scorobey
                          26.02.2017 10:25

                          Для того чтобы дискутировать не достаточно быть знакомым с чат-ботом.Репертуар Вашего просмотра фильмов и объясняет Ваше не понимание смысла.Смотрите фильмы не только про чат-бота но и читайте книги про Незнайку (может это поможет). На Ваше " системное" наблюдение о регистрации на аккаунте сообщаю своё. В Вашем ответе нет информации по сути статьи, например предложений о изменении кода или постановки задачи, вместе этого Ваша сравнительная интерпретация лексики любимого Вами героя.


      1. DaneSoul
        24.02.2017 13:36

        Сейчас и Python и Perl — языки общего назначения, но Python исходно таким и создавался, а Perl исходно создавался для написания скриптов для команд Unix и для парсинга отчетов, то есть затачивался по-сути на работу с текстом.


  1. DaneSoul
    24.02.2017 12:21

    Хорошо бы добавить в статью о парсинге добавить хотя бы пару слов о Scrapy — это весьма востребованный фреймворк, который позволяет не только парсить отдельные страницы, но и обходить сайт как web-crawler


  1. SomeOneWhoCares
    24.02.2017 12:54

    Благодарю за совет. В будущих публикациях Scrapy будет использован, где я и объясню его использование.


  1. x893
    24.02.2017 13:09
    +11

    Язык C для работы с байтами. C# для работы с резкостью.


    1. lany
      24.02.2017 13:16
      +11

      Java для работы с визиторами абстрактных фабрик синглтонов.


      1. x893
        24.02.2017 13:23
        +11

        Go для пешеходов и бегунов.


      1. qw1
        24.02.2017 15:26
        +9

        javascript для показа банеров и попапов.


  1. goiliago
    24.02.2017 13:30
    +3

    Вместо page_count = page_count+[get_page_count(get_html(page_count[count]),page_count)] лучше использовать page_count.append(get_page_count(get_html(page_count[count]),page_count))


    Вместо str(input()) лучше использовать raw_input(), т.к. input выполняет eval(raw_input()), что позволяет выполнять произвольный код.


    Вместо count = count + 1 лучше использовать count += 1


    Да и функцию get_html можно переписать с использованием requests, для того, чтобы убрать один import:


    def get_html(url):
        r = requests.get(url)
        return r.text
    

    Однако я не уверен, что ничего не сломается, давно requests не пользовался.


    ИМХО можно переименовать переменную perehod на redirect, а переменную a (используется для записи файла) на f. Так будет понятнее, что это именно файл.


    1. SomeOneWhoCares
      24.02.2017 13:31

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


    1. DaneSoul
      24.02.2017 13:40

      Вместо str(input()) лучше использовать raw_input(), т.к. input выполняет eval(raw_input()), что позволяет выполнять произвольный код.


      Это справедливо только для Python 2, в Python 3 нет raw_input(), его функцию выполняет input() и выполнять он код уже не позволяет.


      1. goiliago
        24.02.2017 13:48

        Но если автор использует Python 3, то преобразование в строку излишне


  1. drafterleo
    24.02.2017 18:35
    +1

    Плюсанул статью за блок-схему алгоритма на иллюстрации. Но потом присмотрелся и с лёгкой печалью (эх, молодёжь...) осознал, что картинка в начале не имеет никакого отношения к последующему коду.


  1. gsedometov
    25.02.2017 14:32
    +1

    Кажется, автор путает понятия HTTP и URL. Да и обработку исключений использует не по назначению.


  1. po_lli
    25.02.2017 20:02
    +5

    Вижу, что автор только начинает углубляться в суть Python и программирования.
    На мое усмотрения, очень неплохое начало! Не стоит набрасываться на пользователя с злобными комментариями и насмешками, лучше дать дельный совет в случаи неточностей или ошибок.
    Автору спасибо большое и удачи в своих творениях! :)))


  1. LingvoLena
    05.03.2017 15:17

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


    1. SomeOneWhoCares
      05.03.2017 15:23

      Ваше предложение было реализовано в моей статье здесь.