Пример парсинга всей страницы на Python
На пути к реализации полнофункционального парсера/скрапера каждый раз приходится сталкиваться с задачей разбора отдельных тегов на странице для извлечения требуемой информации. Так и в нашем случае, чтобы заработала программа автоматического закачивания сведений с заданного сайта, требуется реализовать не только модули общей функциональности (обход страниц и ссылок на них), но и методы прохождения по тегам и извлечения их контента.
Напомню, что мы занимаемся поэтапной реализацией парсера, согласно приведенной схеме (подробнее здесь):
Мы столкнулись с необходимостью заполнения специфических для сайта полей (инструментарий "Специфического модуля"). Задачу будем рассматривать на примере разбора страницы о результатах поединка, организованного спортивной организацией UFC (подробнее здесь):
Ранее мы рассмотрели применение методов объекта BeautifulSoup find и findAll, позволяющих получать первый и все результаты поиска тегов, свойств BeautifulSoup parent и children для навигации по родительским и дочерним тегам (подробнее здесь), а также написали функцию, извлекающую набор тегов из контейнера get_items_list_from2tags (подробнее здесь). В результате скачана информация из верхней половины страницы с именами бойцов, деталями завершения схватки (метод выигрыша, раунд, время завершения, судья поединка).
Оставшаяся часть информации организована в виде секций:
Для их извлечения необходимо набрать следующие команды:
sections = bsObj.findAll('section', {'class':'b-fight-details__section js-fight-section'})
sections = [sec for i, sec in enumerate(sections) if not i in [0,3] ]
Первая и четвертая по порядку секции нас не интересуют, так как содержат только заголовки "TOTALS" и "SIGNIFICANT STRIKES". Кроме того, по непонятным причинам секция с таблицей общих результатов по областям нанесенных повреждений находится не в секции, а в теге "table" (но в окружении тегов section).
Так как соответствующий тег не имеет значимых атрибутов (только стиль), простой поиск с использованием findAll дает много результатов. Поэтому я сначала нашел родителя искомого элемента (тег div с 'class'='b-fight-details, он в единственном экземпляре на странице) а затем в цикле по дочерним тегам выбрал необходимый по имени и наличию поля стиля (остальные - это секции, у которых этого поля нет):
sign_st_parent = bsObj.find('div',{'class':'b-fight-details'})
for child in sign_st_parent.children:
try:
if 'style' in child.attrs:
sign_st_table = child
except:
pass
Теперь требуется в собранных секциях и таблице провести сбор следующих списков: названий столбцов для статистики активности (находятся под заголовком "TOTALS"), общих значений для каждого столбца, и значений для каждого раунда, а также названий столбцов для статистики по распределению значимых ударов, их общих значений и значений для каждого раунда.
Для этого можно вызвать описанный ранее метод класса ItemsParser - get_items_list_from2tags, который для заданной секции, тега контейнера и набора вложенных тегов, извлечет теги со значениями названий столбцов, а затем для каждого можно вызвать get_text().strip() для извлечения текста и удаления пробелов:
sec_head_t = ItemsParser.get_items_list_from2tags(sections[0],'thead,class,b-fight-details__table-head',\
'th,class,b-fight-details__table-col', delay)
[item.get_text().strip() for item in sec_head_t]
Этот процесс можно облечь в функцию. И соответственно, так можно получить нужные списки альтернативным образом:
def get_head_details(section, tag_container, tag_els, delay):
sec_head_t = ItemsParser.get_items_list_from2tags(section,tag_container,tag_els, delay)
sec_head = [item.get_text().strip() for item in sec_head_t]
return sec_head
sign_act_head = get_head_details(sections[0],'thead,class,b-fight-details__table-head', 'th,class,b-fight-details__table-col', delay)
sign_st_head = get_head_details(sign_st_table,'thead,class,b-fight-details__table-head', 'th,class,b-fight-details__table-col', delay)
Чтобы получить значения столбцов для общей статистики потребуется сначала извлечь теги, в которых хранятся пары результатов (по одному для каждого из бойцов), а затем посредством вызова функции get_fight_det_l с этими тегами на входе получить список пар. В данной функции для получения каждой очищенной пары, извлекается содержимое тега их родителя (get_text), а потом, так как результат содержит много пробельных и символов перевода на новую строку, происходит разбиение содержимого по '\n' с взятием только начала и конца последовательности (где и будут искомые значения):
def get_fight_det_l(tags_l):
res_l = []
for item in tags_l:
l = item.get_text().strip().split('\n')
res_l.append([item.strip() for i, item in enumerate(l) if i in[0,len(l)-1]])
return res_l
sign_act_body_t = ItemsParser.get_items_list_from2tags(sections[0],'tbody,class,b-fight-details__table-body', 'td,class,b-fight-details__table-col', delay)
sign_act_body = get_fight_det_l(sign_act_body_t)
sign_st_body_t = ItemsParser.get_items_list_from2tags(sign_st_table,'tbody,class,b-fight-details__table-body','td,class,b-fight-details__table-col', delay)
sign_st_body_res = get_fight_det_l(sign_st_body_t)
И напоследок извлечение пораундовой статистики, которое в обоих случаях (для таблиц статистики активности и распределения значимых ударов ) происходит схожим образом. Сначала получаем теги с содержимым статистики по раундам (первый тег ненужный), а затем для каждого собираем искомые значения посредством вышеуказанной функции get_fight_det_l:
sign_act_rounds = []
rounds_body = sections[1].findAll('tr',{'class','b-fight-details__table-row'})
for i,sec in enumerate(rounds_body):
if (i!=0):
sign_act_rounds.append(get_fight_det_l(sec.findAll('td',{'class':'b-fight-details__table-col'})))
sign_st_rounds=[]
rounds_body = sections[2].findAll('tr',{'class','b-fight-details__table-row'})
for i,sec in enumerate(rounds_body):
if (i!=0):
sign_st_rounds.append(get_fight_det_l(sec.findAll('td',{'class':'b-fight-details__table-col'})))