Python Async v2.5

XVideos API

A fully asynchronous Python API wrapper and scraper for XVideos. Fetch videos, metadata, pornstar profiles, channels, playlists, and download content — all powered by the eaf_base_api networking engine.

GitHub
⚡ Recommended AI Workflow Official MCP Server: https://mcp.echteralsfake.me/mcp

Supercharge your development by connecting your AI coding assistant (Cursor, VS Code / Copilot, Claude Desktop, Windsurf, Zed, Antigravity). No authentication required · No strict rate limits · Covers all 16 APIs · Always up to date · Completely anonymous (0 logs, no profiling). Connecting this MCP server to your AI assistant is absolutely the recommended way to work on and build with these APIs.

⚠️ Legal Disclaimer
This tool is an unofficial, independent project and is not affiliated with, endorsed by, or sponsored by the target website. This software is provided "as is" for educational and personal purposes only. The developer assumes no responsibility for any consequences arising from the use of this tool, including but not limited to account suspension, IP blocking, or any violation of the target website's Terms of Service. Users are solely responsible for ensuring their use complies with all applicable laws and policies. Use at your own risk.
💚 Support & Commercial Licensing
If you find this project helpful, please consider donating to support its continued development!

Other Options:
• PayPal
• Ko-Fi

For extended features, enterprise integrations, or custom commercial licensing, please contact EchterAlsFakeBS@proton.me.

📦 Installation

Install from PyPI using pip:

bash
pip install unofficial-api-for-xvideos

For TS → MP4 remuxing support (recommended), install with the optional av dependency:

bash
pip install unofficial-api-for-xvideos[av]
Note
Requires Python ≥ 3.12. eaf_base_api ≥ 4.0.0 is installed automatically as a dependency.

🚀 Quick Start

Every method in this API is asynchronous. You need to run your code inside an async function:

python
import asyncio
from xvideos_api import Client

async def main():
    client = Client()

    # Fetch a video
    video = await client.get_video("https://www.xvideos.com/video...")

    # Access metadata
    print(video.title)
    print(video.views)
    print(video.likes)

    # Download the video
    from xvideos_api import DownloadConfigHLS
    config = DownloadConfigHLS(quality="best", path="./downloads")
    await video.download(configuration=config)

asyncio.run(main())

⚙️ Configuration

The API uses eaf_base_api ≥ 4.0.0. Pass a custom BaseCore to configure its singular proxy, bounded request attempts, timeouts, and other networking behavior.

Please refer to the eaf_base_api Documentation for the complete reference on how to set up RuntimeConfig and properly integrate it with this API.

python
from base_api import BaseCore
from base_api.modules.config import RuntimeConfig
from xvideos_api import Client

my_config = RuntimeConfig()
my_config.proxy = "socks5://127.0.0.1:9050"
my_config.request_attempts = 3

core = BaseCore(configuration=my_config)
client = Client(core=core)

🔌 Client

The Client class is your entry point for all interactions. It manages the session and provides methods to fetch videos, search, and access channels/pornstars.

Methods

Fetch Video get_video()

async
Fetches a video page and returns a populated Video object.
await client.get_video( url: str, load_html: bool = True ) -> Video

Parameters

  • url str — The full XVideos video URL
  • load_html bool — If True (default), fetches and parses the HTML page for full metadata

Returns

→ Video

Search Videos search()

