Парсинг сайта на Python: requests и BeautifulSoup с нуля
За полчаса соберёте свой первый парсер: скачаете HTML через requests, вытащите заголовки и ссылки через BeautifulSoup и сохраните всё в CSV. Разбираем шаг за шагом, включая частые ошибки.

К концу этого руководства у вас будет рабочий скрипт на Python: он скачивает страницу, находит на ней нужные элементы (например, заголовки статей и ссылки) и записывает результат в CSV-файл. На всё уйдёт 20–40 минут, если Python уже стоит. Работаем на Python 3.10+, библиотеках requests 2.x и beautifulsoup4 4.x — это стандартная связка для простого парсинга статических страниц.
Что нужно до начала
Проверьте, что всё на месте, — иначе споткнётесь на середине.
- Python 3.10 или новее. Проверьте командой
python --version(на некоторых системахpython3 --version). Должна вывестись строка вродеPython 3.11.5. - Доступ в терминал и умение запускать в нём команды.
- Две библиотеки. Установите их:
pip install requests beautifulsoup4. После установки должна появиться строкаSuccessfully installed .... - Текстовый редактор — VS Code, PyCharm или любой другой, где можно сохранить файл
.py.
Парсер мы напишем на демо-сайте quotes.toscrape.com — он создан специально для тренировки и парсинг там разрешён. Прежде чем парсить любой боевой сайт, проверьте его robots.txt и пользовательское соглашение: не везде это допустимо.
Парсинг чужого сайта — не техническая, а сначала юридическая задача. Код работает за минуту, а вопрос «а можно ли вообще» решается до того, как вы его запустили.
Шаг 1. Скачиваем HTML страницы
Первое, что делает любой парсер, — получает исходный HTML. Этим занимается requests: он отправляет HTTP-запрос и возвращает ответ сервера.
import requests
url = "https://quotes.toscrape.com"
# Многие сайты отклоняют запросы без User-Agent, поэтому представимся браузером
headers = {"User-Agent": "Mozilla/5.0 (обучающий парсер)"}
response = requests.get(url, headers=headers, timeout=10)
print(response.status_code)
print(response.text[:300]) # первые 300 символов HTML
Запустите файл. В консоли должно появиться число 200 (это значит «ОК») и начало HTML-кода страницы. Если видите 200 и теги <html>, <head> — страница скачана.
timeout=10 нужен, чтобы скрипт не завис навсегда, если сервер не отвечает. headers с User-Agent часто обязателен: без него часть сайтов отдаёт 403 или пустую заглушку.
Шаг 2. Разбираем HTML через BeautifulSoup
Строка HTML — это просто текст. Чтобы искать в нём по тегам и классам, текст нужно превратить в дерево объектов. Это делает BeautifulSoup.
from bs4 import BeautifulSoup
# response.text — тот самый HTML из шага 1
soup = BeautifulSoup(response.text, "html.parser")
# Заголовок страницы
print(soup.title.text)
В консоли должно появиться Quotes to Scrape. Второй аргумент "html.parser" — встроенный парсер Python, отдельно ставить ничего не нужно. Он немного медленнее lxml, но для старта его достаточно.
Как понять, что именно искать
Откройте страницу в браузере, кликните правой кнопкой по нужному элементу и выберите «Просмотреть код» (Inspect). Вы увидите, в каком теге и с каким классом лежит нужный текст. На quotes.toscrape.com каждая цитата обёрнута в <div class="quote">, сам текст — в <span class="text">, автор — в <small class="author">.
Шаг 3. Достаём нужные данные
Метод find_all возвращает список всех элементов, подходящих под условие. Пройдём по каждой цитате и вытащим текст и автора.
quotes = soup.find_all("div", class_="quote")
for quote in quotes:
text = quote.find("span", class_="text").text
author = quote.find("small", class_="author").text
print(f"{author}: {text}")
В консоли должен появиться список из десяти строк вида «Albert Einstein: “The world as we have created it...”». Обратите внимание: class_ пишется с подчёркиванием на конце — потому что class в Python зарезервированное слово.
Шаг 4. Собираем ссылки на теги
Ссылки лежат в атрибуте href тега <a>. Достаются они как значение из словаря.
for quote in quotes:
tags = quote.find_all("a", class_="tag")
tag_names = [tag.text for tag in tags]
tag_links = [tag["href"] for tag in tags]
print(tag_names, tag_links)
Ссылки на демо-сайте относительные — вида /tag/love/page/1/. Чтобы получить абсолютный URL, склейте их с базовым адресом через urljoin из стандартной библиотеки: from urllib.parse import urljoin, затем urljoin(url, tag["href"]).
Шаг 5. Сохраняем в CSV
Печать в консоль — для проверки. Реальный результат нужен в файле. Собираем всё в один скрипт и пишем CSV стандартным модулем.
import csv
import requests
from bs4 import BeautifulSoup
url = "https://quotes.toscrape.com"
headers = {"User-Agent": "Mozilla/5.0 (обучающий парсер)"}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status() # выбросит ошибку, если статус не 200
soup = BeautifulSoup(response.text, "html.parser")
quotes = soup.find_all("div", class_="quote")
# newline="" и encoding="utf-8" обязательны, иначе будут пустые строки и кракозябры
with open("quotes.csv", "w", newline="", encoding="utf-8") as file:
writer = csv.writer(file)
writer.writerow(["author", "text"]) # шапка
for quote in quotes:
text = quote.find("span", class_="text").text
author = quote.find("small", class_="author").text
writer.writerow([author, text])
print("Готово, записано строк:", len(quotes))
После запуска рядом со скриптом появится файл quotes.csv. Откройте его — внутри столбцы автора и текста. Если открываете в Excel и видите иероглифы, дело в кодировке при импорте, а не в скрипте: файл сохранён в UTF-8 корректно.
Если не получилось
Пять ошибок, на которые натыкаются почти все новички.
ModuleNotFoundError: No module named 'bs4'— библиотека не установлена или установлена в другой Python. Выполнитеpip install beautifulsoup4тем же интерпретатором, которым запускаете скрипт.AttributeError: 'NoneType' object has no attribute 'text'—findничего не нашёл и вернулNone. Значит, тег или класс указан неправильно. Проверьте разметку через Inspect: класс мог измениться.- Статус 403 или 429 — сайт заблокировал запрос. 403 часто лечится добавлением User-Agent, 429 значит «слишком много запросов»: добавьте паузу
time.sleep(1)между запросами. - Список
quotesпустой, ошибок нет — самая частая причина: данные подгружаются через JavaScript, аrequestsего не выполняет. См. раздел ниже. - Кракозябры в тексте —
requestsневерно определил кодировку. Задайте её вручную:response.encoding = "utf-8"до обращения кresponse.text.
Где эта связка не сработает
requests скачивает только тот HTML, который сервер отдал сразу. Если контент подгружается скриптами уже в браузере (бесконечная лента, данные после клика, содержимое из API), в response.text его не будет — вы увидите пустой каркас страницы. Признак: в браузере данные есть, а find_all возвращает пустой список.
В таких случаях есть два пути, но это уже отдельная тема:
- найти запрос к API во вкладке Network браузера и обращаться к нему напрямую через requests — часто данные приходят готовым JSON;
- использовать инструмент, который запускает настоящий браузер, — например Playwright или Selenium. Они умеют дожидаться JavaScript, но работают медленнее и сложнее в настройке.
Что делать дальше
Первый парсер готов. Логичные следующие шаги:
- Научитесь ходить по страницам пагинации: на демо-сайте внизу есть кнопка «Next» со ссылкой на следующую страницу — соберите их в цикле.
- Добавьте паузы между запросами (
time.sleep), чтобы не нагружать сервер, — это и вежливо, и снижает риск блокировки. - Изучите CSS-селекторы: метод
soup.select("div.quote span.text")часто короче и читаемее, чем цепочка изfind. - Для больших проектов посмотрите фреймворк Scrapy — он берёт на себя очереди запросов, повторные попытки и экспорт данных.
Защитник прав потребителей: Досудебные претензии на возврат товара со ссылками на ЗоЗПП.
Спросить за 15 ₽Источники: Beautiful Soup Documentation, Requests: HTTP for Humans
Частые вопросы
Что делать, если у меня другая версия Python или библиотек?
Список элементов пустой, хотя в браузере данные есть. Почему?
Законно ли парсить сайты?
Почему сайт возвращает 403 вместо страницы?
html.parser или lxml — что выбрать?
Как не получить блокировку при парсинге многих страниц?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.