Video Metadata Scraper avatar

Video Metadata Scraper

Pricing

Pay per usage

Go to Apify Store
Video Metadata Scraper

Video Metadata Scraper

One scraper for thousands of video and audio sites - YouTube, TikTok, Vimeo, SoundCloud, Twitch and more. Get title, views, likes, duration, thumbnails, formats and subtitles as clean JSON, one row per URL. Failed URLs return a typed reason, never silently dropped.

Pricing

Pay per usage

Rating

0.0

(0)

Developer

Zeshan Max

Zeshan Max

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

Media Metadata Extractor

Extract metadata, formats, thumbnails and subtitles from thousands of yt-dlp-supported websites — YouTube, Vimeo, SoundCloud, TikTok, Twitch, X, and many more, plus direct media files.

Give it one URL or a thousand. Every URL produces exactly one Dataset row, including the ones that fail, so your row count always matches your input count.

This Actor does not download media. It reads the page's metadata and returns it as clean, consistently-shaped JSON.


What you get

One record per URL. Every successful record carries the same key set, so you can index into it without guarding — missing data is null, or [] / {} for collections.

{
"success": true,
"input_url": "https://youtu.be/dQw4w9WgXcQ",
"webpage_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"extractor": "Youtube",
"content_type": "video",
"id": "dQw4w9WgXcQ",
"title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
"description": "The official video for …",
"uploader": "Rick Astley",
"channel": "Rick Astley",
"channel_follower_count": 4210000,
"duration": 213,
"duration_string": "3:33",
"upload_date": "2009-10-25",
"timestamp": 1256440800,
"thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
"thumbnails": [{ "id": "39", "url": "…", "width": 1920, "height": 1080 }],
"view_count": 1814660272,
"like_count": 19384769,
"comment_count": 2400000,
"tags": ["rick astley", "never gonna give you up"],
"categories": ["Music"],
"language": "en",
"age_limit": 0,
"availability": "public",
"live_status": "not_live",
"chapters": [],
"formats": [
{
"format_id": "137",
"ext": "mp4",
"resolution": "1920x1080",
"width": 1920,
"height": 1080,
"fps": 25,
"vcodec": "avc1.640028",
"acodec": "none",
"tbr": 4000.5,
"filesize": 123456789,
"protocol": "https"
}
],
"format_count": 44,
"subtitles": { "en": [{ "ext": "vtt", "url": "…", "name": "English" }] },
"subtitle_languages": ["de-DE", "en", "es-419", "ja", "pt-BR"],
"has_subtitles": true,
"automatic_captions": {},
"error_type": null,
"error": null,
"extracted_at": "2026-09-12T02:41:14Z"
}

A failure keeps the same shape and tells you why:

{
"success": false,
"input_url": "https://www.youtube.com/watch?v=aaaaaaaaaaa",
"extractor": null,
"error_type": "unavailable",
"error": "[youtube] aaaaaaaaaaa: This video is unavailable",
"retryable": false,
"extracted_at": "2026-09-12T02:41:14Z"
}

Filter on success to split the two, or use the built-in Failures only Dataset view.


Input

FieldTypeDefaultWhat it does
urlsarrayThe URLs to extract.
urlstringShortcut for a single URL. Merged with urls if you give both.
proxyConfigurationobjectApify Proxy, RESIDENTIALKeep this on. See below.
playlistModeenumsinglesingle / expand / index — see below.
maxItemsPerPlaylistinteger50Caps playlist expansion.
includeFormatUrlsbooleanfalseAdds signed stream URLs to each format.
includeAutomaticCaptionsbooleanfalseAdds auto-generated captions.
concurrencyinteger5URLs processed in parallel (1–20).
timeoutSecsinteger90Per-URL wall clock (10–600).

Minimal input:

{ "urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"] }

Proxies matter

Datacenter IP ranges are frequently blocked by the larger video sites. Without a residential proxy the Actor will look broken on exactly the sites you test first. The default input turns Apify Proxy on with the RESIDENTIAL group; leave it that way unless you have a reason not to.

If proxy credentials are missing, the run does not fail — it logs a warning and continues on a direct connection.

Playlists and channels

