This page is a pipeline. pandas pulls Trends API, concatenates keywords, and writes parquet or a warehouse table. It is not a notebook and not the languages page that stops at one DataFrame. POST https://api.trendsapi.ai/api. Parse the string body before DataFrame. Schedule the job with Airflow or cron. Fields: the API reference. Caps: pricing.

Pull function returns records, not the envelope

import json, os, requests, pandas as pd

def series_frame(keywords, source="google search"):
    session = requests.Session()
    session.headers.update({
        "Authorization": f"Bearer {os.environ['TRENDSAPI_API_KEY']}",
        "Content-Type": "application/json",
    })
    frames = []
    for keyword in keywords:
        resp = session.post(
            "https://api.trendsapi.ai/api",
            json={"mode": "get_time_series", "source": source, "keyword": keyword},
            timeout=60,
        )
        resp.raise_for_status()
        points = json.loads(resp.json()["body"])
        frames.append(pd.DataFrame.from_records(points))
    return pd.concat(frames, ignore_index=True)

from_records on the inner array yields date, value, keyword, source. pd.read_json on the envelope does not. The parse rule is on parse the body.

Reuse one Session for the list. Sleep with jitter on 429. Do not map a process pool that stampedes the monthly cap. Parse date with to_datetime. A missing volume key is not a zero score. That split is on volume versus score.

Two frames, then a write

weekly = series_frame(["bitcoin", "ethereum"])
weekly.to_parquet("s3://bucket/series/part.parquet")

A growth job is a separate function that reads results and DataFrame.from_records. Do not merge weekly value onto growth without renaming. 0-100 and a signed percent are different columns. Mode shapes sit on get_time_series and get_growth.

Warehouse DDL is on BigQuery. pandas can to_gbq after the concat. Retry the load from parquet if the insert fails. Do not POST the same keyword again for a load error.