Масштабный парсинг — это всегда огромное количество запросов и ответов, передаваемых по сети Интернет. Тут стоит помнить, что по протоколам HTTP и HTTPS могут передаваться данные разных типов: текст, аудио, видео, документы и любые другие файлы. Из-за перезапуска парсера многие запросы в очереди могут уйти в работу повторно. А каждый из них — это трафик, время, вычислительные ресурсы и в конечном итоге деньги.
Простейший пример, если вы не сохраните прогресс работы парсера (промежуточные данные), например, итоговый HTML-код с тяжёлых динамических страниц, написанных на JavaScript, то вам придётся начинать всё сначала. Выход из ситуации есть — это механизмы кеширования и сохранения состояния работы парсера.
В общем, этот материал о том, как использовать кеширование в Python при парсинге и как сделать этот процесс эффективным.
Что такое кеширование в Python и почему оно важно для веб-скрапинга
Если вы только начинаете путь в веб-скрапинге, вы наверняка сталкивались с двумя проблемами:
- медленная работа скрипта, особенно, если используются headless-браузеры или антидетекты (на обработку каждой страницы может тратиться по несколько десятков секунд, если масштабировать буквально на 1000 страниц, то уже выходит несколько часов непрерывной работы в один поток);
- риск, что сайт перестанет отвечать после множества запросов (система защиты сайта, например, продвинутый файрвол веб-приложений/WAF, может заблокировать ваш скрипт в любой момент, если сочтёт его действия враждебными).
Обе эти проблемы помогает решить кеширование.
Кеширование — это процесс временного сохранения данных. Такие данные могут храниться в оперативной памяти, на жёстком диске или внутри баз данных. Основная задача кеширования — ускорение обращения к информации, чтобы важные рабочие данные всегда были под рукой (без дополнительных сложных действий).
Кеширование в скрапинге — это предметная техническая система кеширования данных, с которыми работает парсер. В данном случае цель кеширования — сохранить актуальное состояние процесса парсинга, чтобы программа могла начать работу с того момента, на котором остановилась из-за блокировки, после команды пользователя или в случае непредвиденной ошибки. Реже кеширование может решать вопрос хранения «сырых» данных, которые были получены с сайта, но ещё не были проанализированы (извлечены).
Распространённые подходы к реализации кеша в Python

Так как мы говорим о реализации парсера на Python, то имеет смысл выбрать и показать работу тех систем, стандартных механизмов, функций и библиотек, которые уже реализованы для этого языка программирования.
Как и было упомянуто ранее, чаще всего кеширование реализуется для хранения данных в оперативной памяти, на диске и в базах данных. Поэтому давайте рассмотрим инструменты кеширования Python с разбивкой на эти категории.
Кеширование в памяти
В Python для кеширования в памяти (мемоизации) могут использоваться встроенные инструменты, например, словари, и готовые библиотеки. Но в некоторых случаях работу можно облегчить с помощью внешних хранилищ, таких как Memcached (всем, кто работал с PHP и с веб-серверами, эта библиотека должна быть знакома).
Цель кеширования в оперативной памяти устройства (сервера или ПК) — ускорить повторное обращение к результатам выполнения функций и запросов, вместо того чтобы заново выполнять те же операции.
Что можно использовать для кеширование в памяти:
1. Обычный словарь (dict)
Dict — это самый простой способ кеширования в Python, который сводится к тому, что URL-адрес страницы используется как ключ, а HTML-ответ как значение. Логика работы такая:
- При первом запросе страница загружается с сайта и сохраняется в словаре.
- При повторном вызове с тем же URL данные возвращаются из словаря (кеша), без обращения к серверу.
Такой способ не требует сторонних библиотек, но подходит только для небольших объёмов данных и однопоточных задач.
Простейший пример реализации:
import requests
cache = {}
def get_page(url):
if url in cache:
return cache[url] # возвращаем из кеша
response = requests.get(url)
cache[url] = response.text # сохраняем результат
return response.text
html = get_page("https://example.com")
2. Декоратор кеширования functools.lru_cache
Это встроенный декоратор для кеширования результатов функций с повторяющимися параметрами в Python. «LRU» в названии означает «Least Recently Used», что дословно переводится как «наименее недавно использованный». Схема работы простая и понятная — если кеш в памяти заполнен, то удаляется тот элемент, который использовался максимально давно.
Примеры использования (только основная часть кода):
import requests
from functools import lru_cache
@lru_cache(maxsize=100) # здесь задаётся максимальный размер кеша
def get_page(url):
return requests.get(url).text
html = get_page("https://example.com")
3. Cachetools
Для Python есть сторонняя библиотека, которая упрощает работу с lru_cache — cachetools. Пример кеширования страниц с её помощью:
import requests
from cachetools import TTLCache
cache = TTLCache(maxsize=100, ttl=3600)
def get_page(url):
if url not in cache:
cache[url] = requests.get(url).text
return cache[url]
Помимо LRU cachetools умеет работать с LFU (least frequently used), RRCache (Random Replacement) и TTLCache (с ограничением по времени).
4. Memcached
Memcached — это библиотека для организации кеширования данных в оперативной памяти на основе хеш-таблиц (in-memory key-value хранилище), работает по принципу локального сервиса (с клиент-серверной архитектурой). Для интеграции с Memcached в Python используется библиотека python-memcached. Memcached будет идеальным решением при работе с большим объёмом данных.
Пример использования:
import requests
import memcache
mc = memcache.Client(['127.0.0.1:11211']) # обращение к клиенту memcache
def get_page(url):
cached = mc.get(url)
if cached:
return cached
response = requests.get(url)
mc.set(url, response.text, time=3600) # кешируем на 1 час
return response.text
html = get_page("https://example.com")
Резидентные прокси
Идеальные прокси-серверы для доступа к ценным данным со всего мира.
Кеширование Python на основе файлов

