Сбор данных - жизненно важная часть любого проекта машинного обучения, но многие учебные пособия, как правило, используют данные, которые уже существуют в удобном формате. Это замечательно, например, для кейсов, но не для изучения всего процесса. В реальном мире не все данные можно найти в Google… по крайней мере, пока. Это часть руководства, в которой мы собираем всю статистику, необходимую для обучения нашей нейронной сети прогнозированию НХЛ, но, что более важно, я покажу вам, как вы можете применять эти концепции и собирать любые данные из всемирной паутины.

Особенности

  • Создание среды парсинга
  • Использование селена для удаления HTML
  • Обработка с помощью BeautifulSoup
  • Запись в файл CSV

Предпосылки

  • Должен уметь понимать Python

1- Обзор

В этом примере мы возьмем статистику ежедневных игр НХЛ за весь сезон. Выходные данные будут состоять из двух строк данных, каждая из которых представляет точку зрения одной команды на матч. Эти данные будут сохранены в текстовый файл в формате CSV (значения, разделенные запятыми), который по сути представляет собой просто рабочий лист Excel, но вместо строк, разделяющих ячейки, в нем есть запятые и символы новой строки. Для начала мы начнем со страницы ежедневной статистики NHL.com и закончим выходным файлом, содержащим 2,1 тыс. Строк:

Инструменты, необходимые для работы

from bs4 import BeautifulSoup as soup
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from numpy import array
import pandas as pd
import csv
from datetime import date

Selenium - это, по сути, библиотека, которая может открывать экземпляр веб-браузера и взаимодействовать с ним автоматически, а BeautifuSoup - это то, что извлекает HTML-код со страницы, чтобы мы могли использовать . Numpy и Pandas используются для обработки и форматирования данных, что не требуется, если вы просто хотите создать веб-парсер, но поскольку мы будем использовать эти данные позже с Tensorflow, проще всего сразу использовать массивы Numpy, особенно учитывая мои планы в отношении этих данных (не говоря уже о том, что это более эффективно, чем обычная логика Python).

2- Веб-страница в HTML

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

Этот короткий метод выполняет всю фактическую «очистку». Если вы дадите ему URL-адрес, HTML-код страницы будет возвращен в функциональном формате, из которого вы сможете найти то, что вам нужно на веб-странице. Давайте разберемся, как это работает.

def url_to_soup(url):
    # selenium driver setup
    driver = webdriver.Firefox()
    # get url
    driver.get(url)
    # create sauce (raw HTML) using driver
    source = driver.page_source
    driver.close()
    

Первый шаг - определить веб-драйвер, который по сути является браузером, который будет использовать селен. Что мне нравится в Selenium, так это то, что он автоматически открывает веб-страницу в реальном времени, так что вы можете видеть каждое действие своей программы. Я решил использовать Firefox. Вы можете использовать любой браузер, и selenium выполнит всю работу по доступу к этому браузеру, это очень просто, просто укажите, какой из них вы хотите использовать. Теперь все, что вам нужно сделать, это дать драйверу URL-адрес, и он откроет страницу автоматически, но не забудьте закрыть драйвер, как только вы закончите извлечение со страницы.

Переменная source содержит необработанный HTML-код, извлеченный со страницы. Я использую термин «сырой», потому что он именно такой, сырой и грязный. Сначала мы должны превратить эту бурю текстов в суп, который мы сможем легко съесть. Итак, мы просто запускаем его через функцию «суп», которая принимает два входа; исходную страницу и парсер.

Функция синтаксического анализатора не является синтаксическим анализатором, как вы, вероятно, знаете. Вы можете посмотреть на B.S. документация , чтобы найти различные виды синтаксических анализаторов, но все они делают одно и то же, а именно очищают HTML, чтобы мы могли анализировать его с помощью нашего собственного кода. Некоторые из них могут работать, а другие - нет, но в данном случае lxml сработал. Однако есть одна хитрость. Прежде чем вы сможете указать свой парсер, вы должны открыть командную строку и загрузить свой парсер. Поскольку я выбрал lxml, мне нужно было сначала сделать:

$ pip install lxml

После этого все готово, и вы можете запускать следующие две строки кода ...

# soup(input, parser)
url_soup = soup(sauce, 'lxml')
# return the parsed soup
return url_soup

3- Сбор достоверных данных

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

Одна из важных концепций для понимания этого парсера - это манипулирование URL-адресами. Веб-сайт НХЛ использует одну и ту же структуру URL-адресов для каждого отдельного экземпляра статистики в таблице. Ниже приведена ссылка на один из примеров страницы ежедневной статистики на NHL.com. Как видите, дата встроена в этот URL-адрес в двух разных местах. Если вы измените дату на другую, вы обнаружите, что ссылка все еще работает, и вы будете перенаправлены на страницу со статистикой за этот день.

http://www.nhl.com/stats/team?reportType=game&dateFrom=2018-10-08&dateTo=2018-10-08&gameType=2&filter=gamesPlayed,gte,1&sort=points,wins

Мы можем использовать это в своих интересах, просто изменив дату, автоматически встроенную в URL. Чтобы реализовать эту концепцию, нам нужна функция, которая будет генерировать ссылку на страницу NHL.com для любой конкретной даты, и это именно то, что делают пять строк кода ниже.

def nhl_daily_data(year, month, day):
    nhl_daily = f"http://www.nhl.com/stats/team?reportType=game&dateFrom={year}-{month}-{day}&dateTo={year}-{month}-{day}&gameType=2&filter=gamesPlayed,gte,1&sort=points,wins"
    nhl_soup = url_to_soup(nhl_daily)

