В этой статье мы покажем, как использовать библиотеки Python и синтаксический анализ HTML для извлечения полезной информации с веб-сайта, а затем ответим на некоторые важные вопросы аналитики.

В проекте по науке о данных почти всегда самая трудоемкая и беспорядочная часть - это сбор и очистка данных. Всем нравится создавать одну или две крутые модели глубокой нейронной сети (или XGboost) и демонстрировать свои навыки с помощью крутых интерактивных 3D-графиков. Но для начала модели нуждаются в необработанных данных, и они не могут быть легкими и понятными.

В конце концов, жизнь - это не Kaggle, где zip-файл, полный данных, ждет вас, чтобы его распаковали и смоделировали :-)

Но зачем вообще собирать данные или строить модель? Основная мотивация - ответить на деловой, научный или социальный вопрос. Есть ли тенденция? Это связано с этим? Может ли измерение этого объекта предсказать исход этого явления? Это потому, что ответ на этот вопрос подтвердит вашу гипотезу как ученого / практикующего специалиста. Вы просто используете данные (в отличие от пробирок, как химик, или магнитов, как физик), чтобы проверить свою гипотезу и доказать / опровергнуть ее с научной точки зрения. Это «научная» часть науки о данных. Ни больше, ни меньше…

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

Давайте сегодня займемся одним из таких вопросов ...

Есть ли какая-либо связь между ВВП (с точки зрения паритета покупательной способности) страны и процентом ее пользователей Интернета? И похожа ли эта тенденция для стран с низким / средним / высоким доходом?

Теперь вы можете придумать любое количество источников для сбора данных для ответа на этот вопрос. Я обнаружил, что веб-сайт ЦРУ (да, «АГЕНТСТВО»), на котором размещена основная фактическая информация обо всех странах мира, является хорошим местом для сбора данных.

Итак, мы будем использовать следующие модули Python для создания нашей базы данных и визуализаций,

  • Панды, Numpy, matplotlib / seaborn
  • Python urllib (для отправки HTTP-запросов)
  • BeautifulSoup (для анализа HTML)
  • Модуль регулярных выражений (для поиска точного совпадающего текста)

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

Чтение первой HTML-страницы и переход к BeautifulSoup

Вот как выглядит первая страница всемирного справочника ЦРУ:

Мы используем простой запрос urllib с контекстом игнорирования ошибки SSL, чтобы получить эту страницу, а затем передать ее волшебному BeautifulSoup, который анализирует HTML для нас и создает красивый текстовый дамп. Для тех, кто не знаком с библиотекой BeautifulSoup, они могут посмотреть следующее видео или прочитать эту отличную информативную статью о Medium.

Итак, вот фрагмент кода для чтения HTML-кода главной страницы,

ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
# Read the HTML from the URL and pass on to BeautifulSoup
url = 'https://www.cia.gov/library/publications/the-world-factbook/'
print("Opening the file connection...")
uh= urllib.request.urlopen(url, context=ctx)
print("HTTP status",uh.getcode())
html =uh.read().decode()
print(f"Reading done. Total {len(html)} characters read.")

Вот как мы передаем его BeautifulSoup и используем метод find_all, чтобы найти все названия стран и коды, встроенные в HTML. По сути, идея состоит в том, чтобы найти теги HTML с именем «option». Текст в этом теге - это название страны, а символы 5 и 6 в значении тега представляют двухсимвольный код страны.

Теперь вы можете спросить, как вы узнали, что вам нужно извлечь только 5-й и 6-й символы? Простой ответ заключается в том, что вам нужно самостоятельно изучить основной текст, то есть проанализировать HTML-текст, и определить эти индексы. Универсального метода для определения этого не существует. Каждая HTML-страница и основная структура уникальны.

soup = BeautifulSoup(html, 'html.parser')
country_codes=[]
country_names=[]
for tag in soup.find_all('option'):
    country_codes.append(tag.get('value')[5:7])
    country_names.append(tag.text)
temp=country_codes.pop(0) # To remove the first entry 'World'
temp=country_names.pop(0) # To remove the first entry 'World'

Сканирование: загрузите все текстовые данные по всем странам в словарь, очищая каждую страницу отдельно.

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

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

# Base URL
urlbase = 'https://www.cia.gov/library/publications/the-world-factbook/geos/'
# Empty data dictionary
text_data=dict()
# Iterate over every country
for i in range(1,len(country_names)-1):
    country_html=country_codes[i]+'.html'
    url_to_get=urlbase+country_html
    # Read the HTML from the URL and pass on to BeautifulSoup
    html = urllib.request.urlopen(url_to_get, context=ctx).read()
    soup = BeautifulSoup(html, 'html.parser')
    txt=soup.get_text()
    text_data[country_names[i]]=txt
    print(f"Finished loading data for {country_names[i]}")
    
print ("\n**Finished downloading all text data!**")

Если хотите, храните на свалке соленья

