Задача, которая перед нами стоит — скачивание музыкальных произведений с сайта предоставляющего такую возможность. Использовать будем язык-программирования 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 для прямого скачивания
В последней функции мы найдем ссылки для прямого скачивания. Здесь мы будем использовать две новые процедуры:
- re.sub(Шаблон, Новым фрагмент, Строка для замены) ищет все совпадения с шаблоном и заменяет их указанным значением. В качестве первого параметра можно указать ссылку на функцию.
- 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
#возвращаем пустой массив
Запись файла
Нам остается скачать и записать файл.
- Процедура get позволяет отправлять HTTP-запрос, который позже проверим процедурой req.status_code: это список кодов состояния HTTP (список можно найти в Интеренете, статус <200> означает удачный вход).
- Процедура 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)
SomeOneWhoCares
24.02.2017 12:54Благодарю за совет. В будущих публикациях Scrapy будет использован, где я и объясню его использование.
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
. Так будет понятнее, что это именно файл.SomeOneWhoCares
24.02.2017 13:31Благодарю за совет, и дельные замечания. Впредь буду подходить к делу с большим рвением.
DaneSoul
24.02.2017 13:40Вместо str(input()) лучше использовать raw_input(), т.к. input выполняет eval(raw_input()), что позволяет выполнять произвольный код.
Это справедливо только для Python 2, в Python 3 нет raw_input(), его функцию выполняет input() и выполнять он код уже не позволяет.
drafterleo
24.02.2017 18:35+1Плюсанул статью за блок-схему алгоритма на иллюстрации. Но потом присмотрелся и с лёгкой печалью (эх, молодёжь...) осознал, что картинка в начале не имеет никакого отношения к последующему коду.
gsedometov
25.02.2017 14:32+1Кажется, автор путает понятия HTTP и URL. Да и обработку исключений использует не по назначению.
po_lli
25.02.2017 20:02+5Вижу, что автор только начинает углубляться в суть Python и программирования.
На мое усмотрения, очень неплохое начало! Не стоит набрасываться на пользователя с злобными комментариями и насмешками, лучше дать дельный совет в случаи неточностей или ошибок.
Автору спасибо большое и удачи в своих творениях! :)))
LingvoLena
05.03.2017 15:17Сбор аудио файлов с сайтов дает дополнительную возможность для их анализа. Предложенная автором реализация относительно простая, что делает статью полезной.Однако следует реализовать многопоточный метод для экономии ресурсов.
DaneSoul
А можно ссылочку, откуда это? Вы точно с Perl не путаете?
estin
Лучше бы автор "отжег" так: Python — язык программирования, предназначенный для парсинга сайтов
SomeOneWhoCares
Здравствуйте, на сколько мне известно, Perl — язык программирования общего назначения, а Python специализируется на работе с текстом. Точнее ничего не могу сказать, так как, с Perl я не работаю. Если не прав, прошу меня извинить.
alexey-m-ukolov
А вы статью, на которую дали ссылку в самом начале, не открывали?
fireSparrow
Да вы, похоже, и с питоном особо не работали :))
Вот только некоторые применения питона:
— Бэк-енд в вебе
— Написание бизнес-логики при работе с БД
— Парсинг страниц
— EML-скрипты
— «Админские» скрипты
— Статистическая обработка данных
— Научные вычисления
— Простые десктоп-приложения с графическим интерфейсом
— Мобильные приложения
— И многое другое!
SomeOneWhoCares
Здравствуйте, я догадывался что эта фраза вызовет негатив. И если я не прав в данном изречении, поправить плод нашего дискурса в лучшую сторону, это самый верный вариант. Не стоит насмехаться над кем-то, это никого не красит.
fireSparrow
Я не хотел, чтобы это выглядело так, будто я весь такой на негативе злобно насмехаюсь над вами. Но всё-таки вы взялись писать статью о языке, о возможностях и применении которого имеете крайне поверхностное представление. И допускаете ляпы, которые кажутся очень забавными.
Имхо, в такой ситуации лёгкое подтрунивание в комментах — вполне адекватная реакция. Не надо на это болезненно реагировать.
Лучше, посмейтесь вместе с нами. Здоровая самоирония — это прекрасно.
fireSparrow
*ETL-скрипты, конечно же. Спутал аббревиатуры :)
Scorobey
Да очень похоже что Вы знакомы с Python только на уровне читающего заголовки. Основным преимуществом Python является библиотека NLTK которая работает с корпусами текстов book, Brown и другими. Кроме того для тематического анализа текстовой информации есть ещё Gensim и BigArtm не говоря уже о модулях re,pyMorfologik, pymorphy2, LDA которые составляют почти половину библиотек Python. Поэтому воздержитесь от суждения о "ляпах" и не читайте только заголовки.
fireSparrow
Это можно считать основным преимуществом только для людей, работающих в области лингвистики. Однако же область применения питона не только не ограничивается лингвистикой, но и на самом деле в основном находится за её пределами.
У питона невероятно большое количество библиотек на все случаи жизни!
Если следовать вашей логике, то каждый человек мог бы назвать своё основное преимущество питона:
— веб-программист назвал бы Django, Flask или Pyramid
— тот, кто работает с базами назвал бы sqlalchemy и alembic
— учёный назвал бы scipy
— Data Scintist назвал бы scikit-learn или TensorFlow
а ведь ещё есть множество библиотек для создания игр, работы с аудио и графикой, фреймворки для мобильных приложений и ещё много чего.
Так что фраза «Питон — это язык для работы с текстом» — однозначно ляп.
Scorobey
О чем идёт речь в статье об обработке текстовых текстовых данных. Руководствуясь Вашей же логикой можно утверждать что для этой задачи Pythoon лучший для обработки текстов такого же мнения и думаю известный Вам датчанин. А создатель знает для чего он создавал Python.
fireSparrow
Вы этот комментарий скриптом что-ли сгенерировали?
Вообще же невозможно понять ту мысль, которую вы в него вложили, если она там вообще есть.
Создатель языка — голландец, а не датчанин.
И он никогда не говорил, что питон — специализированный язык для обработке текстов. Если я ошибаюсь, приведите ссылку на цитату.
Создатели питона делали его как универсальный язык, в него не было заложено никакого специального инструментария для работы с текстами. Все библиотеки, которые вы назвали — сторонние, созданы совсем другими людьми. Кроме модуля «re», который вполне вписывается в концепцию языка универсального назначения.
Scorobey
В таком объёме как 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 только для анализа текста. Поэтому не надо разбрасываться "ляпами'/ Вашее утверждение — и на самом деле в основном находится за её пределами.лишено основания. А проверку датчанином Вы прошли. Но это единственное с чем можно согласиться. ,
DaneSoul
NLTK была разработана через 10 лет после релиза самого Python.
СЕЙЧАС в статье нет ляпа, там исправленный текст, исходно там была формулировка, процитированная в первом сообщении этой ветки.
Scorobey
О чем говорит Ваша фраза -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. Но я думаю, что сообществу нужно заняться не бесплодной критикой молодых авторов а подсказкой по улучшению кода. Думаю автор статьи ждёт от Вас именно этого.
fireSparrow
Дискутировать с вами — всё равно, что с чат-ботом. Вроде бы предложения грамматически правильные, но полностью лишены смысла.
Кстати, забавно, что единственные два комментатора, которые сочли автора статьи несправедливо обиженным и кинулись защищать, — Scorobey и po_lli — оба зарегистрировались на хабре сегодня, и пока комментировали только эту статью.
Scorobey
Для того чтобы дискутировать не достаточно быть знакомым с чат-ботом.Репертуар Вашего просмотра фильмов и объясняет Ваше не понимание смысла.Смотрите фильмы не только про чат-бота но и читайте книги про Незнайку (может это поможет). На Ваше " системное" наблюдение о регистрации на аккаунте сообщаю своё. В Вашем ответе нет информации по сути статьи, например предложений о изменении кода или постановки задачи, вместе этого Ваша сравнительная интерпретация лексики любимого Вами героя.
DaneSoul
Сейчас и Python и Perl — языки общего назначения, но Python исходно таким и создавался, а Perl исходно создавался для написания скриптов для команд Unix и для парсинга отчетов, то есть затачивался по-сути на работу с текстом.