Complete guide to meter: intelligent web scraping with change detection
meter is an intelligent web scraping platform that automatically generates extraction strategies and monitors websites for changes. Simply describe what you want to extract, and meter figures out how to get it—no coding required.
meter provides three main capabilities:
meter makes web scraping simple with an AI-powered workflow:
Provide a URL and describe what you want to extract. meter's AI analyzes the page structure and automatically creates an extraction strategy. This is a one-time setup that typically takes 10-30 seconds.
The system intelligently identifies the best way to extract your data, handling edge cases automatically.
Review the preview data to see what was extracted. If something is missing or incorrect, provide feedback and the strategy will be refined automatically—no need to re-scrape the page.
The system automatically handles common issues like invalid selectors, missing containers, and variant class names.
Once you have a strategy, you can run scrapes on-demand or set up automatic scheduling. All scrapes use the saved strategy with raw scraping—no AI costs, instant results.
How it works: meter uses an LLM once during strategy generation to analyze the page and create extraction rules. After that, all scrapes use fast, consistent extraction instead of LLM calls. This gives you the best of both worlds: AI-powered setup with the speed, consistency, and cost-efficiency of traditional scraping.
Each scrape extracts the data and automatically detects changes compared to previous runs, so you only process what's new.
meter automatically detects when content changes, ignoring noise like ads, timestamps, and layout updates. You only get notified about meaningful changes.
You can compare any two scrapes to see what changed. For automated workflows, set up webhooks when creating schedules to receive scrape results via HTTP POST when jobs complete.
$0.02-0.10 per scrape → $730-3,650/year for daily scrapes
$0.02-0.06 once → $0/year for unlimited scrapes
Set up 100 sites for $2-6, then scrape forever at no cost.
A strategy is a saved extraction configuration that defines how to extract data from a website. meter automatically figures out how to extract your data based on your description.
Strategies are generated once with LLMs, then reused for all future scrapes at $0 cost.
A job is a single execution of a strategy against a URL. Jobs are processed asynchronously and include:
A schedule defines recurring scrapes. Supports:
0 9 * * * for daily at 9 AM)Schedules can be enabled/disabled and updated without deleting them. When a scheduled scrape completes, you can receive the results via webhook for real-time processing.
meter automatically detects when content changes by comparing each scrape to previous runs. It intelligently identifies meaningful changes while ignoring noise like ads, timestamps, and layout updates.
The API provides endpoints to compare any two jobs and see what changed.
Let's extract Hacker News front page posts. This tutorial uses a public URL with no authentication required.
Navigate to your dashboard and click "New Strategy" or "New Job".
URL: https://news.ycombinator.com
Description: "Extract post titles and scores"
Click "Generate" and wait 10-30 seconds. meter's AI will analyze the page and create an extraction strategy.
Review the preview data to see what was extracted. If it looks good, click "Run Job" to execute a scrape.
View your results in JSON format. You've successfully completed your first scrape!
Use the web dashboard (recommended for most users)
Use the Python SDK (for automation and integrations)
Takes 2 minutes, no coding required.
Sign up at /login. API keys are automatically generated when you sign in. You can view your API key in the dashboard.
from meter_sdk import MeterClientclient = MeterClient(api_key="sk_live_...")result = client.generate_strategy( url="https://example.com/products", description="Extract product names and prices", name="Product Scraper")strategy_id = result["strategy_id"]print(result["preview_data"])If the preview data looks incomplete, refine the strategy with feedback:
refined = client.refine_strategy( strategy_id=strategy_id, feedback="Missing the first product on the page")Create a job to execute the scrape (no LLM costs!):
job = client.create_job( strategy_id=strategy_id, url="https://example.com/products")# Wait for completioncompleted = client.wait_for_job(job["job_id"])results = completed["results"]Set up automatic monitoring:
schedule = client.create_schedule( strategy_id=strategy_id, url="https://example.com/products", interval_seconds=3600, # Every hour webhook_url="https://your-app.com/webhooks/scrape-results" # Optional)Or use cron expressions for more complex schedules. See the Webhooks Deep Dive section for details on webhook payloads.
When a scheduled scrape completes, meter sends a POST request to your webhook URL with the job results.
Your endpoint will receive a POST request with the following JSON payload:
{ "job_id": "job_abc123", "strategy_id": "strat_xyz789", "status": "completed", "results": [ {"title": "Example Post", "score": 42}, ... ], "has_changes": true, "change_summary": { "items_added": 3, "items_removed": 1, "items_modified": 2 }, "timestamp": "2024-01-15T10:30:00Z"}Your endpoint should respond with 200 OK to indicate successful receipt.
Retry Logic: meter will retry failed webhooks up to 3 times with exponential backoff. If your endpoint is down or returns an error, meter will:
Here's a simple Python Flask example:
from flask import Flask, requestapp = Flask(__name__)@app.route("/webhooks/scrape-results", methods=["POST"])def handle_webhook(): data = request.json if data["has_changes"]: print(f"Changes detected: {data['change_summary']}") process_results(data["results"]) return "OK", 200Pull-based change detection allows you to poll for content changes instead of receiving webhooks. This is useful for:
Migration: 003_add_seen_flag.sql
-- Add seen flag to scrape_jobsALTER TABLE scrape_jobs ADD COLUMN seen BOOLEAN DEFAULT FALSE;-- Index for fast unseen lookupsCREATE INDEX idx_scrape_jobs_schedule_unseenON scrape_jobs (schedule_id, seen, completed_at DESC)WHERE seen = FALSE AND status = 'completed';ScrapeJob Model: seen (boolean) - Whether this change has been acknowledged
GET /api/schedules/{schedule_id}/changes
Query Parameters:
mark_seen (boolean, default: true) - Auto-mark changes as seen when fetchedResponse:
{ "schedule_id": "uuid", "changes": [ { "id": "uuid", "url": "https://example.com", "status": "completed", "results": [...], "item_count": 10, "completed_at": "2024-01-15T10:30:00Z", "seen": true } ], "count": 3, "marked_seen": true}Returns:
curl -X GET "https://api.meter.sh/api/schedules/{schedule_id}/changes" \-H "Authorization: Bearer YOUR_API_KEY_HERE"Returns changes and marks them as seen
curl -X GET "https://api.meter.sh/api/schedules/{schedule_id}/changes?mark_seen=false" \-H "Authorization: Bearer YOUR_API_KEY_HERE"Returns changes but keeps them unseen
// Poll every 30 seconds for changessetInterval(async () => { const response = await fetch(`/api/schedules/${scheduleId}/changes`); const { changes, count } = await response.json(); if (count > 0) { console.log(`${count} new changes detected`); updateUI(changes); }}, 30000);A job is considered "changed" if:
Per-URL tracking means:
| Feature | Push (Webhooks) | Pull (Changes Endpoint) |
|---|---|---|
| Delivery | Real-time POST to webhook URL | Poll on demand |
| Setup | Configure webhook_url on schedule | Just call endpoint |
| Use Case | Trigger actions immediately | Display in dashboard |
| Network | Requires publicly accessible endpoint | Any client can poll |
| State | Stateless | Tracks seen/unseen |
Use both together:
200 OK within 30 secondsAll API endpoints require authentication via API key. API keys are automatically generated on the frontend when you sign in.
Note: You can use the Python SDK for programmatic access, or use the web interface for all operations. The SDK provides a clean, Pythonic interface to the API.
Install the Python SDK from GitHub (not yet available on PyPI):
Initialize the client with your API key (generated automatically on the frontend):
from meter_sdk import MeterClientclient = MeterClient(api_key="sk_live_...")Remember: All operations can also be done directly from the web interface—no coding required!
client.generate_strategy(url, description, name) - Generate a new strategyclient.refine_strategy(strategy_id, feedback) - Refine with feedbackclient.list_strategies() - List all strategiesclient.get_strategy(strategy_id) - Get strategy detailsAll of these can also be done from the web dashboard.
client.create_job(strategy_id, url) - Create a scrape jobclient.get_job(job_id) - Get job status and resultsclient.wait_for_job(job_id) - Poll until completionclient.compare_jobs(job_id1, job_id2) - Compare two jobsJobs can be created and monitored from the dashboard.
client.create_schedule(strategy_id, url, interval_seconds, webhook_url) - Create a schedule (webhook optional)client.update_schedule(schedule_id, enabled, ...) - Update scheduleclient.list_schedules() - List all schedulesSchedules can be created and managed from the dashboard. Webhooks receive POST requests with job results when scrapes complete.
For complete SDK documentation, see the SDK README. For raw API documentation, visit /docs on your API server.
| Feature | meter | Firecrawl | Apify |
|---|---|---|---|
| Cost per scrape | $0 (after setup) | $0.002-0.01 | Credit-based |
| Change detection | ✅ Built-in | Manual re-scrape | Basic webhooks |
| AI-powered extraction | ✅ Yes | ❌ No | ❌ No |
| Handles anti-bot | ✅ Yes | ⚠️ Partial | ✅ Yes |
| Learning curve | 5 minutes | 30 minutes | 2+ hours |
meter gives you Firecrawl's simplicity + Apify's power, without either's costs or complexity.
Extract all forum posts including the original post and replies:
from meter_sdk import MeterClientclient = MeterClient(api_key="sk_live_...")result = client.generate_strategy( url="https://forum.example.com/thread/123", description="Extract all forum posts including the original post and all replies. Each post has author and content.", name="Forum Posts")# Run a scrapejob = client.create_job(result["strategy_id"], "https://forum.example.com/thread/123")completed = client.wait_for_job(job["job_id"])print(f"Extracted {len(completed['results'])} posts")The system will automatically find all posts, including the original post and all replies, even if they use different class names.
Extract product names and prices:
from meter_sdk import MeterClientclient = MeterClient(api_key="sk_live_...")strategy = client.generate_strategy( url="https://shop.example.com/products", description="Extract product names and prices", name="Product Scraper")# Set up daily monitoringschedule = client.create_schedule( strategy_id=strategy["strategy_id"], url="https://shop.example.com/products", cron_expression="0 9 * * *", # Daily at 9 AM webhook_url="https://your-app.com/webhooks/products" # Optional)The system will automatically identify product listings and extract names and prices from each product. You can also do this entirely from the web interface.
Extract metrics with numbers and labels:
from meter_sdk import MeterClientclient = MeterClient(api_key="sk_live_...")strategy = client.generate_strategy( url="https://company.com/metrics", description="Extract metrics with numbers like '340,000', '85,000+', '97.6%' and their labels like 'Engineering Hours Saved', 'Vulnerabilities'", name="Metrics Dashboard")# Check for changes over timehistory = client.get_strategy_history(strategy["strategy_id"])if len(history) > 1 and history[0]["has_changes"]: print("Metrics have changed!")The system will automatically find metric cards and extract the numeric values with their corresponding labels. All of this can also be done from the web dashboard.
Cause: Container selector doesn't exist or item selector is wrong.
Solution: System auto-fixes invalid containers and retries. Check logs for "Auto-fixed invalid container" message.
Manual fix: Refine with feedback: "Found 0 items, try using body as container"
Cause: First item may have a different class name than other items.
Solution: The system automatically detects variant class names. If it still misses items, refine with feedback.
Manual fix: Refine with feedback: "Missing the first item at the top"
Cause: Item selector too broad or wrong container.
Solution: Use a more specific container or item class.
Manual fix: Refine with feedback: "Getting navigation items, need only post content"
Cause: Item selector matches parent instead of individual items.
Solution: System validates and warns if items are identical.
Manual fix: Look for a more specific selector that matches individual items.
Possible causes:
Solution: Verify the URL is accessible in a browser, check if the page requires login, review error messages in the dashboard.
meter uses a cost-efficient model:
Example: Set up 100 sites for $2-6 one-time, then run daily scrapes forever at no additional cost. This is much cheaper than traditional LLM-based scraping that charges per scrape.
Need more help? Check the API documentation or contact support.