Для удобства я предпочитаю сериализовать и в любом случае хранить эти данные в Python pickle object. Таким образом, в следующий раз, когда я открою записную книжку Jupyter, я смогу просто прочитать данные напрямую, не повторяя шаги веб-сканирования.

import pickle
pickle.dump(text_data,open("text_data_CIA_Factobook.p", "wb"))
# Unpickle and read the data from local storage next time
text_data = pickle.load(open("text_data_CIA_Factobook.p", "rb"))

Использование регулярного выражения для извлечения данных о ВВП на душу населения из текстового дампа

Это основная аналитическая часть программы, в которой мы используем модуль регулярного выражения, чтобы найти то, что мы ищем в огромной текстовой строке, и извлечь соответствующие числовые данные. Теперь регулярное выражение - это богатый ресурс в Python (или практически во всех языках программирования высокого уровня). Это позволяет искать / сопоставлять определенный образец строк в большом корпусе текста. Здесь мы используем очень простые методы регулярного выражения для сопоставления точных слов, таких как ВВП - на душу населения (ППС):, а затем читаем несколько символов после этого, извлекаем позиции определенных символов, таких как $ и круглые скобки, чтобы в конечном итоге извлечь числовое значение ВВП на душу населения. Вот идея, проиллюстрированная рисунком.

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

# 'b' to catch 'billions', 't' to catch 'trillions'
start = re.search('\$',string)
end = re.search('[b,t]',string)
if (start!=None and end!=None):
    start=start.start()
    end=end.start()
    a=string[start+1:start+end-1]
    a = convert_float(a)
    if (string[end]=='t'):
    # If the GDP was in trillions, multiply it by 1000
        a=1000*a

Вот пример фрагмента кода. Обратите внимание на несколько проверок обработки ошибок в коде. Это необходимо из-за в высшей степени непредсказуемого характера HTML-страниц. Не все страны могут иметь данные о ВВП, не все страницы могут иметь одинаковые формулировки данных, не все числа могут выглядеть одинаково, не все строки могут иметь одинаковые символы $ и (). Любое количество вещей может пойти не так.

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

# Initialize dictionary for holding the data
GDP_PPP = {}
# Iterate over every country
for i in range(1,len(country_names)-1):
    country= country_names[i]
    txt=text_data[country]       
    pos = txt.find('GDP - per capita (PPP):')
    if pos!=-1: #If the wording/phrase is not present
        pos= pos+len('GDP - per capita (PPP):')
        string = txt[pos+1:pos+11]
        start = re.search('\$',string)
        end = re.search('\S',string)
        if (start!=None and end!=None): #If search fails somehow
            start=start.start()
            end=end.start()
            a=string[start+1:start+end-1]
            #print(a)
            a = convert_float(a)
            if (a!=-1.0): #If the float conversion fails somehow
                print(f"GDP/capita (PPP) of {country}: {a} dollars")
                # Insert the data in the dictionary
                GDP_PPP[country]=a
            else:
                print("**Could not find GDP/capita data!**")
        else:
            print("**Could not find GDP/capita data!**")
    else:
        print("**Could not find GDP/capita data!**")
print ("\nFinished finding all GDP/capita data")

Не забудьте использовать метод внутреннего / левого соединения pandas

Следует помнить, что вся эта текстовая аналитика будет создавать фреймы данных с немного разным набором стран, поскольку разные типы данных могут быть недоступны для разных стран. Можно использовать левое соединение панд для создания фрейма данных с пересечением всех общих стран, для которых доступны / могут быть извлечены все фрагменты данных.

df_combined = df_demo.join(df_GDP, how='left')
df_combined.dropna(inplace=True)

А теперь самое интересное, моделирование… но подождите! Сначала займемся фильтрацией!

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

# Create a filtered data frame and x and y arrays
filter_gdp = df_combined['Total GDP (PPP)'] > 50
filter_low_income=df_combined['GDP (PPP)']>5000
filter_high_income=df_combined['GDP (PPP)']<25000
df_filtered = df_combined[filter_gdp][filter_low_income][filter_high_income]

Наконец, визуализация

Мы используем функцию seaborn regplot для создания графиков разброса (процент пользователей Интернета в сравнении с ВВП на душу населения) с линейной регрессией и показанными диапазонами 95% доверительного интервала. Они выглядят следующим образом. Результат можно интерпретировать как

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

Резюме

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

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

Я надеюсь, что читатели смогут извлечь пользу из предоставленного файла записной книжки и использовать его в соответствии со своими собственными требованиями и воображением. Дополнительные блокноты для веб-аналитики данных: см. Мой репозиторий.

Если у вас есть какие-либо вопросы или идеи, которыми вы хотите поделиться, свяжитесь с автором по адресу tirthajyoti [AT] gmail.com. Также вы можете проверить авторские репозитории GitHub на предмет других забавных фрагментов кода на Python, R или MATLAB и ресурсов машинного обучения. Если вы, как и я, увлечены машинным обучением / наукой о данных, пожалуйста, не стесняйтесь добавить меня в LinkedIn или подписаться на меня в Twitter.