Для парсинга сайтов с Selenium откройте страницу в браузере, дождитесь нужных элементов, извлеките поля каждой записи и сохраните результат в CSV. Ниже — установка, два готовых скрипта на Python и проверка результата на учебном сайте Quotes to Scrape.
Selenium нужен, когда задача требует выполнения JavaScript или действий в браузере. Если нужные данные уже есть в доступном HTTP-ответе, проще использовать HTTP-клиент, например Requests, или подходящий API. На странице Quotes to Scrape JavaScript создаёт видимые карточки, но исходные данные также встроены в HTML: здесь браузер используется для обучения.
Начните с корректной выгрузки одной страницы. Затем добавьте пагинацию и проверяйте причину остановки:
last_page— следующей ссылки нет,page_limit— достигнут заданный лимит,error— произошёл сбой. Совпадение числа записей в CSV с отчётом подтверждает выгрузку, но не полноту охвата сайта.
1. Установите Python, Selenium и Chrome
Для примера нужны Python 3.10 или новее и установленный Chrome. Команды ниже создают отдельное виртуальное окружение и устанавливают Selenium версии 4.51.0.
Требование Python 3.10+ указано на странице пакета Selenium в PyPI. Откройте терминал в папке проекта.
Windows PowerShell:
py -m venv .venv
.\.venv\Scripts\python.exe -m pip install selenium==4.51.0
macOS и Linux:
python3 -m venv .venv
.venv/bin/python -m pip install selenium==4.51.0
Активировать окружение необязательно: команды обращаются прямо к его интерпретатору. Если Windows не распознаёт py, но команда python --version работает, создайте окружение через python -m venv .venv. Команды для macOS и Linux эквивалентны по назначению; скриншоты показывают запуск в Windows.
Selenium Manager подбирает драйвер автоматически, если вы не указали его вручную. Вызов webdriver.Chrome(options=options) при необходимости запускает этот механизм. При первом запуске может потребоваться доступ в интернет. Ограничения сети, неподдерживаемая среда или конфликтующий драйвер в PATH иногда требуют ручной настройки.
Сохраните следующий скрипт в файл scrape_quotes.py. Не называйте файл selenium.py: он может перекрыть импорт установленного пакета.
2. Соберите данные с одной страницы и сохраните CSV
Первый скрипт ждёт появления карточек с заполненными обязательными полями и сохраняет каждую запись отдельной строкой CSV. Для извлечения данных достаточно Selenium и стандартной библиотеки Python.
import csv
from pathlib import Path
from selenium import webdriver
from selenium.common.exceptions import (
NoSuchElementException, StaleElementReferenceException, WebDriverException
)
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
def read_records(driver):
"""Wait until the currently rendered cards have their required fields."""
try:
cards = driver.find_elements(By.CSS_SELECTOR, ".quote")
if not cards:
return False
records = []
for card in cards:
quote = card.find_element(By.CSS_SELECTOR, ".text").text.strip()
author = card.find_element(By.CSS_SELECTOR, ".author").text.strip()
tags = [tag.text for tag in card.find_elements(By.CSS_SELECTOR, ".tag")]
if not quote or not author:
return False
records.append({"quote": quote, "author": author,
"tags": " | ".join(tags)})
return records
except (NoSuchElementException, StaleElementReferenceException):
return False
options = webdriver.ChromeOptions()
options.add_argument("--window-size=1280,900")
driver = webdriver.Chrome(options=options)
try:
driver.set_page_load_timeout(30)
driver.get("https://quotes.toscrape.com/js/")
records = WebDriverWait(driver, 15).until(read_records)
for record in records:
record["source_url"] = driver.current_url
output = Path("quotes.csv").resolve()
with output.open("w", newline="", encoding="utf-8-sig") as file:
writer = csv.DictWriter(
file, fieldnames=["quote", "author", "tags", "source_url"]
)
writer.writeheader()
writer.writerows(records)
print(f"Page: {driver.current_url}")
print(f"Saved {len(records)} records to {output}")
finally:
try:
driver.quit()
except WebDriverException:
pass
Сохраните код целиком. Проверьте, что файл называется scrape_quotes.py, а не scrape_quotes.py.txt, и откройте терминал в той же папке.
Запуск в Windows:
.\.venv\Scripts\python.exe scrape_quotes.py
Запуск в macOS и Linux:
.venv/bin/python scrape_quotes.py
В терминал нужно вставить только команду запуска. Строки Page: и Saved — сообщения программы.
Chrome откроется и автоматически закроется после завершения работы. В терминале появятся адрес страницы, число сохранённых записей и полный путь к quotes.csv. Файл создаётся в текущей рабочей папке; повторный запуск перезаписывает его.
Эта версия обрабатывает только текущую страницу. Если браузер не запустится или данные не удастся извлечь, Python выведет исключение вместо сообщения об успешном сохранении. Проверяйте время изменения CSV: после ошибки в папке может остаться файл от предыдущего запуска.

