Python · Async · v5.4

PornHub API

A fully asynchronous Python API wrapper and scraper for Pornhub. Fetch videos, GIFs, shorts, albums, playlists, pornstars, models, channels, and user profiles — with full account login support. Powered by the eaf_base_api networking engine.

$ pip install unofficial-api-for-pornhub 📋
GitHub →
⚠️ 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-pornhub

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

bash
pip install unofficial-api-for-pornhub[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 pornhub_api import Client

async def main():
    client = Client()

    # Fetch a video
    video = await client.get_video("https://www.pornhub.com/view_video.php?viewkey=...")

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

    # Download the video
    from base_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 for networking. Configure its singular proxy, bounded request attempts, timeouts, and other runtime behavior through a custom BaseCore.

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 pornhub_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, headers, cookies, and authentication — providing methods to fetch individual resources, search, and access account features.

python
from pornhub_api import Client
from base_api import BaseCore

# Basic initialization
client = Client()

# With custom core and login credentials
client = Client(
    core=BaseCore(),
    email="user@example.com",
    password="password",
    login=False,
)

# Run inside an async function so login can be awaited.
await client.login()

Constructor Parameters

  • core BaseCore — Networking core instance. The source default is one module-created BaseCore() shared by callers that omit this argument; pass an explicit core for independent configuration and lifecycle.
  • email str | None — Account email for login
  • password str | None — Account password for login
  • login bool — If True, schedules login with asyncio.create_task() and therefore requires a running event loop. Prefer login=False followed by await client.login() when completion must be observed.

Methods

Fetch Video get_video()

async
Fetches a video page and returns a populated Video object. By default, loads metadata via the Webmaster API; set load_html=True for full HTML-scraped data including categories, tags, and author information.
await client.get_video( url: str, load_html: bool = False, load_api: bool = True ) -> Video

Parameters

  • url str — The full Pornhub video URL
  • load_html bool — If True, fetches and parses the full HTML page for extended metadata (categories, tags, author info, m3u8 URLs)
  • load_api bool — If True (default), fetches metadata via the Webmaster API for faster loading

Returns

→ Video

Fetch Pornstar get_pornstar()

async
Fetches a pornstar profile page and returns a populated Pornstar object with bio, about, and info fields.
await client.get_pornstar( url: str, load_html: bool = True ) -> Pornstar

Parameters

  • url str — The full Pornhub pornstar profile URL
  • load_html bool — If True (default), parses the HTML profile page for metadata

Returns

→ Pornstar

Fetch Model get_model()

async
Fetches a model profile page and returns a populated Model object.
await client.get_model( url: str, load_html: bool = True ) -> Model

Parameters

  • url str — The full Pornhub model profile URL
  • load_html bool — If True (default), parses the HTML profile page

Returns

→ Model

Fetch User get_user()

async
Fetches a regular user profile page and returns a populated User object.
await client.get_user( url: str, load_html: bool = True ) -> User

Parameters

  • url str — The full Pornhub user profile URL
  • load_html bool — If True (default), parses the HTML profile page

Returns

→ User

Fetch Channel get_channel()

async
Fetches a channel page and returns a populated Channel object with name, stats, and metadata.
await client.get_channel( url: str, load_html: bool = True ) -> Channel

Parameters

  • url str — The full Pornhub channel URL
  • load_html bool — If True (default), parses the HTML page

Returns

→ Channel

Fetch Playlist get_playlist()

async
Fetches a playlist page and returns a populated Playlist object with title, description, tags, and video counts.
await client.get_playlist( url: str, load_html: bool = True ) -> Playlist

Parameters

  • url str — The full Pornhub playlist URL
  • load_html bool — If True (default), parses the HTML page for metadata

Returns

→ Playlist

Fetch Album get_album()

async
Fetches a photo album page and returns a populated Album object with rating, views, tags, and author information.
await client.get_album( url: str, load_html: bool = True ) -> Album

Parameters

  • url str — The full Pornhub album URL
  • load_html bool — If True (default), parses the HTML page

Returns

→ Album

Fetch GIF get_gif()

async
Fetches a GIF page and returns a populated GIF object with title, votes, views, tags, and direct download URL.
await client.get_gif( url: str, load_html: bool = True ) -> GIF

Parameters

  • url str — The full Pornhub GIF URL
  • load_html bool — If True (default), parses the HTML page

Returns

→ GIF

Fetch Short get_short()

async
Fetches a short video page and returns a populated Short object with title, likes, and streaming URLs.
await client.get_short( url: str, load_html: bool = True ) -> Short

Parameters

  • url str — The full Pornhub short URL
  • load_html bool — If True (default), parses the HTML page

Returns

→ Short

Search Videos search_videos()

async
Searches for videos matching the query text. Supports filtering by production type, sort order, and duration range. See Search & Filtering for details.
async for result in client.search_videos( query: str, production_type: Literal["professional", "homemade"] | None = None, sort_by: Literal["mr", "mv", "tr"] | None = None, duration_min: Literal["10", "20", "30"] | None = None, duration_max: Literal["10", "20", "30"] | None = None, pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • query str — Search keywords
  • production_type str | None — Filter by "professional" or "homemade"
  • sort_by str | None "mr" (Most Recent), "mv" (Most Viewed), "tr" (Top Rated)
  • duration_min str | None — Minimum duration in minutes: "10", "20", or "30"
  • duration_max str | None — Maximum duration in minutes: "10", "20", or "30"
  • pages int — Number of result pages to iterate (default: 5)
  • iterator_config IteratorConfig | None — Optional v4 concurrency, ordering, eager-source, retry, and error-handling policy. By default, no media source is loaded eagerly.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Search GIFs search_gifs()

async
Searches for GIFs matching the query. Supports category and sort filtering.
async for result in client.search_gifs( query: str, category: Literal["gay", "transgender"] | None = None, search_filter: Literal["mr", "mv", "tr"] | None = None, pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[GIF], None]

Parameters

  • query str — Search keywords
  • category str | None — Category filter: "gay" or "transgender" (default: straight)
  • search_filter str | None "mr" (Most Recent), "mv" (Most Viewed), "tr" (Top Rated)
  • pages int — Number of result pages (default: 5)
  • iterator_config IteratorConfig | None — Optional v4 iterator policy. By default, no media source is loaded eagerly.

Returns

→ AsyncGenerator[ScrapeResult[GIF], None]

Search HubTraffic API search_hubtraffic()

async
Searches for videos using the HubTraffic (Webmaster) API. Faster and provides pre-parsed metadata without HTML scraping.
async for result in client.search_hubtraffic( query: str, category: str | None = None, sort_by: Literal["newest", "mostviewed", "rating"] | None = None, period: Literal["weekly", "monthly", "alltime"] | None = None, pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • query str — Search keywords
  • category str | None — Category filter string
  • sort_by str | None "newest", "mostviewed", or "rating"
  • period str | None — Time period: "weekly", "monthly", or "alltime"
  • pages int — Number of result pages (default: 5)
  • iterator_config IteratorConfig | None — Optional v4 iterator policy. The default uses higher page/item concurrency for HubTraffic but still performs no eager media-source load.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Login login()

async
Authenticates the client with email/password credentials. On success, populates client.account with user data and enables access to account-specific methods (recommended, history, favorites, feed, subscriptions).
await client.login( force: bool = False, throw: bool = True ) -> bool

Parameters

  • force bool — If True, re-login even if already logged in
  • throw bool — If True (default), raises LoginFailed on failure; if False, returns False instead

Returns

→ bool

Raises

  • LoginFailed — If credentials are invalid or login request fails
  • ClientAlreadyLogged — If already logged in and force=False

🎬 Video

dataclass Inherits from BaseMedia. Represents a single video with metadata from both HTML scraping and the Webmaster API. Supports dual loading modes: API-only (fast) and HTML (full data including m3u8 URLs for downloading).

Attributes

AttributeTypeSourceDescription
urlstrThe video page URL
video_idstr | NoneExtracted viewkey from URL
titlestr | NoneAPI / HTMLVideo title
durationint | NoneAPI / HTMLVideo duration in seconds
thumbnailstr | NoneAPI / HTMLPreview thumbnail URL
viewsstr | NoneAPI / HTMLView count
likesstr | NoneAPI / HTMLLike / rating count
publish_datestr | NoneAPI / HTMLUpload / publish date
categorieslist[str] | NoneAPI / HTMLCategory names
tagslist[str] | NoneAPI / HTMLTag names
rating_percentstr | float | NoneAPIWebmaster API rating percentage
is_hdbool | NoneHTMLWhether the video is HD
is_vrbool | NoneHTMLWhether the video is VR
is_verticalbool | NoneHTMLWhether the video is vertical (portrait)
is_video_unavailablebool | NoneHTMLWhether the video is unavailable
is_video_unavailable_in_your_countrybool | NoneHTMLWhether the video is geo-blocked
available_qualitieslist[int] | NoneHTMLSorted list of available resolution heights
m3u8_base_urlstr | NoneHTMLSynthesized master m3u8 playlist for HLS download
author_thumbnailstr | NoneHTMLAuthor's avatar image URL
author_linkstr | NoneHTMLAuthor's profile URL
author_informationdict | NoneHTMLAuthor details: name, link, video count, subscriber count
Important
To download a video, you must load the HTML source (either via client.get_video(url, load_html=True) or by calling await video.load_sources("html") afterward). The m3u8 streaming URLs are only available from that source.

Methods & Properties

Download Video download()

async
Downloads the video via HLS streaming. Auto-appends the video title to the output path unless no_title=True is set on the config.
await video.download( configuration: DownloadConfigHLS ) -> bool | DownloadReport

Parameters

  • configuration DownloadConfigHLS — HLS download settings. See Downloading.

Returns

→ bool | DownloadReport

Get Author author

property async
Returns the video's author as a Pornstar, Model, or Channel based on the author-link pattern. The source-aware property loads the author link on demand.
await video.author -> Pornstar | Model | Channel | None

Returns

→ Pornstar | Model | Channel | None

Pornstar

dataclass Inherits from UserHelperBaseMedia. Represents a pornstar profile. Shared base with Model and User for profile info and video iteration.

Attributes

AttributeTypeDescription
urlstrThe pornstar profile URL
biostr | NoneShort bio / tagline text
aboutstr | NoneExtended about section text
infodict | NoneStructured info fields (e.g., gender, age, location)

Methods

Get Videos get_videos()

async
Iterates over videos featured on the pornstar's profile.
async for result in pornstar.get_videos( pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • pages int — Number of profile pages.
  • iterator_config IteratorConfig | None — Optional v4 iterator policy; the default does not eagerly load html or api.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Get Uploads get_uploads()

async
Iterates over videos directly uploaded by the pornstar (as opposed to featured videos).
async for result in pornstar.get_uploads( pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • pages int — Number of upload pages.
  • iterator_config IteratorConfig | None — Optional v4 iterator policy; the default constructs results without eager source loading.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Get GIFs get_gifs()

async
Iterates over GIFs associated with the pornstar's profile.
async for result in pornstar.get_gifs( pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[GIF], None]

Parameters

  • pages int — Number of GIF pages.
  • iterator_config IteratorConfig | None — Optional v4 iterator policy; the default constructs results without eager source loading.

Returns

→ AsyncGenerator[ScrapeResult[GIF], None]

👤 Model

dataclass Inherits from UserHelperBaseMedia. Represents a model profile and inherits get_videos() from UserHelper.

Attributes

Same as Pornstar: url, bio, about, info.

Methods

Get Videos get_videos()

async
Iterates over videos on the model's profile. Inherited from UserHelper.
async for result in model.get_videos( pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • pages int — Number of profile pages.
  • iterator_config IteratorConfig | None — Optional v4 iterator policy; the default constructs results without eager source loading.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

🧑 User

dataclass Inherits from UserHelperBaseMedia. Represents a regular user profile and inherits get_videos() from UserHelper.

Attributes

Same as Pornstar: url, bio, about, info.

Methods

Get Videos get_videos()

async
Iterates over videos on the user's profile. Inherited from UserHelper.
async for result in user.get_videos( pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • pages int — Number of profile pages.
  • iterator_config IteratorConfig | None — Optional v4 iterator policy; the default constructs results without eager source loading.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

📺 Channel

dataclass Inherits from BaseMedia. Represents a production channel with studio-level metadata.

Attributes

AttributeTypeDescription
urlstrThe channel page URL
namestr | NoneChannel display name
is_award_winnerbool | NoneWhether the channel has won awards
video_viewsstr | NoneTotal video views across the channel
subscribersstr | NoneSubscriber count
total_videosstr | NoneTotal number of videos
rankstr | NoneChannel rank
descriptionstr | NoneChannel description text
join_datestr | NoneWhen the channel joined
websitestr | NoneExternal website URL
user_linkstr | NoneAssociated user profile URL

Methods

Get Videos get_videos()

async
Iterates over videos published by the channel.
async for result in channel.get_videos( pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • pages int — Number of channel pages.
  • iterator_config IteratorConfig | None — Optional v4 iterator policy; the default constructs results without eager source loading.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Get User get_user()

async
Returns the User object associated with this channel.
await channel.get_user( load_html: bool = True ) -> User

Returns

→ User

📋 Playlist

dataclass Inherits from BaseMedia. Represents a video playlist with title, description, tags, and video count. Uses the chunked API endpoint internally for video iteration.

Attributes

AttributeTypeDescription
urlstrThe playlist page URL
titlestr | NonePlaylist title
descriptionstr | NonePlaylist description text
viewsstr | NoneView count
rating_percentstr | NoneRating percentage
likesstr | NoneLike count
dislikesstr | NoneDislike count
video_countstr | NoneTotal number of videos in the playlist
unavailable_videosint | NoneNumber of hidden / unavailable videos
tagsdict[str, str] | NoneTag name → URL mapping
author_linkstr | NonePlaylist author's profile URL
tokenstr | NoneInternal token for chunked API requests
playlist_idstr | NoneExtracted playlist numeric ID

Methods

Get Videos get_videos()

async
Iterates over videos in the playlist. Uses the internal chunked API for paginated fetching.
async for result in playlist.get_videos( pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • pages int — Number of chunked playlist pages.
  • iterator_config IteratorConfig | None — Optional v4 iterator policy; the default constructs results without eager source loading.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Get Author get_author()

async
Returns the User who created the playlist.
await playlist.get_author( load_html: bool = True ) -> User

Returns

→ User

🖼️ Album

dataclass Inherits from BaseMedia. Represents a photo album with rating, views, tags, and individual photo download support.

Attributes

AttributeTypeDescription
urlstrThe album page URL
rating_percentagestr | NoneAlbum rating percentage
viewsstr | NoneView count
publish_datestr | NonePublish date string
tagsdict[str, str] | NoneTag name → URL mapping
votesstr | NoneVote count string
author_linkstr | NoneAlbum author's profile URL

Methods

Get Photos get_photos()

async
Iterates over all photos in the album. Each result is a dictionary containing url, download_url, rating, and views. Uses ProcessPoolExecutor for parallel HTML parsing across pages.
async for photo in album.get_photos( pages: int ) -> dict

Parameters

  • pages int Required. Number of album pages to fetch

Returns

→ AsyncGenerator[dict, None]

Each dict contains: url, download_url, rating, views

Download Photo download_photo()

async
Downloads a single photo using the RAW downloader. Use the download_url from get_photos() results.
await album.download_photo( url: str, path: str ) -> bool

Parameters

  • url str — Direct download URL of the photo
  • path str — Output file path

Returns

→ bool

Get Author author

property async
Returns the Pornstar object who authored this album.
await album.author -> Pornstar

Returns

→ Pornstar

🎞️ GIF

dataclass Inherits from BaseMedia. Represents a single GIF with voting data, source video link, and a direct download URL.

Attributes

AttributeTypeDescription
urlstrThe GIF page URL
titlestr | NoneGIF title
vote_countstr | NoneTotal vote count
vote_percentagestr | NonePositive vote percentage
viewsstr | NoneView count
publish_datestr | NoneUpload date
thumbnailstr | NoneThumbnail image URL
content_urlstr | NoneDirect MP4/WebM download URL
source_video_urlstr | NoneURL of the source video this GIF was created from
tagsdict[str, str] | NoneTag name → URL mapping

Methods

Download GIF download()

async
Downloads the GIF using the RAW downloader (direct file download). Auto-appends the title to the output path unless no_title=True.
await gif.download( configuration: DownloadConfigRAW ) -> bool

Parameters

  • configuration DownloadConfigRAW — RAW download settings. See Downloading.

Returns

→ bool

📱 Short

dataclass Inherits from BaseMedia. Represents a Pornhub short-form video with metadata extracted from inline JSON.

Attributes

AttributeTypeDescription
urlstrThe short page URL
titlestr | NoneShort title
video_idstr | NoneInternal video ID
video_keystr | NoneVideo key identifier
likesstr | NoneLike count
dislikesstr | NoneDislike count
favoritesstr | NoneFavorite count
comment_countstr | NoneComment count
is_hdbool | NoneWhether the short is HD
thumbnailstr | NoneThumbnail image URL
embed_urlstr | NoneEmbed URL
author_namestr | NoneAuthor display name
author_linkstr | NoneAuthor profile URL
avatarstr | NoneAuthor avatar image URL
video_urlstr | NoneLink to the full video version
m3u8_base_urlstr | NoneSynthesized master m3u8 playlist
media_definitionsdict | NoneRaw media quality definitions

Methods

Download Short download()

async
Downloads the short via HLS streaming. Auto-appends the title to the output path unless no_title=True.
await short.download( configuration: DownloadConfigHLS ) -> bool | DownloadReport

Parameters

  • configuration DownloadConfigHLS — HLS download settings. See Downloading.

Returns

→ bool | DownloadReport

Get Author get_author()

async
Returns the Pornstar object who created this short.
await short.get_author( load_html: bool = True ) -> Pornstar

Returns

→ Pornstar

🔐 Account

Represents the authenticated user account. Access via client.account after calling client.login(). Provides methods for recommendations, history, favorites, feed, and subscriptions.

Attributes

AttributeTypeDescription
namestr | NoneAccount username
avatarstr | NoneAvatar image URL
is_premiumboolWhether the account has premium status
userUser | NoneAssociated User object for the account profile
python
from pornhub_api import Client

client = Client(email="user@example.com", password="password")
await client.login()

print(client.account.name)        # Username
print(client.account.is_premium)   # True/False

# Get recommended videos
async for result in client.account.get_recommended():
    if result.succeeded:
        video = result.unwrap()
        await video.load_sources("html")
        print(video.title)

Methods

Get Recommended get_recommended()

async
Gets recommended videos for the logged-in account. Automatically fixes recommendation cookies before fetching.
async for result in account.get_recommended( pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • pages int — Number of recommendation pages.
  • iterator_config IteratorConfig | None — Optional v4 iterator policy; the default performs no eager source loading.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Get History get_history()

async
Gets the watch history for the logged-in account. Requires authentication.
async for result in account.get_history( pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • pages int — Number of history pages.
  • iterator_config IteratorConfig | None — Optional v4 iterator policy; the default performs no eager source loading.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Get Favorites get_favorites()

async
Gets favorite/liked videos for the logged-in account. Requires authentication.
async for result in account.get_favorites( pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • pages int — Number of favorites pages.
  • iterator_config IteratorConfig | None — Optional v4 iterator policy; the default performs no eager source loading.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Get Feed get_feed()

async
Gets the subscription feed for the logged-in account. Filterable by section type.
async for result in account.get_feed( section: str = "videos", pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[Video], None]

Parameters

  • section str — Section to filter: "videos", "photos", "posts", etc.
  • pages int — Number of feed pages.
  • iterator_config IteratorConfig | None — Optional v4 iterator policy; the default performs no eager source loading.

Returns

→ AsyncGenerator[ScrapeResult[Video], None]

Get Subscriptions get_subscriptions()

async
Gets all subscribed users/creators for the logged-in account.
async for result in account.get_subscriptions( pages: int = 5, iterator_config: IteratorConfig | None = None ) -> AsyncGenerator[ScrapeResult[User], None]

Parameters

  • pages int — Number of subscription pages.
  • iterator_config IteratorConfig | None — Optional v4 iterator policy; the default performs no eager source loading.

Returns

→ AsyncGenerator[ScrapeResult[User], None]

🔍 Search & Filtering

The PornHub API offers three distinct search methods with rich filtering options:

Video Search Filters

ParameterValuesDescription
production_type"professional", "homemade"Filter by content type
sort_by"mr", "mv", "tr"Most Recent, Most Viewed, Top Rated
duration_min"10", "20", "30"Minimum duration in minutes
duration_max"10", "20", "30"Maximum duration in minutes

GIF Search Filters

ParameterValuesDescription
category"gay", "transgender"Category filter (default: straight)
search_filter"mr", "mv", "tr"Most Recent, Most Viewed, Top Rated

HubTraffic API Filters

ParameterValuesDescription
categoryAny category stringFree-form category filter
sort_by"newest", "mostviewed", "rating"Sort order
period"weekly", "monthly", "alltime"Time period filter
python
# Search professional videos, sorted by most viewed
async for result in client.search_videos(
    query="blonde",
    production_type="professional",
    sort_by="mv",
    pages=3
):
    if result.succeeded:
        video = result.unwrap()
        await video.load_sources("html")
        print(video.title)

# Fast search via HubTraffic API
async for result in client.search_hubtraffic(
    query="amateur",
    sort_by="newest",
    period="weekly"
):
    if result.succeeded:
        video = result.unwrap()
        print(video.title, video.views)

⬇️ Downloading

The PornHub API uses two download modes depending on the content type:

  • HLS (DownloadConfigHLS) — For Videos and Shorts. Downloads HLS streams with quality selection, threaded segment downloading, resume support, and optional TS→MP4 remuxing.
  • RAW (DownloadConfigRAW) — For GIFs and Album photos. Direct file downloads with multi-threaded range request support.

HLS Download (Videos & Shorts)

python
from base_api import DownloadConfigHLS

config = DownloadConfigHLS(
    quality="best",           # "best", "half", "worst", or height int (e.g. 720)
    path="./downloads",       # Output directory or file path
    no_title=False,           # Auto-append video.title + ".mp4" if False
)

# Must have HTML loaded for m3u8 URLs
video = await client.get_video(url, load_html=True)
success = await video.download(configuration=config)

RAW Download (GIFs & Photos)

python
from base_api import DownloadConfigRAW

config = DownloadConfigRAW(
    quality="best",           # "best", "half", "worst", or height int
    path="./downloads",       # Output directory or file path
    no_title=False,           # Auto-append title + ".mp4" if False
    allow_multipart=True,     # Use multi-threaded range requests
    max_workers=5             # Number of parallel download threads
)

gif = await client.get_gif(url)
success = await gif.download(configuration=config)

For full details on all download configuration options, refer to the eaf_base_api Documentation.

📄 Pagination & Iterators

Methods like search_videos(), pornstar.get_videos(), channel.get_videos(), and the account iterators yield typed ScrapeResult[T] 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_videos("college", pages=2):
    if result.succeeded:
        video = result.unwrap()
        await video.load_sources("html")
        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
itemT | NoneThe constructed Video, GIF, or User 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. Pornhub's 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.

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.

Pornhub source loading is opt-in

Pornhub's default iterator configuration loads no media source eagerly: it constructs objects from listing data. Call await item.load_sources("html") or opt in with load_specific_sources=("html",), as below. Use "api" only when the selected media type supports that loader.

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_videos(
    "college", pages=2, iterator_config=iterator_config
):
    if result.succeeded:
        print(result.unwrap().title)
    else:
        print(result.stage, result.error)

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 pornhub_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, such as login, may still raise package or core exceptions directly.

ExceptionWhen Raised
NotFoundServer returned HTTP 404
NetworkErrorGeneral network request failure (wraps NetworkRequestError)
BotDetectionCloudflare or similar bot protection triggered
ProxyErrorInvalid or failing proxy
UnknownNetworkErrorUnexpected network errors
DownloadFailedDownload operation failed
LoginFailedAuthentication failed (invalid credentials or token)
ClientAlreadyLoggedAttempted login when already authenticated
VideoDisabledThe video has been disabled by the platform
GifPendingReviewThe GIF is still pending review and cannot be accessed
python
from base_api import MediaLoadError
from pornhub_api.modules.errors import NotFound, BotDetection, LoginFailed

try:
    video = await client.get_video(url, load_html=True, load_api=False)
except MediaLoadError as error:
    if isinstance(error.original_error, NotFound):
        print("Video does not exist")
    elif isinstance(error.original_error, BotDetection):
        print("Bypassing bot protection failed")
    else:
        raise

try:
    await client.login()
except LoginFailed as e:
    print(f"Login failed: {e}")

💻 CLI Usage

The PornHub API package includes a built-in CLI accessible via the phub command:

bash
# Download a single video
phub --download "https://www.pornhub.com/view_video.php?viewkey=..." --quality best --output ./downloads --no-title False

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

# Download liked videos (requires login)
phub --liked --email user@example.com --password pass --quality best --output ./favorites --no-title False

# Download with video ID as filename and a limit
phub --download "https://www.pornhub.com/view_video.php?viewkey=..." --quality best --output ./downloads --no-title False --id-as-title --limit 10

CLI Options

FlagDescription
--download URLDownload from the specified URL (video, short, GIF, album, pornstar, model, user, channel, or playlist)
--file FILERead and download URLs from a line-separated text file
--quality QUALITYRequired. The video quality: best, half, worst
--output DIRRequired. The destination path/directory
--no-title True/FalseRequired. Skip auto-appending video title to output filename
--pages NNumber of pages to fetch for iterables (default: 1)
--email EMAILAccount email for login
--password PASSAccount password for login
--id-as-titleUse the video ID as the output filename instead of the title
--limit NMaximum number of videos to download
--likedDownload liked/favorite videos (requires login)
--recommendedDownload recommended videos (requires login)
--watchedDownload watched/history videos (requires login)

📝 Changelog

5.4 — 2026-08-11

  • 68f6a65 / b16c2ea — Sanitized Short, GIF, and Video download titles to prevent path traversal and illegal filenames, then released 5.4.
  • 6a5c064 — Added generic ScrapeStream/ScrapeResult typing and the PEP 561 py.typed marker; iterator retry fields remain unset so they resolve from RuntimeConfig.

5.3.1 fixes — 2026-08-10

  • 1acf957 — Made iterator source loading opt-in by default, fixed profile /videos URL construction, separated upload/model/playlist extractors for richer structured results and deduplication, tolerated missing playlist descriptions, and corrected count parsing (issues #96, #97, and #98).

5.3.1 iterator synchronization — 2026-08-08

  • 69bffd6 — Consolidated iterator concurrency, ordering, source loading, retry, and error controls into IteratorConfig.

5.3.1 v4 migration — 2026-08-07

  • 7142475 — 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.

5.3.1 — 2026-08-06

  • f93c3eb — Fixed Video.author(load_html), corrected the CLI entry point, and released the 5.3.1 patch.

🖥️ Supported Platforms

PlatformArchitectureStatus
Windows 11x64✅ Tested
macOS Sequoiax86_64✅ Tested
Linux (Arch)x86_64✅ Tested
Android 16aarch64✅ Tested