<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Vatican</title><generator>teletype.in</generator><description><![CDATA[Frontend architect]]></description><image><url>https://img1.teletype.in/files/41/40/4140a194-0637-4945-a19a-179a72b8cf29.png</url><title>Vatican</title><link>https://vatican-blog.cvv.life/</link></image><link>https://vatican-blog.cvv.life/?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=vatican_joy</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/vatican_joy?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/vatican_joy?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Fri, 18 Sep 2026 13:19:37 GMT</pubDate><lastBuildDate>Fri, 18 Sep 2026 13:19:37 GMT</lastBuildDate><item><guid isPermaLink="true">https://vatican-blog.cvv.life/Reality_Reversal</guid><link>https://vatican-blog.cvv.life/Reality_Reversal?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=vatican_joy</link><comments>https://vatican-blog.cvv.life/Reality_Reversal?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=vatican_joy#comments</comments><dc:creator>vatican_joy</dc:creator><title>Reality Reversal</title><pubDate>Fri, 18 Sep 2026 10:20:25 GMT</pubDate><category>Dev</category><description><![CDATA[<img src="https://img3.teletype.in/files/66/e8/66e89196-e734-482b-88a1-d1734b4208da.jpeg"></img>В 1986 году The Mentor написал &quot;Сознание хакера&quot;. Прошло почти 40 лет. Мир изменился. Мы — нет.]]></description><content:encoded><![CDATA[
  <p id="GvkA"><em>В 1986 году The Mentor написал &quot;Сознание хакера&quot;. Прошло почти 40 лет. Мир изменился. Мы — нет.</em></p>
  <blockquote id="LTti"><strong>Мы верим, что:</strong><br /><strong>Знание — это власть. Не доступ к власти. Не инструмент власти. Сама власть.</strong></blockquote>
  <blockquote id="TV69"><strong><br />Тот, кто понимает механику системы — контролирует её. Тот, кто контролирует — не спрашивает разрешения.</strong></blockquote>
  <h3 id="G11s">Наши принципы:<br />1. Разбирай всё</h3>
  <blockquote id="74ct"><em>Всё, что построено — можно разобрать. Всё, что работает — можно понять. Всё, что можно понять — можно воспроизвести. Нет &quot;закрытых&quot; систем. Есть системы, которые ты ещё не вскрыл.</em></blockquote>
  <h3 id="3e73">2. Поверхность — это ложь</h3>
  <blockquote id="TDR4"><em>То, что показывают — витрина. То, что под ней — сущность. API скрыт? Значит есть. Данные &quot;недоступны&quot;? Значит просто не нашли endpoint. Реальность имеет слои, и большинство живёт на первом.</em></blockquote>
  <h3 id="nlkw">3. Слабость системы — не твоя проблема</h3>
  <blockquote id="1bWH"><em>Если их пароль — 123456. Если их API отдаёт всё без токена. Если их &quot;защита&quot; — проверка на фронте. Это не ты эксплуатируешь. Это они построили картонный дом.</em></blockquote>
  <h3 id="kp1a">4. Инструмент не выбирает цель</h3>
  <blockquote id="mN0d"><em>Молоток не решает, что строить. Скрипт не решает, что парсить. Тот, кто держит инструмент в руках — определяет применение.</em></blockquote>
  <h3 id="oSfT">5. Эффективность — единственная метрика</h3>
  <blockquote id="QlQ1"><em>Работает / не работает. Быстро / медленно. Обнаружили / не обнаружили. Остальное — шум.</em></blockquote>
  <h2 id="6tmV">Три типа тех, кто в этой игре:</h2>
  <ol id="aCky">
    <li id="HovK">Играет по правилам, которые написал кто-то другой. Ждёт bug bounty. Отчитывается. Просит.</li>
    <li id="c75z">Берёт что находит. Не спрашивает. Не оглядывается. Иногда проигрывает.</li>
    <li id="dHkt">Понимает обе стороны. Использует ту, которая даёт результат. Границу двигает под задачу.</li>
  </ol>
  <blockquote id="ZoKA"><em>Этот канал — не про этику. Не про закон. Не про &quot;как правильно&quot;.<br />Этот канал — про то, <strong>как всё устроено на самом деле</strong>.<br />Система видит то, что показывает. Мы видим то, что она скрывает.</em></blockquote>
  <blockquote id="CO9o"><em>By Vatican</em></blockquote>

]]></content:encoded></item><item><guid isPermaLink="true">https://vatican-blog.cvv.life/dark_side_API</guid><link>https://vatican-blog.cvv.life/dark_side_API?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=vatican_joy</link><comments>https://vatican-blog.cvv.life/dark_side_API?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=vatican_joy#comments</comments><dc:creator>vatican_joy</dc:creator><title>Тёмная сторона API</title><pubDate>Fri, 18 Sep 2026 10:05:36 GMT</pubDate><category>Dev</category><description><![CDATA[<img src="https://img1.teletype.in/files/01/6e/016ea2aa-72cf-471c-a2cf-4d19958edb59.jpeg"></img>Всё, что ты видишь в браузере — это данные. Вопрос только в том, как их достать.]]></description><content:encoded><![CDATA[
  <h2 id="2dNy">Часть 1: Анатомия данных</h2>
  <p id="iAAF"><em>Всё, что ты видишь в браузере — это данные. Вопрос только в том, как их достать.</em></p>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="xhNT">Сайт в браузере</p>
    <ul id="ClcM">
      <li id="20dt">Открытый API (мб будет документация)</li>
      <li id="Ni6w">Скрытый API (документации нету но стучать можно)</li>
      <li id="MasQ"><em>GraphQL endpoint</em></li>
      <li id="sulH"><em>WebSocket поток</em></li>
      <li id="qB0c"><em>Серверный рендеринг (HTML с данными)</em></li>
      <li id="yeVV"><em>Клиентский рендеринг (JS собирает данные)</em></li>
      <li id="5KC9"><em>Скрытые поля, комментарии, метаданные</em></li>
    </ul>
  </section>
  <blockquote id="TAjH"><strong><em><u>Если данные видны в браузере — их можно достать программно.</u></em></strong></blockquote>
  <h2 id="Ut8t">Часть 2: Уровни доступа к данным</h2>
  <h3 id="6e9S"><em>Уровень 0 — Официальное API</em></h3>
  <blockquote id="dV37">Самое простое. Документация открыта, ключ бесплатный.</blockquote>
  <pre id="lZlU" data-lang="python">import requests