3. Разберитесь в селекторах, ожиданиях и формате данных
Извлекайте связанные поля внутри одной карточки и ждите готовности данных через явное ожидание. Так меньше риск перепутать автора с цитатой или прочитать страницу до завершения её обновления.
Как подобрать CSS-селекторы
Откройте учебную страницу Quotes to Scrape с JavaScript в Chrome. Нажмите правой кнопкой мыши на цитату и выберите «Просмотреть код» или Inspect.
| Элемент | CSS-селектор | Что извлекаем |
|---|---|---|
| Карточка | .quote |
Контейнер одной записи |
| Текст | .text |
Цитату |
| Автор | .author |
Имя автора |
| Теги | .tag |
Список тегов внутри карточки |
Сначала find_elements() находит все контейнеры .quote. Затем find_element() ищет текст и автора внутри каждого из них. Если отдельно собрать все цитаты и всех авторов, а потом сопоставить их по позиции, пропущенное поле может незаметно сдвинуть соответствие.
В этом примере удобны CSS-селекторы. XPath тоже подходит, но длинный абсолютный XPath из DevTools часто зависит от случайной вложенности элементов. Выбирайте устойчивые атрибуты и связи между элементами.

Как дождаться загрузки данных в Selenium
Вызов driver.get() учитывает стратегию загрузки страницы, но не гарантирует завершения всех последующих обновлений JavaScript. В документации Selenium об ожиданиях также рекомендуется не смешивать неявные и явные ожидания.
Конструкция WebDriverWait(driver, 15).until(read_records) повторно вызывает функцию чтения с тайм-аутом 15 секунд, пока та не вернёт готовые записи. Условие выполнено, когда карточки найдены, а у всех проверяемых карточек заполнены текст и автор. Если элемент ещё не появился или уже заменён в DOM, функция возвращает False, и проверка повторяется.
Для учебной страницы этого достаточно. Для сайта, который подгружает записи несколькими порциями, нужен дополнительный сигнал: известное количество результатов или индикатор окончания загрузки. Даже presence_of_all_elements_located может сработать, как только появились совпадения: это не подтверждение, что загружены все будущие результаты.
Какие значения попадут в CSV
Свойство .text возвращает видимый текст. Метод get_attribute("href") нужен для адреса ссылки: надпись Next и URL следующей страницы — разные значения.
В CSV используются четыре поля:
quote— текст цитаты;author— автор;tags— теги, объединённые через|;source_url— адрес страницы, с которой сохранена запись.
В версии с пагинацией ключ удаления дублей состоит из текста цитаты и автора. Поле source_url хранит адрес первого появления сохранённой записи. Для товаров обычно лучше использовать устойчивый ID товара, а не название.
Модуль csv корректно экранирует запятые, кавычки и переносы строк. Параметр newline="" соответствует рекомендациям Python по работе с CSV, а кодировка utf-8-sig помогает табличным редакторам распознать Unicode. Считайте записи CSV-парсером: физические строки файла не равны числу записей, если внутри поля есть перенос строки.

