跳到正文
DEV Community· Omar Eldeeb·· 3 小時前AI 評分51

如何不依賴 Keepa,自建 Amazon 價格歷史追蹤系統

Amazon Price History Without Keepa: Build Your Own

AI 導讀

Omar Eldeeb 示範以排程抓取建立 Amazon 價格歷史,並將每次讀數追加至 SQLite,作為 Keepa 以外的自建方案。流程使用 Apify 上的 E-commerce Product Scraper Pro 抓取資料,再按 UTC 日期計算每日最低價和中位數,並繪製階梯圖。

正文

To see an Amazon product's past prices today, use Keepa or camelcamelcamel: both chart price history on the product page and send price-drop alerts. To own the data instead (any Amazon storefront or other retailer, your own schedule, raw rows in your database), schedule a scraper and append every reading to SQLite. Your own history starts the day you start, so begin before you need it.

This guide covers what the two trackers offer, when your own history is the better choice, and a tested Python pipeline: E-commerce Product Scraper Pro runs on Apify, rows go into SQLite, and a report prints the daily minimum and median with a step chart.

What Keepa and camelcamelcamel offer

From their own browser-extension listings, checked on 2026-10-20:

Keepa camelcamelcamel (The Camelizer)
History chart on the Amazon page Yes: "View interactive price history charts directly on the Amazon product page" Yes: "price history charts inside Firefox when browsing products at Amazon"
Alerts Price drops below your target and back-in-stock Email when the price drops to your target
Amazon storefronts .com, UK, Germany, France, Japan, Canada, Italy, Spain, India, Mexico, Brazil "United States, Canada, (most of) Europe, Australia"
Cost "No account required to start"; see keepa.com for plans and prices Described as a "free service"
Raw data API with a Keepa API key Not offered in the listing

When they've been tracking a product for a while, their charts show prices from before you heard of it. Nothing you build today can match that. For a quick "is this a good price?" check on a single Amazon product, use them.

When your own history is better

  • Other stores in the same table. Keepa and camelcamelcamel are Amazon trackers. If you compare Amazon with Walmart, Target, IKEA or Shopify stores, one pipeline that writes every store's rows to one table is simpler.
  • Your own cadence. Hourly readings in Prime Day week, daily otherwise, on exactly the products you choose.
  • Raw rows you keep. Timestamps, stock state, currency and the run that produced each reading, ready to join with your own sales or to feed a model.
  • Fields a price chart doesn't show. Each reading also carries inStock, availability, stockQty ("only 1 left"), rating and reviewCount.

The trade-off: no backfill. If you need last year's Prime Day prices, you need a tracker that was watching last year.

Step 1: record Amazon prices on a schedule

The input, with product URLs from two storefronts (the actor supports amazon.com and amazon.de; the other 22 Amazon storefronts are best-effort and often need residential traffic):

{
  "detailsUrls": [
    { "url": "https://www.amazon.com/dp/B088NRLMPV" },
    { "url": "https://www.amazon.com/dp/B09XS7JWHH" },
    { "url": "https://www.amazon.de/dp/B0CHYDJW36" }
  ],
  "includeVariants": false,
  "maxProductResults": 3
}

Run on 2026-10-20, it returned the Anker USB-C cable at $9.99 (InStock), the Sony WH-1000XM5 at $199.99 (LimitedAvailability) and a LISEN USB-C cable on amazon.de at €6.28. Each Amazon storefront is pinned to its own currency.

