#!/usr/bin/env python # coding: utf-8 # # Основы программирования в Python # # ## Семинар 8: решения # Импортируем необходимые библиотеки: # In[1]: from bs4 import BeautifulSoup import requests # Загрузим страницу с проходными баллами с сайта Вышки: # In[2]: page = requests.get('https://ma.hse.ru/passingrade') # Создадим объект `soup` и сохраним в него html-код страницы: # In[3]: soup = BeautifulSoup(page.text, 'lxml') # Проходные баллы и названия программ хранятся в таблице, причём не просто в таблице, а в таблице с атрибутом `class`, равным `data table bordered` (см. исходный код страницы): # In[4]: table = soup.findAll('table', {'class' : 'bordered'})[0] # Баллы и названия курсов нужно искать в пределах этой таблицы. Обратим внимание, что все баллы заключены в тэги ``: # In[5]: table.findAll('strong')[0:5] # для примера первые 5 элементов # Сохраним все баллы в список, вытащив из тэгов текст. Воспользуемся списковым включением: # In[64]: sc = table.findAll('strong') scores = [score.text for score in sc] scores[0:5] # первые несколько примеров # Значения сохранились с пробелами вначале, которые в текущей кодировке страницы записываются как `\xa0`. Пока их трогать не будем, лучше преобразуем, когда созраним все значения в датафрейм. Лучше обратим внимание на такую проблему: в списке не хватает одного значения: проходной балл для коммерческих мест на программе «Физика». Соответствующая ячейка не заполнена (таких нет), но это сбивает последующую нумерацию элементов, а нам она пригодится. Добавим пропущенное значение (дефис) сами на нужное место: # In[65]: print(scores.index('110')) # In[66]: scores.insert(23, '-') # In[68]: scores[22:25] # Похожая история с магистратурой «Церковь, общество, государство». Добавим недостающее «пустое» значение: # In[70]: scores.insert(153, '-') # Как отделить баллы, соответствующие бюджетным местам, от баллов на коммерческие места? Можно заметить, что все баллы на бюджетные места имеют чётные индексы в списке `scores`, а на коммерческие – нечётные. Воспользуемся этим и рассортируем значения: # In[74]: budget = [] comm = [] for i in range(0, len(scores)): if i % 2 == 0: budget.append(scores[i]) else: comm.append(scores[i]) # Списки с баллами сформированы, осталось выгрузить названия образовательных программ. Заметим, что названия сохранены в тех же тэгах, что и ссылки на них (тэг `a`). Воспользуемся этим: # In[71]: links = table.findAll('a') masters = [link.text for link in links] masters[0:5] # несколько примеров из списка # Теперь попробуем объединить полученные списки в датафрейм, предварительно создав словарь, где ключами являются названия столбцов, а значениями – списки значений: # In[72]: import pandas as pd # In[13]: df = pd.DataFrame({'programme' : masters, 'budget' : budget, 'commerce' : comm}) # Не получилось! Почему? Потому что списки имеют разную длину. Проверим! # In[75]: print(len(budget), len(comm), len(masters)) # Действительно, длины разные. Посмотрим, в чём проблема: # In[16]: masters[0:5] # Англоязычные названия программ, реализуемых на английском языке, тоже сохраняются в списке! Уберём дубликаты с помощью регулярных выражений: отфильтруем только те названия, которые на кириллице. # In[76]: import re # импортируем модуль для регулярных выражений re.findall('[А-Я|а-я]+', masters[0]) # для первого элемента # In[77]: # поместим всё в цикл progs = [] for p in masters: res = re.findall('([А-Я]|[а-я])', p) if len(res) != 0: progs.append(p) progs[0:10] # готово! # Проверим длины списков теперь: # In[78]: print(len(budget), len(comm), len(progs)) # Опять много! Уберём баллы для очно-заочной формы обучения (в списках баллов их нет, если что, сгрузим отдельно): # In[79]: progs = progs[0:-7] # In[80]: print(len(budget), len(comm), len(progs)) # и снова неудача! # Из-за того, что в случае совместного конкурса в ячейке с программами по экономике стоят сразу две программы («Прикладная экономика» и «Экономика: исследовательская программа»), у нас образовалось лишнее значение. Давайте одно удалим, а второе честно поправим на более общее: # In[81]: progs.index("Экономика: исследовательская программа") # In[82]: progs.remove("Экономика: исследовательская программа") progs[31] = 'Единый конкурс на образовательные программы "Прикладная экономика" и "Экономика: исследовательская программа"' # In[83]: print(len(budget), len(comm), len(progs)) # ура! # In[85]: df = pd.DataFrame({'programme' : progs, 'budget' : budget, 'commerce' : comm}) df.head() # Осталось привести в порядок значения в столбцах с баллами – сделать их числовыми (пока они текстовые). Напишем функцию, которая будет принимать на вход строку, убирать пробелы и конвертировать её в целое число. Если это сделать невозможно (например, в графе стоит прочерк, то функция будет возвращать `None`). # In[86]: def to_number(x): n = x.strip() try: res = int(n) except: res = None return res # In[91]: df['commerce'] = df.commerce.apply(to_number) # In[92]: df.info() # готово! # Сохраним таблицу в Excel-файл: # In[93]: df.to_excel('master-hse.xlsx')