Все разработчики парсеров без исключения рано или поздно задаются вопросом – легален ли парсинг? И главное – как сделать так, чтобы скрипт парсинга работал в строгом соответствии с законом и требованиями целевых сайтов? В этом материале расставляем все точки над «i».
Итак, легальность веб-парсинга, во-первых, определяется его целями. Если вы собираете чужие персональные данные или авторский контент без согласия правообладателей – это нарушение закона в большинстве стран. Где-то парсинг может вообще попасть под статью о промышленном шпионаже.
Большинство юридических рисков возникает не на этапе сбора данных, а уже после – при их дальнейшем хранении, публикации, перепродаже или коммерческом использовании.
Во-вторых, если ваш скрипт парсинга создаёт чрезмерную нагрузку на инфраструктуру провайдера и проводит к ошибкам в работе, а также к недоступности сервиса для «живых» пользователей, то вполне может возникнуть финансовая ответственность – в размере понесённых убытков. Это тоже актуально для многих стран мира.
Вместе с тем, если данные публичные, вы их видите и можете скопировать вручную, например, для исследования или для сводного анализа, то технически парсинг – это лишь автоматизация вашего исследования.
Получается, что легальный веб-парсинг вполне возможен – без каких-либо специальных разрешений и договоров.
Но, чтобы вас не могли привлечь к какой-либо ответственности, следует:
Ниже добавим больше технических деталей.
Все технические правила для поисковых ботов (куда можно «ходить», а куда нельзя и кому) изложены в файле robots.txt. Официальную документация по правилам и директивам для чтения и понимания robots.txt можно найти на сайте проекта, а также в справочных разделах поисковых систем, например, Google.
Так как ничто не мешает вашему боту представиться любым именем, из robots.txt имеет смысл «читать» только общие директивы и каталоги, которые помечены на исключение из обхода.
Пример обработчика для Python (сами директивы вам нужно будет скопировать и прописать вручную):
# Массив с исключениями / директивы Disallow
disallow_rules = [
"/admin/",
"/private/",
"/search"
]
# Пример целевого запроса парсера
url = "https://example.com/private/data"
# Проверка запроса на наличие исключений
path = urlparse(url).path
# Срабатывание правила блокировки
blocked = any(path.startswith(rule) for rule in disallow_rules)
# Сохранение информации в логи
if blocked:
logger.warning(
f"URL skipped by robots.txt policy: {url}"
)
# Если исключений нет, то просто продолжаем парсинг
else:
queue.append(url)
Так как юридические правила и соглашения для пользователей не имеют чётких директив, вам придётся изучить их самостоятельно или пропустить через ИИ-агента. Тогда нужные правила и ограничения можно задать самостоятельно или принять в готовом виде от нейросети.
Прокси для доступа к ценным данным со всего мира.
При написании любого парсера у разработчика есть два пути:
Раз материал о юридических аспектах и легальности веб-парсинга, то мы настоятельно рекомендуем идти по первому пути. Идентификация бота (юзер-агента) осуществляется на уровне специальных HTTP-ответов.
Ниже примеры настройки уникальных цифровых отпечатков для бота.
Вариант для работы через HTTP-клиент Requests:
import requests
headers = {
# В параметрах уникальный идентификатор и контактная информация
"User-Agent": (
"ResearchBot/1.4 "
"(+https://crawler.example.com; "
"contact: crawler@example.com)"
),
# Плюс ряд параметров, которые позволяют «синхронизировать» тип обрабатываемого содержимого и некоторые другие параметры
"Accept": "text/html,application/xhtml+xml",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"From": "crawler@example.com"
}
# Отправка первого запроса с указанными параметрами
response = requests.get(
"https://example.com", # целевой URL
headers=headers, # Тут массив заголовков
timeout=10 # Таймаут
)
Вариант для headless-браузеров
Так как в headless-браузерах уровень сложности цифрового отпечатка выше (принимающий сервер может анализировать не только HTTP-заголовки, но и JavaScript-окружение клиента), то и параметров в заголовках нужно передавать больше.
Базовый профиль для Playwright (Chromium) может выглядеть так:
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
headless=True
)
context = browser.new_context(
user_agent=(
"CrawlerRenderer/2.1 "
"(Rendering Bot; "
"+https://crawler.example.com)"
),
locale="en-US",
extra_http_headers={
"From": "crawler@example.com",
"DNT": "1"
}
)
page = context.new_page()
page.goto("https://example.com")
В расширенном описании отпечатка можно также предусмотреть параметры для:
navigator.webdriver;Именно по ним специальные системы защиты определяют работу ботов.
Материал по теме: Цифровой отпечаток браузера (как вас идентифицируют в сети).
Слой и правила балансировки нагрузки наиболее важны. Если вы обращаетесь к целевому сайту слишком часто, то нагрузка на серверы возрастает кратно. В какой-то момент обычные пользователи могут начать сталкиваться с замедлением сервиса или даже с его полным отказом – тут всё будет зависеть от производительности принимающего сервера и сетевой архитектуры (внутренней маршрутизации и балансировки нагрузки).
Самый зрелый и правильный подход – распланировать нагрузку, которую потенциально может создать ваш бот.
В противном случае легальный парсинг данных легко превращается в нелегальный.
Со стороны скрипта веб-парсинга слой балансировки нагрузки может выглядеть так:
Простейшая реализация задержек с небольшим «шумом» относительно базового времени:
import time
import random
BASE_DELAY = 3
while queue:
url = queue.pop()
response = requests.get(url)
# Базовая задержка + случайный шум
sleep_time = (
BASE_DELAY +
random.uniform(0.5, 2.0)
)
time.sleep(sleep_time)
Для многопоточных парсеров особенно важно ограничивать количество одновременных соединений:
from concurrent.futures import ThreadPoolExecutor
MAX_WORKERS = 5
with ThreadPoolExecutor(
max_workers=MAX_WORKERS
) as executor:
executor.map(parse_page, urls)
Если сервис отдаёт в ответ ошибку доступа, то логично увеличивать время задержки – с большой вероятностью сервер сейчас перегружен. Пример реализации роста задержки – повтор ошибки при запросе будет кратно увеличивать паузу:
retry_delay = 5
for attempt in range(5):
try:
response = requests.get(url)
if response.status_code == 200:
break
except Exception:
pass
time.sleep(retry_delay)
retry_delay *= 2
Для распределения нагрузки часто используется ротация прокси:
import random
proxies = [
"http://proxy1.example.com:8080",
"http://proxy2.example.com:8080",
"http://proxy3.example.com:8080"
]
proxy = random.choice(proxies)
response = requests.get(
url,
proxies={
"http": proxy,
"https": proxy
}
)
5 континентов, без ограничений
Получите доступ к прокси-сети с 200+ локациями и 10+ миллионами IP-адресов.
Сейчас многие сайты и веб-сервисы не работают без авторизации, например, мессенджеры, соцсети и т.п. Соответственно, если принимающий сайт обнаружит автоматические запросы, он не просто может ограничить доступ по IP, но и полностью заблокировать аккаунт, под которым велось подключение.
К механизму сессий можно отнести:
При парсинге с авторизацией риски юридической ответственности всегда максимальные.
Если скрипт делается без учёта требований к легальности веб-парсинга, то он может имитировать браузерные отпечатки и куки, автоматически создавать новые и ротировать заранее созданные аккаунты, открывать параллельно большое количество сессий, отправлять запросы «по максимуму» и т.п.
Легальный парсер должен придерживаться следующих принципов:
Простейший пример хранения независимой сессии в Python:
import requests
session = requests.Session()
session.headers.update({
"User-Agent": "ResearchBot/1.0"
})
response = session.get(
"https://example.com/profile"
)
Пример простейшей блокировки повторных запросов:
if response.status_code == 403:
logger.warning(
"Access denied. Session disabled."
)
disable_worker()
Минимизация сводится к:
Лучший способ снижения юридических рисков – не хранить и не собирать чувствительные данные.
Одновременно с рисками минимизируется нагрузка, исключается вероятность утечки, оптимизируется число запросов к целевому сайту и снижается стоимость хранения данных.
Пример. Извечный вопрос: легален ли парсинг email? И да, и нет одновременно, как кот Шредингера. Если вы собираете данные из открытых источников, вас никто останавливать не будет. Но если вы попытаетесь сделать рассылку по собранным email-адресам без разрешения их владельцев или попробуете продать базу таких email, то вполне может наступить юридическая ответственность.
Если парсеру нужны только цены товаров, то нет смысла сохранять всю страницу или профиль продавца. Вот так может выглядеть скрипт парсера с использованием синтаксического анализатора Beautiful Soup:
from bs4 import BeautifulSoup
html = response.text
soup = BeautifulSoup(
html,
"html.parser"
)
result = {
# Извлечение идентификатора продукта
"product_id": soup.select_one(
".product"
)["data-id"],
# Извлечение цены продукта
"price": soup.select_one(
".price"
).text.strip()
}
Ниже код для автоматического удаления email и номеров телефонов:
import re
text = response.text
# Удаление email
text = re.sub(
r'[\w\.-]+@[\w\.-]+',
'[EMAIL_REMOVED]',
text
)
# Удаление телефонов
text = re.sub(
r'\+?\d[\d\s\-\(\)]{7,}\d',
'[PHONE_REMOVED]',
text
)
В более зрелых системах парсинга фильтрация чувствительных данных может выноситься в отдельные пайплайны или модули:
Материал по теме: Кеширование в Python при скрапинге – сокращение количества запросов и ускорение сбора данных.
В случае проблем, судебных разбирательств или споров именно логи, а не исходный код вашего скрипта, смогут подтвердить легальность парсинга. Плюс логи могут быть полезны для анализа работы больших распределённых систем, а также для отлова ошибок.
Поэтому, чем подробнее и точнее будет система логирования, тем вам же лучше.
Какую информацию имеет смысл выносить в логи:
Простейший пример логирования запросов и ошибок:
import logging
import requests
import time
logging.basicConfig(
filename="crawler.log",
level=logging.INFO,
format=(
"%(asctime)s "
"[%(levelname)s] "
"%(message)s"
)
)
url = "https://example.com"
try:
start = time.time()
response = requests.get(
url,
timeout=10
)
duration = round(
time.time() - start,
2
)
logging.info(
f"URL={url} "
f"STATUS={response.status_code} "
f"TIME={duration}s"
)
except Exception as e:
logging.error(
f"URL={url} "
f"ERROR={str(e)}"
)
Соблюдение официальных правил и требований целевых сайтов, а также простая человеческая логика и этика всегда должны оставаться в приоритете. Только так можно добиться высоких стандартов качества и максимальной легальности веб-парсинга для своих скриптов.
Если вы ищите качественные прокси для профессиональной работы, с официальным сотрудничеством и удобными расчётами, выбирайте Froxy. Мы работаем абсолютно легально, в правовом поле Европы и предоставляем все необходимые закрывающие документы.