4. Добавьте пагинацию и отчёт о завершении
Версия с пагинацией проверяет переход на следующую страницу, удаляет дубли и отдельно записывает причину остановки. Наличие CSV само по себе не означает, что сбор завершился успешно.
Когда проверите одностраничную выгрузку, сохраните следующий код в файл scrape_quotes_pages.py в той же папке.
import argparse
import csv
import json
from pathlib import Path
from urllib.parse import urljoin
from selenium import webdriver
from selenium.common.exceptions import (
NoSuchElementException, StaleElementReferenceException, WebDriverException
)
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
START_URL = "https://quotes.toscrape.com/js/"
FIELDS = ["quote", "author", "tags", "source_url"]
def read_records(driver):
"""Wait until the currently rendered cards have their required fields."""
try:
cards = driver.find_elements(By.CSS_SELECTOR, ".quote")
if not cards:
return False
records = []
for card in cards:
quote = card.find_element(By.CSS_SELECTOR, ".text").text.strip()
author = card.find_element(By.CSS_SELECTOR, ".author").text.strip()
tags = [tag.text for tag in card.find_elements(By.CSS_SELECTOR, ".tag")]
if not quote or not author:
return False
records.append({"quote": quote, "author": author,
"tags": " | ".join(tags)})
return records
except (NoSuchElementException, StaleElementReferenceException):
return False
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--pages", type=int, default=1)
parser.add_argument("--headless", action="store_true")
args = parser.parse_args()
if args.pages < 1:
parser.error("--pages must be at least 1")
options = webdriver.ChromeOptions()
options.add_argument("--window-size=1280,900")
if args.headless:
options.add_argument("--headless=new")
driver = None
rows, seen, visited = [], set(), set()
raw_count = 0
status, detail = "error", "Browser did not start"
try:
driver = webdriver.Chrome(options=options)
driver.set_page_load_timeout(30)
wait = WebDriverWait(driver, 15)
driver.get(START_URL)
for page_number in range(1, args.pages + 1):
current_url = driver.current_url
if current_url in visited:
raise ValueError("Repeated page URL; pagination stopped")
batch = wait.until(read_records)
visited.add(current_url)
raw_count += len(batch)
for record in batch:
key = (record["quote"], record["author"])
if key not in seen:
seen.add(key)
rows.append({**record, "source_url": current_url})
print(f"Page {page_number}: {len(batch)} records | {current_url}")
links = driver.find_elements(By.CSS_SELECTOR, ".pager .next a")
if not links:
status, detail = "last_page", "No Next link on the processed page"
break
if page_number == args.pages:
status, detail = "page_limit", "More pages exist; requested limit reached"
break
expected_url = urljoin(current_url, links[0].get_attribute("href"))
old_card = driver.find_element(By.CSS_SELECTOR, ".quote")
links[0].click()
wait.until(EC.staleness_of(old_card))
wait.until(EC.url_to_be(expected_url))
except (WebDriverException, ValueError) as exc:
status = "error"
detail = f"{type(exc).__name__}: {str(exc).splitlines()[0]}"
if driver is not None:
try:
driver.save_screenshot("failure.png")
except WebDriverException:
pass
finally:
if driver is not None:
try:
driver.quit()
except WebDriverException:
pass
output = Path("quotes-pages.csv").resolve()
with output.open("w", newline="", encoding="utf-8-sig") as file:
writer = csv.DictWriter(file, fieldnames=FIELDS)
writer.writeheader()
writer.writerows(rows)
report = {
"status": status, "detail": detail,
"pages_processed": len(visited), "raw_records": raw_count,
"unique_records": len(rows), "duplicates_removed": raw_count - len(rows),
"csv": str(output)
}
Path("run-status.json").write_text(json.dumps(report, indent=2), encoding="utf-8")
print(json.dumps(report, indent=2))
if status == "error":
raise SystemExit(1)
if __name__ == "__main__":
main()
Запросите обработку не более 20 страниц.
Windows PowerShell:
.\.venv\Scripts\python.exe scrape_quotes_pages.py --pages 20
macOS и Linux:
.venv/bin/python scrape_quotes_pages.py --pages 20
Активировать окружение не требуется. Скрипт создаёт quotes-pages.csv и run-status.json; файл quotes.csv от первого примера сохраняется. Повторный запуск перезаписывает результаты многостраничной версии. Число 20 — верхний предел обработки, а не количество страниц сайта.
После чтения страницы программа ищет .pager .next a. Если ссылка найдена, она:
- Запоминает адрес назначения и одну карточку старой страницы.
- Нажимает Next.
- Ждёт, пока старая карточка перестанет относиться к текущему документу:
EC.staleness_of(old_card). - Проверяет ожидаемый URL через
EC.url_to_be(expected_url). - На следующей итерации ждёт готовности новых записей.
Если сразу после клика проверять только наличие .quote, условие могут выполнить ещё не исчезнувшие карточки старой страницы. Проверка выше подходит для пагинации с заменой документа. Для SPA, где содержимое обновляется без такой навигации, нужен другой сигнал: например, новый номер страницы и изменившиеся ID записей.
Как проверить результат парсинга
Сначала прочитайте run-status.json, затем проверьте количество и содержимое строк CSV.
| Статус | Что произошло | Как трактовать результат |
|---|---|---|
last_page |
На обработанной странице нет ссылки Next по заданному селектору | При верных селекторе и разметке достигнут конец пагинации; это не доказывает доступность всех записей на сервере |
page_limit |
Ссылка Next есть, но достигнут лимит --pages |
Сбор остановился по ограничению; оставшиеся страницы не обработаны |
error |
Сбой запуска, извлечения данных или навигации | Код выхода — 1; скрипт пытается сохранить уже собранные записи, результат может быть частичным |
Отчёт также содержит:
pages_processed— количество обработанных страниц;raw_records— количество записей до удаления дублей;unique_records— количество сохранённых уникальных записей;duplicates_removed— количество удалённых дублей;csv— полный путь к выгрузке;detail— пояснение причины остановки.
Страница, на которой обязательные поля так и не стали доступны, не считается обработанной. Отсутствие дублей не подтверждает полноту данных: сравните охват с задачей и проверьте несколько записей вручную.

