Вступление
Веб-скрапинг - это программный сбор информации с различных веб-сайтов. Хотя существует множество библиотек и фреймворков на разных языках, которые могут извлекать веб-данные, Python долгое время был популярным выбором из-за множества опций для парсинга веб-страниц.
Эта статья даст вам ускоренный курс по парсингу веб-страниц в Python с помощью Beautiful Soup - популярной библиотеки Python для синтаксического анализа HTML и XML.
Этичный парсинг веб-сайтов
Веб-скрапинг повсеместен и дает нам данные, как если бы мы получали их с помощью API. Однако, как хорошие граждане Интернета, мы обязаны уважать владельцев сайтов, с которых мы очищаемся. Вот несколько принципов, которых должен придерживаться веб-парсер:
- Не заявляйте, что извлеченный контент принадлежит нам. Владельцы веб-сайтов иногда тратят много времени на создание статей, сбор сведений о продуктах или сбор другого контента. Мы должны уважать их труд и оригинальность.
- Не очищайте веб-сайт, который не нужно очищать. Веб-сайты иногда
поставляются с
robots.txt
который определяет части веб-сайта, которые можно очистить. У многих веб-сайтов также есть Условия использования, которые могут не разрешать очистку. Мы должны уважать веб-сайты, которые не хотят обрабатывать. - Есть ли уже доступный API? Прекрасно, нам не нужно писать скребок. API-интерфейсы создаются для предоставления доступа к данным контролируемым способом, определенным владельцами данных. Мы предпочитаем использовать API, если они доступны.
- Выполнение запросов к веб-сайту может отрицательно сказаться на его работе. Веб-парсер, который делает слишком много запросов, может быть таким же изнурительным, как и DDOS-атака. Мы должны выполнять очистку со всей ответственностью, чтобы не нарушить нормальную работу веб-сайта.
Обзор Beautiful Soup
HTML-содержимое веб-страниц можно анализировать и очищать с помощью Beautiful Soup. В следующем разделе мы рассмотрим те функции, которые полезны для очистки веб-страниц.
Что делает Beautiful Soup таким полезным, так это множество функций, которые он предоставляет для извлечения данных из HTML. На этом изображении ниже показаны некоторые функции, которые мы можем использовать:
{.ezlazyload}
Давайте на практике посмотрим, как мы можем анализировать 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 для навигации по нашему веб-сайту и извлечения данных.
Переход к определенным тегам
Из объекта супа, созданного в предыдущем разделе, возьмем тег заголовка
doc.html
:
soup.head.title # returns <title>Head's title</title>
Вот разбивка каждого компонента, который мы использовали для получения названия:
{.ezlazyload}
Beautiful Soup - мощный инструмент, потому что наши объекты Python соответствуют вложенной структуре HTML-документа, который мы очищаем.
Чтобы получить текст первого <a>
, введите следующее:
soup.body.a.text # returns '1'
Чтобы получить заголовок в теге body HTML (обозначается классом title), введите в терминале следующее:
soup.body.pb # 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>]
Вот разбивка каждого компонента, который мы использовали для поиска тега:
{.ezlazyload}
Мы также можем искать теги определенного класса, class_
аргумент
class_. Beautiful Soup использует class_
потому что class
- это
зарезервированное ключевое слово в Python. Давайте искать все a
теги ,
которые имеют класс «элемент»:
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, пришло время применить наши знания на практике. Давайте создадим парсер для извлечения данных из https://books.toscrape.com/ и сохранения их в файл CSV. Сайт содержит случайные данные о книгах и является отличным местом для тестирования ваших методов парсинга.
Сначала создайте новый файл с именем scraper.py
. Импортируем все
библиотеки, которые нам нужны для этого скрипта:
import requests
import time
import csv
import re
from bs4 import BeautifulSoup
В упомянутых выше модулях:
requests
- выполняет запрос URL и извлекает HTML-код веб-сайта.time
- ограничивает, сколько раз мы очищаем страницу за один разcsv
- помогает нам экспортировать очищенные данные в файл CSV.re
- позволяет нам писать регулярные выражения, которые пригодятся для выбора текста на основе его шаблонаbs4
- с уважением, модуль парсинга HTML
У вас bs4
, а time
, csv
и re
- встроенные пакеты в Python. Вам
нужно будет установить requests
напрямую следующим образом:
$ pip3 install requests
Прежде чем начать, вам необходимо понять, как структурирован HTML-код веб-страницы. В вашем браузере перейдите по адресу http://books.toscrape.com/catalogue/page-1.html . Затем щелкните правой кнопкой мыши компоненты веб-страницы, которые нужно очистить, и нажмите кнопку проверки , чтобы понять иерархию тегов, как показано ниже.
Это покажет вам базовый HTML-код того, что вы проверяете. На следующем рисунке показаны эти шаги:
{.ezlazyload}
Изучив 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
Каждый раз, когда мы получаем объект супа, проверяется
наличие кнопки «далее», чтобы мы могли остановиться на последней
странице. Мы отслеживаем счетчик номера страницы, который увеличивается
на 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}")
Если вы хотите узнать больше о if __name__ == "__main__"
,
ознакомьтесь с нашим руководством о том, как он
работает .
Вы можете выполнить сценарий, как показано ниже, в вашем терминале и получить вывод как:
$ 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
Отличная работа! Если вы хотите взглянуть на код парсера в целом, вы можете найти его на GitHub .
Заключение
В этом уроке мы узнали об этике написания хороших парсеров. Затем мы
использовали Beautiful Soup для извлечения данных из HTML-файла с
использованием свойств объекта Beautiful Soup и его различных методов,
таких как find()
, find_all()
и get_text()
. Затем мы создали
парсер, который извлекает список книг в Интернете и экспортирует его в
CSV.
Веб-скрапинг - полезный навык, который помогает в различных действиях, таких как извлечение данных, таких как API, выполнение контроля качества на веб-сайте, проверка неработающих URL-адресов на веб-сайте и многое другое. Какой следующий скребок вы собираетесь построить?