# Пример: публичное API

response = requests.get(&quot;https://api.example.com/v1/data&quot;)
data = response.json()</pre>
  <p id="t15K"><strong>Плюсы:</strong> Стабильно, легально, документация<br /><strong>Минусы:</strong> Rate limits, неполные данные, ключ могут отозвать</p>
  <h3 id="wTVY">Уровень 1 — Скрытый API (Reverse Engineering)</h3>
  <p id="vl3c"><em>Сайты часто имеют внутренние API, которые фронтенд использует для получения данных. Документации нет, но endpoint работает.</em></p>
  <p id="fLPD"><strong>Как найти:</strong></p>
  <ol id="460M">
    <li id="DQ6B">Открой DevTools (F12)</li>
    <li id="btoM">Вкладка Network → XHR/Fetch</li>
    <li id="bf3G">Обнови страницу</li>
    <li id="r5yX">Смотри какие запросы летят</li>
    <li id="teu4">Найди endpoint с нужными данными</li>
  </ol>
  <p id="xaPM"></p>
  <blockquote id="VLOE"><strong>Пример — что видишь в Network:<br />GET https://site.com/api/v2/products?page=1&amp;limit=50</strong></blockquote>
  <p id="cr5e"></p>
  <p id="y1Pp"><strong>Воспроизводим</strong></p>
  <pre id="2O5i" data-lang="python">import requests

headers = {
    &quot;Authorization&quot;: &quot;Bearer eyJhbGciOiJIUzI1...&quot;,
    &quot;User-Agent&quot;: &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64)&quot;,
    &quot;Accept&quot;: &quot;application/json&quot;,
    &quot;Referer&quot;: &quot;https://site.com/catalog&quot;
}