Оперативная память всегда в дефиците, так как доступ к ней хотят получить буквально все приложения операционной системы. Но в случае с кешированием, если у вас быстрый жёсткий диск, можно обыграть хранение HTML-кода в виде файлов. Места на диске всегда больше, чем в RAM.
Самая простая реализация файлового кеширования в Python — своими силами. Можно выделить отдельный каталог для хранения результирующих HTML-страниц, а файлы называть на основе их URL-адресов или на основе хеша. Тогда из скрипта парсинга всегда можно проверить наличие скачанной версии страницы и пропустить её в очереди на парсинг.
Пример реализации кеша в Python на основе файлов и хеширования URL:
import os, hashlib, requests, time
# Определяем свою папку для хранения HTML
CACHE_DIR = 'cache_html'
EXPIRE = 3600 * 24 # срок жизни кеша — 1 день
os.makedirs(CACHE_DIR, exist_ok=True)
def get_html(url):
# генерируем уникальное имя файла на основе хеша URL
filename = hashlib.md5(url.encode()).hexdigest() + '.html'
path = os.path.join(CACHE_DIR, filename)
# если файл есть и не устарел — читаем из кеша
if os.path.exists(path) and (time.time() - os.path.getmtime(path)) < EXPIRE:
with open(path, 'r', encoding='utf-8') as f:
return f.read()
# иначе - качаем заново (вместо requests можно использовать headless-браузер)
response = requests.get(url, timeout=10)
response.raise_for_status()
# сохраняем в кеш
with open(path, 'w', encoding='utf-8') as f:
f.write(response.text)
return response.text
Если устаревшие файлы занимают место и мешаются, то их можно удалять отдельной функцией. Чистим кеш Python:
def clear_old_cache():
for f in os.listdir(CACHE_DIR):
path = os.path.join(CACHE_DIR, f)
if time.time() - os.path.getmtime(path) > EXPIRE:
os.remove(path)
Так как с файлами в нашем скрипте работает только один процесс, конфликтов скорее всего не будет. Но если у вас сложный многопоточный парсер с большим объёмом параллельных операций, то вполне возможны конфликты из-за доступа к записи/чтения файлов. Да и централизованного управления сроками хранения кеша не добиться. Более комплексная реализация возможна на основе готовых библиотек.
Файловое кеширование в Python с pickle
Pickle — стандартный модуль Python для сериализации объектов. Он позволяет записывать и считывать любые структуры данных (списки, словари и пр.) в бинарные файлы. Что вполне можно использовать для кеширования. Код становится в разы компактнее. Пример простейшей реализации:
import pickle, os
# Функция получения кеша на основе URL-адреса страницы
def get_cache(url):
if os.path.exists('cache.pkl'):
with open('cache.pkl', 'rb') as f:
cache = pickle.load(f)
else:
cache = {}
if url in cache:
return cache[url]
# Эмуляция парсинга
data = f"Данные по {url}"
cache[url] = data
with open('cache.pkl', 'wb') as f:
pickle.dump(cache, f)
return data
Пример кеша для комплексных объектов с joblib
Joblib — это специализированная библиотека для хранения больших объектов, например массивов NumPy или результатов работы ML-моделей (для понимания — материал о фреймворках LangChain и LangGraph). Joblib использует сжатие и быстрее работает с крупными файлами. Типовая интеграция в скрапер на Python:
from joblib import dump, load
import os
if os.path.exists('cache.joblib'):
data = load('cache.joblib')
else:
data = {"page": "parsing results"}
dump(data, 'cache.joblib')
Кеширование в Python с shelve
Shelve — это одна из реализаций персистентного словаря (работает по тем же принципам, что и встроенный dict), но хранит данные на диске. Вполне может подойти для организации небольшого файлового кеша (до 1 ГБ) между перезапусками парсера.
Пример кода:
import shelve
with shelve.open('cache.db') as cache:
if 'url1' not in cache:
cache['url1'] = 'Query results'
print(cache['url1'])
Плавно подходим к более сложным реализациям.
Requests-cache с filesystem backend
Специально для библиотеки Requests есть своя реализация системы кеширования — requests-cache. Это лучший выбор для веб-скрапинга в паре Requests, так как эта библиотека автоматически сохраняет ответы requests.get() и requests.post() в файлы (ну или в SQLite/Redis, в зависимости от используемого бэкенда). Рассмотрим вариант реализации с файловой системой:
import requests
import requests_cache
requests_cache.install_cache('cache_pages', expire_after=3600) # Устанавливаем время жизни кеша в секундах
# первый запрос — с загрузкой из сети
r1 = requests.get('https://example.com')
# второй — уже из кеша
r2 = requests.get('https://example.com')
#Проверяем, что страница есть в кеше
print(r2.from_cache)
Diskcache — мощный файловый кеш для многопоточного веб-парсинга
Это быстрая и надёжная библиотека, использующая кеширование на основе файлов. Хорошо подходит для больших объёмов и многопоточных задач. Имеет настройки времени жизни записей (TTL) и автоочистку. API совместим с functools.lru_cache. Библиотека может работать как очередь или кешированный словарь.
Пример кода с файловым кешированием (при желании можно подключить SQLite):
from diskcache import Cache
cache = Cache('cache_disk')
@cache.memoize(expire=3600)
def fetch_data(url):
import requests
return requests.get(url).text
html = fetch_data('https://example.com')
Dogpile.cache — для организации «корпоративного» кеширования
Это более гибкий и серьёзный вариант, который подходит для работы в сложных парсерах. Поддерживает точечную настройку TTL, логики обновления и разные бэкенды (файлы, память, Redis, memcached и т.д.). Нас пока интересуют только работа с файловым хранилищем:
from dogpile.cache import make_region
region = make_region().configure(
'dogpile.cache.dbm',
arguments={'filename': './file_cache.dbm'}
)
@region.cache_on_arguments()
def get_html(url):
import requests
return requests.get(url).text
Кеширование на основе баз данных

