Извлекайте информацию с веб-сайтов в кратчайшие сроки и с высокой степенью автоматизации

Обзор

О чем рассказывается в статье

  • Технические и юридические аспекты парсинга веб-страниц
  • Пример парсинга платформы на основе поиска с использованием метода на основе HTML с библиотекой Python Beautiful Soup
  • Общие методы устранения аномалий и несоответствий в данных при парсинге
  • Обзор того, как обсуждаемый пример можно преобразовать в конвейер данных с использованием платформы облачных вычислений Amazon, AWS и Apache Airflow для регулярного сбора данных.

Что такое парсинг веб-страниц?

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

Как в целом работают парсеры?

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

Разница по сравнению с вызовами API

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

Юридические соображения

Веб-скрапинг может нарушать нормы авторского права или условия обслуживания владельца веб-сайта, особенно когда он используется для получения конкурентного преимущества, получения непредвиденных финансовых доходов или в целях злоупотребления в целом (например, отправка запросов в очень короткие промежутки времени). Тем не менее, сбор данных, которые являются общедоступными и используются для 1) личного потребления, 2) академических целей или 3) других случаев без коммерческих намерений, как правило, можно считать безвредным с юридической точки зрения.

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

Настройка Beautiful Soup на Python

Beautiful Soup - это библиотека Python для сбора данных с веб-сайтов путем доступа к базовому HTML-коду.

Установите последнюю версию Beautiful Soup в свой терминал:

$ pip install beautifulsoup4

Установите requests , чтобы иметь возможность вызывать веб-сайты (библиотека отправляет HTTP-запросы):

$ pip install requests

В файле записной книжки Python или Jupyter импортируйте эти библиотеки:

from bs4 import BeautifulSoup
import requests

И несколько стандартных библиотек для этапов обработки и преобразования данных:

import re
from re import sub
from decimal import Decimal
import io
from datetime import datetime
import pandas as pd

0. Введение

Представьте, что мы хотим очистить платформу, содержащую общедоступные объявления о недвижимости. Мы хотим получить такую ​​информацию, как 1) цена объекта, 2) его адрес и 3) расстояние, 4) название станции и 5) тип транспорта до ближайших остановок общественного транспорта до узнать, как цены на недвижимость распределяются между остановками общественного транспорта в конкретном городе.

Например, какова средняя цена на жилье для остановки общественного транспорта XY, если мы рассмотрим 50 ближайших к этой станции объектов?

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

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

Как только мы узнаем, в каком макете и структуре показываются объявления, нам нужно придумать логику парсинга, которая позволит нам извлекать все желаемые функции (например, цену, адрес, информацию об общественном транспорте) из каждого объявления, доступного на странице результатов. .

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

  1. Как получить одну точку данных для одной функции?
    (Например, получить ценник из первого объявления.)
  2. Как получить все точки данных для одной функции со всей страницы?
    (например, получить ценники всех объявлений на странице.)
  3. Как получить все точки данных для одной функции, доступные на всех страницах результатов?
    (например, получить ценники каждого объявления, показанного по определенному поисковому запросу.)
  4. Как устранить несоответствие, если интересующий объект данных не всегда применим в объявлении?
    (Например, в некоторых объявлениях в поле цены указано «Цена по заявке». Мы бы в конечном итоге получаем столбец, состоящий из числовых и строковых значений, что не позволяет в нашем случае готовый анализ. Конечно, мы могли бы просто исключить строковые значения при выполнении анализа. Этот шаг просто демонстрирует, как предвидеть более чистый набор данных с самого начала, который может быть даже более ценным в других случаях.)
  5. Как лучше извлекать сложную информацию?
    (Например, предположим, что каждое объявление содержит информацию об общественном транспорте, например «0,5 мили до станции метро XY». Как должна выглядеть логика, чтобы мы можем хранить эту комбинацию информации непосредственно в правильном формате: distance = [0.5], transport_type = [«метро»], station = [«name XY»])

1. Логика получения одной точки данных

Важное примечание: Все фрагменты кода, обсуждаемые ниже, также можно найти в полном файле Jupyter Notebook в моем репозитории на GitHub.

Позвонить на сайт

Сначала мы реплицируем поисковый запрос, который мы сделали в браузере, в скрипте Python:

# search area of interest
url = 'https://www.website.com/london/page_size=25&q=london&pn=1'

# make request with url provided and get html text
html_text = requests.get(url).text

# employ lxml as a parser to extract html code
soup = BeautifulSoup(html_text, 'lxml')

Переменная soup теперь содержит весь исходный HTML-код страницы результатов.

HTML-теги для конкретных функций поиска

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

Для этого шага нам понадобится помощь браузера. Некоторые популярные браузеры предлагают удобный способ напрямую получить HTML-информацию о конкретном элементе. В Google Chrome вы: 1) отмечаете поле конкретной функции и 2) нажимаете правой кнопкой мыши, чтобы получить возможность проверить элемент (или просто примените сочетание клавиш Cmd + Shift + C). Затем исходный код открывается рядом с представлением браузера, и вы непосредственно видите информацию HTML.

В примере с ценой использование HTML-класса css-aaabbbccc вернет информацию £550,000, как показано в представлении браузера.

Понимание HTML-классов и атрибутов id

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

Единственная разница между классом и идентификатором заключается в том, что идентификаторы уникальны на странице и могут применяться только к одному элементу, тогда как классы могут применяться к нескольким элементам³.

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

Вот почему мы сначала настраиваем таргетинг на рекламу и ищем класс HTML только в исходном коде конкретного объявления:

Использование .text в конце метода find() позволяет нам возвращать только простой текст, как показано в браузере. Без .text он вернет весь исходный код строки HTML, на которую ссылается класс:

Важное примечание. Нам всегда нужно предоставлять элемент HTML, которым в данном случае является p. Также обратите внимание на то, чтобы не забыть подчеркивание в конце имени атрибута class_.

Получите другие функции с той же логикой

  1. Осмотрите интересующий объект
  2. Определите соответствующий HTML-класс или идентификатор
  3. Расшифровать исходный код с помощью find(...).text

Например, получив адрес, HTML-класс может выглядеть так:

(...)
# find address in ad
address = ad.find('p', class_ = 'css-address-123456').text
# show address
address

2. Логика для получения всех точек данных с одной страницы.

Чтобы получить ценники для всех объявлений, мы применяем метод find.all() вместо find() для перехвата рекламы:

# get all ads within one page
ads = ad.find_all('p', class_ = 'css-ad-wrapper-123456')

Переменная ads теперь содержит HTML-код для каждого применимого объявления первых страниц результатов в виде списка списков. Этот формат хранения очень полезен, поскольку он позволяет получить доступ к исходному коду для конкретного объявления с помощью index:

# identify how many ads we have fetched
len(ads)
# show source code of second ad
print(ads[0])

В окончательном коде, чтобы получить все ценники, мы используем dictionary для сбора данных и перебора объявлений, применяя for-loop:

Важное примечание. Добавление id позволяет идентифицировать объявления в dictionary:

# show first ad
map[1]

3. Получите точки данных со всех доступных страниц результатов.

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

Случай 1. Веб-сайт имеет пагинацию

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

Как видно на рисунке выше, окончание URL-адреса относится к номеру страницы результатов.

Важное примечание: номер страницы в URL-адресе обычно становится видимым со второй страницы. Использование базового URL-адреса с дополнительным фрагментом &pn=1 для вызова первой страницы по-прежнему будет работать (в большинстве случаев).

Применение еще одного for-loop поверх другого позволяет нам перебирать страницы результатов:

Определите последнюю страницу результатов

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

Чтобы решить эту проблему, мы можем включить логику, которая проверяет, применима ли ссылка в кнопке разбивки на страницы:

Случай 2. На веб-сайте есть бесконечная прокрутка

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

4. Устранение несоответствия информации

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

Если мы хотим избежать данных шума с самого начала, мы можем применить следующий обходной путь:

  1. Определите функцию для обнаружения аномалий

2. Применить функцию в цикле сбора данных

3. Необязательно: очистка данных на лету

Возможно, вы уже заметили, что формат цены £ XX,XXX,XXX по-прежнему представляет собой строковое значение из-за наличия знака валюты и разделителей запятой. Действуйте эффективно и проводите очистку во время соскабливания:

Определите функцию:

Включить функцию в сбор данных for-loop:

5. Извлечение вложенной информации

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

1. Отгоняйте информацию по правилам

