Web scraping страниц с помощью Beautiful Soup и Python 3

Часто анализ данных, big data и проекты машинного обучения используют scraping для сбора данных, с которыми потом можно работать. Язык программирования Python широко используется в датологии и предлагает экосистему модулей и инструментов, которые вы можете использовать в своих собственных проектах. В этом мануале мы рассмотрим модуль Beautiful Soup.

Название модуля Beautiful Soup отсылает к песне Черепахи Квази из 10 главы сказки Льюиса Кэрролла «Алиса в Стране чудес». Эта библиотека Python позволяет быстро обрабатывать web-scraping. В настоящее время доступна версия Beautiful Soup 4, совместимая с Python 2.7 и Python 3. Beautiful Soup создает дерево синтаксического анализа из разобранных HTML и XML-документов (включая документы с незакрытыми тегами, tag soup и другие виды искаженной разметки).

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

Требования

Для работы вам понадобится локальная или удаленная среда разработки Python:

Также нужно предварительно установить Requests и Beautiful Soup:

Кроме того, нужно иметь базовые знания и навыки работы с HTML.

Немного о данных

В этом мануале для примера используются данные Национальной галереи искусств США. Это художественный музей, который находится в парке Национальная аллея (Вашингтон). В нем хранится более 120 000 произведений искусства, выполненных более чем 13 000 художников от эпохи Возрождения до наших дней.

Для scraping мы используем указатель художников, который доступен по этой ссылке.

Поскольку это простой тестовый scraping-проект Beautiful Soup, загружать слишком много данных с сайта не нужно. Достаточно выбрать одну букву в списке; в мануале используется буква Z.

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

https://www.nga.gov/collection/anZ1.htm

Для анализа важно сразу отметить, сколько всего страниц есть в списке по выбранной букве. В данном случае здесь всего 5 страниц, последняя из которых посвящена художнику Zykmund, Václav и имеет такой URL:

https://www.nga.gov/collection/anZ5.htm

Чтобы ознакомиться с DOM страницы, откройте в браузере Developer Tools.

Читайте также: Использование консоли разработчика JavaScript

Импорт библиотек

Разверните среду разработки Python 3. Для этого перейдите в каталог, в котором находится среда, и запустите команду:

. my_env/bin/activate

Затем нужно создать файл nga_z_artists.py (можете выбрать другое имя):

nano nga_z_artists.py

С помощью этого файла можно импортировать библиотеки Requests и Beautiful Soup.

Библиотека Requests позволяет использовать HTTP в программах Python в удобочитаемом формате, а модуль Beautiful Soup предназначен для быстрого scraping-а.

Чтобы импортировать библиотеки, используйте выражение import. Чтобы импортировать последнюю версию Beautiful Soup, укажите пакет bs4.

# Import libraries
import requests
from bs4 import BeautifulSoup

Сбор и анализ веб-страницы

Теперь нужно собрать и проанализировать данные первой веб-страницы с помощью Requests. Присвойте URL-адрес первой страницы переменной page с помощью метода request.get().

import requests
from bs4 import BeautifulSoup
# Collect first page of artists’ list
page = requests.get('https://www.nga.gov/collection/anZ1.htm')

Читайте также: Использование переменных в Python 3

Затем нужно создать объект BeautifulSoup, или дерево синтаксического анализа. Этот объект принимает в качестве аргумента документ page.text из Requests (ответ сервера), а затем анализирует его из встроенного в Python html.parser.

import requests
from bs4 import BeautifulSoup
page = requests.get('https://www.nga.gov/collection/anZ1.htm')
# Create a BeautifulSoup object
soup = BeautifulSoup(page.text, 'html.parser')

Создав объект BeautifulSoup, можно загрузить текстовые данные.

Загрузка текстовых данных

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

Для этого перейдите в браузер и кликните правой кнопкой мыши (в macOS CTRL + клик) по имени первого художника в списке, Zabaglia, Niccola. В появившемся меню выберите Inspect Element (Firefox) или Inspect (Chrome).

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