A playlist URL is one input that can mean a thousand videos, so you choose what happens:

ModeResultCost
single (default)One row describing the playlist itself — title, uploader, entry count.1 request
expandFull metadata for each video, capped by maxItemsPerPlaylist. Each entry is extracted independently, so one private video does not sink the rest.1 + N
indexIds, titles and URLs only. Fastest way to enumerate a channel.1 request

Two switches that change record size a lot

Both are off by default, measured on a single YouTube video:

SettingRecord size
Defaults36 KB
includeFormatUrls + includeAutomaticCaptions557 KB (15×)

includeAutomaticCaptions adds ~160 machine-translated caption languages. Turn these on only if you need them — and note that format URLs are signed and expire within hours, so a stored one is usually dead by the time you read it.


Error types

One failed URL never stops the run. Every failure is labelled:

error_typeMeaningRetried
invalid_urlMalformed, empty, or not a URLno
unsupported_urlNo yt-dlp extractor matches the siteno
no_mediaSite supported, page read fine — there's just no video on itno
private_contentPrivate video or private accountno
unavailableDeleted, removed, 404no
login_requiredSign-in wall, members-only, premium-onlyno
age_restrictedAge gate blocks extractionno
geo_restrictedBlocked in the exit node's countryyes
rate_limitedHTTP 429 or a bot checkyes
network_errorDNS, reset, socket timeoutyes
timeoutExceeded timeoutSecsno
extraction_failedThe extractor broke — usually the site changedyes

Retryable failures are re-attempted on a fresh proxy session before being recorded — transient transport errors (network_error, rate_limited) get two retries, everything else one.

The run itself fails only when the input is unusable (no URLs at all). Progress is written to the Dataset as each URL finishes, so an aborted run still keeps everything done so far.

A run summary lands in the default key-value store under OUTPUT:

{
"total_records": 9,
"succeeded": 4,
"failed": 5,
"failures_by_type": { "invalid_url": 2, "login_required": 1, "unavailable": 1, "unsupported_url": 1 },
"input_urls": 9,
"playlist_mode": "single",
"proxy_used": true,
"finished_at": "2026-09-12T02:41:14Z"
}

Coverage

yt-dlp supports thousands of sites and chooses the extractor for you. It is not every site on the internet, and some sites require a login this Actor does not perform — those come back as login_required rather than silently empty. Run yt-dlp --list-extractors for the current list.


Running it locally

No Apify account or CLI needed — the SDK falls back to local storage on disk.

py -3.13 -m venv .venv
.venv/Scripts/activate # macOS/Linux: source .venv/bin/activate
pip install -r requirements-dev.txt
mkdir -p storage/key_value_stores/default
echo '{"urls": ["https://www.youtube.com/watch?v=dQw4w9WgXcQ"]}' \
> storage/key_value_stores/default/INPUT.json
python -m src

Results land in storage/datasets/default/, the run summary in storage/key_value_stores/default/OUTPUT.

Tests

pytest # 55 offline unit tests, no network
python -m tests.live_matrix # end-to-end matrix against real sites
python -m tests.live_matrix --all # plus the proxy-sensitive rows

live_matrix runs the real Actor over a fixed set of URLs and checks both the per-site outcomes and the cross-cutting invariants — one record per unique URL, an identical key set on every success record, no record near the 9 MB Dataset ceiling, no private yt-dlp keys leaked. Run it before every release: a red row usually means a site changed, not that the code did.


Maintenance

yt-dlp needs to stay current. Sites change and yt-dlp ships extractor fixes constantly; a stale image is the most likely reason a working Actor stops working. requirements.txt floors the version rather than pinning it — schedule a weekly rebuild on Apify so each build picks up the latest release.


Project layout

.actor/ Apify manifest, input + dataset schemas, Dockerfile
src/
__main__.py asyncio entrypoint
main.py input parsing, proxy, worker pool, push_data
extractor.py the only module that talks to yt-dlp
normalize.py info dict -> output contract
errors.py exception -> error_type
constants.py limits, defaults, field allow-lists
tests/
test_errors.py classification table, offline
test_normalize.py the output contract, offline
live_matrix.py end-to-end, needs network

Built with yt-dlp and the Apify Python SDK.