Как показано выше, каждая часть информации об общественном транспорте представлена ​​в следующем формате: «[числовое значение] мили [название станции]». Посмотрите, как «мили» действуют здесь как разделитель, и мы можем использовать его для разделения строки по этому термину.

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

Первоначально переменная transport хранит два списка в списке, поскольку есть две строки информации об общественном транспорте (например, «0,3 мили Слоун-сквер», «0,5 мили Южного Кенсингтона»). Мы перебираем эти списки, используя len из transport в качестве значений индекса, и разделяем каждую строку на две переменные, distance и station.

2. Найдите дополнительные атрибуты HTML для декодирования визуальной информации.

Если мы углубимся в код HTML, мы найдем атрибут HTML, testid, который показывает имя значка, который используется для отображения типа транспорта (например, «метро_станция» - см. Иллюстрацию выше). Эта информация служит метаданными и не отображается в представлении браузера. Мы используем соответствующий HTML-класс css-StyledIcon, чтобы получить весь исходный код этого раздела, и добавляем testid, чтобы вырезать информацию:

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

6. Преобразовать во фрейм данных и экспортировать как CSV.

Когда задача парсинга выполнена, все извлеченные данные доступны в словаре словарей.

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

Вывод первого объявления в словаре:

# show data of first ad
map[0]

1. Превратите словарь в список списков, чтобы избавиться от вложенной информации.

Мы преобразовываем словарь в список списков, чтобы каждая категория содержала только одно значение.

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

2. Создать фрейм данных, используя список списков в качестве входных

df = pd.DataFrame(result, columns = ["ad_id", "price", "address",  
                     "distance", "station", "transport_type"])

Фрейм данных можно экспортировать как CSV следующим образом:

# incorporate timestamp into filename and export as csv
filename = 'test.csv'
df.to_csv(filename)

Преобразование для передачи всех объявлений во фрейм данных

Мы сделали это! Это был последний шаг. Вы создали свой первый скребок, готовый к испытаниям!

7. Ограничения парсинга HTML и альтернативные методы.

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

Однако у парсеров HTML есть и недостатки:

  • Может получить доступ только к информации в HTML-коде, который загружается непосредственно при вызове URL-адреса. Веб-сайты, требующие JavaScript и Ajax для загрузки контента, работать не будут.
  • HTML-классы или идентификаторы могут измениться из-за обновлений веб-сайта (например, новой функции, редизайна веб-сайта).
  • Невозможно передать пользовательский контент на веб-сайт, например условия поиска или информацию для входа (кроме поисковых запросов, которые могут быть включены в URL-адрес, как показано в примере).
  • Может быть легко обнаружен, если запросы кажутся аномальными для веб-сайта (например, очень большое количество запросов в течение короткого промежутка времени). Этого можно избежать путем определения ограничений скорости (например, разрешить выполнение только ограниченного числа действий в определенное время) или других показателей, таких как размер экрана или тип браузера, для идентификации реального пользователя.

Узнайте, как превратить простой скрипт веб-скрейпинга в облачный конвейер данных.

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

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

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

Репозиторий GitHub

Https://github.com/christopherkindl/web-scraper-template

использованная литература

[1]: Тони Пол. (2020). Законен ли парсинг веб-страниц? Путеводитель на 2021 год https://www.linkedin.com/pulse/web-scraping-legal-guide-2021-tony-paul/?trk=read_related_article-card_title. Дата обращения 10 мая 2021.

[2]: W3schools.com. (2021 г.). Атрибут HTML-класса https://www.w3schools.com/html/html_classes.asp. Дата обращения 10 мая 2021.

[3]: GeeksforGeeks.org. (2021 г.). Разница между идентификатором и классом в HTML? Https://www.geeksforgeeks.org/difference-between-an-id-and-class-in-html/#:~:text=Difference%20between%20id%20and%20class,can%20apply%20to%20multiple % 20 элементов . Дата обращения 10 мая 2021.

[4]: JonasCz. (2021 г.). Как предотвратить парсинг веб-страниц https://www.w3schools.com/html/html_classes.asp. Дата обращения 10 мая 2021.

[5]: Эдвард Робертс. (2018). Является ли веб-парсинг незаконным? Зависит от значения слова https://www.w3schools.com/html/html_classes.asp. Дата обращения 10 мая 2021.