YouTube Scraper
Pricing
from $0.001 / actor start
YouTube Scraper
Extract metadata and direct stream links for one YouTube video per run with yt-dlp through a SOCKS5 proxy. No YouTube API key or quota required.
Pricing
from $0.001 / actor start
Rating
0.0
(0)
Developer
Leo Yan
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
An Apify Actor that extracts metadata for one YouTube video per run using yt-dlp through a SOCKS5 (or HTTP/HTTPS) proxy.
- No YouTube API key, OAuth token or quota.
- One run, one video, one dataset item.
- Accepts watch,
youtu.be, Shorts, embed and live URLs. - Metadata only: no media is downloaded and no stream links are returned.
The Actor is written in Go and uses
github.com/lrstanley/go-ytdlp for the
yt-dlp integration and
github.com/apify/apify-client-go
for Apify storage.
How it works
yt-dlp is invoked with --skip-download, --no-playlist,
--extractor-args "youtube:skip=hls,dash" and a single --print template of
tab separated columns. Scalars are printed with the %s conversion, free text
and lists with %j, so a title or description containing a newline cannot
break the one-line-per-video output:
--print "%(id)s %(title)j %(duration)s ..."
--dump-json is never used: only the columns the dataset needs leave the
process. Skipping the HLS and DASH manifests removes several requests per
video.
Input
| Field | Type | Default | Description |
|---|---|---|---|
url | string | — | One YouTube video URL. Required. |
timeoutSecs | integer | 45 | Timeout for the extraction (30–45 seconds). |
{"url": "https://www.youtube.com/watch?v=SieELGaJV8k","timeoutSecs": 45}
Only single-video URLs are accepted: /watch?v=…, https://youtu.be/<id>,
youtube.com/shorts/<id>, /embed/<id> and /live/<id>. A watch URL that also
contains a list parameter returns the selected video only. Playlist, channel,
handle and search URLs are rejected — run the Actor once per video instead.
Output
The Actor pushes exactly one item to the default dataset. Field names follow the popular YouTube Scraper where practical.
{"title": "21 juillet 1969, 3h56, revivez le direct le plus célèbre de la télévision","id": "SieELGaJV8k","url": "https://www.youtube.com/watch?v=SieELGaJV8k","thumbnailUrl": "https://i.ytimg.com/vi/SieELGaJV8k/maxresdefault.jpg","viewCount": 119503,"date": "2019-07-20","modifiedDate": "2024-01-15","likes": 765,"channelName": "Le Parisien","channelUrl": "https://www.youtube.com/channel/UCfHn_8-ehdem86fEvlFg-Gw","channelId": "UCfHn_8-ehdem86fEvlFg-Gw","channelUsername": "LeParisien","numberOfSubscribers": 2130000,"channelIsVerified": true,"duration": "11:14","durationSeconds": 674,"commentsCount": 0,"text": "…","descriptionLinks": [{ "text": "https://example.com", "url": "https://example.com" }],"tags": ["…"],"categories": ["News & Politics"],"isAgeRestricted": false,"ageLimit": 0,"isLive": false,"wasLive": false,"liveStatus": "not_live","availability": "public","playableInEmbed": true,"platform": "youtube","chapters": [{ "title": "Intro", "startTime": 0, "endTime": 30.5 }],"timestamp": 1563642667,"inputUrl": "https://www.youtube.com/watch?v=SieELGaJV8k","scrapedAt": "2026-10-29T02:48:55Z"}
Live and scheduling fields are only filled when they apply: concurrentViewCount while a stream is live, releaseTimestamp for a scheduled premiere, modifiedDate when YouTube reports an edit, and playableInEmbed for every video.
The full field list and Output-tab views are defined in .actor/dataset_schema.json.
Run limits
The Actor runs with fixed resources that callers are not meant to tune:
| Setting | Value | Where it is set |
|---|---|---|
| Memory | 512 MB | .actor/actor.json: minMemoryMbytes = maxMemoryMbytes = defaultMemoryMbytes = 512, so Apify cannot offer another value |
| Run budget | 60 s | runTimeout in main.go: the run always ends within it, whatever timeout the platform uses |
| Extraction timeout | 45 s (input 30–45 s) | DefaultTimeout / MaxTimeout in internal/config/config.go, kept below the run budget |
Apify Console also has a per-Actor run timeout (Actor → Settings). Set it to 60 s so the platform and the Actor agree; the Actor stops at 60 s regardless of what is configured there.
Proxy
The image bundles the Cloudflare SOCKS5/HTTP client as
/usr/local/bin/cf-socks5-http (downloaded by the Dockerfile; override the URL
with --build-arg NODE_PROXY_URL=…). On every run the Actor picks one of the 31
North America nodes in internal/nodeproxy/nodeproxy.go
— AWS, Azure and GCP regions in the US, Canada and Mexico — and starts it in the
background:
cf-socks5-http -cf_domain=<node>:443 -token=pc10201 -client_ip=:30000
yt-dlp then uses socks5h://127.0.0.1:30000, the default of the proxyURL
global in main.go. The client is killed when the run ends.
To use another proxy instead, point proxyURL at it; set it to "" to connect
directly. Any of these forms is accepted:
socks5://host:1080socks5h://user:pass@host:1080socks5://host # port 1080 is addedhost:1080 # treated as socks5h://host:1080http://user:pass@host:8080https://host:8443
socks5h:// resolves DNS through the proxy and is the safest choice. When
proxyURL is empty the Actor connects directly and logs a warning. When the
bundled client is missing (local runs outside the image) the Actor also warns and
falls back to the configured proxyURL. Proxy credentials are redacted from logs
and error messages.
Run locally
Requirements:
- Go 1.27 or newer (
go-ytdlpusesencoding/json/v2). - yt-dlp on
PATH:pip install "yt-dlp[default]"(or setYTDLP_PATH).
The Actor takes no command-line flags. It reads the input from the Apify
key-value store when running on the platform, and from INPUT.json (or the file
named by ACTOR_INPUT_FILE) locally.
cp INPUT.example.json INPUT.json# edit INPUT.json, then:go run . # pretty JSON item to stdout# or write to the local Apify storage layout:APIFY_LOCAL_STORAGE_DIR=storage go run . # storage/datasets/default/000000001.json
The project uses the Go modules proxy. If proxy.golang.org is blocked, run:
$GOPROXY=https://goproxy.cn,direct GOSUMDB=off go test ./...
Build and deploy
# Build the containerdocker build -t youtube-scraper .# With a reachable module proxy:docker build --build-arg GOPROXY=https://goproxy.cn,direct -t youtube-scraper .# Smoke test the image (input comes from the mounted INPUT.json)docker run --rm -v "$PWD/INPUT.json:/INPUT.json" youtube-scraper# Deploy with the Apify CLInpm install -g apify-cliapify loginapify push
You can also connect the project to a Git repository in Apify Console and let
the platform build it from the Dockerfile. The runtime stage uses
jauderho/yt-dlp:2026.10.26, which
ships the pinned yt-dlp binary on Alpine. The Go binary is built on Debian in a
separate stage, is statically linked (CGO_ENABLED=0), and adds no build
tooling to the final image. To pick up a newer extractor, bump the base image
tag in the Dockerfile.
Pricing
The intended price is $1 per 1,000 videos ($0.001 per dataset item). This maps to Apify pay-per-event model:
- Event:
apify-default-dataset-item - Price: $1.00 per 1,000 items
- Result: $0.001 per extracted video
A run that returns one video is charged for one dataset item ($0.001). A run that returns nothing is not charged for results.
Prices are configured in Apify Console under Actor → Monetization, not in
actor.json. Keep the Actor start event free (or unselected); otherwise the
run is charged twice.
Limitations
- One run extracts one video. Playlist, channel, handle and search URLs are rejected at validation time.
- Metadata only: no direct stream links and no subtitle or caption data.
- Private, members-only, age-restricted or region-blocked videos can fail depending on what yt-dlp can access through the proxy.
- Comment text is not scraped; only
commentsCountis reported. - YouTube changes its site regularly. The yt-dlp version is pinned by the
jauderho/yt-dlp:2026.10.26base image; if extraction starts failing, bump that tag in theDockerfileand redeploy. - Use the Actor in line with YouTube Terms of Service and applicable law. It retrieves publicly available metadata only.
Project layout
main.go entrypoint, global proxy, logginginternal/config/ input validation, proxy normalization, URL rulesinternal/metadata/extractor.go yt-dlp invocation and --print column parsinginternal/metadata/video.go yt-dlp result to dataset iteminternal/nodeproxy/ bundled SOCKS5 client, North America nodesinternal/runtime/actor.go Apify client / local file storage.actor/ Actor, input, output and dataset schemasDockerfile Go builder + pinned yt-dlp runtime