async
Searches for videos and yields results as an async generator. See Search & Filtering for details.
async for result in client.search( query: str, sorting_sort: str | Sort = Sort.Sort_relevance, sorting_date: str | SortDate = SortDate.Sort_all, sorting_time: str | SortVideoTime = SortVideoTime.Sort_all, sort_quality: str | SortQuality = SortQuality.Sort_all, pages: int | str = "all", iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • query str — Search terms
  • pages int | "all" — Number of result pages or "all" for automatic iteration
  • iterator_config IteratorConfig | None — Optional v4 concurrency, ordering, eager-source, retry, and error-handling policy. The package default eagerly loads html.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Get Playlist get_playlist()

async
Fetches videos from a playlist page by page.
async for result in client.get_playlist( url: str, pages: int = 2, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • url str — The playlist URL
  • pages int — Number of pages to scrape
  • iterator_config IteratorConfig | None — Optional v4 iterator policy. The default eagerly loads each video's html source.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Fetch Pornstar Profile get_pornstar()

async
Fetches a pornstar profile.
await client.get_pornstar( url: str, load_html: bool = True ) -> Pornstar

Returns

→ Pornstar

Fetch Channel Profile get_channel()

async
Fetches a channel profile.
await client.get_channel( url: str, load_html: bool = True ) -> Channel

Returns

→ Channel

Get Account get_account()

standard
Creates an Account instance for authenticated actions using cookies. See Account.
client.get_account( cookies: dict | None = None ) -> Account

Returns

→ Account

🎬 Video

dataclass Inherits from BaseMedia. Represents a single video with all its metadata.

Attributes

AttributeTypeDescription
urlstrThe video page URL
titlestr | NoneVideo title
descriptionstr | NoneVideo description
thumbnail_urlstr | NoneThumbnail image URL
preview_video_urlstr | NoneShort preview clip URL
publish_datestr | NoneUpload date
content_urlstr | NoneDirect content URL
tagslist | NoneList of tag strings
viewsstr | NoneView count
likesstr | NoneLike count
dislikesstr | NoneDislike count
rating_votesstr | NoneTotal rating votes
comment_countstr | NoneComment count
author_linkstr | NoneUploader profile URL
lengthstr | NoneVideo duration
pornstars_urlslist | NoneURLs of pornstars featured
embed_urlstr | NoneEmbeddable iframe URL
cdn_urlstr | NoneOptional CDN URL carried by listing data
m3u8_base_urlstr | NoneHLS master playlist URL
video_idstr | NoneInternal video ID

Methods

Download Video download()

async
Downloads the video using the HLS threaded downloader. The video title is automatically appended to the output path unless no_title=True.
await video.download( configuration: DownloadConfigHLS ) -> bool | DownloadReport

Parameters

  • configuration DownloadConfigHLS — Download settings (quality, path, etc.). See Downloading.

Returns

→ bool | DownloadReport

Get Author Channel get_author

property async
Lazily loads and returns the uploader's Channel object.
await video.get_author -> Channel | None

Returns

→ Channel | None

Get Performers get_pornstars

property async
Yields Pornstar objects for each featured performer.
async for star in video.get_pornstars -> Pornstar

Returns

→ AsyncGenerator[Pornstar, None]
Tip — Lazy Loading
All objects inherit from BaseMedia. Accessing an unresolved attribute raises DataNotLoadedError. Use await media.load_sources("html") to load a complete source or await media.load_fields(...) for selected fields. By default, client.get_video(..., load_html=True) calls load_sources("html") and populates the HTML-backed attributes.

📺 Channel

dataclass Inherits from BaseChannelPornstar → BaseMedia. Represents a channel profile.

Attributes

AttributeTypeDescription
urlstrChannel profile URL
namestr | NoneChannel name
thumbnail_urlstr | NoneProfile picture URL
total_videosint | NoneNumber of uploaded videos
per_pageint | NoneNumber of videos returned per listing page
total_pagesint | NoneCalculated number of video pages
profile_hitsstr | NoneTotal profile views
subscribersstr | NoneSubscriber count
total_videos_viewsstr | NoneTotal views across all videos
signed_upstr | NoneRegistration date
last_activitystr | NoneLast activity date
worked_for_with_linkslist | NoneLinks to associated studios/channels

Methods

Get Channel Videos videos()

async
Iterates over the channel's uploaded videos. Set pages=0 (default) to fetch all pages.
async for result in channel.videos( pages: int = 0, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • pages int — Maximum pages; 0 uses the profile's full page count.
  • iterator_config IteratorConfig | None — Optional v4 iterator policy. The default eagerly loads each video's html source.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Get Related Channels worked_for_with()

async
Returns a list of channels/studios this entity has worked for/with.
await channel.worked_for_with( load_html: bool = True ) -> list[Channel]

Returns

→ list[Channel]

⭐ Pornstar

dataclass Inherits from BaseChannelPornstar. Represents a performer profile. Has all Channel attributes plus:

Extra AttributeTypeDescription
genderstr | NoneGender
agestr | NoneAge
video_tagsstr | NoneMost common tags

Methods

Get Performer Videos videos()

async
Iterates over the pornstar's uploaded videos. Set pages=0 (default) to fetch all pages.
async for result in pornstar.videos( pages: int = 0, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • pages int — Maximum pages; 0 uses the profile's full page count.
  • iterator_config IteratorConfig | None — Optional v4 iterator policy. The default eagerly loads each video's html source.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Get Related Co-workers/Studios worked_for_with()

async
Returns a list of channels/studios this performer has worked for/with.
await pornstar.worked_for_with( load_html: bool = True ) -> list[Channel]

Returns

→ list[Channel]

🔑 Account

Provides access to authenticated endpoints. Requires login cookies.

python
# Provide cookies from your browser session
my_cookies = {
    "session_token": "<your_token>",
    "session_token_auth": "<your_auth_token>",
}

account = client.get_account(cookies=my_cookies)

# Get your liked videos
async for result in account.get_liked_videos(pages=3):
    if result.succeeded:
        print(result.unwrap().title)

These authenticated listing endpoints make the required page requests with POST internally. Callers pass only the public IteratorConfig controls and should not set private request-method fields.

Methods

Get Liked Videos get_liked_videos()

async
Fetches your liked videos page by page; the required account-page POST request is handled internally.
async for result in account.get_liked_videos( pages: int = 2, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • pages int — Number of authenticated listing pages.
  • iterator_config IteratorConfig | None — Optional public v4 iterator policy; account transport details remain internal.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Get Recommended Videos get_recommended_videos()

async
Fetches your recommended feed videos page by page; the required account-page POST request is handled internally.
async for result in account.get_recommended_videos( pages: int = 2, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • pages int — Number of authenticated listing pages.
  • iterator_config IteratorConfig | None — Optional public v4 iterator policy; account transport details remain internal.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Get Watch Later Videos get_watch_later_videos()

async
Fetches your watch later videos page by page; the required account-page POST request is handled internally.
async for result in account.get_watch_later_videos( pages: int = 2, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • pages int — Number of authenticated listing pages.
  • iterator_config IteratorConfig | None — Optional public v4 iterator policy; account transport details remain internal.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

🔍 Search & Filtering

Use the client.search() method with the sorting enums for powerful filtering:

python
from xvideos_api.modules.sorting import Sort, SortDate, SortVideoTime, SortQuality

async for result in client.search(
    query="example search",
    sorting_sort=Sort.Sort_rating,
    sorting_date=SortDate.Sort_month,
    sorting_time=SortVideoTime.Sort_long,
    sort_quality=SortQuality.Sort_1080_plus,
    pages=5,
):
    if result.succeeded:
        video = result.unwrap()
        print(f"{video.title}")

Sorting Enums

Sort (Relevance)

Enum ValueDescription
Sort.Sort_relevanceMost relevant (default)
Sort.Sort_upload_dateNewest first
Sort.Sort_ratingHighest rated
Sort.Sort_lengthLongest first
Sort.Sort_viewsMost viewed
Sort.Sort_randomRandom order

SortDate (Time Period)

Enum ValueDescription
SortDate.Sort_allAll time (default)
SortDate.Sort_last_3_daysLast 3 days
SortDate.Sort_weekThis week
SortDate.Sort_monthThis month
SortDate.Sort_last_3_monthsLast 3 months
SortDate.Sort_last_6_monthsLast 6 months

SortVideoTime (Duration)

Enum ValueDescription
SortVideoTime.Sort_allAny duration (default)
SortVideoTime.Sort_short1–3 minutes
SortVideoTime.Sort_middle3–10 minutes
SortVideoTime.Sort_long10+ minutes
SortVideoTime.Sort_long_10_20min10–20 minutes
SortVideoTime.Sort_really_long20+ minutes

SortQuality (Resolution)

Enum ValueDescription
SortQuality.Sort_allAny quality (default)
SortQuality.Sort_720p720p and above
SortQuality.Sort_1080_plus1080p and above

⬇️ Downloading

All downloads are configured using the DownloadConfigHLS dataclass:

python
from xvideos_api import DownloadConfigHLS

config = DownloadConfigHLS(
    quality="best",           # "best", "half", "worst", or int like 720
    path="./downloads",       # Output directory (title auto-appended)
    no_title=False,           # If True, use path as exact filename
    remux=True,               # Convert TS segments to MP4 (needs av)
    return_report=True,       # Return DownloadReport instead of bool
)

report = await video.download(configuration=config)
print(report.status)       # "completed", "failed", or "cancelled"
print(report.downloaded)    # Segments downloaded
print(report.total)         # Total segments

DownloadConfigHLS — Full Options

ParameterTypeDefaultDescription
qualitystr | int—Required. "best", "half", "worst", or pixel height (720, 1080, etc.)
pathstr"./"Output directory or exact file path
no_titleboolFalseSkip auto-appending video title to filename
callback(int, int) → NoneNoneProgress callback (downloaded, total). Falls back to text progress bar.
stop_eventasyncio.EventNoneSet this event to cancel the download
remuxboolFalseRemux TS → MP4 using PyAV
start_segmentint0Skip first N segments
segment_state_pathstr | NoneNonePath for resume state JSON file
segment_dirstr | NoneNoneDirectory for individual segment files
return_reportboolFalseReturn DownloadReport instead of bool
cleanup_on_stopboolTrueDelete temp files when cancelled
keep_segment_dirboolFalseKeep segment files after completion
ios_supportboolFalseRestrict audio codec to AAC-only for iOS

📄 Pagination & Iterators

Methods like search(), get_playlist(), and channel.videos() return async generators that yield typed ScrapeResult[Video] values. Use succeeded to branch safely or unwrap() to return the item and raise its terminal error on failure:

python
async for result in client.search("query", pages=3):
    if result.succeeded:
        video = result.unwrap()
        print(video.title)
    else:
        print(result.stage, result.url, result.error)

ScrapeResult

AttributeTypeDescription
stageScrapeStageWhether the result came from the page or item stage
urlstrThe video URL
page_indexintZero-based source page index
item_indexint | NoneZero-based item index, or None for a page failure
attemptsintNumber of attempts used by the yielding stage
itemVideo | NoneThe populated video on success
errorScrapeOperationError | NoneThe typed terminal page or item error on failure
succeededboolTrue when error is None

IteratorConfig, bounded retries, and custom handlers

All iterator-only controls now live in one IteratorConfig. XVideos' package default uses ErrorMode.SKIP for terminal page failures; a supplied config replaces that behavior, and a bare IteratorConfig instead defaults to ErrorMode.YIELD. A retry policy's max_attempts includes the initial attempt, so this example makes at most three stage attempts per failed page or item. Each stage attempt may itself perform the request retries configured on BaseCore. Fully populated results require the html source.

Leave page_retry or item_retry as None to derive that stage's bounded policy from the active RuntimeConfig request-attempt and backoff settings; an explicit RetryPolicy overrides it per stage.

python
from base_api import ErrorAction, ResultOrder, RetryPolicy, ScrapeErrorContext
from base_api.modules.config import IteratorConfig

async def handle_scrape_error(context: ScrapeErrorContext) -> ErrorAction:
    if context.attempt < context.max_attempts:
        return ErrorAction.RETRY
    return ErrorAction.YIELD

retry = RetryPolicy(
    max_attempts=3, base_delay=0.5, multiplier=2.0,
    max_delay=4.0, jitter=0.2
)
iterator_config = IteratorConfig(
    max_page_concurrency=2,
    max_item_concurrency=8,
    max_pending_items=16,
    order=ResultOrder.ORIGINAL,
    load_specific_sources=("html",),
    page_retry=retry,
    item_retry=retry,
    page_error_handler=handle_scrape_error,
    item_error_handler=handle_scrape_error,
)

async for result in client.search(
    "query", pages=3, iterator_config=iterator_config
):
    if result.succeeded:
        print(result.unwrap().title)
    else:
        print(result.stage, result.error)

Authenticated account iterators also accept iterator_config; their required POST request is selected internally, so do not configure private transport fields.

ScrapeErrorContext supplies stage, url, error, attempt, max_attempts, page_index, and item_index. A handler returns ErrorAction.RETRY, RAISE, YIELD, or SKIP.

Independent page and item handlers

The core routes page-stage failures to page_error_handler and item-stage failures to item_error_handler. Assign the same callable to both fields only when both stages should use the same policy; otherwise configure either handler independently.

⚠️ Error Handling

Source loaders translate request failures into exceptions from xvideos_api.modules.errors. Calls that load media expose ordinary loader failures through base_api.MediaLoadError (or MediaLoadErrors for several sources); inspect original_error/errors as shown. Operations outside media loading may still raise package or core exceptions directly.

Request and download failures are logged with the operation, target URL, and full original traceback. Translated exceptions retain the original error in __cause__. Download preparation failures (including metadata loading, quality selection, and output path setup) are also wrapped in DownloadFailed; inspect its cause when diagnosing a failure. The specific availability exceptions listed below remain supported. An explicit DownloadCancelled or asyncio.CancelledError propagates without being wrapped in DownloadFailed. Base downloader False and DownloadReport results remain supported; inspect the result as well as handling exceptions.

The common provider errors NotFound, NetworkError, BotDetection, ProxyError, UnknownNetworkError, and DownloadFailed are catchable through base_api.modules.errors. They derive from ScraperException, which now derives from BaseScraperError. Existing provider import paths remain valid.

NoLoginCookies now derives from the shared ScraperException; its existing provider import and msg attribute remain available.

See Logging & Cleanup for application logging setup.

ExceptionWhen Raised
NotFoundServer returned HTTP 404
NetworkErrorRequest failed due to a network error, exhausted request retries, or a non-404 HTTPStatusError
BotDetectionBot protection triggered
ProxyErrorInvalid or failing proxy
UnknownNetworkErrorUnexpected network errors
DownloadFailedDownload preparation or transfer failed; the video URL is included and __cause__ retains the original exception
NoLoginCookiesAccount accessed without cookies set
python
from base_api import MediaLoadError
from xvideos_api.modules.errors import NotFound, BotDetection

try:
    video = await client.get_video(url)
except MediaLoadError as error:
    if isinstance(error.original_error, NotFound):
        print("Video does not exist")
    elif isinstance(error.original_error, BotDetection):
        print("Bot protection triggered — try a proxy")
    else:
        raise

📝 Changelog

2.5 — 2026-09-15

  • 23dec61 / bb96133 / d03b382 — Adopted shared request and download error handling from base_api.modules.provider (requiring eaf-base-api>=4.2.0). Failed downloads now raise DownloadFailed with complete diagnostic context. Modernized HTML scraping for Video, Channel, and Pornstar classes with layout anchors and resilient fallback selectors (JSON-LD, setVideoTitle, setThumbUrl, setVideoHLS, data-id, model links). Added comprehensive unit test suites for channel, pornstar, and video extraction.

2.5 — 2026-08-11

  • c1e34b3 / f8ebcb3 — Added generic ScrapeResult[Video] typing and the PEP 561 py.typed marker, left iterator retry policies unset so they resolve from RuntimeConfig, and released 2.5.

2.4 — 2026-08-08

  • 9a88d09 — Consolidated account, profile, search, and playlist iterator concurrency, ordering, source loading, retry, and error controls into IteratorConfig; authenticated account listings use their required POST transport internally.

2.4 migration — 2026-08-07

  • e39c5ec — Migrated to the eaf_base_api v4 request and scraping model: fetch_text()/request(), structured exceptions, source-aware media loading, typed scrape streams/results, and bounded retries.

💻 CLI Usage

The CLI entry point configures console logging at INFO level. Caught per-URL failures include the URL and full traceback; the default log format shows the logger, file, line, and function. Library applications should configure logging once at startup.

XVideos API includes a command-line interface accessible via xvideos_api or python -m xvideos_api:

bash
# Download a single video
xvideos_api --download "https://www.xvideos.com/video..." --quality best --output ./video.mp4 --no-title True

# Download from a file of URLs
xvideos_api --file urls.txt --quality 720 --output ./downloads/ --no-title False

CLI Options

FlagDescription
--download URLVideo URL to download
--file PATHText file with URLs (one per line)
--qualityVideo quality: best, half, worst
--outputOutput path (directory or file)
--no-titleTrue/False — Skip auto-appending title

🖥️ Supported Platforms

PlatformArchitectureStatus
Windows 11x64✅ Tested
macOS Sequoiax86_64✅ Tested
Linux (Arch)x86_64✅ Tested
Android 16aarch64✅ Tested
Note
PyAV (for TS→MP4 remuxing) is not available on Termux/Android. Use remux=False on those platforms.