All notable changes to this actor are documented in this file.
The actor could never build, let alone run. Dockerfile has always contained:
COPY requirements.txt ./
RUN pip3 install --no-cache-dir -r requirements.txt
but there was no requirements.txt anywhere in the actor's source directory
(only .actor/, Dockerfile, README.md, .dockerignore/.gitignore, and
src/ existed — confirmed by listing the actor directory before making any
change). Docker fails a COPY of a file that does not exist in the build
context with a hard, immediate build error ("requirements.txt": not found),
so every build of this actor failed before a single line of src/main.py ever
ran — the identical failure mode independently found and fixed this same
session in the sibling actor youtube-channel-finder-with-contact-info-extraction.
We did not stop at "add the missing file" — before concluding that was the
only bug, we independently verified live, from this session (not from inside
the actor), that src/main.py's actual scraping logic is correct for
YouTube's current (August 2026) behavior:
yt_dlp.YoutubeDL({"extract_flat": True, "force_generic_extractor": True})
against ytsearch{N}:{keyword}#channel still returns entries carrying a
usable channel_url per result (tested live against python programming,
installed yt-dlp 2026.3.13) — the keyword-search discovery path works.
- A channel's
/channel/{id}/about page still embeds ytInitialData with the
exact path extract_channel_metadata()
reads, carrying description, canonicalChannelUrl, subscriberCountText,
videoCountText, viewCountText, joinedDateText, channelId, and links
— tested live against a real channel (UCWv7vMbMWH4-V0ZXdmDpPBA) and every
field came back present and correctly assigned (no subscriber/video-count
swap on this endpoint — that quirk is specific to the search API's
channelRenderer, which this actor does not use, since it discovers
channels via yt_dlp instead of a raw InnerTube search call).
- The
/videos tab InnerTube browse endpoint (POST /youtubei/v1/browse
with params=EgZ2aWRlb3PyBgQKAjoA) used by the optional
includeChannelKeywords feature still returns richItemRenderer items
whose content.lockupViewModel has with contentId (video id) and the title under
metadata.lockupMetadataViewModel.title.content — tested live and matches
_extract_recent_videos() exactly, including its already-correct fallback
away from the older videoRenderer shape.
- A video watch page's
ytInitialPlayerResponse.videoDetails.keywords (used
by fetch_video_tags() for per-video tag extraction) is still present and
populated on a live request — tested against a real video and returned real
tags with playabilityStatus.status == "OK".
None of that needed changing. The scraping logic in src/main.py was already
correct for current YouTube; the actor simply never shipped a working
container because its dependencies were never captured in a
requirements.txt.
- Added
requirements.txt (apify==4.0.1, requests==2.32.3,
yt-dlp==2026.3.13, beautifulsoup4==4.13.3, anthropic==1.2.0,
openai==2.28.0) covering every third-party import in src/ — requests,
yt_dlp, bs4 (used unconditionally by main.py), and anthropic/openai
(lazily imported by ai_enricher.py only when the optional, off-by-default
aiEnhancement input is turned on) — so the Dockerfile's
COPY requirements.txt ./ / pip3 install -r requirements.txt steps
succeed and the image actually builds.
- Pinned the Dockerfile's base image to
apify/actor-python:3.13 instead of
the floating apify/actor-python:latest tag, so a future upstream
base-image update cannot silently change the actor's Python/toolchain
version out from under it.
src/main.py, src/__main__.py, and src/ai_enricher.py are unmodified —
the keyword-search discovery, /about-page enrichment, keyword
match/density scoring, /videos-tab SEO-keyword extraction, and optional
AI theme-labeling logic were already correct against YouTube's current site
behavior, as verified above.