# Google Trends Scraper: Bulk Keyword Research (`whoareyouanas/google-trends-batch-research`) Actor

Run bulk Google Trends keyword research across countries and date windows. Get historical interest, regional data and top/rising queries in CSV/JSON reports, with quality notes and resumable batches. Use the Apify API or n8n for agency reporting.

- **URL**: https://apify.com/whoareyouanas/google-trends-batch-research.md
- **Developed by:** [Anas Nadeem](https://apify.com/whoareyouanas) (community)
- **Categories:** SEO tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 research-analysis-completes

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

**Google Trends Scraper: Bulk Keyword Research** collects Google Trends data for keyword lists across countries and date windows. Get historical interest over time, interest by region, and top/rising related queries in CSV/JSON analyst reports. Run it in Apify Console or through the Apify API, and resume interrupted batches with an explicit outcome for every planned analysis.

**Google Trends values are relative interest, not absolute search volume.** Independent requests have independent scales. Use an explicit comparison group when you need a native shared timeline scale.

### What can this Google Trends scraper do?

- **Agency SEO research:** expand a keyword list across countries and periods, with one report row per planned keyword and analysis.
- **Content planning:** inspect seasonal interest and rising related searches as leads for human review.
- **Brand comparisons:** compare two to five terms together within one native source scale.
- **Regional research:** inspect where relative interest is concentrated, with the source unit and query context retained.
- **Recurring reporting:** download CSV, JSON or NDJSON, use the included n8n template, or build your own API integration.
- **Batch recovery:** reuse captured work after interruptions and keep partial, failed and unstarted analyses visible.

The input accepts up to 200 keywords, ten geographies and four periods, subject to a maximum of **1,000 expanded analyses**. This is an input limit; source availability and runtime determine how much finishes in one run. Related topics and Trending Now feeds are outside this actor's scope.

### How to export Google Trends data in bulk

1. Enter your keywords, country codes and date windows in the **Input** tab.
2. Keep historical timeline selected, and optionally add regional interest and related queries. Timeline is required.
3. Add `"planOnly": true` for a workload and event-price preview without fetching Google data.
4. Remove that setting or set it to `false`, check the current **Pricing** tab and your run spending limit, then start the research run.
5. Read the **Analyst report** in the dataset or download `ANALYST-REPORT.csv` from the run's key-value store.
6. Check `OUTPUT.completedAllWork` and the outcome counts. If work is incomplete, use the generated `RESUME-INPUT.json` after the previous run stops.

Start with a small brief:

```json
{
  "keywords": ["running shoes", "trail running", "yoga mat"],
  "geographies": ["US", "GB", "IN"],
  "timeWindows": ["today 3-m"],
  "surfaces": ["timeline", "regions", "relatedQueries"]
}
```

This creates **nine analyses**: three keywords × three countries × one period. For an estimate without fetching Google data, add `"planOnly": true`.

For repeatable reports, use fixed windows such as `"2026-01-01 2026-06-30"`. Google controls available observations and granularity. A relative preset such as `today 3-m` moves with the capture date; exports retain effective source periods where supplied.

### Native comparisons

```json
{
  "mode": "compare",
  "comparisonGroups": [
    { "label": "Brand comparison", "keywords": ["Nike", "Adidas", "Puma"] }
  ],
  "geographies": ["US", "GB"],
  "timeWindows": ["today 12-m"],
  "surfaces": ["timeline", "regions"]
}
```

This produces two analyses. Each group stays together in its source request; groups are never silently split or calibrated with an anchor. The supported classic backend accepts **two to five terms per group**. Google's modern Explore interface has a different limit, which this backend does not imply support for.

Remove the independent `keywords` field when using comparison groups. Related queries are supported in independent mode; submit separate keyword analyses for them. Topic IDs such as `/m/02vqfm` are preserved as supplied. A plain term is not automatically converted into a topic.

### Output

Start with the **Analyst report** in Apify Console or download `ANALYST-REPORT.csv`. It puts each keyword's outcome, timeline quality, latest complete interest, explicitly labeled change period, regional highlights, and rising-query leads on one row.

The default dataset API retains one bundle per analysis: query context, overall status, per-surface results, summary statistics, billing state, checkpoint revision, and an additive `report` array. The run's key-value store contains 18 tabular exports across six tables and three formats:

| File | Contents |
|---|---|
| `ANALYST-REPORT.csv` / `.json` / `.ndjson` | One row per planned keyword and analysis, with quality/metric notes, highlights, and source context |
| `SUMMARY.csv` / `.json` / `.ndjson` | One row per keyword and analysis; scoped curve statistics, extraction outcome, and data quality |
| `TIMELINE.csv` / `.json` / `.ndjson` | Source timestamps, values, availability flags and partial intervals |
| `REGIONS.csv` / `.json` / `.ndjson` | Source region names/codes, values and units |
| `RELATED-QUERIES.csv` / `.json` / `.ndjson` | Top/rising related queries, source display values and Breakout flags |
| `QUERY-REVIEW.csv` / `.json` / `.ndjson` | Optional lexical annotations for every captured related-query row; original values, source order and provenance retained |
| `MANIFEST.json` | Every planned analysis, including failed and unstarted work |
| `RESUME-INPUT.json` | Input for continuing from this run |
| `PREFLIGHT` | Expanded workload and current research-event price ceiling |
| `OUTPUT` | Completion counts, request usage and data limits |
| `N8N-WORKFLOW.json` / `N8N-SETUP.md` | Inactive integration template and credential/setup instructions |

The report includes failed, deferred, confirmed-empty, and preflight work with explicit outcomes and null metrics where appropriate. Reading older checkpoints rebuilds the report from stored captures without additional source calls or research-event charges. `OUTPUT.reportRows` counts keyword/analysis pairs: a three-term comparison contributes three report rows and one logical analysis.

#### Example analyst report

This reduced row comes from a recorded `coffee`, US, `today 12-m` capture on 15 October 2026, with current derived reporting fields. It is a separate historical example from the nine-analysis brief above. A fresh run can return different periods and values.

```json
{
  "keyword": "coffee",
  "geography": "US",
  "time": "today 12-m",
  "effectiveTime": "2025-10-15 2026-10-15",
  "analysisStatus": "complete",
  "qualityState": "available",
  "latestCompleteInterest": 70,
  "latestCompleteDate": "2026-10-04T00:00:00.000Z",
  "recentChangePercent": 1.0344827586206806,
  "changeLabel": "Last 4 complete weekly intervals vs previous 4",
  "coverageLabel": "52 / 52 returned complete intervals usable"
}
```

Here `70` is a relative source index. The change compares two stated windows within that same capture; it is not a search-volume estimate or a forecast.

Use the report as a research brief, with these interpretation rules:

- **Read quality alongside extraction.** `complete` describes collection; `limited`, `unconfirmed`, and other quality states describe the returned timeline. Coverage means usable **returned nonpartial intervals**, not every expected date in the requested period. Plain-language notes explain sparse histories, missing flags, and unavailable change metrics.
- **Keep comparison boundaries.** `groupKeywords`, `groupLabels`, `analysisId`, and the timeline `captureScope` identify a native comparison. Independent keywords/countries are not ranked by demand. Exact method, source unit, previous/recent window means and bucket-start boundaries remain in the report files.
- **Interpret regional highlights by their stated unit.** Independent analyses show regional relative interest indices; native comparisons show share among the compared terms. Up to three usable returned regions are sorted by that metric, preserving source order for ties. Confirmed zeros are retained. Unavailable/null observations and unconfirmed zeros are omitted from highlights; positive indices with missing availability flags retain a disclosure in JSON. These values do not establish market size.
- **Review rising queries as leads.** Up to three usable rising rows retain source order and source rank. `growthPercent` is numeric where supplied; `breakout` is categorical with null exact growth. No relevance, commercial-value, or conversion assessment is inferred. All raw related-query rows remain available.

CSV contains every scalar report field, with readable text columns for highlights and notes. JSON/NDJSON additionally retain structured `topRegions`, `risingQueries`, `dataQuality`, group arrays, and `surfaceProvenance`. Each surface keeps its own source URL, capture time, effective period, resolution, scope, status, and error. Source links reopen query context; relative presets can move and Google can resample even a fixed period.

The Console's default view expands `report` into keyword rows; **Analysis details** retains the technical bundles. API consumers request the flattened view explicitly with `/items?view=analyst`; `/items` remains bundles. Dataset pagination counts original bundles, so one paginated item can expand into multiple keyword rows. Generated CSV files are the spreadsheet-safe exports; JSON preserves original text. [Apify's view/unwind documentation](https://docs.apify.com/storage/dataset-schema#unwind-arrays), [Dataset API pagination](https://docs.apify.com/api/v2/dataset-items-get).

#### Review related-query text without removing source results

`reviewRelatedQueries` defaults to `true`. Selected rising leads gain a structured `queryReview` object and readable review facts in the analyst view. `QUERY-REVIEW` annotates **every** captured top/rising row, including source-unavailable rows, with its original values, `sourceRowIndex`, per-keyword `sourceRankWithinKind`, and related-query capture context. It never filters, changes source ranks, or changes which primary leads appear. Breakout remains categorical with a null exact growth value.

- `seedTokenOverlap` is `present` when any eligible seed token occurs as a whole normalized token, `absent` when none occurs, or `not_evaluated` when the comparison is unsupported. Eligible Latin text normalizes Unicode width, case, and decomposable combining accents: `CAFÉ` and `cafe` can match. Punctuation separates tokens; there is no stemming, translation, fuzzy matching, or stopword removal. `coffee` does not match `coffeemaker`. Topic IDs, operator/quoted expressions, non-Latin/mixed-script text, and inputs without letter tokens remain explicitly unevaluated. Missing tokens can reflect useful synonyms such as `espresso`; they do not establish irrelevance.
- `url_looking_text` records a protocol/`www` prefix or domain-shaped text, including a narrow rule for a label followed by a spaced `.com`, `.net`, or `.org` suffix. For example, the captured `coffee deets .com` lead receives this flag. This is neither URL validation nor an exhaustive detector.
- `promotional_phrase` records only these exact English phrases after width/case normalization, with Unicode word boundaries: `buy now`, `shop now`, `promo code`, `discount code`, `coupon code`, `free shipping`, and `limited time offer`. Generic seasonal phrases such as `national coffee day deals` are not flagged by this rule. The matched phrases are exposed for inspection.

These are transparent text facts, **not semantic relevance, spam, confidence, or commercial-value judgments**. All original rows remain in `RELATED-QUERIES`; annotations live separately. `OUTPUT.queryReviewRows` counts all annotated source rows and uses `queryReviewRowsScope: "all_captured_related_query_rows"`.

Set `"reviewRelatedQueries": false` to disable annotation, including on resume. The report explicitly says review is disabled, each selected lead has `queryReview: null`, and the review exports contain a CSV header/empty JSON array/empty NDJSON. `OUTPUT.queryReviewStatus` is `disabled` with zero annotated rows. Original data and primary lead selection remain identical. This is a report setting; changing it preserves the research plan's analysis IDs and reuses completed captures without new source calls or research-event charges.

Read the status before using the numbers:

- `complete`: useful timeline data and all selected surfaces resolved as data or confirmed empty.
- `no_data`: the requested surfaces returned confirmed empty/no-data outcomes.
- `partial`: some useful data is available, but a requested surface remains unresolved or the required timeline is confirmed empty.
- `failed`: extraction did not produce a usable selected surface.
- `deferred`: work was not completed before a deadline, charge limit, or source circuit breaker stopped it.

A blocked page, missing widget, or malformed response is never converted into zero interest. A source zero with `hasData: true` remains a valid observation. All-zero observations without availability flags remain unconfirmed. A partial final time interval is retained in timeline rows and excluded from summary calculations.

#### Read data quality separately from extraction status

`complete` describes successful extraction. It can include a short or sparse timeline, so each keyword summary also includes `dataQuality`. Comparison terms receive their own assessment: a well-populated term cannot hide missing observations for another term. These fields are regenerated from captured rows when reading older checkpoints; no extra source requests or research charges are needed.

| Quality state | Meaning |
|---|---|
| `available` | Completed observations are usable without the limited-history, coverage, or sparse-positive review flags below |
| `limited` | Some observations were captured, but the history, returned coverage, or positive pattern needs review |
| `no_data` | The source explicitly returned empty data or marked every observation unavailable for this keyword |
| `unconfirmed` | Completed zero values lack source-availability evidence; summary metrics remain empty |
| `unavailable` | Collection failed or the captured timeline contains no observations for this keyword without an explicit empty outcome |
| `not_collected` | The timeline has not been collected, including deferred work |

`returnedIntervalCoverage` is the usable fraction of **returned non-partial source intervals**. For example, 8 usable observations among 10 returned complete intervals gives 0.8. It does not measure whether Google returned every date in the requested period. It is null when no complete intervals were returned or their zero-value availability remains unconfirmed. Counts separately expose source no-data observations, missing availability flags, positive values, confirmed zeros, and excluded partial intervals.

Quality version 1 uses these transparent review heuristics:

- `limited_history`: fewer than eight usable completed observations. This generic minimum does not promise that a change metric can be calculated.
- `low_returned_interval_coverage`: fewer than 80% of returned complete intervals are usable.
- `sparse_positive_observations`: at least ten usable completed observations, with more than zero and at most 10% positive values. An isolated spike deserves review; this rule does not prove noise or low search volume.
- `all_zero_complete_series`: all usable completed values are zero. Confirmed source zeros stay valid data and do not become an empty response.
- `partial_intervals_excluded` and `availability_not_reported`: identify unfinished observations and absent source flags. These notes alone do not downgrade otherwise usable history.

The remaining flags explain source failure, explicit no-data, unconfirmed zeros, missing keyword observations, or work not collected. These states and flags are interpretation aids, **not statistical confidence scores or evidence of absolute demand**. They do not change extraction status or billing eligibility. CSV files flatten the fields under `quality*` columns; JSON also retains the full `dataQuality` object. `OUTPUT` and `MANIFEST.json` contain `qualityCounts` with `qualityCountsScope: "keyword_summaries"`: a three-term comparison contributes three quality assessments and one analysis outcome.

Each surface has a `captureScope`; matching input terms do not calibrate separate captures. Summary rows retain their timeline's capture scope, capture time, and effective period. Multi-term regional percentages have a different unit from the single-term regional index. Related-query growth percentages and `Breakout` are also separate metrics. Breakout is retained as a flag rather than an invented exact growth value.

Curve summaries describe each source scale. Do not rank independently researched keywords by mean interest as if it were absolute demand.

#### Interpret the change period explicitly

Current summaries have `summaryVersion: 2` and `changeMethod: "adjacent_complete_window_means_v2"`. `recentChangePercent` is `(recent window mean / previous window mean - 1) * 100`, calculated within one captured source scale. Version 2 replaces the original four-versus-four-interval calculation with these granularity-specific rolling windows:

| Timeline resolution | Recent window | Previous window | Required intervals |
|---|---|---|---|
| `DAY` | Last 7 complete daily intervals | Preceding 7 | 14 |
| `WEEK` | Last 4 complete weekly intervals | Preceding 4 | 8 |
| `MONTH` | Last 3 complete monthly intervals | Preceding 3 | 6 |
| `HOUR` | Last 24 complete hourly intervals | Preceding 24 | 48 |

Daily windows contain the same mix of weekdays; they are rolling periods, not necessarily calendar weeks. Monthly continuity follows UTC source bucket timestamps, accounting for different month lengths and year boundaries. Google uses UTC for daily, weekly, and monthly data regardless of the requested time-zone offset. [Google's time-zone explanation](https://support.google.com/trends/answer/4365533?hl=en). Each interval has equal weight in its window mean; monthly means are not weighted by days or converted into search volume. The generic quality-history heuristic remains separate from these metric-specific history requirements.

Each summary includes `changeResolution`, `changeResolutionSource` (`source`, `inferred`, or `unknown`), `changeIntervalsPerWindow`, `changeRequiredIntervals`, and a readable `changeLabel`. The previous/recent `WindowStart`, `WindowLastIntervalStart`, and `WindowMeanInterest` fields expose the exact observations and baseline. Every boundary is a **source interval start timestamp in UTC**, not an invented end date. `latestCompleteDate` is the start of the latest nonpartial source interval; its interest remains null if that interval is unavailable, rather than substituting an older value.

The source resolution takes precedence. Older captures without it use conservative inference only when at least three numeric source timestamps have a regular supported cadence. Unknown, irregular, and explicitly unsupported units (including `MINUTE`) leave the change null with a reason. Source timestamps must be finite integer numbers; missing, string, fractional, or invalid timestamps are not coerced from display dates.

Both windows must be contiguous, uniquely timestamped, available, and complete. Trailing partial intervals are excluded; an internal partial, missing date, duplicate, or unavailable observation suppresses the change. Window bounds and means remain null when those checks fail. A zero baseline gives `zero_baseline` with null change and preserved window means; a positive baseline followed by zero gives `-100%`. These are descriptive changes, not statistical significance estimates or forecasts. Reading or exporting older checkpoints regenerates version 2 metadata from stored rows without extra source requests or research-event charges; preserved historical exports keep their original version.

CSV text is escaped for spreadsheet formula safety. JSON preserves the original text.

### Google Trends API access and integrations

This is an independent Apify actor, not Google's official API. You can call it from Python, JavaScript, n8n or any HTTP client with an Apify account and API token.

Start a run asynchronously with the same JSON used in Console. This example requests one keyword and a $0.10 maximum Actor charge; the charge cap is not a promise of completing a larger brief:

```sh
curl --request POST \
  'https://api.apify.com/v2/acts/whoareyouanas~google-trends-batch-research/runs?memory=1024&maxTotalChargeUsd=0.10' \
  --header "Authorization: Bearer $APIFY_TOKEN" \
  --header 'Content-Type: application/json' \
  --data '{"keywords":["coffee"],"geographies":["US"],"timeWindows":["today 3-m"]}'
```

Save the response's `data.id`, `data.defaultDatasetId` and `data.defaultKeyValueStoreId`. Check `GET /v2/actor-runs/RUN_ID` until the run stops, then inspect its `OUTPUT` record for analysis outcomes. A platform status of `SUCCEEDED` can still include partial or deferred research.

Download your analyst report as a JSON array:

```sh
curl \
  'https://api.apify.com/v2/key-value-stores/STORE_ID/records/ANALYST-REPORT.json' \
  --header "Authorization: Bearer $APIFY_TOKEN"
```

Replace `STORE_ID` with your run's `defaultKeyValueStoreId`. Use `.csv` for a spreadsheet download or `.ndjson` for line-delimited processing. For dataset access, `GET /v2/datasets/DATASET_ID/items?view=analyst` returns keyword rows; the default items endpoint returns analysis bundles. Dataset pagination counts bundles before expansion, so comparison groups can produce several rows per page item.

The run also provides `N8N-WORKFLOW.json` and `N8N-SETUP.md`. Import the inactive template, add your Apify and Google Sheets credentials, set the desired input and spreadsheet, and test before activating it. It reads `ANALYST-REPORT.json`; the template was structurally verified and retrieved from cloud runs, but execution inside a connected n8n instance remains untested. Its small example requests 512 MB; use 1 GB for large matrices. For a fresh scheduled report, start a new run rather than resuming an old source sample. [Apify API documentation](https://docs.apify.com/api/v2), [scheduling documentation](https://docs.apify.com/platform/schedules).

### Resume interrupted research

Once the previous run has stopped, submit its downloaded resume input, or:

```json
{ "resumeFromRunId": "YOUR_PREVIOUS_RUN_ID" }
```

Resume requires this actor and the same Apify account. It imports the original research plan, reuses finished/confirmed-empty work, and retries incomplete surfaces. Execution/report settings such as concurrency, pacing, timeout, proxy configuration, or `reviewRelatedQueries` may be overridden. Research fields cannot be changed during resume; start a new run for a fresh brief.

Use sequential resumes. Once a run has a recorded successor, resume that successor rather than branching from the same checkpoint. Simultaneous resumes are not a supported workflow. A hard kill can prevent final exports; durable captures are retained so a later run can rebuild them.

Confirmed-empty surfaces are reused too. If a timeline is confirmed empty while another surface contains data, the result remains partial; resume cannot create missing timeline observations. Start a new research run when you want a fresh sample.

Checkpoints live in the actor-created `google-trends-research-checkpoints-v1` store under your account, separated by run ID. Keep that store while you need resume support; deleting it removes the corresponding recovery data. Runs use limited Apify permissions.

Dataset appends and charging are separate operations. Raw dataset appends may be duplicated after an ambiguous storage acknowledgment; the manifest and generated files reconcile logical analyses by `analysisId`. Ambiguous prior charge intents are not blindly charged again.

### Limits and proxies

The actor accepts up to 200 input keywords, ten geographies, and four time windows, with a hard maximum of **1,000 expanded entries**. Oversized inputs are rejected before extraction. The default run budget is one hour, with two concurrent analyses and one source request per second across workers. These are execution limits, not a guarantee that every supported matrix finishes within one run.

Residential Apify proxy sessions are the production default. The requested geography is independent of the proxy's exit country. For direct local testing, use:

```json
{ "proxyConfiguration": { "useApifyProxy": false } }
```

Google may still rate-limit or block requests. Retries, deadlines, source pacing, and a circuit breaker bound the work. Increase `requestDelayMillis` when needed. See the **Input** tab for all accepted fields.

Large exports require additional time and memory. Source dispatch reserves at least half a second per planned analysis for final storage/export work. Allocate at least 1 GB for matrices around 600 analyses or unusually large response sets. The reporting release's 600-analysis regeneration was measured at 1 GB; the original 1.0 live benchmark used 512 MB. The actor defaults to 1 GB, with a configured range of 512–2048 MB. The n8n template explicitly requests 512 MB for its small example brief; change `memory=512` to `memory=1024` in **Start research** for large matrices. The 1,000-entry input cap is not a verified fresh-run capacity guarantee.

#### What has been tested?

The reporting release passed 145 regression tests and nine private-cloud scenarios. Its 600-analysis historical regeneration reconciled all 18 exports, retained original captures and receipts, and preserved 599 complete analyses plus one partial analysis with a confirmed-empty required timeline. Regeneration made zero Google requests. Four fresh keyword/country analyses completed separately.

The earlier fresh 600-analysis workload required sequential recovery runs totaling 66.02 active minutes. These observations do not establish a fresh-run speed guarantee, multi-day reliability or proven savings for agencies. Configured paid-event billing was not part of those private-cloud runs.

### How much does Google Trends batch research cost?

See the **Pricing** tab for the current configured rate. The proposed launch model is **$0.01 per newly completed analysis**, or **$10 per 1,000 completed analyses**. This rate applies only after monetization is configured; examples below use that rate.

One analysis is **one independent keyword or one comparison group × one geography × one date window**, including the supported selected surfaces and report exports. It is not one timeline point, regional row or report row. Completion requires usable timeline data and every selected surface resolved as data or confirmed empty; a short or sparse but complete timeline can still qualify and keeps its quality notes.

| Brief | Analyses | Event cost at $0.01 if every analysis qualifies |
|---|---:|---:|
| Three keywords × three countries × one window | 9 | $0.09 |
| 100 keywords × three countries × two windows | 600 | $6.00 |
| One five-term comparison × two countries × one window | 2 | $0.02 |

The implementation uses the custom event `research-analysis-complete`. Partial, failed, deferred, wholly empty, inherited completed and plan-only work produce no new research event. A partial analysis that first completes on resume can qualify once. Reusing a completed capture produces no additional research event; starting a fresh research run is new work. Preflight reads the actual configured price and shows the event-price ceiling.

Compute, proxy and storage are platform costs even when there is no research event. Under standard PPE these are covered by the creator; if **Pay per event + usage** is enabled, users also pay platform usage. Check the Pricing tab rather than assuming a no-event run has no possible account costs. [Apify PPE pricing](https://docs.apify.com/actors/publishing/monetize/pay-per-event).

### Frequently asked questions

#### Can I export multiple Google Trends keywords across countries?

Yes. Independent mode expands keywords × geographies × time windows and provides an outcome for each planned analysis. It supports up to 200 input keywords and 1,000 expanded analyses. Each independent capture has its own scale, so comparing their means does not rank absolute demand.

#### Can I compare more than five keywords on one shared scale?

This actor supports native groups of two to five terms. You can submit multiple groups, but separate captures are not calibrated with each other. Related queries require independent mode; comparison mode supports timeline and regional data.

#### Does it return monthly search volume or keyword difficulty?

No. It returns relative interest and source-related queries, with regional units and capture scopes preserved. It does not supply absolute monthly search volume, keyword difficulty, CPC, SERP rankings or an estimate of market size. Use a separate source for those measurements.

#### Why are some changes null, and what does Breakout mean?

A change can be null because history is too short, intervals are partial/missing/unavailable, cadence is unsupported, or the baseline is zero. The report states the method and reason. Breakout is the source's categorical rising-query label; the actor preserves it without inventing an exact growth percentage.

#### Can I use the results in Excel, Google Sheets or n8n?

Yes. Download `ANALYST-REPORT.csv` for spreadsheet import, or read its JSON array through the API. CSV protects formula-like source text; JSON preserves original text. The included n8n template needs your credentials and testing before activation. Review related-query leads: lexical flags describe text patterns and do not certify relevance or commercial value.

#### Does resume refresh the source data?

Resume preserves completed and confirmed-empty captures and retries unresolved surfaces. It requires the same actor/account and sequential stopped runs. Start a new run for a fresh Google sample or a new brief; a confirmed-empty required timeline can remain partial after resume.

#### Is this a Trending Now scraper or an official Google service?

No. It is an independent historical keyword-research actor using Google's classic Explore backend. It does not collect Trending Now/RSS feeds or related topics and is not affiliated with Google.

#### How do I report a problem?

Use this actor's **Issues** tab with the run ID, expected behavior and a small reproducible input. Include the relevant outcome/error fields, but omit tokens, proxy passwords and other credentials.

# Actor input Schema

## `mode` (type: `string`):

Research each keyword independently or compare explicit groups on a native shared timeline scale.

## `keywords` (type: `array`):

Search terms or Google topic IDs. Required in independent mode.

## `comparisonGroups` (type: `array`):

Compare mode only. Each object contains keywords (2–5 distinct terms) and an optional label. Related queries require independent mode.

## `geographies` (type: `array`):

Country/subregion codes, such as US, GB, IN or US-CA. Use an empty string for worldwide.

## `timeWindows` (type: `array`):

Google preset or YYYY-MM-DD YYYY-MM-DD. Use fixed dates for repeatable research.

## `surfaces` (type: `array`):

Timeline is required. Related queries are available for independent keyword research.

## `category` (type: `integer`):

Google Trends category ID; zero includes all categories.

## `property` (type: `string`):

The Google search surface to measure.

## `language` (type: `string`):

Interface language for source labels; does not translate keywords.

## `timezoneOffsetMinutes` (type: `integer`):

Google/JavaScript convention: India is -330, UTC is 0. Does not set research geography.

## `maxAnalyses` (type: `integer`):

Reject a larger expanded matrix before any source requests; maximum 1,000.

## `maxRunSeconds` (type: `integer`):

Stop new work before this runtime budget and preserve results for resume.

## `concurrency` (type: `integer`):

Maximum analyses in flight; requests also follow a shared pacing limit.

## `requestDelayMillis` (type: `integer`):

Minimum time between source requests across all workers. Increase when Google rate-limits requests.

## `maxRetries` (type: `integer`):

Maximum additional attempts per source operation, excluding the first attempt.

## `requestTimeoutSeconds` (type: `integer`):

Maximum duration of one HTTP request.

## `reviewRelatedQueries` (type: `boolean`):

Add literal seed-token overlap, URL-looking text and exact promotional-phrase annotations. Never filters or ranks relevance. Disable without changing the research plan, including on resume.

## `planOnly` (type: `boolean`):

Produce a workload and charge estimate without fetching Google or charging research events.

## `proxyConfiguration` (type: `object`):

Residential sessions are recommended for production. Set useApifyProxy to false for direct local tests.

## `resumeFromRunId` (type: `string`):

Imports the original plan. Remove keywords, geographies, date windows and other research fields when resuming.

## Actor input object example

```json
{
  "mode": "independent",
  "keywords": [
    "coffee"
  ],
  "geographies": [
    "US"
  ],
  "timeWindows": [
    "today 12-m"
  ],
  "surfaces": [
    "timeline",
    "regions",
    "relatedQueries"
  ],
  "property": "web",
  "language": "en-US",
  "maxAnalyses": 1000,
  "maxRunSeconds": 3600,
  "concurrency": 2,
  "requestDelayMillis": 1000,
  "maxRetries": 2,
  "requestTimeoutSeconds": 30,
  "reviewRelatedQueries": true,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `analystReport` (type: `string`):

No description

## `analystReportJson` (type: `string`):

No description

## `analystView` (type: `string`):

No description

## `dataset` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `timeline` (type: `string`):

No description

## `regions` (type: `string`):

No description

## `relatedQueries` (type: `string`):

No description

## `queryReview` (type: `string`):

No description

## `queryReviewJson` (type: `string`):

No description

## `manifest` (type: `string`):

No description

## `resume` (type: `string`):

No description

## `n8n` (type: `string`):

No description

## `n8nSetup` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "independent",
    "keywords": [
        "coffee"
    ],
    "geographies": [
        "US"
    ],
    "timeWindows": [
        "today 12-m"
    ],
    "surfaces": [
        "timeline",
        "regions",
        "relatedQueries"
    ],
    "category": 0,
    "property": "web",
    "language": "en-US",
    "timezoneOffsetMinutes": 0,
    "maxAnalyses": 1000,
    "maxRunSeconds": 3600,
    "concurrency": 2,
    "requestDelayMillis": 1000,
    "maxRetries": 2,
    "requestTimeoutSeconds": 30,
    "reviewRelatedQueries": true,
    "planOnly": false,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("whoareyouanas/google-trends-batch-research").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "independent",
    "keywords": ["coffee"],
    "geographies": ["US"],
    "timeWindows": ["today 12-m"],
    "surfaces": [
        "timeline",
        "regions",
        "relatedQueries",
    ],
    "category": 0,
    "property": "web",
    "language": "en-US",
    "timezoneOffsetMinutes": 0,
    "maxAnalyses": 1000,
    "maxRunSeconds": 3600,
    "concurrency": 2,
    "requestDelayMillis": 1000,
    "maxRetries": 2,
    "requestTimeoutSeconds": 30,
    "reviewRelatedQueries": True,
    "planOnly": False,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("whoareyouanas/google-trends-batch-research").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "independent",
  "keywords": [
    "coffee"
  ],
  "geographies": [
    "US"
  ],
  "timeWindows": [
    "today 12-m"
  ],
  "surfaces": [
    "timeline",
    "regions",
    "relatedQueries"
  ],
  "category": 0,
  "property": "web",
  "language": "en-US",
  "timezoneOffsetMinutes": 0,
  "maxAnalyses": 1000,
  "maxRunSeconds": 3600,
  "concurrency": 2,
  "requestDelayMillis": 1000,
  "maxRetries": 2,
  "requestTimeoutSeconds": 30,
  "reviewRelatedQueries": true,
  "planOnly": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call whoareyouanas/google-trends-batch-research --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,whoareyouanas/google-trends-batch-research"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/n97XdosST1xVGEYNn/builds/PDZqTG7wuX4DFSZNp/openapi.json
