Руководство по синтаксическому анализу HTML с помощью BeautifulSoup в Python
Веб-скрапинг - это программный сбор информации с различных веб-сайтов. Несмотря на то, что существует множество библиотек и фреймворков на разных языках, которые могут извлекать веб-данные, Python уже давно стал популярным выбором из-за множества опций для парсинга веб-страниц.
Эта статья даст вам ускоренный курс по парсингу веб-страниц в Python с помощью Beautiful Soup - популярной библиотеки Python для синтаксического анализа HTML и XML.
Этический веб-скрапинг
Веб-скрапинг повсеместен и дает нам данные, как если бы мы получали их с помощью API. Однако, как хорошие граждане Интернета, мы обязаны уважать владельцев сайтов. Вот несколько принципов, которых должен придерживаться веб-парсер:
- Не заявляйте, что извлеченный контент принадлежит вам. Владельцы веб-сайтов иногда тратят много времени на создание статей, сбор сведений о продуктах или сбор другого контента. Мы должны уважать их труд и оригинальность.
- Не парсите веб-сайт, который не хочет этого. Веб-сайты иногда поставляются с файлом
robots.txt
, который определяет части веб-сайта, которые можно получить. У многих веб-сайтов также есть Условия использования, которые могут не разрешать парсинг. Мы должны уважать веб-сайты, которые не хотят что-бы их контент парсили. - Есть ли уже доступный API? Прекрасно, нам не нужно писать парсер. API-интерфейсы создаются для предоставления доступа к данным контролируемым способом, определенным владельцами данных. Мы предпочитаем использовать API, если они доступны.
- Отправка запросов на веб-сайт может отрицательно сказаться на его работе. Веб-парсер, который делает слишком много запросов, может быть таким же изнурительным, как и DDOS-атака. Мы должны выполнять чистку ответственно, чтобы не нарушать нормальную работу веб-сайта.
Обзор Beautiful Soup
HTML-содержимое веб-страниц можно проанализировать и очистить с помощью Beautiful Soup. В следующем разделе мы рассмотрим те функции, которые полезны для очистки веб-страниц.
Что делает Beautiful Soup таким полезным, так это множество функций, которые он предоставляет для извлечения данных из HTML. На этом изображении ниже показаны некоторые функции, которые мы можем использовать:
Давайте поработаем и посмотрим, как мы можем анализировать HTML с помощью Beautiful Soup. Рассмотрим следующую HTML-страницу, сохраненную в файл как doc.html
:
<html>
<head>
<title>Head's title</title>
</head>
<body>
<p class="title"><b>Body's title</b></p>
<p class="story">line begins
<a href="http://example.com/element1" class="element" id="link1">1</a>
<a href="http://example.com/element2" class="element" id="link2">2</a>
<a href="http://example.com/avatar1" class="avatar" id="link3">3</a>
<p> line ends</p>
</body>
</html>
Следующие фрагменты кода протестированы на Ubuntu 20.04.1 LTS
. Вы можете установить модуль BeautifulSoup
, набрав в терминале следующую команду:
pip3 install beautifulsoup4
HTML-файл doc.html
необходимо подготовить. Это делается путем передачи файла конструктору BeautifulSoup
, давайте воспользуемся для этого интерактивной оболочкой Python, чтобы мы могли мгновенно распечатать содержимое определенной части страницы:
from bs4 import BeautifulSoup
with open("doc.html") as fp:
soup = BeautifulSoup(fp, "html.parser")
Теперь мы можем использовать Beautiful Soup для навигации по нашему веб-сайту и извлечения данных.
Переход к определенным тегам
Из объекта soup, созданного в предыдущем разделе, получим тег заголовка doc.html
:
soup.head.title # returns <title>Head's title</title>
Вот разбивка каждого компонента, который мы использовали для получения названия:
Beautiful Soup - мощный инструмент, потому что наши объекты Python соответствуют вложенной структуре HTML-документа, который мы очищаем.
Чтобы получить текст первого тега <a>
, введите следующее:
soup.body.a.text # returns '1'
Чтобы получить заголовок в HTML теге body (обозначается классом «title»), введите в терминале следующее:
soup.body.p.b # returns <b>Body's title</b>
Для глубоко вложенных HTML-документов навигация может быстро стать утомительной. К счастью, Beautiful Soup поставляется с функцией поиска, поэтому нам не нужно перемещаться, чтобы получить элементы HTML.
Поиск тегов
Метод find_all()
принимает HTML-тег в качестве строкового аргумента и возвращает список элементов, которые соответствуют указанной поисковой строке. Например, если мы хотим, найти все теги a
в doc.html
:
soup.find_all("a")
Мы увидим этот список тегов a
в качестве вывода:
[<a class="element" href="http://example.com/element1" id="link1">1</a>, <a class="element" href="http://example.com/element2" id="link2">2</a>, <a class="element" href="http://example.com/element3" id="link3">3</a>]
Вот разбивка каждого компонента, который мы использовали для поиска тега:
Мы также можем искать теги определенного класса, указав аргумент class_
. Beautiful Soup использует class_
, потому что class
является зарезервированным ключевым словом в Python. Поищем все теги a
, у которых есть класс element:
soup.find_all("a", class_="element")
Поскольку у нас есть только две ссылки с классом «element», вы увидите следующий результат:
[<a class="element" href="http://example.com/element1" id="link1">1</a>, <a class="element" href="http://example.com/element2" id="link2">2</a>]
Что, если бы мы хотели получить ссылки, встроенные в теги a
? Давайте получим атрибут ссылки href
с помощью опции find()
. Он работает точно так же как find_all()
, но возвращает первый соответствующий элемент вместо списка. Введите это в свою оболочку:
soup.find("a", href=True)["href"] # returns http://example.com/element1
Функции find()
и find_all()
также принимают регулярное выражение вместо строки. За кулисами текст будет фильтроваться с использованием метода скомпилированного регулярного выражения search()
. Например:
import re
for tag in soup.find_all(re.compile("^b")):
print(tag)
Список после итерации выбирает теги, начинающиеся с символа b
, который включает <body>
и <b>
:
<body>
<p class="title"><b>Body's title</b></p>
<p class="story">line begins
<a class="element" href="http://example.com/element1" id="link1">1</a>
<a class="element" href="http://example.com/element2" id="link2">2</a>
<a class="element" href="http://example.com/element3" id="link3">3</a>
<p> line ends</p>
</p></body>
<b>Body's title</b>
Мы рассмотрели наиболее популярные способы получения тегов и их атрибутов. Иногда, особенно для менее динамичных веб-страниц, нам просто нужен текст. Посмотрим, как мы сможем это получить!
Получение всего текста
Функция get_text()
извлекает весь текст из HTML - документа. Получим весь текст HTML-документа:
soup.get_text()
Ваш результат должен быть таким:
Head's title
Body's title
line begins
1
2
3
line ends
Иногда печатаются символы новой строки, поэтому ваш вывод также может выглядеть так:
"\n\nHead's title\n\n\nBody's title\nline begins\n 1\n2\n3\n line ends\n\n"
Теперь, когда мы понимаем, как использовать Beautiful Soup, давайте очистим веб-сайт!
Beautiful Soup в действии - очистка списка книг
Теперь, когда мы освоили компоненты Beautiful Soup, пришло время применить наши знания на практике. Давайте создадим парсер для извлечения данных с https://books.toscrape.com/ и сохранения их в файл CSV. Сайт содержит случайные данные о книгах и является отличным местом для проверки ваших методов парсинга.
Сначала создайте новый файл с именем scraper.py
. Импортируем все библиотеки, которые нам нужны для этого скрипта:
import requests
import time
import csv
import re
from bs4 import BeautifulSoup
В упомянутых выше модулях:
requests
- выполняет URL-запрос и получает HTML-код сайтаtime
- ограничивает, сколько раз мы очищаем страницу одновременноcsv
- помогает нам экспортировать очищенные данные в файл CSVre
- позволяет нам писать регулярные выражения, которые пригодятся для выбора текста на основе его шаблонаbs4
- модуль парсинга для парсинга HTML
Вы уже установили bs4
, а time
, csv
и re
являются встроенными пакетами в Python. Вам нужно будет установить только модуль requests
следующим образом:
pip3 install requests
Прежде чем начать, вам нужно понять, как структурирован HTML-код веб-страницы. В вашем браузере перейдите по адресу http://books.toscrape.com/catalogue/page-1.html. Затем щелкните правой кнопкой мыши компоненты веб-страницы, которые нужно очистить, и нажмите кнопку проверки, чтобы понять иерархию тегов, как показано ниже.
Это покажет вам базовый HTML-код того, что вы проверяете. На следующем рисунке показаны эти шаги:
Изучив HTML, мы узнаем, как получить доступ к URL-адресу книги, изображению обложки, заголовку, рейтингу, цене и другим полям из HTML. Давайте напишем функцию, которая очищает элемент книги и извлекает его данные:
def scrape(source_url, soup): # Takes the driver and the subdomain for concats as params
# Find the elements of the article tag
books = soup.find_all("article", class_="product_pod")
# Iterate over each book article tag
for each_book in books:
info_url = source_url+"/"+each_book.h3.find("a")["href"]
cover_url = source_url+"/catalogue" + \
each_book.a.img["src"].replace("..", "")
title = each_book.h3.find("a")["title"]
rating = each_book.find("p", class_="star-rating")["class"][1]
# can also be written as : each_book.h3.find("a").get("title")
price = each_book.find("p", class_="price_color").text.strip().encode(
"ascii", "ignore").decode("ascii")
availability = each_book.find(
"p", class_="instock availability").text.strip()
# Invoke the write_to_csv function
write_to_csv([info_url, cover_url, title, rating, price, availability])
Последняя строка приведенного выше фрагмента указывает на функцию для записи списка очищенных строк в файл CSV. Давайте добавим эту функцию сейчас:
def write_to_csv(list_input):
# The scraped info will be written to a CSV here.
try:
with open("allBooks.csv", "a") as fopen: # Open the csv file.
csv_writer = csv.writer(fopen)
csv_writer.writerow(list_input)
except:
return False
Поскольку у нас есть функция, которая может очищать страницу и экспортировать в CSV, нам нужна другая функция, которая просматривает веб-сайт с разбивкой на страницы, собирая данные о книгах на каждой странице.
Для этого давайте посмотрим на URL-адрес, для которого мы пишем этот парсер:
"http://books.toscrape.com/catalogue/page-1.html"
Единственным изменяющимся элементом URL-адреса является номер страницы. Мы можем динамически форматировать URL-адрес, чтобы он стал исходным URL-адресом:
"http://books.toscrape.com/catalogue/page-{}.html".format(str(page_number))
Этот строковый URL-адрес с номером страницы можно получить с помощью метода requests.get()
. Затем мы можем создать новый объект BeautifulSoup. Каждый раз, когда мы получаем объект soup, проверяется наличие кнопки «next», чтобы мы могли остановиться на последней странице. Мы отслеживаем счетчик номера страницы, который увеличивается на 1 после успешного извлечения страницы.
def browse_and_scrape(seed_url, page_number=1):
# Fetch the URL - We will be using this to append to images and info routes
url_pat = re.compile(r"(http://.*\.com)")
source_url = url_pat.search(seed_url).group(0)
# Page_number from the argument gets formatted in the URL & Fetched
formatted_url = seed_url.format(str(page_number))
try:
html_text = requests.get(formatted_url).text
# Prepare the soup
soup = BeautifulSoup(html_text, "html.parser")
print(f"Now Scraping - {formatted_url}")
# This if clause stops the script when it hits an empty page
if soup.find("li", class_="next") != None:
scrape(source_url, soup) # Invoke the scrape function
# Be a responsible citizen by waiting before you hit again
time.sleep(3)
page_number += 1
# Recursively invoke the same function with the increment
browse_and_scrape(seed_url, page_number)
else:
scrape(source_url, soup) # The script exits here
return True
return True
except Exception as e:
return e
Функция browse_and_scrape()
, вызывается рекурсивно, пока функция soup.find("li",class_="next")
не вернет None
. На этом этапе код очистит оставшуюся часть веб-страницы и завершит работу.
Для последней части пазла мы запускаем процесс парсинга. Мы определяем seed_url
и вызываем browse_and_scrape()
для получения данных. Это делается в блоке if __name__ == "__main__"
:
if __name__ == "__main__":
seed_url = "http://books.toscrape.com/catalogue/page-{}.html"
print("Web scraping has begun")
result = browse_and_scrape(seed_url)
if result == True:
print("Web scraping is now complete!")
else:
print(f"Oops, That doesn't seem right!!! - {result}")
Вы можете выполнить сценарий, как показано ниже, в вашем терминале и получить вывод как:
python scraper.py
Web scraping has begun
Now Scraping - http://books.toscrape.com/catalogue/page-1.html
Now Scraping - http://books.toscrape.com/catalogue/page-2.html
Now Scraping - http://books.toscrape.com/catalogue/page-3.html
.
.
.
Now Scraping - http://books.toscrape.com/catalogue/page-49.html
Now Scraping - http://books.toscrape.com/catalogue/page-50.html
Web scraping is now complete!
Очищенные данные можно найти в текущем рабочем каталоге под именем файла allBooks.csv
. Вот пример содержимого файла:
http://books.toscrape.com/a-light-in-the-attic_1000/index.html,http://books.toscrape.com/catalogue/media/cache/2c/da/2cdad67c44b002e7ead0cc35693c0e8b.jpg,A Light in the Attic,Three,51.77,In stock
http://books.toscrape.com/tipping-the-velvet_999/index.html,http://books.toscrape.com/catalogue/media/cache/26/0c/260c6ae16bce31c8f8c95daddd9f4a1c.jpg,Tipping the Velvet,One,53.74,In stock
http://books.toscrape.com/soumission_998/index.html,http://books.toscrape.com/catalogue/media/cache/3e/ef/3eef99c9d9adef34639f510662022830.jpg,Soumission,One,50.10,In stock
Вывод
В этом уроке мы узнали об этике написания хороших парсеров. Затем мы использовали Beautiful Soup для извлечения данных из HTML-файла с помощью свойств объекта Beautiful Soup и его различных методов, таких как find()
, find_all()
и get_text()
. Затем мы создали парсер, который извлекает список книг в Интернете и экспортирует его в CSV.
Очистка веб-страниц - полезный навык, который помогает в различных действиях, таких как извлечение данных, таких как API, выполнение контроля качества на веб-сайте, проверка неработающих URL-адресов на веб-сайте и многое другое.