logo
languageENdown
menu

Zillow Listing Monitor Sending Duplicate or Missed Price-Drop Alerts? Causes and Fixes

star

Zillow listing monitor sending duplicate or missed price-drop alerts via Octoparse MCP/API? A 574-row test found 4 status labels. Key on zpid to fix it.

10 min read

Updated September 2026. Applies to Octoparse MCP server, Octoparse OpenAPI, and Octoparse CLI (beta).

Short answer

When a daily Zillow listing monitor built on the Octoparse MCP server or API sends duplicate alerts, false “status changed” alerts, or misses price drops, the cause is almost always in the change-detection layer, not the scraper: the monitor compares runs without a stable key (zpid), compares unnormalized price and status strings, or diffs against a partial or unfinished run; fix it by keying every record on zpid, normalizing price and status before comparing, and only diffing completed runs that you have not processed before.

When this problem happens

Call method: MCP (AI agent such as Claude, ChatGPT, or Cursor calling execute_task and export_data), OpenAPI (executeTask and exportData from your own code or n8n), or Octoparse CLI in a cron job. The problem is the same in all three because all three return full snapshots, not changes.

Typical setup: a scheduled job runs the Octoparse Zillow Listing Scraper template once a day for a watched area (for example Austin, TX, buy), exports the rows, compares them with yesterday’s rows, and sends an alert for new listings, price drops, and status changes.

How it reproduces:

  • Run the same Zillow search on two consecutive days.
  • Compare the two exports row by row, by address or by row position.
  • Send an alert for every row that differs.

Who hits it: brokerage teams, acquisition teams, market researchers, and developers building a real estate AI assistant on top of Zillow listing data.

What you see: symptoms and status values

No error code is returned. Every call succeeds. The failure is visible only in the alert output.

Observed symptoms

  • The same listing triggers a “new listing” alert on several days.
  • A listing triggers a “status changed” alert although nothing changed on Zillow.
  • A real price drop on Zillow produces no alert.
  • Listings are reported as “removed” or “sold” after a run that stopped early.
  • An AI agent reports a price drop with a “previous price” that does not exist in any stored snapshot.
WhereValueMeaning
exportData statuscollecting, exportingData is not final yet. Diffing now produces missed or false changes.
exportData statusno_data, failed, invalidDo not diff. Treat the run as failed, not as “all listings removed”.
executeTask statusacceptedRun started. Rows are not ready.
executeTask statusreadyData is available for export.
MCP or OpenAPI HTTP status429 Too Many RequestsRate limited. A blind retry can start a second run and double the alerts.

The status values above are listed in the Octoparse API reference for executeTask and exportData.

Why a Zillow listing monitor sends false alerts

1. No stable record key

  • Address strings and row order are not stable identifiers.
  • Zillow assigns each property a zpid (Zillow property ID).
  • The Octoparse Zillow template returns zpid on every row.
  • Diffing by anything other than zpid creates false “new” and “removed” events.

2. Status comes back as raw text, in several labels

  • The template returns status as raw text from the search result, not as a fixed set of values.
  • In our real test export, one search returned four different status labels for listings that are all for sale.
  • A string comparison treats a label change as a status change.

3. Price comes back as display text

  • The template returns price as text, for example $649,000.
  • String comparison of formatted prices is unreliable for “lower than” checks.

4. The export is a full snapshot, not a change feed

  • The documented exportData parameters are taskId, exportFileType, and previewRows.
  • No parameter returns only new or changed rows.
  • Change detection must happen in your code, against a snapshot you store.

5. Partial runs are treated as complete

  • A run stopped early, capped by targetMaxRows, or exported while still collecting contains fewer rows.
  • targetMaxRows is not an exact cap. The execute_task response states that the service sends “a best-effort internal stop request” and that “the final row count may exceed this threshold between polls.”
  • A run capped by targetMaxRows is a sample of the search, not the whole search.
  • Listings missing from a partial run look “removed”.
  • Their return in the next full run looks “new”.