В Windows сразу после завершения скрипта проверьте код выхода и число записей:
Write-Output "Exit code: $LASTEXITCODE"
Write-Output "CSV records: $(@(Import-Csv .\quotes-pages.csv).Count)"
Код 0 означает, что программа не сообщила об ошибке. Статус page_limit тоже даёт код 0, поэтому отчёт всё равно нужно прочитать. Число записей в CSV должно совпадать с unique_records. Это проверка выгрузки, а не правильности каждого поля или полноты данных сайта.
5. Адаптируйте парсер под свой сайт
При переходе на другой сайт меняйте адрес, селекторы, поля и условия пагинации вместе. Новый URL со старыми селекторами .quote обычно приводит к пустому результату или тайм-ауту.
Проверьте настройки по порядку:
- Стартовая страница. Замените
START_URLна страницу списка, которую разрешено обрабатывать. Проверьте итоговый адрес после перенаправлений. - Контейнер записи. Вместо
.quoteукажите повторяющуюся карточку или строку таблицы. Сопоставьте найденные элементы с видимыми записями. - Поля и дубли. Одновременно обновите извлечение полей,
FIELDSи ключ уникальности. Необязательное поле не должно блокировать чтение всей страницы. - Следующая порция данных. Замените
.pager .next aи выберите признак завершения перехода или подгрузки.
Для кнопки «Показать ещё» сохраняйте уже собранные строки и ждите новых ID либо другого сигнала готовности. Ожидание замены всего документа здесь не подходит, если навигации нет.
При бесконечной прокрутке собирайте текущую порцию, прокручивайте страницу и ждите новых ID. Ограничьте длительность или число итераций. Если ожидание истекло, но явного признака конца списка нет, фиксируйте неопределённую причину остановки. Виртуализированные списки удаляют старые элементы из DOM: извлечение только после последней прокрутки может потерять ранние записи.
Если данные находятся в iframe, сначала переключитесь в нужный фрейм. Для Shadow DOM и содержимого canvas нужен отдельный способ извлечения: увеличение тайм-аута не превращает их в обычные элементы документа.
6. Исправьте пустые результаты, повторные страницы и ошибки драйвера
При сбое сначала проверьте текущий URL, фактический DOM и сообщение об ошибке. Увеличение времени ожидания помогает только тогда, когда нужные данные действительно появляются позже.
| Симптом | Что проверить и исправить |
|---|---|
| Нет записей, ожидание постоянно истекает | Проверьте селектор, контекст фрейма и наличие нужного содержимого. Ожидание не исправит селектор без совпадений |
StaleElementReferenceException |
Узел заменён или документ изменился. Найдите элемент заново после обновления |
| Повторяется одна страница | Проверьте следующий URL или индикатор страницы. Оставьте защиту от повторного адреса; удаление дублей не исправляет навигацию |
| Chrome не запускается | Разделите ошибку загрузки драйвера и ошибку запуска браузера. Проверьте исключение, установленный Chrome, драйвер в PATH и ограничения ОС |
| В CSV не хватает данных | Проверьте статус запуска, сообщения терминала и время изменения файлов. Выгрузка может содержать только часть записей |
PermissionError при сохранении |
Закройте CSV в Excel или другом редакторе, проверьте права записи в папку и повторите запуск |
Справочник ошибок Selenium объясняет, почему сохранённая ссылка на элемент после изменения DOM не находит его заново автоматически.
Прокси браузера не исправляет автоматически проблемы сети, через которую Selenium Manager скачивает драйвер. Если среда требует ручной настройки, используйте доступную разрешённую сеть или явно укажите совместимый драйвер. Отключать защиту браузера ради любого сбоя запуска не следует.
У одностраничной версии проверяйте исключение в терминале и дату quotes.csv; у многостраничной — run-status.json и quotes-pages.csv. Ошибка записи файла может помешать созданию нового отчёта, поэтому старые файлы нельзя считать подтверждением текущего запуска.
Если вместо списка появилась CAPTCHA или страница отказа в доступе, остановите автоматические повторы и проверьте условия использования, разрешения и доступные API. Блокировка — не обычный пустой список.
7. Запустите Selenium без окна браузера
Флаг --headless включает фоновый режим в многостраничном скрипте. Сначала проверьте работу с видимым окном Chrome, затем сравните данные и статус обоих запусков.
Windows PowerShell:
.\.venv\Scripts\python.exe scrape_quotes_pages.py --pages 20 --headless
macOS и Linux:
.venv/bin/python scrape_quotes_pages.py --pages 20 --headless
В коротком одностраничном примере обработчика этого флага нет. В многостраничной версии он добавляет параметр Chrome --headless=new.
Оба режима используют размер окна 1280 × 900, чтобы уменьшить различия в вёрстке. В headless-режиме браузер продолжает работать, но не показывает окно. Подробности — в документации Chrome о headless-режиме.
Если сессия браузера уже создана, многостраничный скрипт при ошибке извлечения или навигации пытается сохранить failure.png. При сбое запуска изображения может не быть. Проверяйте дату снимка: файл мог остаться от предыдущего запуска. Не публикуйте диагностические снимки с учётными данными или персональной информацией.
Фоновый режим сам по себе не гарантирует ускорения, снижения затрат или успешного доступа. Перед запуском по расписанию сравните выгрузку и отчёт с результатом в обычном режиме.
8. Выберите прокси для Selenium с учётом региона и сессии
Прокси позволяет направить трафик браузера через выбранную точку выхода в сеть. Для мониторинга цен и регионального контента важны два параметра: нужная геолокация и время сохранения одного IP-адреса.
| Задача | Что проверить |
|---|---|
| Сравнение цен или контента в разных регионах | Доступные локации и фактическое местоположение выходного IP |
| Сбор независимых страниц отдельными порциями | Нужна ли смена IP между порциями и какие интервалы запросов предусмотрены |
| Пагинация или сценарий, зависящий от сессии | Сохраняется ли один выходной IP на протяжении задачи |
| Регулярные проверки с одного адреса | Срок аренды статического IP и условия его обслуживания |
У Socks5.IO есть резидентные прокси с ротацией IP для задач со сменой точки выхода и статические резидентные прокси для длительной работы с одним адресом. Перед подключением проверьте доступные регионы, длительность сессии и поддерживаемый способ авторизации выбранного продукта.

Для настройки используйте руководство по подключению прокси в Selenium. Сначала проверьте внешний IP и его геолокацию. Затем запустите сбор одной страницы и протестируйте пагинацию с теми же параметрами сессии. Увеличивайте лимит только после проверки результата.
Для авторизации на прокси в Chrome нужен поддерживаемый способ настройки: добавить username:password в --proxy-server недостаточно. Соблюдайте разрешения сайта и ограничения частоты запросов.