response = requests.get(
    &quot;https://site.com/api/v2/products&quot;,
    params={&quot;page&quot;: 1, &quot;limit&quot;: 50},
    headers=headers
)
print(response.json())</pre>
  <p id="rX4d"></p>
  <p id="x0vv"><strong>Инструменты для реверса:</strong></p>
  <ul id="UJ66">
    <li id="kjfo"><strong>DevTools (Network)</strong> — базовый</li>
    <li id="SGLH"><strong>mitmproxy</strong> — перехват трафика мобильных приложений</li>
    <li id="mGkA"><strong>Charles Proxy</strong> — то же, но с GUI</li>
    <li id="uzck"><strong>Postman</strong> — тестирование найденных endpoints</li>
    <li id="Rw3d"><strong>Frida</strong> — хук методов в мобильных приложениях</li>
    <li id="IbZ1"><strong>Burp Suite — </strong>Позволяет перехватывать, анализировать и модифицировать HTTP- и HTTPS-трафик между браузером</li>
  </ul>
  <h3 id="TAw5">Уровень 2 — GraphQL Endpoints</h3>
  <blockquote id="LKJH"><em>Многие современные сайты используют GraphQL. Он отдаёт ровно те данные, которые запросишь.</em></blockquote>
  <p id="nrEd"><strong>Как обнаружить:</strong></p>
  <ol id="s1he">
    <li id="r7Rz">В DevTools ищи POST-запросы на /graphql</li>
    <li id="5s3u">Или /api/graphql</li>
    <li id="48N5">Или любые POST с query в body</li>
  </ol>
  <p id="iCtC"></p>
  <p id="OfEu"><strong>Интроспекция — всей схемы:</strong></p>
  <pre id="4XUC" data-lang="graphql"># Запрос всей схемы API
{
  __schema {
    types {
      name
      fields {
        name
        type {
          name
        }
      }
    }
  }
}</pre>
  <p id="7R2v"><strong>Пример запроса данных:</strong></p>
  <pre id="ozaX" data-lang="python">import requests

query = &quot;&quot;&quot;
{
  users(first: 100) {
    id
    username
    email
    posts {
      title
      createdAt
    }
  }
}
&quot;&quot;&quot;

response = requests.post(
    &quot;https://site.com/graphql&quot;,
    json={&quot;query&quot;: query},
    headers={&quot;Content-Type&quot;: &quot;application/json&quot;}
)
print(response.json())</pre>
  <p id="H7bc"><strong>Инструменты:</strong></p>
  <ul id="ZWR8">
    <li id="Cd5c"><strong>GraphiQL / Altair</strong> — IDE для GraphQL</li>
    <li id="Ey4b"><strong>graphql-path-enum</strong> — перебор скрытых полей</li>
    <li id="hCP0"><strong>InQL (Burp extension)</strong> — автоматическая интроспекция</li>
  </ul>
  <p id="DmHI"></p>
  <h3 id="5uYi">Уровень 3 — Парсинг HTML</h3>
  <blockquote id="uJ2c"><em>Когда API нет или оно закрыто — парсим то, что видит браузер.</em></blockquote>
  <h4 id="d9Em">Базовый — requests + BeautifulSoup</h4>
  <pre id="RXvG" data-lang="python">import requests
from bs4 import BeautifulSoup