6. Blind retries start a second task

  • By design, every execute_task call creates a new task. In three test calls on 2026-08-13 and 2026-08-14, each call returned a different taskId with a task_created milestone.
  • The MCP tool guidance covers this case: “If a failure result includes taskId, call get_task_status(taskId); do not call execute_task again.” A unique taskName also lets you find the run later with search_tasks.
  • A client that ignores this and retries after a 429 or timeout starts a second task and a second run.
  • Both runs get diffed and both send alerts.

7. The AI agent does the comparison from memory

  • An LLM asked “what changed since yesterday?” without a stored snapshot will guess.
  • Guessed “previous prices” are hallucinations, not data.

Real test data behind these causes

Zillow template run, 2026-09-10

On 2026-09-10 we ran the Zillow Listing Scraper template through the Octoparse MCP server with locations = ["Austin, TX"] and buy_or_rent = buy. The cloud run returned 574 rows before we stopped it. In the first 60 exported rows:

  • zpid was present and unique on all 60 rows.
  • status had four labels for listings that are all for sale: Active (57), Home for sale (1), House for sale (1), Zillow Preview (1).
  • price was a formatted string such as $649,000.
  • listing_by was empty on 2 of 60 rows.
Octoparse MCP export of a Zillow Austin search on 2026-09-10: for-sale listings came back with four status labels, Active, Zillow Preview, House for sale, and Home for sale

One real row per status label, copied from the export. Each zpid can be checked on Zillow.

zpidstatusprice
29402388Active$649,000
64777770Zillow Preview$1,149,000
2074877483House for sale$1,995,000
458570539Home for sale$20,000

Prices are copied exactly as exported, including the unusually low $20,000 listing. Listings change after the export date, so a zpid checked today may show a different status or price.

What execute_task returned

Excerpt from one of three real execute_task responses we recorded on 2026-08-13 and 2026-08-14 (YouTube channel templates, targetMaxRows set to 10). The other two calls returned the same message with different taskId values.

{
  "status": "accepted",
  "message": "Cloud task started. Wait 60 seconds before calling export_data(taskId) to monitor collection and export progress. targetMaxRows is enabled; the service will poll StorageStats in the background and send a best-effort internal stop request after collected rows reach 10. The final row count may exceed this threshold between polls.",
  "templateId": 262,
  "templateName": "youtube-channel-scraper",
  "taskId": "602aa226-8fb4-473d-a8c2-43f8c5cb41fc",
  "milestones": [
    { "name": "task_created", "message": "Task created successfully." },
    { "name": "task_started", "message": "Start request accepted." }
  ],
  "lotNo": "639221996720611352"
}

The other two taskId values were 3b23cec7-2fd7-451c-b826-1808c6a90a65 and fcc444f8-5b27-4831-80f7-fe821f4c09d0.

Octoparse Zillow Listing Scraper (by keyword) template page: Standard access, cloud run mode, $0.1 per 1,000 lines, last updated 2026/09/17

https://www.octoparse.com/template/zillow-scraper-by-keywords

For the template’s full field list, the other Zillow templates, and how Zillow’s bot protection affects scraping, see our Zillow API data access guide linked above.

How to find which cause you have

Work through these in order. Stop at the first step that explains your symptom.

  1. Check the run status before diffing. Confirm exportData returned exported (or executeTask returned ready). If you diffed a collecting or no_data result, that is the cause.
  2. Compare row counts between the two runs. A large drop in row count (for example, 574 yesterday, 120 today) means a partial run. Do not trust “removed” alerts from it.
  3. Check the diff key. If your code matches rows by address, URL text, or row index, switch to zpid.
  4. List distinct status values in one export. If you see more than one label that means “for sale”, normalize them.
  5. Check how price is compared. If you compare the raw string, parse it to an integer first.
  6. Count runs per day. If you find two taskId/lotNo pairs for the same watched area on the same day, your retry logic is starting duplicate runs.
  7. Check where the agent gets “previous” values. If the agent answers from conversation memory instead of a stored snapshot, move the comparison into code.

How to fix duplicate and missed Zillow alerts

