Данная статья это вводный учебник по библиотеке BeautifulSoup Python. Примеры из данной статьи помогут вам понять как находить HTML теги, обходить элементы из HTML документа, менять содержимое тегов и парсить веб-страницы.
Причина использования python в этом руководстве довольно очевидна: python очень гибкий и имеет большую поддержку сообщества. Даже если вы новичок, пытающийся научиться веб-скрейпингу с помощью python, эта статья будет очень полезна для вас. Это длинное руководство, поэтому пристегните ремни и давайте начнем.
Прежде чем приступить к веб-скрейпингу в Python, давайте поймем важность HTTP заголовков при парсинге любой веб-страницы. Мы подробно рассмотрим HTTP заголовки. Возможно, я ошибаюсь, но когда я начинал программировать, меня очень пугали HTTP заголовки. Но вскоре я понял, что использовать заголовки при составлении HTTP-запросов очень просто.
HTTP-заголовки (необходимое для веб-скрейпинга в python)
В этом разделе я расскажу о концепции заголовков на некоторых примерах и поделюсь ссылками, чтобы вы могли узнать больше о заголовках в деталях. Итак, давайте перейдем к делу.
Возможно, вы уже знаете, что когда вы выполняете вызовы к API, вы передаете часть информации в «конверте». Допустим, один человек является клиентом, а другой — сервером, и конверт передается в виде API, что и является способом коммуникации.
Содержимое конверта — это данные, которые передаются от одного человека к другому, но вы также можете знать, что когда такие коммуникации происходят в реальной жизни, на верхней части конверта также указывается адрес, по которому эти данные должны быть переданы. Но наряду с этим адресом есть и другой адрес, который используется, когда письмо не получено получателем.
Это просто аналогия, но я пытаюсь объяснить вам, что заголовки тоже выполняют подобную роль.
HTTP Заголовки — это своего рода индикаторы метаданных о том, из чего состоит ответ или запрос. Чтобы понять это, позвольте мне классифицировать заголовки. Итак, в основном их можно разделить на четыре различные категории.
- Заголовки запроса;
- Заголовки ответа;
- Заголовки полезной нагрузки (payload);
- Заголовки представления (определенного типа, Content-Type)
Это не означает, что заголовок запроса не может быть заголовком ответа и наоборот. Давайте разберемся, что на самом деле означает каждый из этих заголовков.
Заголовки запроса
Это пара ключ-значение, как и другие заголовки, и они отправляются клиентом, который запрашивает данные. Они посылаются для того, чтобы сервер мог понять, как он должен отправить ответ. Он также помогает серверу определить отправителя запроса.
Примерами заголовков запроса являются:
- Host: www.python-scripts.com
- User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
- Referer: localhost
- Connection: close
- Accept-Language: ru
- Accept-Encoding; gzip
Помните, что заголовок Content-Type не является заголовком запроса, это заголовок представления. Мы поговорим об этом подробнее, но я просто хотел как можно скорее устранить эту путаницу из вашего сознания.
Из приведенного выше списка заголовков-образцов, Host и User-Agent содержат информацию о том, кто посылает запрос.
Accept-Language говорит серверу, что это язык, на котором я могу понять ваш ответ, и аналогично Accept-Encoding говорит серверу, что даже если у вас сжатые данные, я могу их понять.
Заголовки ответа
Они похожи на заголовки запроса, но передача происходит в обратном порядке. На самом деле, эти заголовки посылаются сервером клиенту. Они объясняют клиенту, что делать с ответом. Он предоставляет дополнительную информацию об отправленных данных.
Пример заголовков ответа:
- Connection: keep-alive
- Date: Mon, 08 Nov 2022
- Server: nginx
- Content-Type: text/html
- Transfer-Encoding: chunked
- Etag: W/”0815”
Etag — это заголовок ответа, который используется для указания версии и кэша. Date сообщает клиенту дату, когда ответ был отправлен от сервера к клиенту. Но опять же Content-Type или Content-Encoding — это заголовки представления, которые мы рассмотрим чуть позже.
Заголовки представления
Заголовки представления указывают на тип переданных данных. Данные, отправленные с сервера к клиенту, могут быть в любом формате, например JSON, HTML, XML, chunked (если размер данных огромен) и т. д. Сервер также сообщает клиенту о диапазоне содержимого.
Примеры заголовков представления:
- Content-Type: text/html
- Content-Encoding: gzip
- Content-Length: 3523
- Content-Range: bytes 50–1000/*
- Content-Location: /docs/fo.xml
Content-Location сообщает клиенту об альтернативном расположении ресурса или данных, которые доступны клиенту для получения информации. Это может быть URL, где хранится данный конкретный ресурс.
Помимо этих заголовков, могут быть и другие заголовки, такие как Trailer, Transfer-Encoding, Etag, if-Not-Match, Authorizations и т.д.
Теперь, что если вы разрабатываете API и хотите определить свои собственные заголовки? Можете ли вы это сделать? Вы совершенно спокойно можете это сделать. Таким же образом, как вы определяете структуру запроса и ответа вашего API, вы можете реализовать собственные заголовки, которые будете принимать вы или сервер.
Примером собственного заголовка может быть заголовок Authorization. Этот заголовок может иметь любое значение. Далее, сервер может использовать это значение для идентификации клиента или для любых других логических операций.
Библиотека BeautifulSoup на примерах
Данная статья это вводный учебник по библиотеке BeautifulSoup Python. Примеры из данной статьи помогут вам понять как находить HTML теги, обходить элементы из HTML документа, менять содержимое тегов и парсить веб-страницы.
BeautifulSoup — это Python библиотека для разбора HTML и XML документов. Она часто используется для веб-скрейпинга. BeautifulSoup преобразует сложный HTML-документ в сложное дерево объектов Python, таких как тег, навигационная строка или комментарий.
Установка BeautifulSoup
Мы используем команду pip для установки необходимых модулей.
1 |
pip install lxml |
Нам необходимо установить модуль lxml, который используется BeautifulSoup.
1 |
pip install bs4 |
BeautifulSoup устанавливается с помощью вышеуказанной команды.
HTML файл с которым мы будем работать
В примерах мы будем использовать следующий HTML-файл:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<!DOCTYPE html> <html> <head> <title>Заголовок</title> <meta charset="utf-8"> </head> <body> <h2>Операционные системы</h2> <ul id="mylist" style="width:150px"> <li>Solaris</li> <li>FreeBSD</li> <li>Debian</li> <li>NetBSD</li> <li>Windows</li> </ul> <p> FreeBSD - это передовая компьютерная операционная система, используемая для современных серверов, настольных компьютеров и встроенных платформ. </p> <p> Debian - это Unix-подобная компьютерная операционная система, которая полностью состоит из свободного программного обеспечения. </p> </body> </html> |
Открываем HTML файл через BeautifulSoup
В первом примере мы используем модуль BeautifulSoup для получения трех тегов.
1 2 3 4 5 6 7 8 9 10 11 |
from bs4 import BeautifulSoup with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') print(soup.h2) print(soup.head) print(soup.li) |
Пример кода выводит HTML-код трех тегов.
1 |
from bs4 import BeautifulSoup |
Мы импортируем класс BeautifulSoup из модуля bs4. BeautifulSoup — это основной класс для выполнения парсинга по HTML документу.
1 2 |
with open('index.html', 'r') as f: contents = f.read() |
Мы открываем файл index.html
и читаем его содержимое с помощью метода read()
.
1 |
soup = BeautifulSoup(contents, 'lxml') |
Создается объект BeautifulSoup, HTML-данные передаются конструктору. Второй параметр определяет синтаксический анализатор.
1 2 |
print(soup.h2) print(soup.head) |
Здесь мы выводим HTML-код двух тегов: <h2>
и <head>
.
1 |
print(soup.li) |
Имеется несколько элементов <li>
, данная строка выведет первый из них.
Полученный результат после выполнения скрипта:
1 2 3 4 5 6 |
<h2>Операционные системы</h2> <head> <title>Заголовок</title> <meta charset="utf-8"/> </head> <li>Solaris</li> |
Приватные прокси для смены IP в библиотеке Requests
Наша зада на данный момент это открыть страницу сайта под другим IP адресом. Для этого нам нужно купить приватные прокси и получить логин и пароль для подключения к прокси. Лучше всего использовать именно приватные прокси, так как это гарантирует безопасность и быстродействие прокси.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import requests username = 'user' password = 'pass' ip = '1.1.1.1' port = 8080 # Настройка proxy proxies = { 'http': f'http://{username}:{password}@{ip}:{port}', 'https': f'http://{username}:{password}@{ip}:{port}', } # API возвращает наш IP url = 'https://api.ipify.org?format=json' # Выполняем запрос response = requests.post(url, proxies=proxies) # Мы обращаемся к API, так что ответ будет в JSON. print(response.json()) |
Как результат мы получим IP-адрес от приватных прокси которых мы купили. Есть proxy для которых не нужно указывать логин и пароль, но в случае приватных прокси — их указывать нужно.
Манипуляция тегами через BeautifulSoup
Атрибут name
тега дает его название, а атрибут text
— его текстовое содержание.
1 2 3 4 5 6 7 8 9 |
from bs4 import BeautifulSoup # Открываем HTML файл для чтения. with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') print(f'HTML: {soup.h2}, имя: {soup.h2.name}, содержимое: {soup.h2.text}') |
В приведенном примере кода выводится HTML, имя и содержимое из тега <h2>
.
Полученный результат:
1 |
HTML: <h2>Операционные системы</h2>, имя: h2, содержимое: Операционные системы |
Обход HTML-дерева используя BeautifulSoup
С помощью метода recursiveChildGenerator
мы обходим HTML-документ.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from bs4 import BeautifulSoup with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') for child in soup.recursiveChildGenerator(): if child.name: print(child.name) |
Пример пробегает по дереву документа и печатает имена всех HTML-тегов.
Полученный результат:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
html head title meta body h2 ul li li li li li p p |
В HTML-документе у нас есть эти теги.
Обход дочерних элементов
С помощью атрибута children
мы можем получить дочерние элементы тега.
1 2 3 4 5 6 7 8 9 10 11 12 |
from bs4 import BeautifulSoup with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') root = soup.html root_childs = [e.name for e in root.children if e.name is not None] print(root_childs) |
Обход потомков элемента
С помощью атрибута descendants
мы получаем всех потомков (детей всех уровней) тега.
1 2 3 4 5 6 7 8 9 10 11 12 |
from bs4 import BeautifulSoup with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') root = soup.body root_childs = [e.name for e in root.descendants if e.name is not None] print(root_childs) |
Пример извлекает все потомки тега <body>
.
Полученный результат:
1 |
['h2', 'ul', 'li', 'li', 'li', 'li', 'li', 'p', 'p'] |
Это все потомки тега <body>
.
Открываем сайт для парсинга через Requests + BeautifulSoup
Requests — это простая HTTP библиотека для Python. Она предоставляет методы для доступа к веб-ресурсам через HTTP запросы.
1 2 3 4 5 6 7 8 9 10 |
from bs4 import BeautifulSoup import requests as req resp = req.get('http://webcode.me') soup = BeautifulSoup(resp.text, 'lxml') print(soup.title) print(soup.title.text) print(soup.title.parent) |
Этот пример извлекает заголовок из простой веб-страницы. Он также выводит информацию о родительском тега <title>
.
1 2 3 |
resp = req.get('http://webcode.me') soup = BeautifulSoup(resp.text, 'lxml') |
Мы получаем HTML исходный код страницы.
1 2 3 |
print(soup.title) print(soup.title.text) print(soup.title.parent) |
Мы получаем HTML-код заголовка <title>
, его текст и HTML-код его родителя.
Полученный результат:
1 2 3 4 5 6 7 8 |
<title>My html page</title> My html page <head> <meta charset="utf-8"/> <meta content="width=device-width, initial-scale=1.0" name="viewport"/> <link href="format.css" rel="stylesheet"/> <title>My html page</title> </head> |
Красивое структурирование HTML кода
С помощью метода prettify мы можем «красиво» структурировать исходный HTML-код.
1 2 3 4 5 6 7 8 |
from bs4 import BeautifulSoup import requests as req resp = req.get('http://webcode.me') soup = BeautifulSoup(resp.text, 'lxml') print(soup.prettify()) |
Полученный результат:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <meta content="width=device-width, initial-scale=1.0" name="viewport"/> <title> My html page </title> </head> <body> <p> Today is a beautiful day. We go swimming and fishing. </p> <p> Hello there. How are you? </p> </body> </html> |
Поиск HTML-элементов по ID
С помощью метода find
мы можем находить элементы по различным признакам, включая id элемента.
1 2 3 4 5 6 7 8 9 10 |
from bs4 import BeautifulSoup with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') print( soup.find('ul', {'id': 'mylist'}) ) |
Данный пример кода находит тег ul, который имеет id mylist
.
Поиск всех HTML-элементов по названию
С помощью метода find_all
мы можем найти все элементы, которые соответствуют некоторым критериям.
1 2 3 4 5 6 7 8 9 |
from bs4 import BeautifulSoup with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') for tag in soup.find_all('li'): print(f'{tag.name}: {tag.text}') |
Пример кода находит и выводит все теги li
.
1 2 3 4 5 |
li: Solaris li: FreeBSD li: Debian li: NetBSD li: Windows |
Метод find_all
может принимать список с названиями элементов для поиска.
1 2 3 4 5 6 7 8 9 10 |
from bs4 import BeautifulSoup with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') tags = soup.find_all(['h2', 'p']) for tag in tags: print(' '.join(tag.text.split())) |
Пример находит все элементы <h2>
и <p>
и выводит их содержимое.
Метод find_all
также может принимать функцию, которая определяет, какие элементы должны быть возвращены.
1 2 3 4 5 6 7 8 9 10 11 12 |
from bs4 import BeautifulSoup def myfun(tag): return tag.is_empty_element with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') tags = soup.find_all(myfun) print(tags) |
В примере выводятся пустые элементы.
1 |
[<meta charset="utf-8"/>] |
Единственным пустым элементом в документе является <meta>
.
Также можно найти элементы с помощью регулярных выражений.
1 2 3 4 5 6 7 8 9 10 11 |
import re from bs4 import BeautifulSoup with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') strings = soup.find_all(string=re.compile('BSD')) for txt in strings: print(' '.join(txt.split())) |
Пример выводит содержимое элементов, которые содержат строку BSD
.
1 2 3 |
FreeBSD NetBSD FreeBSD is an advanced computer operating system used to power modern servers, desktops, and embedded platforms. |
Поиск HTML-элементов по CSS-селектору
С помощью методов select
и select_one
мы можем использовать некоторые селекторы CSS для поиска элементов.
1 2 3 4 5 6 7 8 |
from bs4 import BeautifulSoup with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') print(soup.select('li:nth-of-type(3)')) |
В этом примере используется CSS селектор для вывода HTML кода третьего элемента <li>
.
1 |
[<li>Debian</li>] |
Это третий элемент <li>
.
Символ #
используется в CSS для выбора тегов по их id-атрибутам.
1 2 3 4 5 6 7 8 9 10 |
from bs4 import BeautifulSoup with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') print( soup.select_one('#mylist') ) |
В примере выводится элемент, имеющий идентификатор mylist
.
1 2 3 4 5 6 7 |
<ul id="mylist" style="width:150px"> <li>Solaris</li> <li>FreeBSD</li> <li>Debian</li> <li>NetBSD</li> <li>Windows</li> </ul> |
Создание и добавление тега в тело другого тега
Метод append
добавляет новый тег в HTML-документ.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from bs4 import BeautifulSoup with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') newtag = soup.new_tag('li') newtag.string = 'OpenBSD' ultag = soup.ul ultag.append(newtag) print( ultag.prettify() ) |
В этом примере добавляется новый тег <li>
.
1 2 |
newtag = soup.new_tag('li') newtag.string = 'OpenBSD' |
Сначала мы создаем новый тег с помощью метода new_tag
.
1 |
ultag = soup.ul |
Получаем ссылку на тег <ul>
.
1 |
ultag.append(newtag) |
Мы добавляем только что созданный тег в тело тега <ul>
.
1 |
print(ultag.prettify()) |
Выводим содержимое тега в «красивом» оформлении HTML кода.
Вставка тега в определенном месте
Метод insert
вставляет тег в заданное место.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
from bs4 import BeautifulSoup with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') newtag = soup.new_tag('li') newtag.string = 'OpenBSD' ultag = soup.ul ultag.insert(2, newtag) print( ultag.prettify() ) |
В этом примере созданный тег <li>
вставляется на третью позицию в тело тега <ul>
.
Замена текста в HTML элементе
Метод replace_with
заменяет текст внутри элемента.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from bs4 import BeautifulSoup with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') tag = soup.find(text='Windows') tag.replace_with('OpenBSD') print( soup.ul.prettify() ) |
В этом примере конкретный элемент найден с помощью метода find
, а его содержимое заменено с помощью метода replace_with
.
Удаление HTML элемента
Метод decompose
удаляет тег из HTML-документа и уничтожает его.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from bs4 import BeautifulSoup with open('index.html', 'r') as f: contents = f.read() soup = BeautifulSoup(contents, 'lxml') ptag2 = soup.select_one('p:nth-of-type(2)') ptag2.decompose() print( soup.body.prettify() ) |
В данном примере удаляется второй элемент <p>
.
В этом учебнике мы работали с Python библиотекой BeautifulSoup.
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»