How to scrape YouTube search

Get YouTube search as JSON with one request. It costs 1 credit.

Preview
The API is in early access. Ask for a key; the examples use api.socialscrape.io.

Before you start

  • An API key, sent in the x-api-key header. Keep it on your server.
  • For this endpoint you send query. The YouTube search page lists every parameter.

Step 1: Send the request

GET /v1/youtube/search. Pick your language; each one reads the key from SOCIALSCRAPE_API_KEY.

curl
curl -G "https://api.socialscrape.io/v1/youtube/search" \
  -H "x-api-key: $SOCIALSCRAPE_API_KEY" \
  --data-urlencode 'query=lofi hip hop' \
  --data-urlencode 'type=playlist'
Python
import os
import requests

params = {
    "query": "lofi hip hop",
    "type": "playlist",
}
res = requests.get(
    "https://api.socialscrape.io/v1/youtube/search",
    headers={"x-api-key": os.environ["SOCIALSCRAPE_API_KEY"]},
    params=params,
    timeout=60,
)
body = res.json()

if body["ok"]:
    print(body["data"])
else:
    print(body["error"]["code"], body["error"]["message"])
JavaScript
const url = new URL("https://api.socialscrape.io/v1/youtube/search");
url.search = new URLSearchParams({
  query: "lofi hip hop",
  type: "playlist",
}).toString();
const res = await fetch(url, {
  headers: { "x-api-key": process.env.SOCIALSCRAPE_API_KEY },
});
const body = await res.json();

if (body.ok) {
  console.log(body.data);
} else {
  console.error(body.error.code, body.error.message);
}
TypeScript
const url = new URL("https://api.socialscrape.io/v1/youtube/search");
url.search = new URLSearchParams({
  query: "lofi hip hop",
  type: "playlist",
}).toString();
const res = await fetch(url, {
  headers: { "x-api-key": process.env.SOCIALSCRAPE_API_KEY! },
});
const body = await res.json();

if (body.ok) {
  console.log(body.data);
} else {
  console.error(body.error.code, body.error.message);
}
Go
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
)

func main() {
	q := url.Values{}
	q.Set("query", "lofi hip hop")
	q.Set("type", "playlist")
	req, _ := http.NewRequest("GET", "https://api.socialscrape.io/v1/youtube/search" + "?" + q.Encode(), nil)
	req.Header.Set("x-api-key", os.Getenv("SOCIALSCRAPE_API_KEY"))
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)
	fmt.Println(res.StatusCode, string(body))
}
PHP
<?php
$query = http_build_query([
    'query' => 'lofi hip hop',
    'type' => 'playlist',
]);
$ch = curl_init('https://api.socialscrape.io/v1/youtube/search' . "?" . $query);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["x-api-key: " . getenv("SOCIALSCRAPE_API_KEY")],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);

if ($body["ok"]) {
    print_r($body["data"]);
} else {
    echo $body["error"]["code"], " ", $body["error"]["message"], "\n";
}
Ruby
require "net/http"
require "json"

uri = URI('https://api.socialscrape.io/v1/youtube/search')
uri.query = URI.encode_www_form('query' => 'lofi hip hop', 'type' => 'playlist')
req = Net::HTTP::Get.new(uri)
req["x-api-key"] = ENV.fetch("SOCIALSCRAPE_API_KEY")
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
body = JSON.parse(res.body)

if body["ok"]
  puts body["data"]
else
  puts body["error"]["code"], body["error"]["message"]
end
Java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;

public class Main {
    public static void main(String[] args) throws Exception {
        String query = "query=" + URLEncoder.encode("lofi hip hop", StandardCharsets.UTF_8) + "&type=" + URLEncoder.encode("playlist", StandardCharsets.UTF_8);
        HttpRequest req = HttpRequest.newBuilder()
            .uri(URI.create("https://api.socialscrape.io/v1/youtube/search" + "?" + query))
            .header("x-api-key", System.getenv("SOCIALSCRAPE_API_KEY"))
            .GET()
            .build();
        HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
        System.out.println(res.statusCode() + " " + res.body());
    }
}
C#
using System;
using System.Net.Http;

