It seems Youtube has gotten rid of /channel/XXXX urls on their page, its now /c/username? with username NOT really being a "username". For example
https://www.youtube.com/c/lukemiani
Running a lookup via
https://www.googleapis.com/youtube/v3/channels?part=snippet&forUsername=lukemiani&key=...
returns no results.
I've got a bunch of non-technical users who've been trained to look for /channel/x or /user/x and input the correct thing into my app. Now that /channel is gone how do I (or they) translate /c/x to a channel id?
I'm looking for an API solution, not a view source and reverse engineer code solution.
According to the official support staff, a given channel may have associated an URL of form:
https://www.youtube.com/c/CUSTOM_NAME.
In such a case, the respective channel's customUrl property is CUSTOM_NAME.
Now, your problem may be reformulated as follows:
Given a CUSTOM_NAME for which the URL above points to an existing channel, is there a procedure that is able to produce -- by making use of YouTube Data API -- that channel's ID, such that the respective procedure to be DTOS-compliant (i.e. the procedure works by not scraping the HTML text obtained from the respective custom URL)?
The short answer to the above question is no, there's none. (Please have a look at my answer and the attached comments I gave recently to a similar question).
The longer answer would be the following: yes, it can be imagined an algorithm that solves the problem, but only partially (since there's no guarantee that it'll always give positive results).
Here is the algorithm:
Call the Search.list API endpoint with the following parameters:
q=CUSTOM_NAME,
type=channel, and
maxResults=10.
Extract from the result set obtained the channel IDs (these IDs are located at items[].id.channelId);
For each channel ID in the list obtained at step 2:
Invoke Channels.list API endpoint for to obtain the channel's associated customUrl property (if any);
If the obtained customUrl is equal with CUSTOM_NAME, then stop the algorithm yielding the current channel ID; otherwise, continue executing the current loop;
Stop the algorithm by yielding channel ID not found.
Due to the fuzzy nature of the result sets provided by the Search.list endpoint, one cannot exclude the possibility that there could actually exist custom URLs (i.e. URLs of the form above that are pointing to existing channels) for which this algorithm is not able to yield the ID of the associated channel.
A final note: the Channels.list endpoint accepts its id parameter to be a comma-separated list of channel IDs. Therefore, one may easily modify the algorithm above such that instead of N invocations (N <= 10) of Channels.list endpoint to have only one.
An implementation of the algorithm above in Python language, using Google's APIs Client Library for Python:
def find_channel_by_custom_url(
youtube, custom_url, max_results = 10):
resp = youtube.search().list(
q = custom_url,
part = 'id',
type = 'channel',
fields = 'items(id(kind,channelId))',
maxResults = max_results
).execute()
assert len(resp['items']) <= max_results
ch = []
for item in resp['items']:
assert item['id']['kind'] == 'youtube#channel'
ch.append(item['id']['channelId'])
if not len(ch):
return None
resp = youtube.channels().list(
id = ','.join(ch),
part = 'id,snippet',
fields = 'items(id,snippet(customUrl))',
maxResults = len(ch)
).execute()
assert len(resp['items']) <= len(ch)
for item in resp['items']:
url = item['snippet'].get('customUrl')
if url is not None and \
caseless_equal(url, custom_url):
assert item['id'] is not None
return item['id']
return None
where the function caseless_equal used above is due to this SO answer.
I posted here a simple Python3 script that encompasses the function find_channel_by_custom_url above into a standalone program. Your custom URL applied to this script yields the expected result:
$ python3 youtube-search.py \
--custom-url lukemiani \
--app-key ...
UC3c8H4Tlnm5M6pXsVMGnmNg
$ python3 youtube-search.py \
--user-name lukemiani \
--app-key ...
youtube-search.py: error: user name "lukemiani": no associated channel found
Note that you have to pass to this script your application key as argument to the command line option --app-key (use --help for brief help info).
You can do that in python, or with any http request library, by requesting the link and parsing the response for the channel ID. The channel id is in the canonical link tag that handles the redirection:
import requests
import re
url = "https://www.youtube.com/shroud"
r = requests.get(url, allow_redirects=True)
print(re.search(r'(?<=<link rel="canonical" href="https:\/\/www\.youtube\.com\/channel\/)(-?\w+)*(?=">)', r.text).group(0))
# Returns UCoz3Kpu5lv-ALhR4h9bDvcw
Related
It seems Youtube has gotten rid of /channel/XXXX urls on their page, its now /c/username? with username NOT really being a "username". For example
https://www.youtube.com/c/lukemiani
Running a lookup via
https://www.googleapis.com/youtube/v3/channels?part=snippet&forUsername=lukemiani&key=...
returns no results.
I've got a bunch of non-technical users who've been trained to look for /channel/x or /user/x and input the correct thing into my app. Now that /channel is gone how do I (or they) translate /c/x to a channel id?
I'm looking for an API solution, not a view source and reverse engineer code solution.
According to the official support staff, a given channel may have associated an URL of form:
https://www.youtube.com/c/CUSTOM_NAME.
In such a case, the respective channel's customUrl property is CUSTOM_NAME.
Now, your problem may be reformulated as follows:
Given a CUSTOM_NAME for which the URL above points to an existing channel, is there a procedure that is able to produce -- by making use of YouTube Data API -- that channel's ID, such that the respective procedure to be DTOS-compliant (i.e. the procedure works by not scraping the HTML text obtained from the respective custom URL)?
The short answer to the above question is no, there's none. (Please have a look at my answer and the attached comments I gave recently to a similar question).
The longer answer would be the following: yes, it can be imagined an algorithm that solves the problem, but only partially (since there's no guarantee that it'll always give positive results).
Here is the algorithm:
Call the Search.list API endpoint with the following parameters:
q=CUSTOM_NAME,
type=channel, and
maxResults=10.
Extract from the result set obtained the channel IDs (these IDs are located at items[].id.channelId);
For each channel ID in the list obtained at step 2:
Invoke Channels.list API endpoint for to obtain the channel's associated customUrl property (if any);
If the obtained customUrl is equal with CUSTOM_NAME, then stop the algorithm yielding the current channel ID; otherwise, continue executing the current loop;
Stop the algorithm by yielding channel ID not found.
Due to the fuzzy nature of the result sets provided by the Search.list endpoint, one cannot exclude the possibility that there could actually exist custom URLs (i.e. URLs of the form above that are pointing to existing channels) for which this algorithm is not able to yield the ID of the associated channel.
A final note: the Channels.list endpoint accepts its id parameter to be a comma-separated list of channel IDs. Therefore, one may easily modify the algorithm above such that instead of N invocations (N <= 10) of Channels.list endpoint to have only one.
An implementation of the algorithm above in Python language, using Google's APIs Client Library for Python:
def find_channel_by_custom_url(
youtube, custom_url, max_results = 10):
resp = youtube.search().list(
q = custom_url,
part = 'id',
type = 'channel',
fields = 'items(id(kind,channelId))',
maxResults = max_results
).execute()
assert len(resp['items']) <= max_results
ch = []
for item in resp['items']:
assert item['id']['kind'] == 'youtube#channel'
ch.append(item['id']['channelId'])
if not len(ch):
return None
resp = youtube.channels().list(
id = ','.join(ch),
part = 'id,snippet',
fields = 'items(id,snippet(customUrl))',
maxResults = len(ch)
).execute()
assert len(resp['items']) <= len(ch)
for item in resp['items']:
url = item['snippet'].get('customUrl')
if url is not None and \
caseless_equal(url, custom_url):
assert item['id'] is not None
return item['id']
return None
where the function caseless_equal used above is due to this SO answer.
I posted here a simple Python3 script that encompasses the function find_channel_by_custom_url above into a standalone program. Your custom URL applied to this script yields the expected result:
$ python3 youtube-search.py \
--custom-url lukemiani \
--app-key ...
UC3c8H4Tlnm5M6pXsVMGnmNg
$ python3 youtube-search.py \
--user-name lukemiani \
--app-key ...
youtube-search.py: error: user name "lukemiani": no associated channel found
Note that you have to pass to this script your application key as argument to the command line option --app-key (use --help for brief help info).
You can do that in python, or with any http request library, by requesting the link and parsing the response for the channel ID. The channel id is in the canonical link tag that handles the redirection:
import requests
import re
url = "https://www.youtube.com/shroud"
r = requests.get(url, allow_redirects=True)
print(re.search(r'(?<=<link rel="canonical" href="https:\/\/www\.youtube\.com\/channel\/)(-?\w+)*(?=">)', r.text).group(0))
# Returns UCoz3Kpu5lv-ALhR4h9bDvcw
I call YT.Channel API with a list of Ids and I get an error:
GoogleJsonResponseException: API call to youtube.channels.list failed with error: Request contains an invalid argument.
YouTube.Channels.list('id, snippet, statistics', {
id: resultsParentChannelIdsPerPage.join()
}).getItems()
id = UC5JU3rLOMvB7ZIXI2oi1ubg,UC4mKtxAtWQgmkFC6S6siNVg,UC-4hR9ralZoAW15tAwSbJ_A,UCvdwiASqQrIIozQcVAXvRXw,UCUIKx2bIjRcBWyIa3AQGSCg,UClkJAMCknefAzrYfpk1_0Nw,UCsDm3gAWuDRDLMjZLoUxOuA,UC-OkSUXpLrS2eBpztAJ78wg,UCb6RivygioXGs0icq4McBdg,UCmAf6BdYS-5QmXxBuJZgNpQ,UC6rDrJjome8e_iBQI1MLeZg,UCJ0lYZCucWTPc_IfE2vZNTg,UCVy5saLw3psF5laPWhahGHA,UCXskq-my9ltmT6SpdL7b_cw,UCpctmQs9AEwiIZgTcWSV0mg,UCv4ZlVO5-z-uVIm1Y6ol-DA,UCPtQnd-8OBSzvqnQVKaA_8g,UC_uQYR3lHS36jkBRqdvQIFw,UCoJuf64ajKTOZ6mX30zulKg,UCM-GJfgjXFajHll_uGeZPBQ,UC2ojGYd_fg8oU71beXUMW9g,UCT_dVMJ2eSGKugeXnMZcbfQ,UC6PHRps6wOfBJKoDxEI37Ew,UC2Ra3RK4FW38skAoYEtXJbQ,UC2u7_TuItL4on_9SuwJhO_A,UCt-gKqNg_k2bb-bSe7y3z4g,UC2UotqDGeOMUl-gQG3t9sKw,UCdbVBcrKBtu8oUcp4JuLpvw,UCKHpDyswx5VskURci4FPqUA,UCiNHdTU2jYRbUf1cbMCxdaQ,UC3osMa_MdseDVrXlbZFSZDw,UCbnwaC-Tbi1duuDbjfV-Thg,UCBOrTOryVc2JKsNOu7_wO6g,UCM8UoJ4z4NMvp5pwYQjx8uQ,UCnU2sysDk58MuMQHxqgDd7w,UCuldUFBFddgeVN6HJ-yIBjA,UCsEgNI5S2Vdy3Q7LizK2OSA,UClN9w3sTTs6lpntoeHyYvSA,UCgZlOI3AqanZW0tjCSJCKfA,UCqS9WhAUJ-6PiC-8mZ0rDSQ,UCBH5Es0HeCBBlHvWG9c2ipA,UCpPgyVE8TKJr-MnpB44Au4w,UCJxgQl6TU8FGmMzG24xxYFg,UCpe9hHSEkTs5BD6Ufi0-XuA,UCrk1G4da0dsnLsPjsov5fTA,UCtF629dMzswzAupMUEO-bPw,UCsRcjJkUmU_Ny5D9pn5ZGDw,UC5Wm0eXXRAA6Pcjlq8-EaLw,UCOakkksW_nWRDkPl43VzAQA,UC4JhVBsy-Yfr3dQy7G1EYuQ,UCfS8vOYmleKZU8zNjzsZ6qg,UCPWsJzhhZSPlfQ7DOjSA4Fg,UCPKKa8RgdfoMRBkyt8BhIqA,UCa3bfoq50eqErBDgq0p2cQA,UCyH05wKVW-96Lz2mqhGnzVw,UCiXjl5HwqmuCjr5ZmW00csQ,UCA9tlIHeg-k8z3PUpozkUmA,UC_Bc8M2p5fpYqlfWvbVmw2g,UCnD86huZnRWKDMZISbQx6kg,UCtRn5U4Uz2vbREF0qD0TLuA
I didn't get this in previous similar runs. What has changed?
API explorer
Nothing has changed, as far as I know. Fact is that -- with the exception of CommentThreads.list, Comments.list and Members.list endpoints -- all YouTube Data API endpoints that are providing result sets would not return result sets of more than 50 items.
Therefore, when using the Channels.list endpoint's request parameter id, you'll have to limit yourself to pass to it a comma-separated list of channel IDs of at most 50 elements.
Assumption / What I want to achieve
I want to use YouTube Data API V3 to get the video ID without any omissions, and find out if the cause of the trouble is in the code or in the video settings of YouTube (API side).
Problem
The following code is used to get the video information from YouTube Data API, but the number of IDs I got did not match the number of videos that are actually posted.
from apiclient.discovery
import build
id = "UCD-miitqNY3nyukJ4Fnf4_A" #sampleID
token_check = None
nextPageToken = None
id_info = []
while True:
if token_check != None:
nextPageToken = token_check
Search_Video = youtube.search().list(
part = "id",
channelId = id,
maxResults = 50,
order = 'date',
safeSearch = "none",
pageToken = nextPageToken
).execute()
for ID_check in Search_Video.get("items", []):
if ID_check["id"]["kind"] == "youtube#video":
id_info.append(ID_check["id"]["videoId"])
try:
token_check = Search_Video["nextPageToken"]
except:
print(len(id_info)) #check number of IDs
break
I also used the YouTube Data API function to get the videoCount information of the channel, and noticed that the value of videoCount did not match the number of IDs obtained by the code above, which is why I posted this.
According to channels() API, this channel have 440 videos, but the above code gets only 412 videos (at 10:30 a.m. JST).
Supplemental Information
・Python 3.9.0
・YouTube Data API v3
You have to acknowledge that the Search.list API endpoint does not have a crisp behavior. That means you should not expect precise results from it. Google does not document this behavior as such, but this forum has many posts from users experiencing that.
If you want to obtain all the IDs of videos uploaded by a given channel then you should employ the following two-step procedure:
Step 1: Obtain the ID of the Uploads Playlist of a Channel.
Invoke the Channels.list API endpoint, queried with its request parameter id set to the ID of the channel of your interest (or, otherwise, with its request parameter mine set to true) for to obtain that channel's uploads playlist ID, contentDetails.relatedPlaylists.uploads.
def get_channel_uploads_playlist_id(youtube, channel_id):
response = youtube.channels().list(
fields = 'items/contentDetails/relatedPlaylists/uploads',
part = 'contentDetails',
id = channel_id,
maxResults = 1
).execute()
items = response.get('items')
if items:
return items[0] \
['contentDetails'] \
['relatedPlaylists'] \
.get('uploads')
else:
return None
Do note that the function get_channel_uploads_playlist_id should only be called once for to obtain the uploads playlist
ID of a given channel; subsequently use that ID as many times as needed.
Step 2: Retrieve All IDs of Videos of a Playlist.
Invoke the PlaylistItems.list API endpoint, queried with its request parameter playlistId set to the ID obtained from get_channel_uploads_playlist_id:
def get_playlist_video_ids(youtube, playlist_id):
request = youtube.playlistItems().list(
fields = 'nextPageToken,items/snippet/resourceId',
playlistId = playlist_id,
part = 'snippet',
maxResults = 50
)
videos = []
is_video = lambda item: \
item['snippet']['resourceId']['kind'] == 'youtube#video'
video_id = lambda item: \
item['snippet']['resourceId']['videoId']
while request:
response = request.execute()
items = response.get('items', [])
assert len(items) <= 50
videos.extend(map(video_id, filter(is_video, items)))
request = youtube.playlistItems().list_next(
request, response)
return videos
Do note that, when using the Google's APIs Client Library for Python (as you do), API result set pagination is trivially simple: just use the list_next method of the Python API object corresponding to the respective paginated API endpoint (as was shown above):
request = API_OBJECT.list(...)
while request:
response = request.execute()
...
request = API_OBJECT.list_next(
request, response)
Also note that above I used twice the fields request parameter. This is good practice: ask from the API only the info that is of actual use.
Yet an important note: the PlaylistItems.list endpoint would not return items that correspond to private videos of a channel when invoked with an API key. This happens when your youtube object was constructed by calling the function apiclient.discovery.build upon passing to it the parameter developerKey.
PlaylistItems.list returns items corresponding to private videos only to the channel owner. This happens when the youtube object is constructed by calling the function apiclient.discovery.build upon passing to it the parameter credentials and if credentials refer to the channel that owns the respective playlist.
An additional important note: according to Google staff, there's an upper 20000 limit set by design for the number of items returned via PlaylistItems.list endpoint when queried for a given channel's uploads playlist. This is unfortunate, but a fact.
Using this code allows me to search for YouTube channels and return their statistics; for some reason, however, only about half the channels return any results.
For example, if I use this link to test the API, you can see that I searched for Animal Planet, and that yields no results. However, if we go to YouTube's website and search for the same thing, we get the channel as needed.
Why does this happen and how can this be fixed?
This is the code I used:
#commands.command()
async def yt_test(self, ctx, *, search):
api_key = '...'
youtube = build('youtube', 'v3', developerKey=api_key)
request = youtube.channels().list(
part="snippet,statistics",
forUsername=search,
maxResults=1
)
response = request.execute()
try:
for item in response['items']:
link = item['id']
thumb_image = item['snippet']['thumbnails']['default']['url']
views = "{:,}".format(int(item['statistics']['viewCount']))
subs = "{:,}".format(int(item['statistics']['subscriberCount']))
vidCount = "{:,}".format(int(item['statistics']['videoCount']))
except:
Notfound = discord.Embed(description= "Sorry, but this Channel was not located, check spelling" , color=0xfc0328)
await ctx.send(embed=Notfound)
finalresult = discord.Embed(title = search, url='https://www.youtube.com/channel/' + link,
description= '**Statistics**\nViews: ' + views + '\nSubscribers: ' + subs + "\nVideos: " + vidCount, color=0x00ff00)
finalresult.set_thumbnail(url = thumb_image)
await ctx.send(embed=finalresult)
print(f"{ctx.author} looked up the profile of {search} --- in #{ctx.guild.name}")
You have to acknowledge that invoking the Channels.list API endpoint, by passing to it the parameter forUsername, is by no means the same thing as going to the YouTube's Web UI for to apply a manual search. For these things to come to light, do bear with me for a little while...
According to the official docs, the Channels.list endpoint invoked with forUsername=... produces the meta-data associated to the channel of which username is given as argument to the parameter forUsername:
forUsername (string)
The forUsername parameter specifies a YouTube username, thereby requesting the channel associated with that username.
According to an official Google staff post from 2013-07-11, it's not a requisite that every channel have attached an username. Hence, is perfectly possible for Channels.list endpoint to return no channel at all, if the argument of forUsername does not represent a YouTube username.
If you use one of my public (MIT licensed) Python 3 script -- youtube-search.py --, you'll see that it returns nothing for your queried username Animal Planet:
$ python3 youtube-search.py --user-name "Animal Planet"
youtube-search.py: error: user name "Animal Planet": no associated channel found
But if you invoke that script as follows:
$ python3 youtube-search.py --search-term "Animal Planet" --type channel
UCkEBDbzLyH-LbB2FgMoSMaQ: Animal Planet
UCuTSm59-_kap6J7Se-orUVA: Animal Planet Latinoamérica
UCpvajaPSWvFlyl83xvzs65A: animalplanet românia
UCgyOEJZJLPKclqrDyUkZ6Ag: Animal planet 1
UCypzlOdJyyOR2NhKv0o3zig: 動物星球頻道/ Animal Planet Taiwan
UCmMm-p51c14XJ1p294faCmA: Animal Planet Brasil
UCejVe2sNPjjvfCXg35q_EQQ: Animal Planet Videos
UClQLf_zw2A_nRaB45HWTbtA: Your Animal Planet
UChQEdt9dBiCkcCch6LyrjtA: Cute Animal Planet
UCRIS8Y1kfrFvFF_6vt6_3Cg: Animal Planet Videos
you'll see pretty much the same output as the YouTube's Web UI will show when queried for the same search term and filtered for TYPE being Channel.
Do note that your own results (the one obtained from youtube-search.py and the other provided by the YouTube Web UI) may very likely differ from the above ones; this is so because Google searches are tailored for the user that initiates the query.
The explanation behind the matching results sets obtained from youtube-search.py and YouTube Web UI is the following:
The API endpoint that corresponds to YouTube's own search feature accessible via its Web UI is none other than Search.list.
Do convince yourself about this claim by reading the code of youtube-search.py: when invoking it with --search-term "Animal Planet" --type channel, that script executes the function Service.search_term:
class Service:
...
def search_term(self, term, type = None):
resp = self.youtube.search().list(
q = term,
type = type,
part = 'id,snippet',
fields = 'items(id,snippet(title))',
maxResults = self.max_results
).execute()
...
with term equal to the string Animal Planet and type equal to the string channel. Consequently, this script is calling for the Search.list endpoint, passing to it the parameters q and type as mentioned.
I'm working on a website to load multiple youtube channels live streams. At first i was trying to figure out a way to do this without utilizing youtube's api but have decided to give in.
To find whether a channel is live streaming and to get the live stream links I've been using:
https://www.googleapis.com/youtube/v3/search?part=snippet&channelId={CHANNEL_ID}&eventType=live&maxResults=10&type=video&key={API_KEY}
However with the minimum quota being 10000 and each search being worth 100, Im only able to do about 100 searches before I exceed my quota limit which doesn't help at all. I ended up exceeding the quota limit in about 10 minutes. :(
Does anyone know of a better way to figure out if a channel is currently live streaming and what the live stream links are, using as minimal quota points as possible?
I want to reload youtube data for each user every 3 minutes, save it into a database, and display the information using my own api to save server resources as well as quota points.
Hopefully someone has a good solution to this problem!
If nothing can be done about links just determining if the user is live without using 100 quota points each time would be a big help.
Since the question only specified that Search API quotas should not be used in finding out if the channel is streaming, I thought I would share a sort of work-around method. It might require a bit more work than a simple API call, but it reduces API quota use to practically nothing:
I used a simple Perl GET request to retrieve a Youtube channel's main page. Several unique elements are found in the HTML of a channel page that is streaming live:
The number of live viewers tag, e.g. <li>753 watching</li>. The LIVE NOW
badge tag: <span class="yt-badge yt-badge-live" >Live now</span>.
To ascertain whether a channel is currently streaming live requires a simple match to see if the unique HTML tag is contained in the GET request results. Something like: if ($get_results =~ /$unique_html/) (Perl). Then, an API call can be made only to a channel ID that is actually streaming, in order to obtain the video ID of the stream.
The advantage of this is that you already know the channel is streaming, instead of using thousands of quota points to find out. My test script successfully identifies whether a channel is streaming, by looking in the HTML code for: <span class="yt-badge yt-badge-live" > (note the weird extra spaces in the code from Youtube).
I don't know what language OP is using, or I would help with a basic GET request in that language. I used Perl, and included browser headers, User Agent and cookies, to look like a normal computer visit.
Youtube's robots.txt doesn't seem to forbid crawling a channel's main page, only the community page of a channel.
Let me know what you think about the pros and cons of this method, and please comment with what might be improved rather than disliking if you find a flaw. Thanks, happy coding!
2020 UPDATE
The yt-badge-live seems to have been deprecated, it no longer reliably shows whether the channel is streaming. Instead, I now check the HTML for this string:
{"text":" watching"}
If I get a match, it means the page is streaming. (Non-streaming channels don't contain this string.) Again, note the weird extra whitespace. I also escape all the quotation marks since I'm using Perl.
Here are my two suggestions:
Check my answer where I explain how you can check how retrieve videos from channels who are livesrteaming.
Another option could be use the following URL and somehow make request(s) each time for check if there's a livestreaming.
https://www.youtube.com/channel/<CHANNEL_ID>/live
Where CHANNEL_ID is the channel id you want check if that channel is livestreaming1.
1 Just notice that maybe the URL wont work in all channels (and that depends of the channel itself).
For example, if you check the channel_id UC7_YxT-KID8kRbqZo7MyscQ - link to this channel livestreaming - https://www.youtube.com/channel/UC4nprx9Vd84-ly7N-1Ce6Og/live, this channel will show if he is livestreaming, but, with his channel id UC4nprx9Vd84-ly7N-1Ce6Og - link to this channel livestreaming -, it will show his main page instead.
Adding to the answer by Bman70, I tried eliminating the need of making a costly search request after knowing that the channel is streaming live. I did this using two indicators in the HTML response from channels page who are streaming live.
function findLiveStreamVideoId(channelId, cb){
$.ajax({
url: 'https://www.youtube.com/channel/'+channelId,
type: "GET",
headers: {
'Access-Control-Allow-Origin': '*',
'Accept-Language': 'en-US, en;q=0.5'
}}).done(function(resp) {
//one method to find live video
let n = resp.search(/\{"videoId[\sA-Za-z0-9:"\{\}\]\[,\-_]+BADGE_STYLE_TYPE_LIVE_NOW/i);
//If found
if(n>=0){
let videoId = resp.slice(n+1, resp.indexOf("}",n)-1).split("\":\"")[1]
return cb(videoId);
}
//If not found, then try another method to find live video
n = resp.search(/https:\/\/i.ytimg.com\/vi\/[A-Za-z0-9\-_]+\/hqdefault_live.jpg/i);
if (n >= 0){
let videoId = resp.slice(n,resp.indexOf(".jpg",n)-1).split("/")[4]
return cb(videoId);
}
//No streams found
return cb(null, "No live streams found");
}).fail(function() {
return cb(null, "CORS Request blocked");
});
}
However, there's a tradeoff. This method confuses a recently ended stream with currently live streams. A workaround for this issue is to get status of the videoId returned from Youtube API (costs a single unit from your quota).
I found youtube API to be very restrictive given the cost of search operation. Apparently the accepted answer did not work for me as I found the string on non live streams as well. Web scraping with aiohttp and beautifulsoup was not an option since the better indicators required javascript support. Hence I turned to selenium. I looked for the css selector
#info-text
and then search for the string Started streaming or with watching now in it.
To reduce load on my tiny server that would have otherwise required lot more resources, I moved this test of functionality to a heroku dyno with a small flask app.
# import flask dependencies
import os
from flask import Flask, request, make_response, jsonify
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
base = "https://www.youtube.com/watch?v={0}"
delay = 3
# initialize the flask app
app = Flask(__name__)
# default route
#app.route("/")
def index():
return "Hello World!"
# create a route for webhook
#app.route("/islive", methods=["GET", "POST"])
def is_live():
chrome_options = Options()
chrome_options.binary_location = os.environ.get('GOOGLE_CHROME_BIN')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
chrome_options.add_argument('--headless')
chrome_options.add_argument('--remote-debugging-port=9222')
driver = webdriver.Chrome(executable_path=os.environ.get('CHROMEDRIVER_PATH'), chrome_options=chrome_options)
url = request.args.get("url")
if "youtube.com" in url:
video_id = url.split("?v=")[-1]
else:
video_id = url
url = base.format(url)
print(url)
response = { "url": url, "is_live": False, "ok": False, "video_id": video_id }
driver.get(url)
try:
element = WebDriverWait(driver, delay).until(EC.presence_of_element_located((By.CSS_SELECTOR, "#info-text")))
result = element.text.lower().find("Started streaming".lower())
if result != -1:
response["is_live"] = True
else:
result = element.text.lower().find("watching now".lower())
if result != -1:
response["is_live"] = True
response["ok"] = True
return jsonify(response)
except Exception as e:
print(e)
return jsonify(response)
finally:
driver.close()
# run the app
if __name__ == "__main__":
app.run()
You'll however need to add the following buildpacks in settings
https://github.com/heroku/heroku-buildpack-google-chrome
https://github.com/heroku/heroku-buildpack-chromedriver
https://github.com/heroku/heroku-buildpack-python
Set the following Config Vars in settings
CHROMEDRIVER_PATH=/app/.chromedriver/bin/chromedriver
GOOGLE_CHROME_BIN=/app/.apt/usr/bin/google-chrome
You can find supported python runtime here but anything below python 3.9 should be good since selenium had problems with improper use of is operator
I hope youtube will provide better alternatives than workarounds.
I know this is a old thread, but i thought i share my way of checking to for example grab the status code to use in an app.
This is for a single Channel, but you could easly do a foreach with it.
<?php
#####
$ytchannelID = "UCd0BTXriKLvOs1ANx3puZ3Q";
#####
$ytliveurl = "https://www.youtube.com/channel/".$ytchannelID."/live";
$ytchannelLIVE = '{"text":" watching now"}';
$contents = file_get_contents($ytliveurl);
if ( strpos($contents, $ytchannelLIVE) !== false ){http_response_code(200);} else {http_response_code(201);}
unset($ytliveurl);
?>
Adding onto the other answers here, I use a GET request to https://www.youtube.com/c/<CHANNEL_NAME>/live and then search for "isLive":true (rather than {"text":" watching"})