🐍 Самоучитель по Python для начинающих. Часть 17: Основы скрапинга и парсинга
Научимся извлекать данные из статического и динамического контента с помощью регулярных выражений, XPath, BeautifulSoup, MechanicalSoup и Selenium. В конце статьи – код 10 скриптов для скрапинга данных и изображений с Wikipedia, Habr, LiveLib, IMDb и TIOBE.
Веб-скрапинг – это процесс автоматического сбора информации из онлайн-источников. Для выбора нужных сведений из массива «сырых» данных, полученных в ходе скрапинга, нужна дальнейшая обработка – парсинг. В процессе парсинга выполняются синтаксический анализ, разбор и очистка данных. Результат парсинга – очищенные, упорядоченные, структурированные данные, представленные в формате, понятном конечному пользователю (или приложению).
Скрипты для скрапинга создают определенную нагрузку на сайт, с которого они собирают данные – могут, например, посылать чрезмерное количество GET запросов к серверу. Это одна из причин, по которой скрапинг относится к спорным видам деятельности. Чтобы не выходить за рамки сетевого этикета, необходимо всегда соблюдать главные правила сбора публичной информации:
- Если на сайте есть API, нужно запрашивать данные у него.
- Частота и количество GET запросов должны быть разумными.
- Следует передавать информацию о клиенте в
User-Agent. - Если на сайте есть личные данные пользователей, необходимо учитывать настройки приватности в robots.txt.
Необходимо отметить, что универсальных рецептов скрапинга и парсинга не существует. Это связано со следующими причинами:
- Некоторые сервисы активно блокируют скраперов. Динамическая смена прокси не всегда помогает решить эту проблему.
- Контент многих современных сайтов генерируется динамически – результат обычного GET запроса из приложения к таким сайтам вернется практически пустым. Эта проблема решается с помощью Selenium WebDriver либо MechanicalSoup, которые имитируют действия браузера и пользователя.
Для извлечения данных со страниц с четкой, стандартной структурой эффективнее использовать язык запросов XPath. И напротив, для получения нужной информации с нестандартных страниц с произвольным синтаксисом лучше использовать средства библиотеки BeautifulSoup. Ниже мы подробно рассмотрим оба подхода.
Примеры скрапинга и парсинга на Python
Экосистема Python располагает множеством инструментов для скрапинга и парсинга. Начнем с самого простого примера – получения веб-страницы и извлечения из ее кода ссылки.
Скрапинг содержимого страницы
Воспользуемся модулем urllib.request стандартной библиотеки urllib для получения исходного кода одностраничного сайта example.com:
from urllib.request import urlopen
url = 'http://example.com'
page = urlopen(url)
print(page.read().decode('utf-8'))
Результат:
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
width: 600px;
margin: 5em auto;
padding: 2em;
background-color: #fdfdff;
border-radius: 0.5em;
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
div {
margin: 0 auto;
width: auto;
}
}
</style>
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
Точно такой же результат можно получить с помощью requests:
from bs4 import BeautifulSoup import requests url = 'http://example.com' res = requests.get(url) soup = BeautifulSoup(res.text,'html.parser') print(soup)
Этот результат – те самые сырые данные, которые нужно обработать (подвергнуть парсингу), чтобы извлечь из них нужную информацию, например, адрес указанной на странице ссылки:
Парсинг полученных данных
Извлечь адрес ссылки можно 4 разными способами – с помощью:
- Методов строк.
- Регулярного выражения.
- Запроса XPath.
- Обработки BeautifulSoup.
Рассмотрим все эти способы по порядку.
Методы строк
Это самый трудоемкий способ – для извлечения каждого элемента нужно определить 2 индекса – начало и конец вхождения. При этом к индексу вхождения надо добавить длину стартового фрагмента:
from urllib.request import urlopen
url = 'http://example.com/'
page = urlopen(url)
html_code = page.read().decode('utf-8')
start = html_code.find('href="') + 6
end = html_code.find('">More')
link = html_code[start:end]
print(link)
Результат:
https://www.iana.org/domains/example
Регулярное выражение
В предыдущей главе мы подробно рассматривали способы извлечения конкретных подстрок из текста. Точно так же регулярные выражения можно использовать для поиска данных в исходном коде страниц:
from urllib.request import urlopen
import re
url = 'http://example.com/'
page = urlopen(url)
html_code = page.read().decode('utf-8')
link = r'(https?://\S+)(?=")'
print(re.findall(link, html_code))
Результат:
https://www.iana.org/domains/example
Запрос XPath
Язык запросов XPath (XML Path Language) позволяет извлекать данные из определенных узлов XML-документа. Для работы с HTML кодом в Python используют модуль etree:
from urllib.request import urlopen
from lxml import etree
url = 'http://example.com/'
page = urlopen(url)
html_code = page.read().decode('utf-8')
tree = etree.HTML(html_code)
print(tree.xpath("/html/body/div/p[2]/a/@href")[0])
Результат:
https://www.iana.org/domains/example
Чтобы узнать путь к нужному элементу страницы, в браузерах Chrome и FireFox надо кликнуть правой кнопкой по элементу и выбрать «Просмотреть код», после чего откроется консоль. В консоли по интересующему элементу нужно еще раз кликнуть правой кнопкой, выбрать «Копировать», а затем – копировать путь XPath:
В приведенном выше примере для извлечения ссылки к пути
/html/body/div/p[2]/a/ мы добавили указание для получения значения ссылки @href,
и индекс [0], поскольку результат возвращается в виде списка. Если @href
заменить на text(), программа вернет текст ссылки, а не сам URL:
print(tree.xpath("/html/body/div/p[2]/a/text()")[0])
Результат:
More information...
Парсер BeautifulSoup
Регулярные выражения и XPath предоставляют огромные возможности для извлечения нужной информации из кода страниц, но у них есть свои недостатки: составлять Regex-шаблоны сложно, а запросы XPath хорошо работают только на страницах с безупречной, стандартной структурой. К примеру, страницы Википедии не отличаются идеальной структурой, и использование XPath для извлечения нужной информации из определенных элементов статей, таких как таблицы infobox, часто оказывается неэффективным. В этом случае оптимальным вариантом становится BeautifulSoup, специально разработанный для парсинга HTML-кода.
Библиотека BeautifulSoup не входит в стандартный набор Python, ее нужно установить самостоятельно:
pip install beautifulsoup4
В приведенном ниже примере мы будем извлекать из исходного кода страницы уникальные ссылки, за исключением внутренних:
from bs4 import BeautifulSoup
from urllib.request import urlopen
url = 'https://webscraper.io/test-sites/e-commerce/allinone/phones'
page = urlopen(url)
html = page.read().decode('utf-8')
soup = BeautifulSoup(html, 'html.parser')
links = set()
for link in soup.find_all('a'):
l = link.get('href')
if l != None and l.startswith('https'):
links.add(l)
for link in links:
print(link)
Результат:
https://twitter.com/webscraperio https://www.facebook.com/webscraperio/ https://forum.webscraper.io/ https://webscraper.io/downloads/Web_Scraper_Media_Kit.zip https://cloud.webscraper.io/ https://chrome.google.com/webstore/detail/web-scraper/jnhgnonknehpejjnehehllkliplmbmhn?hl=en
При использовании XPath точно такой же результат даст следующий скрипт:
from urllib.request import urlopen
from lxml import etree
url = 'https://webscraper.io/test-sites/e-commerce/allinone/phones'
page = urlopen(url)
html_code = page.read().decode('utf-8')
tree = etree.HTML(html_code)
sp = tree.xpath("//li/a/@href")
links = set()
for link in sp:
if link.startswith('http'):
links.add(link)
for link in links:
print(link)
Имитация действий пользователя в браузере
При скрапинге сайтов очень часто требуется авторизация, нажатие кнопок «Читать дальше», переход по ссылкам, отправка форм, прокручивание ленты и так далее. Отсюда возникает необходимость имитации действий пользователя. Как правило, для этих целей используют Selenium, однако есть и более легкое решение – библиотека MechanicalSoup:
pip install MechanicalSoup
По сути, MechanicalSoup исполняет роль браузера без графического интерфейса. Помимо имитации нужного взаимодействия с элементами страниц, MechanicalSoup также парсит HTML-код, используя для этого все функции BeautifulSoup.
Воспользуемся тестовым сайтом http://httpbin.org/, на котором есть возможность отправки формы заказа пиццы:
import mechanicalsoup
browser = mechanicalsoup.StatefulBrowser()
browser.open("http://httpbin.org/")
browser.follow_link("forms")
browser.select_form('form[action="/post"]')
print(browser.form.print_summary())
В приведенном выше примере браузер MechanicalSoup перешел по внутренней ссылке http://httpbin.org/forms/post и вернул описание полей ввода:
<input name="custname"/> <input name="custtel" type="tel"/> <input name="custemail" type="email"/> <input name="size" type="radio" value="small"/> <input name="size" type="radio" value="medium"/> <input name="size" type="radio" value="large"/> <input name="topping" type="checkbox" value="bacon"/> <input name="topping" type="checkbox" value="cheese"/> <input name="topping" type="checkbox" value="onion"/> <input name="topping" type="checkbox" value="mushroom"/> <input max="21:00" min="11:00" name="delivery" step="900" type="time"/> <textarea name="comments"></textarea> <button>Submit order</button>
Перейдем к имитации заполнения формы:
browser["custname"] = "Best Customer"
browser["custtel"] = "+7 916 123 45 67"
browser["custemail"] = "trex@example.com"
browser["size"] = "large"
browser["topping"] = ("cheese", "mushroom")
browser["comments"] = "Add more cheese, plz. More than the last time!"
Теперь форму можно отправить:
response = browser.submit_selected()
Результат можно вывести с помощью print(response.text):
{
"args": {},
"data": "",
"files": {},
"form": {
"comments": "Add more cheese, plz. More than the last time!",
"custemail": "trex@example.com",
"custname": "Best Customer",
"custtel": "+7 916 123 45 67",
"delivery": "",
"size": "large",
"topping": [
"cheese",
"mushroom"
]
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "191",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"Referer": "http://httpbin.org/forms/post",
"User-Agent": "python-requests/2.28.1 (MechanicalSoup/1.2.0)",
"X-Amzn-Trace-Id": "Root=1-6404d82d-2a9ae95225dddaec1968ccb8"
},
"json": null,
"origin": "86.55.39.89",
"url": "http://httpbin.org/post"
}
Отлично! Вы освоили все основные инструменты для скрапинга статичных сайтов.
Вы умеете скачивать HTML, парсить его с помощью BeautifulSoup и XPath, и даже автоматизировать заполнение веб-форм. Это мощная база для решения множества задач.
Но что делать, если контент на сайте подгружается динамически при прокрутке или нажатии на кнопки? В полной версии урока вы перейдёте на следующий уровень:
- Научитесь управлять реальным браузером с помощью Selenium для скрапинга самых сложных динамических сайтов.
- Напишете 10 полноценных скрапинг-проектов для своего портфолио, собирая данные с Wikipedia, Habr, IMDb и других реальных ресурсов.