Функция делает это с использованием f-строк. Возможно, вы не знакомы с этим инструментом, но он поставляется с Python 3.6 по умолчанию. По сути, какую бы дату вы ни указали в параметрах функции, строка f будет заполнять позиции года, месяца и дня в URL-адресе NHL.com. Затем он использует ранее использованную функцию url_to_soup (), чтобы вернуть суп для этой страницы. Поэтому вместо того, чтобы вызывать обе функции по отдельности, эта функция делает это за вас.

Например, если вы хотите найти статистику по играм, сыгранным на 10/08/2018, вы должны вызвать функцию со следующими параметрами: nhl_daily_data (2018,10,08), и затем она вытащит соус с этой конкретной веб-страницы NHL.com, пропустите его через синтаксический анализатор и верните как суп.

Вы можете спросить себя, зачем вам эта функция в вашем проекте? Что ж, правда в том, что, может быть, и нет. Первым шагом к поиску данных в Интернете является разработка плана атаки. Возможно, вся желаемая информация хранится на одной странице, и в этом случае вы можете пропустить этот шаг и просто взять все необходимые данные за один проход. Если вы не можете использовать этот трюк с URL-адресом, selenium предлагает функции для ручного щелчка, которые вы можете использовать для навигации по веб-сайту, и вы можете найти их в документации по Selenium.

Далее я расскажу, как самому читать HTML, чтобы вы могли сказать своей программе, что искать.

3.1- Чтение HTML

Чтобы выполнить следующую часть, вам нужно знать, как читать HTML. Первый шаг - перейти на страницу, с которой вам нужны данные, я зашел на случайную страницу ежедневной статистики НХЛ. Посмотрите, где на странице находятся ваши данные. Вы можете видеть, что вся игровая статистика встроена в какую-то таблицу, поэтому вполне вероятно, что эта таблица является отдельным классом. Ваши данные могут не находиться внутри уникального элемента JavaScript, как у меня, но процесс все тот же.

Затем щелкните правой кнопкой мыши текстовую информацию, которую вы хотите использовать, и нажмите «проверить элемент». В вашем браузере появится панель проверки, где вы сможете найти все классы и подклассы страницы. Просмотрите их и найдите «маршрут» к классу, в котором находится желаемый объект, диаграмма или информация. Это необходимо для того, чтобы указать своему скребку, где искать.

Функция find () - это то, что ищет вас в коде. Нам не нужно заставлять программу проходить через все складки HTML, чтобы добраться до таблицы, вам просто нужно указать, какой класс искать, и BeautifulSoup найдет его.

# finding table data
table_body = nhl_soup.find(attrs="rt-table").find(attrs='rt-tbody')

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

Таблица фактически имеет формат 2-мерного массива; каждая игра представляет собой два массива. Каждый массив представляет собой статистику точки зрения одной команды на эту игру.

3.2- Сбор информации

Большой кусок кода ниже может показаться сложным, но на самом деле мы на финишной прямой.

# loading the game data
game_data = []
# finding the chart and looping through rows
for rows in table_body.find_all(attrs="rt-tr-group"):
    row_data = []

Используя то, что мы знаем из чтения HTML, мы можем просто использовать функцию find () и поставить перед ней цикл for, и он автоматически извлечет все в классе, по одной строке за раз. Но мы не можем останавливаться на достигнутом, потому что в элемент таблицы включены не только данные, которые нам нужны, но и некоторые данные, которые нам не нужны, такие как заголовки и деления. Если мы посмотрим на конкретный HTML-код для части таблицы с данными, вы увидите, что у него другой тег: td (данные таблицы). Это означает, что нам нужно выполнить вложенный цикл for, чтобы сузить поиск только до данных этой таблицы.

    # looping through row data
    for td in rows.find_all():
         row_data.append(td.text)
         game_date = f"{year}-{month}-{day}"
         enemy_team = row_data[4][14:].lstrip()
         win = row_data[8]
         loss = row_data[9]
         over_time = row_data[11]
         points = row_data[12]
         goals_for = row_data[13]
         goals_against = row_data[14]
         shots_for = row_data[17]
         shots_against = row_data[18]
         power_play_goals_for = row_data[19]
         power_play_opportunities = row_data[20]
         power_play_percent = row_data[21]
         power_play_goals_against = row_data[23]
         penalty_kill_percent = row_data[24]
         faceoff_wins = row_data[25]
         faceoff_losses = row_data[26]
         faceoff_win_pct = row_data[27]
row_data = [game_date, team_name, enemy_team, home_away, win,   loss, over_time, points, goals_for, goals_against, shots_for, shots_against, power_play_goals_for, power_play_opportunities, power_play_percent, power_play_goals_against, penalty_kill_percent, faceoff_wins, faceoff_losses, faceoff_win_pct]
    game_data.append(row_data)
    return game_data

Это находит для нас список данных строки, по одной строке за раз. Затем мы можем выбрать, какую информацию мы хотим, отсортировать и сохранить.

4- Осталось только одно… сохранить.

Чтобы обернуть это красивым бантом, который мы можем завязать, я определил метод пакетного сбора. Этот метод вызывает метод nhl_daily_data () один день за раз для диапазона дат. Это создает диаграмму всего, что мы собрали, а затем записывает ее на ваш компьютер в виде файла CSV.

def batch_collection(start_date, end_date):
    season_dates = pd.date_range(start=start_date, end=end_date)
    for date in season_dates:
        print(date)
        year = date.year
        month = date.month
        day = date.day
        chart = array(nhl_daily_data(year, month, day))
        # .txt to CSV
        with open('2017-2018season.txt', "a") as output:
            writer = csv.writer(output, lineterminator='\n')
            writer.writerows(chart)

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