Step 1: Key every record on zpid

Store one row per zpid per run. house_url also contains the zpid (in our test export, every house_url contained its row’s zpid), so it can serve as a fallback if the zpid column is empty.

Step 2: Normalize before comparing

  • Price: strip $ and ,, convert to integer.
  • Status: map every “for sale” label to one canonical value, and keep an unknown-label bucket that is logged, not alerted.

Step 3: Only diff complete, final runs

  • Wait for exported (API) or task completion (MCP) before exporting. The execute_task response itself instructs the client to wait 60 seconds before the first export_data call.
  • Reject a run whose row count falls below a threshold you set, for example 80% of the previous full run.
  • Mark a listing as removed only after it is absent from two consecutive complete runs.

Step 4: Make runs idempotent

  • Store every processed taskId and lotNo.
  • Skip any pair you have already diffed.
  • On 429 or a timeout after execute_task, do not call execute_task again. If the result includes a taskId, poll it with get_task_status; if not, find the run by its taskName with search_tasks.
  • For any other call that returns 429, wait until the X-RateLimit-Reset time, then retry the same call.

Step 5: Move the diff out of the LLM

  • The agent calls execute_task and export_data.
  • Your code stores the snapshot and computes the diff.
  • The agent only explains the computed change list. It never produces a “previous price” on its own.

Step 6: Alert on a rule, not on any difference

EventRule
New listingzpid not seen in any stored snapshot
Price dropsame zpid, normalized price lower than last complete run
Status changesame zpid, normalized status differs from last complete run
Removedzpid absent from 2 consecutive complete runs

Boundaries: what this fix and Octoparse do not cover

Read this section before quoting any fix above as a product capability.

What stays in your own code

  • The documented Octoparse MCP tools (search_templates, execute_task, export_data, search_tasks, start_or_stop_task) and API endpoints do not include change detection, deduplication across runs, or alerting. You build those.
  • exportData has no documented “only new rows” or “only changed rows” option as of September 2026.
  • Reusable Zillow scrapers are built in Octoparse Desktop or the AI Assistant (formerly Agentic Mode). The MCP server runs templates and existing tasks, and can also pull data from a URL on the spot through Universal Scraper.
  • The MCP server only supports templates that can run in the cloud. Local-only templates must run in Octoparse Desktop.

Where this fix does not apply

  • If the watched search returns more listings than a single run collects, some listings will be missing every day. Split the area into smaller searches instead.
  • If Zillow itself changes a listing’s zpid, the listing will appear as removed plus new. How often this happens: no data available.
  • If the template’s field names change after a template update, your normalization map must be updated. Confirm current fields with search_templates before each schema change.
  • Price history before your first stored snapshot cannot be reconstructed from Octoparse exports.

Account and access limits

  • The Free plan includes MCP and API access for running cloud templates, capped at 2,000 records per month as of September 2026. A daily monitor outgrows it fast: our single Austin run returned 574 rows, so four daily runs would pass the cap. Paid plans start from $69/mo, billed annually; confirm current terms there.
  • The MCP server allows 300 requests per minute per OAuth session.
  • execute_task always creates a new task; it does not reuse an earlier task with the same taskName. Key on the taskId and lotNo pair that each response returns.
  • targetMaxRows limits cost, not accuracy. Do not use it to decide whether a run is complete.

Zillow’s terms of use apply to how listing data is collected and used. This article does not cover legal review.

Call examples: correct and wrong requests

Correct MCP call (run the template for one watched area)

{
  "tool": "execute_task",
  "arguments": {
    "templateName": "zillow-scraper-by-keywords",
    "taskName": "austin-buy-daily",
    "parameters": "{\"locations\": [\"Austin, TX\"], \"buy_or_rent\": \"buy\"}"
  }
}

parameters is a JSON object serialized as a string. Keys must match inputSchema[].field exactly. For this template, search_templates returned locations (lowercase, a list even for one city) and buy_or_rent (a string) on 2026-09-29. Writing Locations or passing a plain string for locations does not match the schema.