Когда вы работаете с файлами, наступает сложный момент управления большим количеством записей: какие из них просрочены, а какие нет, какие и где хранятся (если структура каталогов сложная), кто и с каким потоком работает. Чтобы хранить все эти данные централизованно, нужны полноценные базы данных. С их помощью можно реализовать более масштабное и сложное кеширование. Эти особенно важно при одновременной работе нескольких потоков парсера.
Самые популярные серверы баз данных для работы с кешем в Python — SQLite и Redis.
Кеширование в Python с SQLite
SQLite — это реляционная система управления базами данных, которая легко встраивается в программы, так как поставляется в виде готовых библиотек для разных языков программирования (и Python здесь не исключение). Это значит, что она не требует работы отдельного сервера баз данных (как MariaDB или MySQL). Обратите внимание, так как база данных хранится единым файлом на диске, будьте предельно осторожны с ним.
Пример использования кеширования с SQLite:
import sqlite3, time
conn = sqlite3.connect('cache.sqlite')
cur = conn.cursor()
cur.execute('CREATE TABLE IF NOT EXISTS cache (url TEXT PRIMARY KEY, data TEXT, ts REAL)')
def get_data(url):
cur.execute('SELECT data, ts FROM cache WHERE url=?', (url,))
row = cur.fetchone()
if row and time.time() - row[1] < 3600:
return row[0]
# эмуляция запроса
data = f"Парсинг {url}"
cur.execute('REPLACE INTO cache VALUES (?, ?, ?)', (url, data, time.time()))
conn.commit()
return data
Кеширование с Redis
Это одна из самых быстрых баз данных, работающих в памяти устройства. Она изначально оптимизирована для кеширования, подходит для многопоточного и асинхронного парсинга. Запускается в виде сервиса, поэтому требует обращения к конкретному порту.
Пример интеграции:
import redis, json, time
r = redis.Redis(host='localhost', port=6379, db=0)
def get_data(url):
cached = r.get(url)
if cached:
return json.loads(cached)
data = {"url": url, "time": time.time()}
r.setex(url, 3600, json.dumps(data)) # хранить 1 час
return data
Практические примеры кеширования в Python для веб-скрапинга