Сначала вы увидите, что таблица имен находится в тегах <div>, где class=»BodyText». Это важно отметить, так как теперь искать текст нужно только в этом разделе веб-страницы. Также можно увидеть, что имя Zabaglia, Niccola находится в теге link, так как оно ссылается на веб-страницу художника. Поэтому нужно будет ссылаться на тег <a>. Имя каждого художника является ссылкой.

Для этого можно использовать методы find() и find_all() библиотеки Beautiful Soup. Они загрузят текст из BodyText <div>.

import requests
from bs4 import BeautifulSoup
# Collect and parse first page
page = requests.get('https://www.nga.gov/collection/anZ1.htm')
soup = BeautifulSoup(page.text, 'html.parser')
# Pull all text from the BodyText div
artist_name_list = soup.find(class_='BodyText')
# Pull text from all instances of <a> tag within BodyText div
artist_name_list_items = artist_name_list.find_all('a')

В конец программы добавьте цикл for, который будет итерировать имена художников, помещенные в переменную artist_name_list_items.

Читайте также: Циклы for

Вывести имена поможет метод prettify(). Он превратит дерево анализа Beautiful Soup в отформатированную строку Unicode.

...
artist_name_list = soup.find(class_='BodyText')
artist_name_list_items = artist_name_list.find_all('a')
# Create for loop to print out all artists' names
for artist_name in artist_name_list_items:

print(artist_name.prettify())

Сохраните изменения в файле и запустите программу:

python nga_z_artists.py

Вы получите такой вывод:

<a href="/cgi-bin/tsearch?artistid=11630">
Zabaglia, Niccola
</a>
...
<a href="/cgi-bin/tsearch?artistid=961">
Zanguidi, Jacopo
</a>
<a href="/collection/anZ2.htm">
Zan-Zep
</a>
<a href="/collection/anZ3.htm">
Zer-Zit
</a>
<a href="/collection/anZ4.htm">
Zoa-Zuc
</a>
<a href="/collection/anZ5.htm">
<strong>
next
<br/>
page
</strong>
</a>

На этом этапе вы увидите полный текст и теги, относящиеся ко всем именам художников в тегах <a>, найденных в <div class = «BodyText»> на первой странице, а также дополнительный текст ссылки. Эту дополнительную информацию рекомендуется исключить.

Удаление лишних данных

Чтобы в конце не появлялись дополнительные ссылки, нужно кликнуть правой кнопкой мыши и выбрать Inspect.

Вы увидите, что ссылки в конце <div class=»BodyText»> хранятся в HTML-таблице <table class=»AlphaNav»>.

Beautiful Soup может найти класс AlphaNav и использовать метод decompose(), чтобы удалить тег и все его содержимое из дерева анализа.

Используйте переменную last_links, чтобы сослаться на лишние ссылки и добавить их в файл программы:

import requests
from bs4 import BeautifulSoup
page = requests.get('https://www.nga.gov/collection/anZ1.htm')
soup = BeautifulSoup(page.text, 'html.parser')
# Remove bottom links
last_links = soup.find(class_='AlphaNav')

last_links.decompose()

artist_name_list = soup.find(class_='BodyText')
artist_name_list_items = artist_name_list.find_all('a')
for artist_name in artist_name_list_items:
print(artist_name.prettify())

Запустите программу:

python nga_z_artist.py

Вы получите вывод:

<a href="/cgi-bin/tsearch?artistid=11630">
Zabaglia, Niccola
</a>
<a href="/cgi-bin/tsearch?artistid=34202">
Zaccone, Fabian
</a>
...
<a href="/cgi-bin/tsearch?artistid=3454">
Zanetti I, Antonio Maria, conte
</a>
<a href="/cgi-bin/tsearch?artistid=961">
Zanguidi, Jacopo
</a>