response = requests.get(&quot;https://example.com/products&quot;)
soup = BeautifulSoup(response.text, &quot;html.parser&quot;)

products = soup.find_all(&quot;div&quot;, class_=&quot;product-card&quot;)

for product in products:
    title = product.find(&quot;h3&quot;).text.strip()
    price = product.find(&quot;span&quot;, class_=&quot;price&quot;).text
    link = product.find(&quot;a&quot;)[&quot;href&quot;]
    
    print(f&quot;{title} | {price} | {link}&quot;)</pre>
  <p id="rp9o"><strong>или когда например html динамический</strong></p>
  <pre id="hCHV" data-lang="python">from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    
    # Перехватываем API-запросы страницы
    responses = []
    page.on(&quot;response&quot;, lambda res: responses.append(res) 
            if &quot;api&quot; in res.url else None)
    
    page.goto(&quot;https://example.com/catalog&quot;)
    page.wait_for_load_state(&quot;networkidle&quot;)
    
    # Данные из перехваченных API-запросов
    for res in responses:
        print(res.url, res.json())
    
    # Или контент из DOM
    content = page.content()
    browser.close()
</pre>
  <h4 id="aYBE">Селекторы — что использовать</h4>
  <pre id="VGxd" data-lang="python"># CSS селекторы (быстрее, чище)
soup.select(&quot;div.product-card &gt; h3.title&quot;)
soup.select(&quot;a[href*=&#x27;/product/&#x27;]&quot;)
soup.select(&quot;span[data-price]&quot;)

# XPath (мощнее для сложной логики)
soup.xpath(&quot;//div[@class=&#x27;item&#x27;][position() &lt; 10]&quot;)
soup.xpath(&quot;//a[contains(@href, &#x27;product&#x27;)]/@href&quot;)</pre>
  <p id="9Gpm"></p>
  <h3 id="om4n">Уровень 4 — Headless Browser (эмуляция)</h3>
  <blockquote id="vDSs"><em>Когда сайт проверяет JS, куки, поведение — эмулируем реального пользователя.</em></blockquote>
  <h4 id="xOAR">Playwright — стандарт</h4>
  <pre id="wGfa" data-lang="python">from playwright.sync_api import sync_playwright
import time
import random

with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=False,
        args=[
            &quot;--disable-blink-features=AutomationControlled&quot;,
            &quot;--no-sandbox&quot;
        ]
    )
    
    context = browser.new_context(
        viewport={&quot;width&quot;: 1920, &quot;height&quot;: 1080},
        user_agent=&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36&quot;,
        locale=&quot;ru-RU&quot;,
        timezone_id=&quot;Europe/Moscow&quot;
    )
    
    page = context.new_page()
    
    # Убираем флаг webdriver
    page.add_init_script(&quot;&quot;&quot;
        Object.defineProperty(navigator, &#x27;webdriver&#x27;, {
            get: () =&gt; undefined
        });
    &quot;&quot;&quot;)
    
    page.goto(&quot;https://example.com&quot;)
    
    # Имитируем человека
    time.sleep(random.uniform(1, 3))
    page.mouse.move(random.randint(100, 800), random.randint(100, 600))
    
    # Скроллим как человек
    page.evaluate(&quot;&quot;&quot;
        async () =&gt; {
            for (let i = 0; i &lt; document.body.scrollHeight; i += 100) {
                window.scrollTo(0, i);
                await new Promise(r =&gt; setTimeout(r, 100));
            }
        }
    &quot;&quot;&quot;)
    
    # Забираем контент
    html = page.content()
    
    browser.close()</pre>
  <h4 id="KV3h">Анти-детект: что проверяют сайты (зависит от фантазии создателя)</h4>
  <p id="jCd5">Основное</p>
  <figure id="erw2" class="m_original">
    <img src="https://img1.teletype.in/files/4e/73/4e73ec50-6381-4ab8-b555-e71c7e38d889.png" width="568" />
  </figure>
  <h3 id="wF0M">Уровень 5 — Мобильные API</h3>
  <blockquote id="t3ii"><em>Приложения часто используют API, недоступные через веб. Данные там чище и полнее.</em></blockquote>
  <p id="rOhj"><strong>Инструменты:</strong></p>
  <ul id="UzEO">
    <li id="56Bc">mitmproxy — перехват HTTPS трафика</li>
    <li id="vB4u">Frida — инъекция кода в приложение</li>
    <li id="Ghsm">Charles Proxy — GUI-альтернатива</li>
    <li id="NHWg">jadx — декомпиляция APK</li>
  </ul>
  <p id="fb7P"><strong>Процесс:</strong></p>
  <ol id="eIui">
    <li id="tdC5">Скачай APK (apkpure, apkmirror)</li>
    <li id="1WY7">Декомпилируй (jadx)</li>
    <li id="YK5I">Ищи строки с &quot;api&quot;, &quot;http&quot;, &quot;endpoint&quot;</li>
    <li id="qKnM">Найди базовый URL и параметры</li>
    <li id="BXQf">Перехвати трафик через mitmproxy</li>
    <li id="wfFf">Воспроизведи запрос в Python</li>
  </ol>
  <h2 id="31ho"><strong>Часть 3: Защита и обход</strong></h2>
  <h3 id="lkcQ">Rate Limiting</h3>
  <p id="E9wD">Сайт ограничивает частоту запросов. Виды:</p>
  <ul id="Yo3o">
    <li id="NSGv">IP-based → ротация прокси</li>
    <li id="19sT">API Key-based → ротация ключей</li>
    <li id="PjYU">Session-based → ротация сессий</li>
    <li id="GOgj">Cookie-based → сброс куки</li>
    <li id="XyCR">Device fingerprint → подмена отпечатка</li>
  </ul>
  <p id="0YE5"><strong>Подход:</strong></p>
  <pre id="nLU3" data-lang="python">import time
import random
from itertools import cycle

class RateLimiter:
    def __init__(self, min_delay=1.0, max_delay=3.0):
        self.min_delay = min_delay
        self.max_delay = max_delay
    
    def wait(self):
        delay = random.uniform(self.min_delay, self.max_delay)
        time.sleep(delay)

# Использование прокси
proxies = cycle([
    &quot;http://proxy1:port&quot;,
    &quot;http://proxy2:port&quot;,
    &quot;http://proxy3:port&quot;,
])

limiter = RateLimiter(1, 3)

for url in urls:
    proxy = next(proxies)
    limiter.wait()
    
    response = requests.get(url, proxiДл&quot;: proxy})
    # Дальше ваша логика...</pre>
  <h3 id="3IjE">Теперь главный враг автоматизации CAPTCHA</h3>
  <blockquote id="Gl6m"><em>CAPTCHA можно проходить самыми разными способами от солвером до обучения локальных ИИ-моделей</em></blockquote>
  <figure id="iVzH" class="m_original">
    <img src="https://img3.teletype.in/files/a1/8f/a18f63fb-34f4-4190-8108-80aa66a9d61f.png" width="560" />
  </figure>
  <p id="Eu6N"><strong>Интеграция с сервисом решения:</strong></p>
  <pre id="F9mf" data-lang="python">import requests
import time

API_KEY = &quot;your_2captcha_key&quot;

def solve_recaptcha(site_key, page_url):
    # Отправляем задачу
    response = requests.post(&quot;https://api.2captcha.com/in.php&quot;, data={
        &quot;key&quot;: API_KEY,
        &quot;method&quot;: &quot;userrecaptcha&quot;,
        &quot;googlekey&quot;: site_key,
        &quot;pageurl&quot;: page_url,
        &quot;json&quot;: 1
    })
    task_id = response.json()[&quot;request&quot;]
    
    # Ждём решение
    for _ in range(30):
        time.sleep(5)
        result = requests.get(&quot;https://api.2captcha.com/res.php&quot;, params={
            &quot;key&quot;: API_KEY,
            &quot;action&quot;: &quot;get&quot;,
            &quot;id&quot;: task_id,
            &quot;json&quot;: 1
        })
        if result.json()[&quot;status&quot;] == 1:
            return result.json()[&quot;request&quot;]
    
    return None</pre>
  <h2 id="7qzo">Часть 4: Инфраструктура</h2>
  <h3 id="7ul7">Прокси — виды и стратегии</h3>
  <p id="jMAn">1. HTTP прокси → обычные, для http запросов<br />2. SOCKS5 прокси → любой трафик<br />3. Резидентные → IP реальных людей (дорого, надёжно)<br />4. Датацентровые → IP серверов (дёшево, палевно)<br />5. Мобильные → IP мобильных операторов (самые надёжные)<br />6. Ротация → новый IP на каждый запрос<br />7. Стик → IP привязан к сессии</p>
  <p id="4H8t"><strong>Правильная ротация:</strong></p>
  <pre id="ylJz" data-lang="python">import aiohttp
import asyncio
from itertools import cycle

class ProxyPool:
    def __init__(self, proxy_list):
        self.proxies = cycle(proxy_list)
        self.failed = set()
    
    def get_proxy(self):
        for _ in range(len(self.proxies)):
            proxy = next(self.proxies)
            if proxy not in self.failed:
                return proxy
        # Все прокси умерли — сбрасываем
        self.failed.clear()
        return next(self.proxies)
    
    def mark_failed(self, proxy):
        self.failed.add(proxy)

async def fetch(session, url, proxy):
    try:
        async with session.get(url, proxy=proxy, timeout=10) as resp:
            return await resp.json()
    except:
        pool.mark_failed(proxy)
        return None

async def main(urls):
    pool = ProxyPool([
        &quot;http://proxy1:8080&quot;,
        &quot;http://proxy2:8080&quot;,
    ])
    
    connector = aiohttp.TCPConnector(limit=20)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = []
        for url in urls:
            proxy = pool.get_proxy()
            tasks.append(fetch(session, url, proxy))
        
        results = await asyncio.gather(*tasks)
    return [r for r in results if r is not None]</pre>
  <h2 id="Pt2p">Часть 5: Практические схемы</h2>
  <h3 id="wCHs">Схема 1: Сбор данных с каталога</h3>
  <pre id="vBbj" data-lang="python">from playwright.sync_api import sync_playwright
from bs4 import BeautifulSoup
import json
import time
import random

class CatalogParser:
    def __init__(self, base_url):
        self.base_url = base_url
        self.results = []
    
    def parse_page(self, page, url):
        page.goto(url)
        page.wait_for_load_state(&quot;networkidle&quot;)
        
        # Ждём появления товаров
        page.wait_for_selector(&quot;.product-card&quot;, timeout=10000)
        
        html = page.content()
        soup = BeautifulSoup(html, &quot;html.parser&quot;)
        
        products = soup.select(&quot;.product-card&quot;)
        
        for product in products:
            self.results.append({
                &quot;title&quot;: product.select_one(&quot;.title&quot;)?.text.strip(),
                &quot;price&quot;: product.select_one(&quot;.price&quot;)?.text.strip(),
                &quot;url&quot;: product.select_one(&quot;a&quot;)?.get(&quot;href&quot;),
                &quot;parsed_at&quot;: time.strftime(&quot;%Y-%m-%d %H:%M&quot;)
            })
        
        # Случайная задержка
        time.sleep(random.uniform(2, 5))
    
    def run(self, pages=10):
        with sync_playwright() as p:
            browser = p.chromium.launch(headless=True)
            page = browser.new_page()
            
            for i in range(1, pages + 1):
                url = f&quot;{self.base_url}?page={i}&quot;
                print(f&quot;Страница {i}/{pages}&quot;)
                self.parse_page(page, url)
            
            browser.close()
        
        return self.results
    
    def save(self, filename=&quot;data.json&quot;):
        with open(filename, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:
            json.dump(self.results, f, ensure_ascii=False, indent=2)

# Использование
parser = CatalogParser(&quot;https://example.com/catalog&quot;)
data = parser.run(pages=50)
parser.save()</pre>
  <h3 id="KwuT"><br />Схема 2: Мониторинг изменений</h3>
  <pre id="JUvg" data-lang="python">import hashlib
import json
from datetime import datetime
from pathlib import Path

class ChangeMonitor:
    def __init__(self, state_file=&quot;state.json&quot;):
        self.state_file = Path(state_file)
        self.state = self.load_state()
    
    def load_state(self):
        if self.state_file.exists():
            return json.loads(self.state_file.read_text())
        return {}
    
    def save_state(self):
        self.state_file.write_text(
            json.dumps(self.state, ensure_ascii=False, indent=2)
        )
    
    def get_hash(self, data):
        return hashlib.md5(json.dumps(data, sort_keys=True).encode()).hexdigest()
    
    def check(self, key, new_data):
        new_hash = self.get_hash(new_data)
        old_hash = self.state.get(key, {}).get(&quot;hash&quot;)
        
        if old_hash is None:
            self.state[key] = {&quot;hash&quot;: new_hash, &quot;data&quot;: new_data, &quot;updated&quot;: datetime.now().isoformat()}
            return {&quot;status&quot;: &quot;new&quot;, &quot;data&quot;: new_data}
        
        elif old_hash != new_hash:
            old = self.state[key][&quot;data&quot;]
            self.state[key] = {&quot;hash&quot;: new_hash, &quot;data&quot;: new_data, &quot;updated&quot;: datetime.now().isoformat()}
            return {&quot;status&quot;: &quot;changed&quot;, &quot;old&quot;: old, &quot;new&quot;: new_data}
        
        return {&quot;status&quot;: &quot;unchanged&quot;}</pre>
  <p id="kKWH"></p>
  <h3 id="Ox4c">Схема 3: Распределённый парсер</h3>
  <pre id="7RC2" data-lang="python">import asyncio
import aiohttp
from dataclasses import dataclass, field
from typing import Optional
import json

@dataclass
class Task:
    url: str
    retries: int = 0
    max_retries: int = 3
    proxy: Optional[str] = None

@dataclass
class Worker:
    id: int
    session: aiohttp.ClientSession
    queue: asyncio.Queue
    results: list = field(default_factory=list)

class DistributedParser:
    def __init__(self, urls, num_workers=5, proxy_list=None):
        self.tasks = [Task(url=url) for url in urls]
        self.num_workers = num_workers
        self.proxy_list = proxy_list or []
        self.results = []
    
    async def worker(self, wid, queue, session):
        while True:
            try:
                task = await asyncio.wait_for(queue.get(), timeout=5)
            except asyncio.TimeoutError:
                break
            
            try:
                proxy = None
                if self.proxy_list:
                    proxy = self.proxy_list[task.retries % len(self.proxy_list)]
                
                async with session.get(
                    task.url, 
                    proxy=proxy,
                    timeout=aiohttp.ClientTimeout(total=15)
                ) as resp:
                    if resp.status == 200:
                        data = await resp.text()
                        self.results.append({
                            &quot;url&quot;: task.url,
                            &quot;data&quot;: data,
                            &quot;worker&quot;: wid
                        })
                        print(f&quot;[Worker {wid}] OK: {task.url}&quot;)
                    else:
                        raise Exception(f&quot;Status {resp.status}&quot;)
                        
            except Exception as e:
                task.retries += 1
                if task.retries &lt; task.max_retries:
                    await queue.put(task)
                else:
                    print(f&quot;[Worker {wid}] FAILED: {task.url} - {e}&quot;)
            
            finally:
                queue.task_done()
    
    async def run(self):
        queue = asyncio.Queue()
        for task in self.tasks:
            queue.put(task)
        
        connector = aiohttp.TCPConnector(limit=self.num_workers * 2)
        
        async with aiohttp.ClientSession(connector=connector) as session:
            workers = [
                asyncio.create_task(self.worker(i, queue, session))
                for i in range(self.num_workers)
            ]
            await queue.join()
            for w in workers:
                w.cancel()
        
        return self.results

# Использование
urls = [f&quot;https://example.com/item/{i}&quot; for i in range(1000)]
parser = DistributedParser(
    urls=urls, 
    num_workers=10,
    proxy_list=[&quot;http://p1:8080&quot;, &quot;http://p2:8080&quot;, &quot;http://p3:8080&quot;]
)
results = asyncio.run(parser.run())</pre>
  <p id="ocw4"></p>
  <h2 id="TLlR">Часть 6: Стек инструментов</h2>
  <h3 id="GVK4">Обязательный минимум</h3>
  <ul id="h0lH">
    <li id="lRso">requests — базовые HTTP запросы</li>
    <li id="TFKA">httpx — async HTTP, HTTP/2</li>
    <li id="bU0M">beautifulsoup4 — парсинг HTML</li>
    <li id="0Ioc">lxml — быстрый парсер</li>
    <li id="6gg4">playwright — headless браузер</li>
    <li id="Nx7q">scrapy — фреймворк для масштабных проектов</li>
  </ul>
  <h3 id="sjxN">Стань лучше:</h3>
  <ul id="a3A4">
    <li id="sXnk">curl_cffi — имитация TLS fingerprint браузера</li>
    <li id="YMnl">tls_client — обход Cloudflare через TLS</li>
    <li id="uGab">camoufox — анти-детект Firefox</li>
    <li id="noed">undetected-chromedriver — анти-детект Chrome</li>
    <li id="d8dJ">aiohttp — async для скорости</li>
    <li id="Ou9y">celery — очереди задач</li>
    <li id="I0JW">redis — кэш и состояние</li>
    <li id="gL8X">postgresql — хранилище данных</li>
    <li id="49kW">docker — изоляция окружения (на уровне базовый минимум)</li>
  </ul>
  <h3 id="ObOv">Инфраструктура (Может не подойти)</h3>
  <p id="YdrV">Прокси-провайдеры:</p>
  <ul id="dXNG">
    <li id="0moO">Bright Data (резидентные)</li>
    <li id="6xwK">SmartProxy (резидентные)</li>
    <li id="ghsl">Proxy-Seller (датацентровые)</li>
    <li id="tC7c">IPRoyal (мобильные)</li>
  </ul>
  <p id="2c5N">Решение капчи:</p>
  <ul id="CHP5">
    <li id="cVeA">2Captcha</li>
    <li id="laYk">Anti-Captcha</li>
    <li id="aEB2">CapSolver</li>
  </ul>
  <p id="plNt">Мониторинг:</p>
  <ul id="To3q">
    <li id="9NZM">Grafana + Prometheus</li>
    <li id="y1ji">Telegram-бот алерты</li>
  </ul>
  <h2 id="l4Hw">Часть 7: Как НЕ надо</h2>
  <ul id="Qhwq">
    <li id="wPeJ">ДДОСить сайт парсером (1000 req/sec)</li>
    <li id="H4ih">Игнорировать ошибки и повторять бесконечно</li>
    <li id="rtx2">Хранить данные без структуры</li>
    <li id="8lxb">Не логировать — не понимаешь что происходит</li>
    <li id="ZLtW">Хардкодить селекторы без фоллбеков</li>
    <li id="M66j">Не обрабатывать изменение вёрстк</li>
    <li id="F0HI">Собирать всё подряд без цели</li>
    <li id="rZ9Y">Не кэшировать — один и тот же запрос 50 раз</li>
    <li id="NCWG">Писать всё в один файл без модульности</li>
    <li id="X0Ml">Игнорировать robots.txt и уважение к серверу</li>
  </ul>
  <p id="ZfAs"></p>
  <p id="kx2b"></p>
  <blockquote id="QOtz"><em>На этом все с вами был Vatican.</em></blockquote>

]]></content:encoded></item></channel></rss>