Save it as a task and add an Apify schedule such as 30 7 * * * (07:30 every day in the schedule's timezone; 30 runs in 30 days). Don't turn on onlyChanges for a history: you want a reading every day, including the days nothing changed.

Step 2: append every run to SQLite

This pulls the rows of your latest successful runs from the Apify API and inserts them. The primary key (ASIN, storefront, timestamp) makes it safe to run as often as you like: rows already stored are skipped.

"""Append every scheduled run's Amazon rows to SQLite. pip install apify-client"""
import os
import re
import sqlite3

from apify_client import ApifyClient

ACTOR = "constructive_calm/ecommerce-product-scraper-pro"   # or your saved task's ID (see below)
DB = sqlite3.connect("price_history.db")
DB.execute("""CREATE TABLE IF NOT EXISTS prices (
    asin TEXT, site TEXT, scraped_at TEXT, price REAL, currency TEXT,
    in_stock INTEGER, availability TEXT, name TEXT, run_id TEXT,
    PRIMARY KEY (asin, site, scraped_at))""")

client = ApifyClient(os.environ["APIFY_TOKEN"])
runs = client.actor(ACTOR).runs().list(desc=True, limit=200, status="SUCCEEDED").items
# For a saved task: client.task("<your-task-id>").runs().list(...)

added = 0
for run in runs:
    for row in client.dataset(run["defaultDatasetId"]).iterate_items():
        m = re.search(r"/dp/([A-Z0-9]{10})", row.get("url") or "")
        if row.get("recordType") != "product" or not m:
            continue
        cur = DB.execute(
            "INSERT OR IGNORE INTO prices VALUES (?,?,?,?,?,?,?,?,?)",
            (m.group(1), row["site"], row["scrapedAt"], row.get("price"), row.get("currency"),
             None if row.get("inStock") is None else int(row["inStock"]),
             row.get("availability"), row.get("name"), run["id"]))
        added += cur.rowcount
DB.commit()
total = DB.execute("SELECT COUNT(*), COUNT(DISTINCT asin || site) FROM prices").fetchone()
print(f"{len(runs)} runs read, {added} new rows, {total[0]} rows for {total[1]} products in the DB")

Run against my account's runs on 2026-10-20 (which include our October test runs of the actor):

200 runs read, 10 new rows, 226 rows for 94 products in the DB

A second run straight after adds 0 rows unless a new run finished in between. For a scheduled task, read client.task(...) runs instead of the actor's, so other jobs don't mix in.

Keep NULL prices. One amazon.de reading came back with no price and the availability text "Dieser Artikel kann nicht an den von dir ausgewählten Zustellungsort versendet werden" (Amazon saying it can't deliver to the selected location). That isn't a price of zero and it isn't out of stock; the report below skips it.

Step 3: daily min, median and a chart

Repeated checks on one day would outweigh quiet days in a median, so the report first keeps the last reading per UTC day:

"""Min / median / max per product from price_history.db, plus a step chart. pip install matplotlib"""
import sqlite3
import statistics
from datetime import datetime

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

DB = sqlite3.connect("price_history.db")
WINDOW = "-30 days"

print(f"{'product':<22}{'days':>5}{'min':>9}{'median':>9}{'max':>9}{'latest':>9}  vs median")
for asin, site in DB.execute("""SELECT asin, site FROM prices WHERE price IS NOT NULL
                                GROUP BY asin, site ORDER BY COUNT(*) DESC LIMIT 6"""):
    rows = DB.execute("""SELECT scraped_at, price FROM prices
                         WHERE asin=? AND site=? AND price IS NOT NULL
                           AND scraped_at >= strftime('%Y-%m-%dT%H:%M:%fZ', 'now', ?)
                         ORDER BY scraped_at""", (asin, site, WINDOW)).fetchall()
    daily = {t[:10]: p for t, p in rows}                  # last reading per UTC day
    prices = list(daily.values())
    if not prices:
        continue
    med, latest = statistics.median(prices), prices[-1]
    print(f"{site + ':' + asin:<22}{len(prices):>5}{min(prices):>9.2f}{med:>9.2f}{max(prices):>9.2f}"
          f"{latest:>9.2f}  {100 * (latest - med) / med:+.1f}%")

asin, site = "B0CHYDJW36", "amazon-de"                    # chart one product
rows = DB.execute("SELECT scraped_at, price FROM prices WHERE asin=? AND site=? AND price IS NOT NULL "
                  "ORDER BY scraped_at", (asin, site)).fetchall()
x = [datetime.fromisoformat(t.replace("Z", "+00:00")) for t, _ in rows]
y = [p for _, p in rows]
fig, ax = plt.subplots(figsize=(8, 3))
ax.step(x, y, where="post", label="price")
daily = list({t[:10]: p for t, p in rows}.values())
ax.axhline(statistics.median(daily), linestyle="--", label="median of daily prices")
ax.axhline(min(daily), linestyle=":", label="min")
ax.set_title(f"{site} {asin}")
ax.legend()
fig.autofmt_xdate()
fig.savefig("history.png", dpi=120, bbox_inches="tight")
print("chart saved: history.png")

Output on 2026-10-20:

product                days      min   median      max   latest  vs median
amazon-us:B088NRLMPV      4     9.99     9.99     9.99     9.99  +0.0%
amazon-de:B0DXDTX47W      3    10.95    12.99    12.99    10.95  -15.7%
amazon-de:B0CHYDJW36      3     6.28     7.99     7.99     6.28  -21.4%
amazon-de:B0GQSYJDHM      2     5.09     5.09     5.09     5.09  +0.0%
amazon-gb:B088NRLMPV      3     7.49     9.49     9.49     7.49  -21.1%
amazon-de:B08D6NCQ1Z      2     9.38     9.38     9.38     9.38  +0.0%

These are three or four days of test data, so treat it as a demonstration of the mechanics, not of Amazon pricing. It already shows something real: the amazon.de cable read €6.28 on 17 October, €7.99 from late that evening through 18 October, and €6.28 again on 20 October. A 30-day median built on daily readings is what tells you whether a "deal" is below the usual price or just back to it.

Two cautions for the chart. A step line assumes the price held until the next reading, so a two-day gap draws a flat line you never actually saw. And vs median is only meaningful once you have a few weeks of days.

What it costs

The actor is pay per event. From the README's pricing table, a product row costs $0.0048 on FREE and $0.0008 on GOLD, plus $0.005 per run start. amazon.com is reached through datacenter connections with no traffic fee:

  • 100 amazon.com ASINs, daily: FREE 100 × $0.0048 + $0.005 = $0.485 per day, $14.55 for 30 days. GOLD 100 × $0.0008 + $0.005 = $0.085 per day, $2.55 for 30 days.
  • amazon.co.uk mostly needs residential traffic, 0.3-0.5 MB per product in the README's measurements, so the README's own example for 100 amazon.co.uk products is $0.46-$0.71 per run on GOLD.

Apify's free plan includes $5 of usage a month with no credit card required; the actor has no in-actor free trial.

Amazon-specific limits

From the README and our tests: storefronts outside the US often need residential traffic; amazon.de delivered 84% and amazon.co.uk 95% of deliverable products in the latest cloud tests; a page that says the product can't ship to the proxy's location is retried on another route and otherwise delivered without a price and with a note (the NULL row above). Expect some run-to-run variation, and read the Status dataset when a product is missing.

During Black Friday and Prime events

Amazon's big events are where a history pays off: Black Friday 2026 is Friday 27 November and Cyber Monday 30 November. In 2025 Amazon's US "Black Friday Week" deals started on 20 November and ran through Cyber Monday, 1 December (About Amazon); the 2026 dates are usually announced in the weeks before. Start daily readings now so you have about a month of baseline, then add a second, temporary schedule on the same task for the event week (for example every three hours) and delete it afterwards. Summarising to one price per day keeps that burst from skewing your medians. The Prime Day price tracker covers Prime Day and Prime Big Deal Days, and the holiday sales calendar lists every event's dates and baseline start.

Related: the Amazon price scraper guide (ASIN inputs across marketplaces), price drop alerts in Slack, and the e-commerce price tracker query builder.

Disclosure: I built the actor and this site. Keepa and camelcamelcamel aren't affiliated with either; the SQLite pipeline works with rows from any scraper.

Sources: Keepa extension listing (Firefox Add-ons), The Camelizer extension listing (Firefox Add-ons), Keepa API backend on GitHub, About Amazon: When is Black Friday 2025, E-commerce Product Scraper Pro README (pricing, Amazon limits), Apify docs: Schedules, and actor runs on 17-20 October 2026.

來源:DEV Community · dev.to