Теперь в выводе больше нет лишних ссылок, отображаются только ссылки, связанные с именами художников.

Загрузка содержимого тегов

Чтобы получить доступ только к именам художников и не выводить весь тег, нужно обозначить как цель содержимое тегов <a>.

Для этого можно использовать метод .contents, который создаст список.

Откорректируйте цикл for. Теперь вместо полной ссылки и тегов он будет выводить список дочерних объектов (полные имена художников).

import requests
from bs4 import BeautifulSoup
page = requests.get('https://www.nga.gov/collection/anZ1.htm')
soup = BeautifulSoup(page.text, 'html.parser')
last_links = soup.find(class_='AlphaNav')
last_links.decompose()
artist_name_list = soup.find(class_='BodyText')
artist_name_list_items = artist_name_list.find_all('a')
# Use .contents to pull out the <a> tag’s children
for artist_name in artist_name_list_items:
names = artist_name.contents[0]

print(names)

Обратите внимание: итерация происходит по вызову индекса каждого элемента.

Запустите программу, и вы получите такой вывод:

Zabaglia, Niccola
Zaccone, Fabian
Zachert, Johann Edvart
...
Zanetti Borzino, Leopoldina
Zanetti I, Antonio Maria, conte
Zanguidi, Jacopo

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

Предположим, нужно также собрать URL-ы, связанные с этими художниками. Извлечь URL-адреса из тега <a> можно с помощью метода get(‘href’).

