A repeated Google query can act as a discovery feed: new pages enter the visible result set, existing pages move, and others disappear. Detecting those changes reliably requires snapshots and comparison rules—not merely comparing two raw arrays.

The basic workflow is:

  1. Collect the same query under the same conditions.
  2. Preserve each raw response.
  3. Derive conservative URL comparison keys.
  4. Compare valid snapshots.
  5. Confirm important changes before alerting.

Define what counts as the same search

Keep these fields constant:

  • Exact query text
  • Country and language settings
  • Search filters
  • Number of pages collected
  • Result-unit definition
  • URL-normalization version

Store the complete submitted Google Search URL and a UTC timestamp with every snapshot. A locale change is not a page entering or leaving; it is a different search context.

Preserve occurrence and identity separately

For every linked result block, retain:

{
  "originalUrl": "https://example.com/report?utm_source=search#summary",
  "normalizedUrl": "https://example.com/report",
  "page": 1,
  "path": [2, 1],
  "text": "Example report",
  "collectedAt": "2026-08-26T15:00:00Z"
}

The original URL and path preserve what was observed. The normalized URL supports comparison. A changed tracking parameter should not necessarily create a false “new page.”

Calculate a set diff

function indexByUrl(rows) {
  const index = new Map();
  for (const row of rows) {
    if (!index.has(row.normalizedUrl)) {
      index.set(row.normalizedUrl, row);
    }
  }
  return index;
}

function diffSnapshots(beforeRows, afterRows) {
  const before = indexByUrl(beforeRows);
  const after = indexByUrl(afterRows);

  const entered = [...after.keys()]
    .filter((url) => !before.has(url))
    .map((url) => after.get(url));

  const exited = [...before.keys()]
    .filter((url) => !after.has(url))
    .map((url) => before.get(url));

  const persistent = [...after.keys()]
    .filter((url) => before.has(url))
    .map((url) => ({
      url,
      before: before.get(url),
      after: after.get(url),
    }));

  return { entered, exited, persistent };
}

This classifies membership. It does not yet describe movement.

Detect position changes among persistent pages

Choose a position definition before calculating movement. For top-level result blocks:

function positionChanges(persistent) {
  return persistent
    .map(({ url, before, after }) => ({
      url,
      beforePosition: before.position,
      afterPosition: after.position,
      movement: after.position - before.position,
    }))
    .filter((row) => row.movement !== 0);
}

If child destinations are flattened, their traversal index is not automatically organic rank. Store their parent path or analyze parent and child positions separately.

Distinguish four kinds of change

A useful change table separates:

  • Entered: present only in the later valid snapshot
  • Exited: present only in the earlier valid snapshot
  • Moved: present in both, but at a different defined position
  • Variant: original URL changed while the normalized identity remained stable

Variants are worth retaining. A publisher may change campaign parameters, canonical paths, or redirects without disappearing as a source.

Confirm before notifying

Search results can fluctuate temporarily. For a low-noise monitor, require an entered page to appear in two consecutive snapshots or an exited page to remain absent twice before sending a high-priority notification.

The exact rule depends on the subject:

  • A fast-moving announcement query may prioritize speed
  • A documentation query may prioritize stability
  • A weekly market scan may tolerate one transient change

Keep both first-seen and confirmed timestamps so confirmation does not erase the original observation.

Never compare against a failed snapshot

A request failure cannot prove that every previous page exited.

Only run the diff when the later collection meets a validity rule, such as:

  • HTTP request completed successfully
  • Response has ok: true
  • Expected query and locale are present
  • Required pages completed
  • Transformation version matches

Store valid zero-result responses separately from transport, parsing, or validation failures.

Use thresholds that match the purpose

Possible notification rules include:

  • Any newly visible page from an official domain
  • Three or more new independent domains
  • An owned page disappearing from every collected page
  • A persistent page moving by more than five declared positions
  • URL churn exceeding its recent baseline

Avoid alerting on every changed tracking parameter or one-position movement. The raw change log can retain everything while notifications select only material events.

Keep a compact audit trail

For every comparison, retain:

{
  "queryId": "q-0017",
  "beforeSnapshot": "2026-08-25T09:00:00Z",
  "afterSnapshot": "2026-08-26T09:00:00Z",
  "normalizationVersion": "url-rules-v1",
  "enteredCount": 2,
  "exitedCount": 1,
  "persistentCount": 9,
  "comparisonValid": true
}

Link the summary to the entered, exited, and persistent rows. That makes every notification explainable.

The method does not claim that an exited page vanished from Google permanently. It says the page was not present in the defined result set at the later observation.

Reserp provides the submitted URL, ordered result blocks, nested destinations, and pagination state needed for each snapshot. See the Google Search API documentation for the current response structure.