Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

80 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HDRezka

PyPI version Downloads Python versions License GitHub stars

Async Python client for HDRezka. Designed for scripts and for multi-user backends (one HDRezkaClient instance per user / request scope).

Disclaimer

This is an unofficial library. It is meant for personal tooling and backends built by people who already have the right to use HDRezka under the service's own rules and under applicable law. Please read and follow the terms published by HDRezka (they change over time). Among other things, bots, downloaders, parsers, scraping, and many third-party clients may be restricted or may require Premium; access can be limited without notice. Use only what you are allowed to use, and only within those limits. This project does not endorse misuse of the service, account sharing, or anything that harms the platform or other users.

Default host is https://rezka.ag/ (free viewing is described there in the service rules). In practice most public mirrors sit behind Cloudflare, and a plain scripted client often gets blocked. You can point host= at another mirror (see mirrors.txt), use a suitable network path, and keep browser-like TLS impersonation enabled (default). That may help reliability; it is not a guarantee, and it is not permission to ignore the service rules.

Install

pip install hdrezka

Socks proxy support:

pip install hdrezka[socks]

Quick start

import asyncio
import os

from hdrezka import HDRezkaClient


async def main():
    async with HDRezkaClient() as client:
        await client.login(os.environ['LOGIN_NAME'], os.environ['LOGIN_PASSWORD'])

        items = await client.search('Breaking Bad').get_page(1)
        player = await client.player(items[0].url)
        # or: player = await Player(items[0].url, client=client)

        print(player.post.info)

        translator_id = None
        for name, id_ in player.post.translators.name_id.items():
            if 'субтитры' in name.casefold():
                translator_id = id_
                break

        stream = await player.get_stream(1, 1, translator_id)
        video = stream.video
        print(await video.last_url[-1])
        print(await video[video.min].last_url[0].mp4)

        subtitles = stream.subtitles
        if subtitles.has_subtitles:
            print(subtitles.default.url)


if __name__ == '__main__':
    asyncio.run(main())

HDRezkaClient

HDRezkaClient owns the HTTP session, active mirror (host), cookies, and a small player cache. Create a separate client for each user when building an API.

Method / property Purpose
await client.login(email, password) Discover an active mirror and authenticate
client.search(query) Bound Search
client.page(url=None) Bound catalog Page
client.favorites(cat_id=None) Bound Favorites page (/favorites/ or /favorites/{id}/)
await client.player(url) PlayerMovie or PlayerSeries
await client.post(url) Initialized Post
await client.navbar() Site top navigation map
await client.series_updates() Sidebar series update feed (home page)
await client.add_favorites_cat(name) Create a favorites collection
await client.rename_favorites_cat(cat_id, name) Rename a collection
await client.add_favorites_post(post_id, cat_id) Add a title to a collection
await client.remove_favorites_cat(cat_id) Delete a collection
client.ajax Bound AJAX helpers (streams, trailers, favorites, …)
client.host Current mirror base URL
await client.aclose() Close the HTTP client (also via async with)

Constructor options: host, proxy, http_client, request_kwargs, headers, redirect_url, impersonate.

Domain types (Search, Player, Post, Page, Favorites, AJAX) require an explicit client= (or are created through the client factories above).

Cloudflare and browser impersonation

By default HDRezkaClient sends browser-like headers and uses curl_cffi (via httpx-curl-cffi) so the TLS/HTTP2 fingerprint looks closer to a real browser. Turn it off or pick a target:

HDRezkaClient()                          # impersonate=True → Chrome
HDRezkaClient(impersonate='safari')      # specific curl_cffi target
HDRezkaClient(impersonate=False)         # plain httpx + browser-like headers

This only helps with fingerprint checks. JavaScript challenges (the “Just a moment…” interstitial), bad IPs, and account/policy limits are out of scope. Prefer a clean residential / personal path over free shared VPNs when the service blocks you. You can also paste browser cookies into the client after solving a challenge manually.

Navigation, filters, and sidebar

These parsers read whatever the HTML currently contains (no hardcoded genre lists). Missing blocks return empty structures.

Navbar