Поскольку мы уже знаем, что программа не может собрать полный URL, нужно использовать конкатенацию и объединить URL с первой его частью (в данном случае это https://www.nga.gov).

Добавьте в цикл такие строки:

...
for artist_name in artist_name_list_items:
names = artist_name.contents[0]
links = 'https://www.nga.gov' + artist_name.get('href')
print(names)
print(links)

Запустите программу. Она вернет имена художников и URL-адреса:

Zabaglia, Niccola
https://www.nga.gov/cgi-bin/tsearch?artistid=11630
Zaccone, Fabian
https://www.nga.gov/cgi-bin/tsearch?artistid=34202
...
Zanetti I, Antonio Maria, conte
https://www.nga.gov/cgi-bin/tsearch?artistid=3454
Zanguidi, Jacopo
https://www.nga.gov/cgi-bin/tsearch?artistid=961

В настоящее время все собранные данные просто выводятся в окно терминала. Их нужно перенаправить в файл.

Перенаправление данных в CSV-файл

Собирать данные в терминал не очень полезно. Файлы CSV позволяют хранить табличные данные в виде обычного текста; CSV является общим форматом для электронных таблиц и баз данных.

Читайте также: Работа с текстовыми файлами в Python 3

Импортируйте модуль csv в файл программы. Для этого поместите в начале файла строку:

import csv

Создайте файл z-artist-names.csv для хранения данных, используйте режим ‘w’. Также нужно указать заголовки верхней строки Name и Link, которые нужно передать методу writerow() в виде списка:

f = csv.writer(open('z-artist-names.csv', 'w'))
f.writerow(['Name', 'Link'])

В цикл for нужно добавить строку, которая будет записывать в файл names и links:

f.writerow([names, links])

Теперь файл программы выглядит так:

import requests
import csv
from bs4 import BeautifulSoup
page = requests.get('https://www.nga.gov/collection/anZ1.htm')
soup = BeautifulSoup(page.text, 'html.parser')
last_links = soup.find(class_='AlphaNav')
last_links.decompose()
# Create a file to write to, add headers row
f = csv.writer(open('z-artist-names.csv', 'w'))

f.writerow(['Name', 'Link'])

artist_name_list = soup.find(class_='BodyText')
artist_name_list_items = artist_name_list.find_all('a')
for artist_name in artist_name_list_items:
names = artist_name.contents[0]
links = 'https://www.nga.gov' + artist_name.get('href')
# Add each artist’s name and associated link to a row
f.writerow([names, links])

Запустите программу. Она не вернет никакого вывода на экран. Вместо этого она создаст файл z-artist-names.csv в рабочем каталоге.

Откройте файл. В нем вы увидите примерно такой вывод:

Name,Link
"Zabaglia, Niccola",https://www.nga.gov/cgi-bin/tsearch?artistid=11630
"Zaccone, Fabian",https://www.nga.gov/cgi-bin/tsearch?artistid=34202
"Zachert, Johann Edvart",https://www.nga.gov/cgi-bin/tsearch?artistid=35577
"Zachmann, Max",https://www.nga.gov/cgi-bin/tsearch?artistid=46892
"Zadkine, Ossip",https://www.nga.gov/cgi-bin/tsearch?artistid=3475
...

Он также может больше походить на таблицу.

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

Извлечение связанных страниц

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

Чтобы собрать данные со всех страниц, нужно пересмотреть код программы.

Для начала нужно инициализировать список для хранения страниц:

pages = []

Добавьте в список такой цикл:

for i in range(1, 6):
url = 'https://www.nga.gov/collection/anZ' + str(i) + '.htm'
pages.append(url)

Поскольку на сайте 5 страниц с именами на Z, цикл for ограничивается диапазоном от 1 до 6, чтобы итерировать все эти 5 страниц.

В данном случае ссылка начинается строкой https://www.nga.gov/collection/anZ, после которой указывается номер страницы. Он представлен целым числом i в цикле for, которое можно преобразовать в строку. Ссылка заканчивается на .htm. Объедините эти строки, а затем добавьте результат в список pages.

Читайте также: Преобразование типов данных в Python 3

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

Читайте также: Циклы for

Эти два цикла for имеют такой вид:

pages = []
for i in range(1, 6):
url = 'https://www.nga.gov/collection/anZ' + str(i) + '.htm'
pages.append(url)
for item in pages:
page = requests.get(item)
soup = BeautifulSoup(page.text, 'html.parser')
last_links = soup.find(class_='AlphaNav')
last_links.decompose()
artist_name_list = soup.find(class_='BodyText')
artist_name_list_items = artist_name_list.find_all('a')
for artist_name in artist_name_list_items:
names = artist_name.contents[0]
links = 'https://www.nga.gov' + artist_name.get('href')
f.writerow([names, links])

Первый цикл for итерирует страницы, а второй цикл – выполняет scraping данных с каждой из этих страниц, а затем добавляет имена художников и ссылки.

Оба цикла должны идти после выражений import, создания файла CSV и переменной pages.

Полный код программы теперь выглядит так:

import requests
import csv
from bs4 import BeautifulSoup
f = csv.writer(open('z-artist-names.csv', 'w'))
f.writerow(['Name', 'Link'])
pages = []
for i in range(1, 6):
url = 'https://www.nga.gov/collection/anZ' + str(i) + '.htm'
pages.append(url)
for item in pages:
page = requests.get(item)
soup = BeautifulSoup(page.text, 'html.parser')
last_links = soup.find(class_='AlphaNav')
last_links.decompose()
artist_name_list = soup.find(class_='BodyText')
artist_name_list_items = artist_name_list.find_all('a')
for artist_name in artist_name_list_items:
names = artist_name.contents[0]
links = 'https://www.nga.gov' + artist_name.get('href')
f.writerow([names, links])

Создание файла не займет много времени.

Дополнительные рекомендации

Выполняя scraping веб-страниц, важно проявить внимание к серверам, с которых вы собираете информацию.

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

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

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

import requests
headers = {
'User-Agent': 'Your Name, example.com',
'From': 'email@example.com'
}
url = 'https://example.com'
page = requests.get(url, headers = headers)

С помощью таких заголовков люди, которые просматривают логи сервера, смогут обратиться к вам.

Заключение

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

Читайте также: Web scraping с помощью Scrapy и Python 3

Tags: , , ,