Навыки Data Science: парсинг веб-страниц с использованием Python

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

В этом руководстве я рассмотрю простой пример того, как очистить веб-сайт, чтобы собрать данные о 100 лучших компаниях 2018 года из Fast Track. Автоматизация этого процесса с помощью веб-скребка позволяет избежать ручного сбора данных, экономит время, а также позволяет хранить все данные о компаниях в одном структурированном файле.

TL; DR В качестве быстрого примера простого парсера на Python вы можете найти полный код, описанный в этом руководстве, на GitHub.

Начиная

Первый вопрос, который следует задать перед тем, как приступить к работе с любым приложением на Python, - «Какие библиотеки мне нужны?»

Для парсинга веб-страниц следует рассмотреть несколько различных библиотек, в том числе:

  • Красивый суп
  • Запросы
  • Scrapy
  • Селен

В этом примере мы будем использовать Beautiful Soup. Используя pip, менеджер пакетов Python, вы можете установить Beautiful Soup со следующим:

pip install BeautifulSoup4

После установки этих библиотек приступим!

Осмотрите веб-страницу

Чтобы узнать, какие элементы вам нужно нацелить в коде Python, вам нужно сначала проверить веб-страницу.

Чтобы собрать данные из 100 лучших компаний Tech Track, вы можете просмотреть страницу, щелкнув правой кнопкой мыши по интересующему элементу и выбрав Проверить. Это вызывает HTML-код, в котором мы можем увидеть элемент, в котором содержится каждое поле.

Поскольку данные хранятся в таблице, для очистки потребуется всего несколько строк кода. Это хороший пример и хорошее место для начала, если вы хотите ознакомиться с парсингом веб-сайтов, но имейте в виду, что это не всегда будет так просто!

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

На веб-странице таблицы рейтингов отображается таблица, содержащая 100 результатов. При просмотре страницы легко увидеть шаблон в html. Результаты содержатся в строках таблицы:

<table class="tableSorter">

Повторяющиеся строки <tr> сохранят наш код минимальным за счет использования цикла внутри Python для поиска данных и записи в файл!

Дополнительное примечание: еще одна проверка, которую можно выполнить, - это проверить, выполняется ли HTTP-запрос GET на веб-сайте, который уже может возвращать результаты в виде структурированного ответа, такого как формат JSON или XML. Вы можете проверить это на вкладке сети в инструментах проверки, часто на вкладке XHR. После обновления страницы запросы будут отображаться по мере их загрузки, и если ответ содержит отформатированную структуру, часто проще сделать запрос с помощью клиента REST, такого как Insomnia, чтобы вернуть результат.

Разберите html веб-страницы с помощью Beautiful Soup

Теперь, когда вы ознакомились со структурой html и ознакомились с тем, что вы очищаете, пора приступить к работе с python!

Первый шаг - импортировать библиотеки, которые вы будете использовать для своего парсера. Мы уже говорили о BeautifulSoup выше, который помогает нам обрабатывать html. Следующая импортируемая библиотека urllib, которая устанавливает соединение с веб-страницей. Наконец, мы будем записывать вывод в CSV, поэтому нам также необходимо импортировать библиотеку csv. В качестве альтернативы здесь можно использовать библиотеку json.

# import libraries
from bs4 import BeautifulSoup
import urllib.request
import csv

Следующим шагом является определение URL-адреса, который вы очищаете. Как обсуждалось в предыдущем разделе, на этой веб-странице все результаты представлены на одной странице, поэтому здесь приводится полный URL-адрес, указанный в адресной строке.

# specify the url
urlpage =  'http://www.fasttrack.co.uk/league-tables/tech-track-100/league-table/'

Затем мы подключаемся к веб-странице и можем анализировать HTML-код с помощью BeautifulSoup, сохраняя объект в переменной «soup».

