2025, Nov 27 18:02
Почему парсер внезапно возвращает None и как стабилизировать парсинг при серверных лимитах
Почему при парсинге каталога ответы «ломаются»: серверные ограничения и антибот‑фильтры. Практика: паузы, ротация User-Agent, ретраи, Selenium и прокси.
При парсинге каталога страниц с книгами всё может работать исправно на десятках URL, а затем внезапно ломаться: парсер возвращает None, HTML выглядит повреждённым, а точка отказа сдвигается от запуска к запуску. Это классический признак серверных ограничений — лимитов по частоте, CAPTCHA или антибот‑фильтров, а не проблема BeautifulSoup или самого парсера.
Минимальный пример, воспроизводящий поведение
Логика проста: запрашиваем страницу, извлекаем заголовок и переходим к следующему адресу. После непредсказуемого числа успешных итераций функция начинает возвращать None, хотя заголовок на странице присутствует.
import requests
from bs4 import BeautifulSoup
def grab_title(page_url):
req_headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) "
"Gecko/20100101 Firefox/108.0"
)
}
resp = requests.get(page_url, headers=req_headers)
dom = BeautifulSoup(resp.content, "html.parser")
h1node = dom.find("h1", class_="book__title")
book_name = h1node.text.strip() if h1node else "Title not found"
return book_name
# Где-то в вызывающем коде
idx = 0
for row in catalog_links: # например, ["https://example.com/booknumber12/", ...]
idx += 1
href = row[0]
print(href + " " + str(idx))
outcome = grab_title(href)
if outcome is None:
print(f"Values niestety none dla: {href} numer {idx}")
break
Что на самом деле происходит и почему
Причина сбоев не в HTML‑парсере. После множества запросов тело ответа начинает приходить в искажённом виде — это соответствует моменту, когда включаются серверные защиты. Характерный признак: сырой контент ответа уже «сломанный» до парсинга — смесь заглавных и строчных «абракадабр» или неожиданная разметка вместо ожидаемой структуры; в таком виде BeautifulSoup не находит узел заголовка. Сначала проверяйте полезную нагрузку ответа — это подтверждает источник проблемы и переключает внимание с парсера на сам ответ.
Практические способы снизить риски и стабилизировать парсинг
Самый действенный способ реже ловить ограничения — замедлиться и варьировать «отпечаток» запроса. На практике это означает паузы между запросами, ротацию значений User-Agent и проверку ответа перед парсингом. Если сайт выдает JavaScript‑челленджи, поможет загрузка страниц в безголовом браузере. Если дросселирование сохраняется, можно подключить прокси и ротировать IP. Простой механизм повторных попыток тоже спасает от кратковременных сбоев. Помните: фиксированная задержка — не серебряная пуля и поодиночке ненадёжна, а один лишь безголовый браузер не преодолеет жёсткие серверные ограничения.
Ниже — правки, которые добавляют задержки, ротацию User-Agent на каждый запрос и защитные проверки до передачи HTML в BeautifulSoup.
import time
import requests
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
def fetch_book_title(target_url):
ua = UserAgent()
dyn_headers = {"User-Agent": ua.random}
try:
resp = requests.get(target_url, headers=dyn_headers, timeout=10)
resp.raise_for_status()
# Быстрая проверка на неожиданный контент
if "book__title" not in resp.text:
print(f"Unexpected content from {target_url}")
return None
dom = BeautifulSoup(resp.content, "html.parser")
h1node = dom.find("h1", class_="book__title")
return h1node.text.strip() if h1node else "Title not found"
except Exception as exc:
print(f"Error fetching {target_url}: {exc}")
return None
# Вежливо итерируемся по каталогу
position = 0
for entry in link_list:
position += 1
page_href = entry[0]
print(page_href + " " + str(position))
page_title = fetch_book_title(page_href)
if page_title is None:
print(f"Values niestety none dla: {page_href} numer {position}")
break
time.sleep(1.5) # Подстройте под «терпимость» сайта
Ротация User-Agent использует fake_useragent. Установите пакет заранее:
pip install fake-useragent
Если сайт проверяет небраузерных клиентов через JavaScript, безголовый браузер может загрузить страницу как обычный браузер, а затем передать полученный HTML в BeautifulSoup для извлечения данных. Подход тяжелее и медленнее, но помогает там, где содержимое появляется только после клиентского выполнения скриптов.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from bs4 import BeautifulSoup
chrome_opts = Options()
chrome_opts.add_argument("--headless")
driver = webdriver.Chrome(options=chrome_opts)
def render_and_extract(page_url):
driver.get(page_url)
html_doc = driver.page_source
dom = BeautifulSoup(html_doc, "html.parser")
h1node = dom.find("h1", class_="book__title")
return h1node.text.strip() if h1node else "Title not found"
Когда сервер блокирует по IP, маршрутизация через прокси распределяет трафик и снижает нагрузку на один адрес. Логика парсинга при этом не меняется — меняется только сетевой путь.
proxy_cfg = {
"http": "http://user:pass@proxy_ip:port",
"https": "http://user:pass@proxy_ip:port",
}
ua = UserAgent()
rot_headers = {"User-Agent": ua.random}
resp = requests.get("https://example.com", headers=rot_headers, proxies=proxy_cfg, timeout=10)
Почему важно понимать этот класс сбоев
Когда скрейпер начинает сбоить прерывисто — после множества успешных запросов — хочется сменить HTML‑парсер или подправить селекторы. В описанной ситуации это не поможет, потому что проблема возникает до парсинга: сервер меняет то, что вы получаете. Понимание серверных контролей позволяет сосредоточиться на темпе запросов, рандомизации «идентичности» и валидации ответов, а не гоняться за мнимыми багами парсера.
Выводы
Сначала подтвердите проблему, изучив сырой ответ. Если полезная нагрузка уже повреждена, замедляйте запросы и рандомизируйте заголовки, чтобы выглядеть менее «ботоподобно». Добавьте простую проверку содержания и обрабатывайте None на ранней стадии, вместо того чтобы отправлять в парсер испорченный HTML. Рассматривайте безголовый браузер только там, где контент требует выполнения JavaScript, и помните: фиксированные паузы не гарантируют успех. Если сайт всё ещё сопротивляется, прокси и аккуратные ретраи помогут стабилизировать запуск, уважая ограничения целевого ресурса.