"""С ограниченной демо-версией: только публичная страница документации example.com."""
import json
import time
import urllib.error
import urllib.request
import urllib.robotparser
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
from urllib.parse import urlsplit
AGENT = 'NstdataResearchTutorial/1.0'
ALLOWED_ORIGIN = 'https://example.com'
MIN_INTERVAL = 1.0
MAX_WAIT = 5.0
MAX_BYTES = 200_000
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
opener = urllib.request.build_opener(NoRedirect())
last_start = None
def request(url):
global last_start
parts = urlsplit(url)
if f'{parts.scheme}://{parts.netloc}' != ALLOWED_ORIGIN:
raise ValueError('URL находится вне явно одобренного источника')
if last_start is not None:
time.sleep(max(0.0, MIN_INTERVAL - (time.monotonic() - last_start)))
last_start = time.monotonic()
req = urllib.request.Request(url, headers={'User-Agent': AGENT})
return opener.open(req, timeout=10)
def retry_delay(value, now=None):
now = now or datetime.now(timezone.utc)
if value is None:
return None
value = value.strip()
if value.isdigit():
return float(value)
try:
когда = parsedate_to_datetime(value)
if когда.tzinfo is None:
когда = когда.replace(tzinfo=timezone.utc)
return max(0.0, (когда - now).total_seconds())
except (TypeError, ValueError, OverflowError):
return None
def robots_allow(url):
robots_url = ALLOWED_ORIGIN + '/robots.txt'
try:
with request(robots_url) as response:
body = response.read(MAX_BYTES + 1)
if len(body) > MAX_BYTES:
return False
except urllib.error.HTTPError as error:
if error.code in (404, 410):
return True # Недоступный файл robots; разрешение все еще отдельно.
return False # Консервативная остановка для отказов, перегрузок и переадресаций.
except (urllib.error.URLError, TimeoutError):
return False
parser = urllib.robotparser.RobotFileParser()
parser.parse(body.decode('utf-8', 'replace').splitlines())
return parser.can_fetch(AGENT, url)
def collect(url):
if not robots_allow(url):
return {'url': url, 'outcome': 'robots_or_policy_stop'}
for attempt in range(2):
try:
with request(url) as response:
content_type = response.headers.get('Content-Type', '')
data = response.read(MAX_BYTES + 1)
status = response.status
if len(data) > MAX_BYTES:
return {'url': url, 'outcome': 'size_stop'}
if 'text/html' not in content_type.lower():
return {'url': url, 'outcome': 'unexpected_content_type'}
text = data.decode('utf-8', 'replace')
if 'Example Domain' not in text:
return {'url': url, 'outcome': 'unexpected_content'}
return {'url': url, 'outcome': 'accepted', 'http_status': status,
'bytes': len(data), 'attempt': attempt + 1,
'captured_at': datetime.now(timezone.utc).isoformat()}
except urllib.error.HTTPError as error:
delay = retry_delay(error.headers.get('Retry-After'))
if error.code not in (429, 503) or attempt == 1:
return {'url': url, 'outcome': 'terminal_http_stop',
'http_status': error.code}
if delay is None or delay > MAX_WAIT:
return {'url': url, 'outcome': 'deferred_to_operator',
'http_status': error.code}
time.sleep(delay) # Никогда не укорачивайте запрашиваемую задержку сервера.
except (urllib.error.URLError, TimeoutError) as error:
return {'url': url, 'outcome': 'transport_stop',
'error_type': type(error).__name__}
if __name__ == '__main__':
fixed = datetime(2026, 1, 1, tzinfo=timezone.utc)
assert retry_delay('3', fixed) == 3.0
assert retry_delay('Thu, 01 Jan 2026 00:00:04 GMT', fixed) == 4.0
assert retry_delay('invalid', fixed) is None
blocked = urllib.robotparser.RobotFileParser()
blocked.parse(['User-agent: *', 'Disallow: /private/'])
assert not blocked.can_fetch(AGENT, ALLOWED_ORIGIN + '/private/file')
print(json.dumps({'unit_checks': 4, 'live_result': collect(ALLOWED_ORIGIN + '/')}, indent=2))
#### Шаг 3: Искренне интерпретируйте результат
Записанный запуск и его точный вывод хранятся с проверочными артефактами этой статьи. Принятие означает, что этот маленький пример достиг своего ожидаемого HTML-содержимого в рамках своей определенной политики. Это не доказывает доступ, пропускную способность или соответствие для неродственного источника.
Для производственной записи сохраняйте идентификацию источника, время захвата, результат ответа, версию трансформации и ссылку на артефакт. Страница, которая содержит запрос на вход или неродной резервный вариант, не должна обновлять запись о продукте, вакансии или документе. [Руководство по краулерам с открытым исходным кодом](https://www.nstdata.io/blog/best-open-source-web-crawler) помогает определить обязанности фреймворка, которые этот небольшой скрипт намеренно оставляет без внимания.
## Nstdata Crawl, когда работа сотрудников по приобретению не является целью
Nstdata Crawl — это управляемый вариант для одобренного приобретения страниц и ограниченного изучения сайтов. Он может рендерить общедоступный контент, когда необходимый материал отсутствует в исходном HTML, и возвращать выбранные представления для парсинга или просмотра. Его управляемые страницы и глубина помогают выразить бюджет коллекции перед тем, как работа расширится. Сервис может сократить браузерную и инфраструктуру приобретения, с которой работает команда, в то время как приложение сохраняет разрешения, интерпретацию полей и правила принятия. Это не гарантирует, что целевой объект позволит сбор или превратит отказ в доступ.
### Объем перед выполнением
Выберите разрешенные страницы, явные ограничения и правила включения/исключения перед отправкой нагрузки. Рассматривайте результат обнаружения поставщика как кандидат на границу, а не как автоматическое расширение разрешения.
### Артефакты перед интерпретацией
Сохраняйте артефакты источника отдельно от принятых деловых записей. Используйте читаемый контент для обработки документов и HTML или визуальные доказательства при проверке несоответствий парсинга. Выбранный формат вывода сам по себе не гарантирует семантическую completeness.
### Затраты и контроль перед масштабированием
Просмотрите [модель использования Crawl](https://www.nstdata.io/pricing/crawl) и учтите приобретение по URL и применимое использование прокси. Измерьте принятые записи и отклоненный контент наряду с количеством запросов. Увеличивайте рабочую нагрузку только тогда, когда политика источника, планировщик и валидация на стороне получателя могут это поддерживать.
## Заключение: меньше запросов и более четкие остановки — полезные улучшения
Ответственное краулинг снижает избегаемые блокировки, устраняя ненужный сбор, координируя темп на уровне источника, уважая временные параметры сервера и останавливаясь при отказе в доступе. Пройдите один одобренный образец через полный путь от открытия до принятия перед планированием большего количества страниц. [Nstdata Proxy Manager](https://www.nstdata.io/proxy-manager) актуален, когда проект требует централизованной маршрутизации и оперативной видимости по авторизованным рабочим нагрузкам сбора.
## Оцените одобренный краулинг с ограничениями
Оцените [Nstdata Crawl](https://www.nstdata.io/scraping) на небольшом одобренном наборе страниц и проверьте как успешные, так и завершенные результаты перед масштабированием.
<a style="margin: 8px; display: inline-block; text-decoration: none; border-left-width: 0px;" href="https://app.nstdata.io/auth/register?utm_source=official&utm_medium=blog&utm_campaign=/crawl-without-getting-blocked/">
<div style="font-weight:bold; max-width:400px; padding:12px 40px; background:#646AEE; border-radius:5px; border:2px solid #646AEE; color:#fff; font-size:18px;">
Попробуйте Nstdata бесплатно →
</div>
</a>
## Часто задаваемые вопросы
**В: Может ли ответственное краулинг гарантировать отсутствие блокировок?**
Нет. Источник может отказать или ограничить доступ. Ответственная работа снижает избегаемую нагрузку и ошибки, но должна уважать это решение.
**В: Разрешение robots.txt разрешает каждое использование контента?**
Нет. Инструкции для краулеров, авторизация доступа, права на повторное использование контента и предполагаемое использование — это отдельные вопросы.
**В: Следует ли повторить запрещенный ответ с другим прокси?**
Нет, чтобы обойти отказ. Остановитесь и проверьте одобренный маршрут доступа, вместо того чтобы переключать идентичности для принуждения к успеху.
**В: Что делать, если Retry-After превышает бюджет ожидания задания?**
Отложите или остановите задание. Не сокращайте запрашиваемое время ожидания сервера, чтобы вписаться в локальный цикл повторных попыток.
**В: Является ли успешный HTTP-ответ успешным скрапом?**
Нет. Проверьте тип контента, идентичность источника, обязательные поля и неожиданный резервный контент перед принятием записи.
**В: Реализует ли образец Python краулинг в масштабе производства?**
Нет. Он демонстрирует один ограниченный источник, явные остановки и проверки контента. Распределенное планирование, надежное управление политикой, устойчивость и сетевые защиты требуют дополнительной инженерии.