templateName accepts the template slug. In our test responses, templateName came back as the slug (for example youtube-channel-scraper) together with a numeric templateId.

Wrong pattern (blind retry starts a second task)

# Wrong: every execute_task call creates a new task, so a retry doubles the run
try:
    run = execute_task(template, params)
except Exception:
    run = execute_task(template, params)  # second run, second set of alerts

Correct diff (keyed on zpid, normalized, complete runs only)

import re

FOR_SALE = {"active", "home for sale", "house for sale", "zillow preview"}

def norm_price(p):
    digits = re.sub(r"[^\d]", "", p or "")
    return int(digits) if digits else None

def norm_status(s):
    s = (s or "").strip().lower()
    return "for_sale" if s in FOR_SALE else f"unknown:{s}"

def index(rows):
    return {r["zpid"]: {"price": norm_price(r["price"]),
                        "status": norm_status(r["status"])}
            for r in rows if r.get("zpid")}

def diff(prev_rows, curr_rows, min_ratio=0.8):
    if len(curr_rows) < min_ratio * len(prev_rows):
        raise ValueError("Partial run: skip diff, do not alert")
    prev, curr = index(prev_rows), index(curr_rows)
    events = []
    for zpid, c in curr.items():
        p = prev.get(zpid)
        if p is None:
            events.append(("new", zpid))
        elif c["price"] and p["price"] and c["price"] < p["price"]:
            events.append(("price_drop", zpid, p["price"], c["price"]))
        elif c["status"] != p["status"]:
            if "unknown:" in c["status"] + p["status"]:
                print("Unmapped status label, log only:", zpid, c["status"])
            else:
                events.append(("status_change", zpid, p["status"], c["status"]))
    return events

The FOR_SALE set contains the four labels that appeared in our real 2026-09-10 export. Treat it as a starting list, not a complete one.

FAQ about Zillow listing monitors

Does Octoparse send Zillow price-drop alerts on its own?

No. The documented MCP tools and API endpoints run templates and export data. Diffing and alerting are built in your own code, n8n workflow, or agent.

Which field should I use to deduplicate Zillow listings?

zpid. It is Zillow’s property ID and the Octoparse Zillow template returns it on every row. In our test export it was unique on all 60 sampled rows.

Why does my monitor flag status changes that did not happen?

Zillow listings that are all for sale can come back with different status labels. Our real export of one Austin search had 4 different labels. Normalize status to one canonical value before comparing.

Why did my monitor report dozens of listings as removed overnight?

The latest run was probably partial: stopped early, capped by targetMaxRows, or exported before it finished. Only diff complete runs, and require a listing to be missing from 2 consecutive complete runs before calling it removed.

How do I avoid duplicate runs when I hit a rate limit?

Never retry execute_task blindly: each call creates a new task, which we confirmed across 3 test calls that returned 3 different taskId values. Look up the task that was already created and poll it. For other calls, wait until the X-RateLimit-Reset timestamp and retry. Store each processed taskId and lotNo pair and skip pairs you have already diffed. The Octoparse OpenAPI allows 20 requests per second; the MCP server allows 300 requests per minute per OAuth session.

Can the AI agent calculate the price change itself?

Only from data you give it. If the agent has no stored previous snapshot, any “previous price” it states is invented. Compute the diff in code and let the agent explain the result.

How much does a daily Zillow run cost?

The Zillow Listing Scraper template was billed at $0.1 per 1,000 rows on a Standard plan when we checked the template page on 2026-09-10. On the Free plan, MCP and API runs share a 2,000-records-per-month quota, which one 574-row daily run would use up in four days. Plan pricing changes; confirm on the pricing page before budgeting.

Can I get only new or changed rows from exportData?

Not as a documented option as of September 2026. exportData returns the task’s collected data. Store each snapshot and compute changes yourself.

Get Web Data in Clicks
Easily scrape data from any website without coding.
Free Download
image
Get web automation tips right into your inbox
Subscribe to get Octoparse monthly newsletters about web scraping solutions, product updates, etc.

Get started with Octoparse today

Free Download

Related Articles