nav = await client.navbar()
for item in nav.items:
    print(item.name, item.url, item.single)
    if item.submenu:
        for genre in item.submenu.genres:
            print(' ', genre.name, genre.url)
        for collection in item.submenu.collections:
            print(' ', collection.name, collection.url, collection.classes)
        if item.submenu.find_best:
            print(' ', item.submenu.find_best.categories, item.submenu.find_best.years)

You can also parse from any page: await client.page().get_navbar(), or call parse_navbar(html) on a saved HTML string / BeautifulSoup tree.

Catalog filters and sorting

On category/genre pages, ul.b-content__main_filters exposes sort links (filter=) and content-type links (genre=).

page = client.page('/films/')
filters = await page.get_filters()
for link in filters.sorts:
    print(link.name, link.param, link.value, link.active)
for link in filters.types:
    print(link.name, link.param, link.value, link.active)

# Apply a filter when fetching titles
items = await page.get_page(1, filter='popular', genre=1)

# Or get titles + filters + updates in one response
content = await page.get_page_content(1, filter='watching')
print(content.filters, content.series_updates)

Series updates (sidebar)

for block in await client.series_updates():
    print(block.date)
    for row in block.items:
        print(row.name, row.url, row.season, row.episode, row.translation)

Post schedule

Many series pages include .b-post__schedule tables (air dates per season). They are parsed when you await a Post:

post = await client.post(url)
for block in post.schedule:
    print(block.title)
    for row in block.items:
        print(row.season, row.episode, row.title, row.date, row.exists)

Or call parse_schedule(html) on saved HTML / a BeautifulSoup tree.

Favorites (collections)

Favorites require login. Mutations go through POST /ajax/favorites/; browsing uses the normal Page flow on /favorites/[id].

async with HDRezkaClient() as client:
    await client.login(email, password)

    # Create a collection in the profile
    created = await client.add_favorites_cat('Смотрю')
    cat_id = created['id']

    # Rename
    await client.rename_favorites_cat(cat_id, 'Не буду смотреть')

    # Add a title (post id from Post / Player)
    post = await client.post('series/thriller/646-vo-vse-tyazhkie-2008.html')
    await client.add_favorites_post(post.id, cat_id)

    # Browse the collection (same pagination / InlineItem API as other pages)
    fav = client.favorites(cat_id)
    titles = await fav.get_page(1)
    cats = await fav.get_cats()  # a.b-favorites_content__cats_list_link
    for cat in cats:
        print(cat.id, cat.name, cat.count, cat.url)

    # Remove the collection
    await client.remove_favorites_cat(cat_id)

Equivalent low-level calls: client.ajax.add_favorites_cat, rename_favorites_cat, add_favorites_post, remove_favorites_cat.

Login and mirrors

Create an HDRezka account only if the service allows it for your case. If registration is disabled, try the flows they document (for example social login and setting a password). Premium and third-party clients are governed by their rules, not by this library.

login requests the standby URL (default https://standby-rezka.tv/), follows the redirect to an active mirror, posts credentials to /ajax/login/, updates client.host, and stores cookies on that client only.

You can set a mirror without login:

client = HDRezkaClient(host='https://hdrezka.tv/')

Many domains are behind Cloudflare; see mirrors.txt. Working access depends on mirror choice, IP reputation, and impersonation — not on the library claiming any special relationship with the site.

Default constants (not shared session state):

from hdrezka.url import Request, DEFAULT_HOST, DEFAULT_REDIRECT_URL

Proxies

from hdrezka import HDRezkaClient

client = HDRezkaClient(proxy='socks5://localhost:9050')

Or pass your own httpx.AsyncClient as http_client= (the library will not close it).

Migration from 4.x

Version 5 removes process-global session state.

4.x 5.x
await login_global(email, password) await client.login(email, password)
DEFAULT_CLIENT / DEFAULT_REQUEST_KWARGS HDRezkaClient(...) / request_kwargs=
Mutating Request.HOST for the process client.host or HDRezkaClient(host=...)
Search('query') client.search('query') or Search('query', client=client)
await Player(url) await client.player(url) or await Player(url, client=client)
AJAX.get_stream(...) (classmethods) client.ajax.get_stream(...)

Parsing and stream logic are unchanged; wire everything through a client.

Documentation

Releases

Used by

Contributors

Languages