Выше мы привели только максимально упрощённые конструкции, но хотелось бы больше конкретики. Показываем предметные примеры.
Кеширование HTTP-ответов через requests-cache + SQLite + BeautifulSoup
import requests
import requests_cache
from bs4 import BeautifulSoup
import time
# Настройка кеша
requests_cache.install_cache(
cache_name='cache_pages', # имя файла базы данных cache_pages.sqlite
backend='sqlite', # тип хранилища — SQLite
expire_after=3600 * 24, # время жизни кеша — 24 часа
allowable_methods=('GET',) # кешируем только GET-запросы
)
def fetch_html(url):
"""Loading an HTML page with caching"""
try:
response = requests.get(url, timeout=10)
source = "cache" if response.from_cache else "network"
print(f"[{time.strftime('%H:%M:%S')}] {url} → source: {source}")
response.raise_for_status()
return response.text
except Exception as e:
print(f"Error loading {url}: {e}")
return None
def parse_title(html):
"""Extracting the page title"""
soup = BeautifulSoup(html, 'html.parser')
return soup.title.text if soup.title else '(without title)'
if __name__ == "__main__":
urls = [
"https://www.python.org/",
"https://www.wikipedia.org/",
"https://example.com/"
]
for url in urls:
html = fetch_html(url)
if html:
print(f"→ {parse_title(html)}")
После первого запуска рядом со скриптом появится файл «cache_pages.sqlite». Это ваша база данных. В ней создаются следующие таблицы: responses («ответы», содержит тело ответов — HTML, JSON и т.д.), urls (это таблица соответствия URL → ID ответа), redirects (история редиректов), metadata (служебные данные — время запроса, TTL и т.п.). Каждая запись хранит: URL и метод (GET, POST), статус ответа (200, 301, 504 и т.п.), дату запроса и время жизни, тело HTML.
При желании кеш можно очистить — requests_cache.clear().
Более сложный синтаксис для удаления старых записей:
from requests_cache import get_cache
get_cache().remove_expired_responses()
А вот так из кода можно проверить существует ли страница в кеше:
from requests_cache import get_cache
cache = get_cache()
print(cache.has_url('https://www.python.org/'))
Кеширование с CacheControl
Это альтернатива с фокусом на HTTP-заголовки кеширования (Cache-Control, ETag, Expires). Полезна, когда сервер сам задаёт правила кеша.
import requests
from cachecontrol import CacheControl
from cachecontrol.caches import FileCache
sess = CacheControl(requests.Session(), cache=FileCache('.web_cache'))
resp = sess.get("https://example.com")
print(resp.from_cache if hasattr(resp, 'from_cache') else False)
Переиспользование проанализированного HTML (объекты BeautifulSoup / lxml)
Чтобы не парсить страницу заново при каждом запуске, можно сохранять уже разобранный HTML или извлечённые данные в файл. За обработку файлового кеша могут отвечать библиотеки pickle, joblib, diskcache и т.д.
import os, pickle, requests
from bs4 import BeautifulSoup
CACHE_FILE = 'parsed_cache.pkl'
# загружаем сохранённые результаты, если есть
if os.path.exists(CACHE_FILE):
with open(CACHE_FILE, 'rb') as f:
cache = pickle.load(f)
else:
cache = {}
def get_parsed(url):
if url in cache:
print("→ from cache ")
return cache[url]
html = requests.get(url).text
soup = BeautifulSoup(html, 'lxml')
title = soup.title.text if soup.title else "(without a title)"
cache[url] = title
with open(CACHE_FILE, 'wb') as f:
pickle.dump(cache, f)
return title
print(get_parsed("https://www.python.org/"))
Сочетание прокси с кешированием для повышения эффективности
Идея проста: кеш исключает лишние запросы, а прокси обеспечивает анонимность и распределение нагрузки. Если ответ найден в кеше, то запрос не идёт в сеть (соответственно не расходуются лимиты и трафик прокси).
import requests
import requests_cache
# подключаем кеш
requests_cache.install_cache(
'cache_proxy',
backend='filesystem',
expire_after=3600 * 12 # Действует 12 часов
)
# список прокси для ротации
proxies_list = [
{"http": "http://proxy1:8080", "https": "http://proxy1:8080"},
{"http": "http://proxy2:8080", "https": "http://proxy2:8080"},
]
def get_html(url, proxy):
response = requests.get(url, proxies=proxy, timeout=10)
print("Source:", "cache" if response.from_cache else "network") return response.text
urls = ["https://example.com", "https://www.python.org"]
for i, url in enumerate(urls):
proxy = proxies_list[i % len(proxies_list)]
html = get_html(url, proxy)
Глобальная сеть прокси
5 континентов, без ограничений
Получите доступ к прокси-сети с 200+ локациями и 10+ миллионами IP-адресов.
Ограничения и подводные камни кеширования в Python
Конечно, кеширование в Python при парсинге существенно экономит трафик и позволяет сохранять состояние работы скрипта. Но есть здесь и некоторые минусы. О них ниже:
- Устаревание информации. Если на странице поменялись цены, то ваш парсер этого не узнает, так как будет подтягивать информацию из сохранённых ранее файлов или баз данных. Именно для этого важно правильно устанавливать время жизни кеша и следить за тем, чтобы «просроченные» страницы запрашивались напрямую с сайта, а не из кеша.
- Разница в версиях страниц при наличии дополнительных параметров в URL-адресах. Очень сложная тема. Иногда сайты «прячут» в параметрах URL дополнительную информацию и настройки, а иногда просто партнёрские ссылки или рекламные идентификаторы. Если хранить в кеше только основную часть URL (без параметров), то можно потерять важные данные. А если брать весь адрес (вместе с параметрами), то могут получаться бесконечные дубли одной и той же страницы (особого профита от кеша в этом случае нет, вы всё равно будете гонять трафик и запросы туда-сюда). Решение по правильной схеме всегда нужно принимать индивидуально — на основе детального анализа.
- Конфликты при многопоточности. Чем больше у вас параллельных процессов парсинга, тем выше вероятность накладок: одновременный доступ к одним и тем же файлам в кеше, случайная перезапись содержимого и т.п. Именно поэтому вместо файлового (или в дополнение к нему) нужно задействовать базы данных или специальные библиотеки, обеспечивающие систему контроля доступа.
- Рост объёма кеша. Чем больше данных вы собираете, тем больше будет ваш кеш. Если данные хранятся в оперативной памяти, она может быстро переполниться, хотя и дисковые хранилища не всегда справляются с большими объёмами. В определённых условиях работа парсера может быть замедлена. Чтобы избежать таких проблем, нужно заранее продумать политику очистки устаревших данных и определиться с лимитами хранилища.
- Разница в содержимом при работе через прокси. Некоторые сайты могут отдавать разный контент на основе типа устройства пользователя и его расположения. Этот факт тоже важно знать и учитывать. Чтобы не было накладок, нужно подбирать прокси из одной локации и с тем же типом клиентского адреса. Подробнее о ротируемых резидентных и мобильных прокси.
Заключение

Кеширование при парсинге на Python может стать как решением определённых проблем, так и добавить новых. Чтобы кеширование заработало как надо, нужно заранее продумать лимиты, способы хранения данных и схемы обновления при их устаревании.
Варианты есть разные, как и способы предметной реализации — от простого файлового хранилища до распределённого кеша на базе Redis. Оптимальный подход будет зависеть от ваших задач: объёма парсинга, частоты обновлений, количества потоков и требований к стабильности. Главное — помнить, что кеш не должен подменять логику сбора данных, а лишь ускорять её и снижать нагрузку на сеть.
Кеширование — не единственная проблема. Не менее часто сайты обновляют схему своей вёрстки, поэтому парсеры перестают работать. Материал о том, как сделать свой парсер более устойчивым.
Ну а мы в свою очередь можем предложить качественные прокси. Они помогут с масштабированием и обходом блокировок. Froxy — это более 10 млн IP с точным таргетингом и автоматической ротацией.