# query the website and return the html to the variable 'page'
page = urllib.request.urlopen(urlpage)
# parse the html using beautiful soup and store in variable 'soup'
soup = BeautifulSoup(page, 'html.parser')

На этом этапе мы можем распечатать переменную soup, которая должна вернуть полный проанализированный HTML-код запрошенной веб-страницы.

print(soup)

Если произошла ошибка или переменная пуста, возможно, запрос не был успешным. На этом этапе вы можете реализовать обработку ошибок с помощью модуля urllib.error.

Искать элементы html

Поскольку все результаты содержатся в таблице, мы можем искать в объекте супа таблицу, используя метод find. Затем мы можем найти каждую строку в таблице, используя метод find_all.

Если мы напечатаем количество строк, мы должны получить результат 101, 100 строк плюс заголовок.

# find results within table
table = soup.find('table', attrs={'class': 'tableSorter'})
results = table.find_all('tr')
print('Number of results', len(results))

Таким образом, мы можем перебрать результаты, чтобы собрать данные.

Распечатав первые 2 строки в объекте супа, мы увидим, что каждая строка имеет следующую структуру:

<tr>
<th>Rank</th>
<th>Company</th>
<th class="">Location</th>
<th class="no-word-wrap">Year end</th>
<th class="" style="text-align:right;">Annual sales rise over 3 years</th>
<th class="" style="text-align:right;">Latest sales £000s</th>
<th class="" style="text-align:right;">Staff</th>
<th class="">Comment</th>
<!--                            <th>FYE</th>-->
</tr>
<tr>
<td>1</td>
<td><a href="http://www.fasttrack.co.uk/company_profile/wonderbly-3/"><span class="company-name">Wonderbly</span></a>Personalised children's books</td>
<td>East London</td>
<td>Apr-17</td>
<td style="text-align:right;">294.27%</td>
<td style="text-align:right;">*25,860</td>
<td style="text-align:right;">80</td>
<td>Has sold nearly 3m customisable children’s books in 200 countries</td>
<!--                                            <td>Apr-17</td>-->
</tr>

В таблице 8 столбцов, содержащих: Ранг, Компания, Местоположение, Конец года, Годовой рост продаж, Последние продажи, Персонал и Комментарии, все это интересные данные, которые мы можем сохранить.

Эта структура единообразна во всех строках на веб-странице (что может не всегда иметь место для всех веб-сайтов!), И поэтому мы снова можем использовать метод find_all для присвоения каждого столбца переменной, которую мы можем записать в CSV или JSON с помощью поиск элемента <td>.

Цикл по элементам и сохранение переменных

В python полезно добавлять результаты в список, чтобы затем записать данные в файл. Мы должны объявить список и установить заголовки csv перед циклом следующим образом:

# create and write headers to a list 
rows = []
rows.append(['Rank', 'Company Name', 'Webpage', 'Description', 'Location', 'Year end', 'Annual sales rise over 3 years', 'Sales £000s', 'Staff', 'Comments'])
print(rows)

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

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

Следующий шаг - перебрать результаты, обработать данные и добавить в rows , который можно записать в CSV.

Чтобы найти результаты в цикле:

# loop over results
for result in results:
    # find all columns per result
    data = result.find_all('td')
    # check that columns have data 
    if len(data) == 0: 
        continue

Поскольку первая строка в таблице содержит только заголовки, мы можем пропустить этот результат, как показано выше. Он также не содержит каких-либо <td> элементов, поэтому при поиске элемента ничего не возвращается. Затем мы можем проверить, обрабатываются ли только результаты, содержащие данные, потребовав, чтобы длина данных была ненулевой.

Затем мы можем начать обработку данных и сохранение в переменных.

    # write columns to variables
    rank = data[0].getText()
    company = data[1].getText()
    location = data[2].getText()
    yearend = data[3].getText()
    salesrise = data[4].getText()
    sales = data[5].getText()
    staff = data[6].getText()
    comments = data[7].getText()

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