var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", Environment.GetEnvironmentVariable("SOCIALSCRAPE_API_KEY"));
var query = string.Join("&", new[] {
    "query=" + Uri.EscapeDataString("lofi hip hop"),
    "type=" + Uri.EscapeDataString("playlist"),
});
var res = await client.GetAsync("https://api.socialscrape.io/v1/youtube/search" + "?" + query);
Console.WriteLine((int)res.StatusCode);
Console.WriteLine(await res.Content.ReadAsStringAsync());

Step 2: Read the response

Results are in data.items. Here is what one item gives you, from a recorded sample.

Python
for item in body["data"]["items"]:
    print(item["title"])
    print(item["id"])
JavaScript
for (const item of body.data.items) {
  console.log(item.title);
  console.log(item.id);
}
Fields printed aboveSample
{
  "title": "1 Hour of Lofi Hip Hop",
  "id": "PLF3eNE6vR-4WsBf8qnJLqBywqX39QczxJ"
}

A field the platform does not give comes back as null. The full field list is on the endpoint page.

Step 3: Get the next page

Send the same request with the cursor you got back, until has_more is false. Each page costs its own price.

Python
import os
import requests

url = "https://api.socialscrape.io/v1/youtube/search"
headers = {"x-api-key": os.environ["SOCIALSCRAPE_API_KEY"]}
params = {"query": "lofi hip hop", "type": "playlist"}

items = []
while len(items) < 100:
    body = requests.get(url, headers=headers, params=params, timeout=60).json()
    if not body["ok"]:
        raise RuntimeError(body["error"]["code"])
    items += body["data"]["items"]
    if not body["has_more"]:
        break
    params["cursor"] = body["cursor"]

print(len(items), "items")
JavaScript
const url = new URL("https://api.socialscrape.io/v1/youtube/search");
url.searchParams.set("query", "lofi hip hop");
url.searchParams.set("type", "playlist");
const headers = { "x-api-key": process.env.SOCIALSCRAPE_API_KEY };

const items = [];
while (items.length < 100) {
  const body = await (await fetch(url, { headers })).json();
  if (!body.ok) throw new Error(body.error.code);
  items.push(...body.data.items);
  if (!body.has_more) break;
  url.searchParams.set("cursor", body.cursor);
}

console.log(items.length, "items");

Step 4: Handle errors

Check ok, then branch on error.code and error.retryable, never on the message.

Python
body = res.json()
if not body["ok"]:
    err = body["error"]
    if err["retryable"]:
        ...  # wait a moment and send the same request again
    else:
        raise RuntimeError(f'{err["code"]}: {err["message"]}')
JavaScript
const body = await res.json();
if (!body.ok) {
  const { code, message, retryable } = body.error;
  if (retryable) {
    // wait a moment and send the same request again
  } else {
    throw new Error(`${code}: ${message}`);
  }
}
CodeHTTPRetryMeaning
invalid_params400noA parameter is missing, unknown or has the wrong type.
not_found404noThe platform says this profile, post or item does not exist.

Other errors, such as unauthorized or rate_limited, are on the errors page.

With the SDK

The Python and TypeScript clients wrap all of this: typed errors, retries, and cursor paging. They are in preview and not published yet.

Python
from socialscrape import SocialScrape

client = SocialScrape()  # reads SOCIALSCRAPE_API_KEY

for item in client.paginate(client.youtube.search, query="lofi hip hop", type="playlist", max_items=100):
    print(item["title"])
TypeScript
import { SocialScrape } from "socialscrape";

const client = new SocialScrape(); // reads SOCIALSCRAPE_API_KEY

for await (const item of client.paginate(client.youtube.search, { query: "lofi hip hop", type: "playlist" }, { maxItems: 100 })) {
  console.log(item.title);
}

What it costs

1 credit. A failed request costs nothing. Add max_age_days to reuse a cached result for 0 credits. Results are kept 30 days.

Next

Questions

How do I watch YouTube search for new items?

Create a monitor on this endpoint and get new items at a webhook, instead of polling. See the monitors reference.

Which language should I use?

Any. It is plain HTTP and JSON, so every language with an HTTP client works. The tabs show the same request nine ways.

Get a key.

Early access keys go to developers and agent builders first.

Get early access