Marketing teams, influencer managers and brand analysts ask YouTube similar questions. How are our videos doing against a competitor's? Which creators in a market get consistent views? What does our channel page show to viewers in another country? Python answers most of these in one of two ways. The official YouTube Data API returns structured statistics and is where to start. For what the API cannot show, chiefly how a page looks from a particular region, a Selenium-driven browser routed through proxies fills the gap.
API or browser: decide per question
| Question | Route |
|---|---|
| Views, likes and comment counts for specific videos | YouTube Data API |
| Videos that match a topic or keyword | YouTube Data API search |
| What a watch page shows to viewers in a given market | Selenium through a proxy in that market |
YouTube's rules come first
Most YouTube data should come through the official API, which is the sanctioned route; the robots.txt file and Terms of Service draw the lines for everything else. In practice that means public, non-personal information only, staying inside the daily API quota, nothing behind a login, and no attempts to defeat bot protection. Caching and reasonable request rates complete the list, and they are also what keeps your access working over time.
First choice: the YouTube Data API
Parsing HTML is fragile; the API is not. To begin, create a key in the Google Cloud Console, switch on "YouTube Data API v3" for the project, and add Google's client library:
pip install google-api-python-client
This function returns the headline statistics for one video ID:
from googleapiclient.discovery import build
API_KEY = "YOUR_API_KEY"
youtube = build("youtube", "v3", developerKey=API_KEY)
def get_video_stats(video_id):
request = youtube.videos().list(
part="snippet,statistics",
id=video_id,
)
response = request.execute()
item = response["items"][0]
return {
"title": item["snippet"]["title"],
"channel": item["snippet"]["channelTitle"],
"views": item["statistics"].get("viewCount"),
"likes": item["statistics"].get("likeCount"),
"comments": item["statistics"].get("commentCount"),
}
print(get_video_stats("dQw4w9WgXcQ"))
Searching and paging
One search call returns at most fifty results. To collect more, pass each response's nextPageToken into the next call, and finish when you have what you need or no token comes back:
def search_videos(query, max_pages=2):
results = []
page_token = None
for _ in range(max_pages):
resp = youtube.search().list(
part="snippet",
q=query,
type="video",
maxResults=50,
pageToken=page_token,
).execute()
for item in resp["items"]:
results.append({
"video_id": item["id"]["videoId"],
"title": item["snippet"]["title"],
})
page_token = resp.get("nextPageToken")
if not page_token:
break
return results
Fallback: a browser for regional views
What viewers in a particular region actually see on a page is outside the API's reach. For that you need the page itself, and because YouTube builds its pages with JavaScript, a plain HTTP request comes back nearly empty. Selenium solves this by driving a real Chrome browser. Install it, along with a current Chrome and a matching driver:
pip install selenium
A proxy in the market you report on
YouTube tailors what it shows to the viewer's location and watches for automated traffic, so many page loads from one IP invite CAPTCHAs. Choose a proxy in the country you are reporting on; for region-specific results, that choice is the whole point of the exercise. Residential proxies show the page as household viewers in that market receive it, and our premium proxies add stability for long sessions. The proxies page lists every type.
Authenticated proxies with selenium-wire
Chrome's own proxy switch accepts a host and port but no login, which is a problem with authenticated proxies. The selenium-wire package fills that gap by handling the login itself; confirm it works with your Selenium version first:
pip install selenium-wire
from seleniumwire import webdriver
from selenium.webdriver.common.by import By
import time
seleniumwire_options = {
"proxy": {
"http": "http://USER:PASS@HOST:PORT",
"https": "http://USER:PASS@HOST:PORT",
"no_proxy": "localhost,127.0.0.1",
}
}
options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument("--lang=en-US")
driver = webdriver.Chrome(
seleniumwire_options=seleniumwire_options,
options=options,
)
Pulling the title and channel from a watch page
Once Chrome is running, load the watch URL and allow a few seconds for its scripts to draw the page before reading anything. YouTube revises its markup now and then, so each lookup is wrapped to return None rather than crash the run:
def scrape_watch_page(video_id):
driver.get(f"https://www.youtube.com/watch?v={video_id}")
time.sleep(5) # let dynamic content load
try:
title = driver.find_element(
By.CSS_SELECTOR, "h1.ytd-watch-metadata"
).text
except Exception:
title = None
try:
channel = driver.find_element(
By.CSS_SELECTOR, "ytd-channel-name #text a"
).text
except Exception:
channel = None
return {"video_id": video_id, "title": title, "channel": channel}
data = scrape_watch_page("dQw4w9WgXcQ")
print(data)
driver.quit()
Operating checklist
- Use the API whenever it holds the data, since it is quicker, tidier and governed by a quota you can plan around.
- Vary the waits between page loads in Selenium instead of repeating one fixed pause.
- Give each browser session its own residential IP on bigger jobs, and look up the exit in our my IP tool before trusting the output.
- End every run with
driver.quit()so Chrome releases its memory, and use headless mode on servers. - Put each endpoint through the proxy checker before the job starts, so a later failure has one less suspect.
Split the work this way, statistics from the API and regional views from Selenium, and YouTube reporting stays dependable without a fight with the platform. Keep request rates sensible, respect the Terms of Service and leave geography to the proxies; the analysis on top is yours to design.