Очистка данных

Если мы распечатаем переменную company, текст будет содержать не только название компании, но и описание. Если мы затем распечатаем sales, он будет содержать нежелательные символы, такие как символы сносок, которые было бы полезно удалить.

    print('Company is', company)
    # Company is WonderblyPersonalised children's books          
    print('Sales', sales)
    # Sales *25,860

Мы хотели бы разделить company на название компании и описание, что мы можем сделать в нескольких строках кода. Снова посмотрев на html, для этого столбца есть элемент <span>, который содержит только название компании. В этом столбце также есть ссылка на другую страницу веб-сайта, где есть более подробная информация о компании. Мы воспользуемся этим чуть позже!

<td><a href="http://www.fasttrack.co.uk/company_profile/wonderbly-3/"><span class="company-name">Wonderbly</span></a>Personalised children's books</td>

Чтобы разделить company на два поля, мы можем использовать методfind для сохранения элемента <span>, а затем использовать strip или replace для удаления названия компании из переменной company, чтобы оставалось только описание.
Чтобы удалить ненужные символы из sales, мы снова можем использовать методыstrip и replace!

    # extract description from the name
    companyname = data[1].find('span', attrs={'class':'company-name'}).getText()    
    description = company.replace(companyname, '')
    
    # remove unwanted characters
    sales = sales.strip('*').strip('†').replace(',','')

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

Чтобы очистить URL-адрес из каждой таблицы и сохранить его как переменную, нам нужно выполнить те же шаги, что и выше:

  • Найдите элемент с URL-адресом страницы компании на сайте быстрого доступа.
  • Сделайте запрос на URL каждой страницы компании
  • Разберите html с помощью Beautifulsoup
  • Найдите интересующие элементы

Глядя на несколько страниц компании, как на скриншоте выше, URL-адреса находятся в последней строке таблицы, поэтому мы можем искать в последней строке элемент <a>.

    # go to link and extract company website
    url = data[1].find('a').get('href')
    page = urllib.request.urlopen(url)
    # parse the html 
    soup = BeautifulSoup(page, 'html.parser')
    # find the last result in the table and get the link
    try:
        tableRow = soup.find('table').find_all('tr')[-1]
        webpage = tableRow.find('a').get('href')
    except:
        webpage = None

Также могут быть случаи, когда веб-сайт компании не отображается, поэтому мы можем использовать условие try except, если URL-адрес не найден.

После того, как мы сохранили все данные в переменных, все еще в цикле, мы можем добавить каждый результат в список rows.

    # write each result to rows
    rows.append([rank, companyname, webpage, description, location, yearend, salesrise, sales, staff, comments])
print(rows)

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

Запись в выходной файл

Вы можете сохранить эти данные для анализа, и это можно сделать очень просто в python из нашего списка.

# Create csv and write rows to output file
with open('techtrack100.csv','w', newline='') as f_output:
    csv_output = csv.writer(f_output)
    csv_output.writerows(rows)

При запуске скрипта python будет сгенерирован выходной файл, содержащий 100 строк результатов, на которые вы можете посмотреть более подробно!

Резюме

В этом кратком руководстве по парсингу веб-страниц с помощью Python описано:

  • Подключение к веб-странице
  • Разбор html с помощью BeautifulSoup
  • Перебираем объект супа в поисках элементов
  • Выполнение простой очистки данных
  • Запись данных в CSV

Это мой первый урок, поэтому дайте мне знать, если у вас возникнут какие-либо вопросы или комментарии, если что-то непонятно!

Спасибо за чтение! Если вам понравилась моя статья, то подпишитесь на мой ежемесячный информационный бюллетень, где вы сможете получать мои последние статьи и лучшие ресурсы прямо на ваш почтовый ящик!

Вы можете подписаться на меня на Medium, чтобы увидеть больше статей, подписаться на меня в Twitter или узнать больше о моих планах на моем веб-сайте.