Get all video titles from a YouTube Playlist page
The other night, a friend was setting up a YouTube playlist for a party. For reasons we don’t have to go into here, she also wanted a text based list of all the video titles, sorted, for easy reference.
I thought this was a good opportunity to do a fun little Javascript snippet, and show her how to open Dev Tools and run it.
TL;DR: This is what we ended up with:
console.log(Array.from(document.querySelectorAll('.ytd-playlist-video-list-renderer #video-title')).map((el) => {return el.textContent.trim()}).sort().join("\n"))
Or shorter (see $$
later):
$$('.ytd-playlist-video-list-renderer #video-title').map((el)=>{return el.textContent.trim()}).sort().join('\n')
The process / finding what we needed
Opening dev tools in my browser, I wanted to confirm that each video title in the playlist had a shared class name, or some way to use a CSS selector to get at them.
Sure enough, each does have a shared identifier. Strangely, they all have an id
of video-title
. This is really weird to me, because that’s not how that should be used, according to the html spec:
When specified on HTML elements, the id attribute value must be unique amongst all the IDs in the element’s tree…
There should only ever be a single element on the page with a given ID value. At least that’s how I learned it, and read it. :shrug:
I didn’t bother investigating why the folks at YouTube went this route instead of just using a class name. So that’s a mystery for another day, I suppose.
One thing we ran into was that for some pages, using a selector limited to that ID value gave unexpected results: sometimes it included video titles that were not in the playlist, and not visible on the page. I suspect there are just visibly hidden recommended videos or something.
So then we just needed to get a class name or selector for the actual list container, to limit our result set a bit!
One thing to note: this snippet only reads what’s currently on the page. YouTube doesn’t automatically load every video for long playlists. You may have to manually scroll the list down a few times until all of the items are loaded, before running this snippet!
Once we target the right part of the page by limiting our selector a bit, and make sure the entire list is loaded, we can run the snippet.
How it works
If you’re not used to looking at this stuff, this isn’t going to be that obvious.
It starts actually with document.querySelectorAll('.ytd-playlist-video-list-renderer #video-title')
. That selects all HTML Elements with an ID of video-title
that are found inside an element with a class of ytd-playlist-video-list-renderer
.
That’s where the actual playlist items are rendered. Again, it’s odd that there would be more than one element on the page with the same ID — but that’s what we’re working with!
Next, we need to loop over each of those and get the title text. But since querySelectorAll doesn’t return an array, we can’t just use map()
. So, first we wrap that in Array.from(), which accepts the NodeList returned from querySelectorAll
and turns it into a proper array, with methods like map, etc.
map()
returns a new array, based on passing each item into a callback function. Here, we’re using Arrow Function syntax to create that callback inline:
(el) => {return el.textContent.trim()}).sort().join("\n")
This takes an element, el
, and returns it’s textContent value (a string) having run trim()
, which cuts off any extra white space before and after the title text. (Try running without trim()
to see why this is necessary to clean up this data.
Before we return what map has for us, we’ll use “chaining” to apply sort
to the returned array. That’s how we get our list of strings in alphabetical order!
Then we continue the “chain” and apply join()
to the array. This returns a string that joins each element of the Array with whatever string we pass in — in this case, a “new line” character (\n
). So the final return value is a string, with each title on its own line.
Possible Improvements
- Not having to scroll the playlist to load all the tracks would be nice. Automating that part would be cool.
- Making a plug-in or snippet, so less-savvy folks don’t have to open Dev Tools to run this.
- Can we use clipboard API’s to automatically copy the list?
- For sorting, I noticed it might be nice to disregard things like quotation marks and punctuation.
- Use
$$
instead ofquerySelectorAll
. I didn’t know about this until this week! It returns an Array, so things likemap()
andforEach()
are ready to go without needing a conversion. I found this works in Chrome and Firefox (and probably others).