πΈοΈ Site Update β Hybrid Web Extraction & Orchestration Node for Android
A mobile-first, on-device data extraction engine. It loads real web pages in a (hidden) Android WebView, runs scripted interactions, extracts exactly the data you care about, tracks changes over time, and delivers the result as a notification, a log entry, or an encrypted webhook to your own backend.
π Table of Contents
- Overview
- Why This Exists
- Feature Summary
- Real-World Use Cases
- Architecture & Execution Pipeline
- Feature Deep Dive
- UI Walkthrough
- Custom JavaScript Guide
- Backend Integration (Webhook + Encryption)
- Log Export Format
- Security Model
- Design Decisions
- Known Limitations
- Responsible Use
- Roadmap
- Engineering Workflow & Role
- Source Code & Contact
π Overview
Site Update turns an Android phone into a small, programmable data-collection node. Instead of relying on a cloud scraper that must be hosted, paid for, and kept alive, the app runs the whole pipeline on the device:
Schedule β Load page β Wait β Click sequence β Extract β Clean β Compare β Notify / Log / Webhook
Every monitor is configured on the phone, runs in the background, and can end in any combination of:
- a native Android notification,
- an entry in a per-monitor history log (exportable as text),
- an optional webhook / API call whose body can be encrypted end-to-end (AES-256-GCM) for your own backend.
The same app handles a one-line CSS selector for a parcel tracker and a full JavaScript routine that walks a job board and returns a formatted result.
π‘ Why This Exists
The project grew out of real problems hit while scraping and monitoring sites by hand:
| Problem | How the app addresses it |
|---|---|
| Pages with no API, RSS, or email alerts (courier status, government notices) | Poll the page on a schedule and notify only when the extracted value changes |
| Content rendered by JavaScript, so a plain HTTP request sees an empty shell | Render the page in a real Android WebView (hidden, in the background) before extracting |
| Content hidden behind tabs, dropdowns, or cookie banners | Scripted click sequences with a wait after each step |
| Login walls with 2FA/CAPTCHA that cannot be automated reliably | Log in once on a desktop browser, export the session cookies as JSON, and import them into the app |
| Random or changing CSS class names | Contextual extraction (anchor on a keyword, traverse to the parent) and full custom JavaScript |
| Raw HTML noise in alerts | Cleaned-text output and a notification template ($s) |
| Getting the data out of a phone into a database | Webhook with optional encrypted payload, plus plain-text log export |
β¨ Feature Summary
| Area | Capability |
|---|---|
| Extraction modes | Matched element with parent traversal Β· Custom CSS selector Β· Custom XPath Β· Regular expression Β· Custom JavaScript |
| Scoping | Base Element: Whole Page Β· CSS Selector Scope Β· XPath Scope Β· JavaScript Code Scope Β· target keyword Β· numeric index for the n-th match |
| Rendering | Standard fetch Β· Hidden background WebView for JavaScript-heavy sites |
| Interaction | Wait (s) after page load Β· optional Auto-Click Sequence (Add Click Action) with a configurable wait after each click |
| Authentication | Persistent auth session with a live status indicator and in-app Login to Site Β· JSON cookie import from a desktop browser |
| Scheduling | Per-monitor interval (minutes) Β· randomized jitter Β· per-monitor on/off toggle |
| Change detection | Stores last extracted value Β· trigger rules (e.g. Contains Target) Β· case-sensitivity option Β· duplicate-alert suppression |
| Output cleaning | Cleaned text output (tags removed, whitespace normalized) |
| Notifications | Native Android notification with a message template ($s = extracted text) |
| History | Timestamped per-monitor log Β· "Visit Website" shortcut Β· copy to clipboard Β· export as text |
| Integrations | Webhook / API call (POST, custom headers, $s body sent as UTF-8 text) |
| Encryption | Optional AES-256-GCM payload encryption (PBKDF2-HMAC-SHA256 key derivation) Β· in-app decryption guides for Python, Node.js, PHP |
| Backup | Password-protected, encrypted export and import of monitor configuration |
| Debugging | "Test & Download Rendered HTML" to inspect exactly what the engine sees after rendering |
π― Real-World Use Cases
- Package tracking: watch the latest milestone on courier and e-commerce tracking pages (Amazon, Bluedart, local couriers) and get the newest status as a notification.
- Job and exam notices: monitor recruitment and exam portals (e.g. staff-selection, teacher-eligibility, and state public-service pages) for new vacancy or notification text.
- Price watching: track a product price and alert when the value changes or contains a target string.
- Weather and status pages: pull a single line (temperature, "feels like") from a page that offers no alerts.
- Personal data pipeline: forward each extracted value to your own API, store it in PostgreSQL, and analyze change history over time.
ποΈ Architecture & Execution Pipeline
High-level flow of one monitor run
flowchart TD
A["Scheduler<br/>interval + jitter"] --> B["Load target URL"]
B --> C{"Hidden WebView<br/>enabled?"}
C -- "No" --> D["Fetch page content"]
C -- "Yes" --> E["Render in background WebView<br/>(with imported session, if any)"]
D --> F["Wait after load<br/>(fixed seconds)"]
E --> F
F --> G["Click sequence<br/>click β wait β click β wait"]
G --> S["Apply search scope<br/>Whole Page / CSS / XPath / JS scope"]
S --> H["Extraction rule<br/>CSS / XPath / Regex / JS / Parent traversal"]
H --> I["Sanitize output<br/>strip tags, normalize whitespace"]
I --> J{"Changed since last run<br/>and trigger rule matched?"}
J -- "No / duplicate" --> K["Stop (no alert)"]
J -- "Yes" --> L["Save to history log"]
L --> M["Android notification<br/>(template with $s)"]
L --> N{"Webhook<br/>enabled?"}
N -- "Yes" --> O["Build body from $s<br/>optionally encrypt AES-256-GCM"]
O --> P["POST to your API"]
Components
| Component | Responsibility |
|---|---|
| Scheduler | Runs each enabled monitor at its interval, with randomized jitter added to avoid a rigid fixed pattern |
| Page loader | Chooses between a standard fetch and a hidden WebView, applies saved cookies/session |
| Interaction engine | Applies the post-load wait, then executes the click sequence step by step |
| Extraction engine | Applies the chosen rule inside the configured scope and returns text |
| Sanitizer | Removes markup and normalizes whitespace so the payload is clean text |
| State store | Remembers the last extracted value per monitor for delta comparison, and keeps the history log |
| Notifier | Formats the message template and posts the Android notification |
| Webhook client | Sends the (optionally encrypted) payload to a user-defined endpoint with custom headers |
| Crypto module | AES-256-GCM encryption with PBKDF2-derived keys, used for payloads and backups |
| Backup module | Password-protected export/import of monitor configuration |
π¬ Feature Deep Dive
1. Four-step monitor configuration
Each monitor is defined in four logical steps:
- Search Scope: choose the Base Element: Whole Page (default), CSS Selector Scope (e.g.
table#data), XPath Scope (e.g.//table), or JavaScript Code Scope. Everything after this step searches only inside that region. - Search Target: a keyword to anchor on, plus a numeric index to pick the n-th occurrence inside the scope.
- What to Extract: the extraction rule, its selector / XPath / pattern, an Index to pick which match to use, and the output format (Cleaned Text: no tags, spaces fixed by default).
- Alert Conditions: trigger rule, case sensitivity, duplicate suppression, interval, jitter, and the notification message.
The top of the form holds the URL, Keep Auth Session (with a session status indicator and a Login to Site button), Use Hidden WebView (with a Wait (s) field), the optional Auto-Click Sequence, and a Test & Download Rendered HTML button for quick debugging.
2. Extraction modes
| Mode | Best for |
|---|---|
| Matched Element (with Parent Traversal) | Pages with random or unstable class names. Anchor on stable visible text, then walk up the DOM to capture the whole surrounding block |
| Custom CSS Selector | Stable, well-structured pages. Targets an element by what it is, not where it sits, so it survives layout shifts |
| Custom XPath | Structural queries that CSS cannot express |
| Regular Expression | Pulling a pattern (price, ID, date) out of text |
| JavaScript Code | Anything dynamic: iterate nodes, combine fields, clean text, apply logic, and return a pre-formatted string |
3. Hierarchical scoping and indexing
Selector ambiguity is a common cause of wrong data on busy pages. The Base Element setting limits the engine to one region of the DOM before any extraction happens:
| Base Element | What it does |
|---|---|
| Whole Page | Searches the entire document (default) |
| CSS Selector Scope | Restricts the search to the node matched by a CSS selector, e.g. table#data |
| XPath Scope | Restricts the search to the node matched by an XPath expression, e.g. //table |
| JavaScript Code Scope | Defines the search region with a script, for pages where neither a selector nor an XPath is enough |
Two Index fields then pick exactly which occurrence to use: one beside the Target Keyword (Step 2) and one beside the Selector/XPath (Step 3), for example the third match inside a given table.
4. Click sequences and timing
Many pages only reveal data after an interaction. Each monitor can define:
- Wait (s), set next to Use Hidden WebView, so the page can finish rendering before anything else happens.
- An optional Auto-Click Sequence: use Add Click Action to append steps. Each step is a CSS selector plus a wait time (seconds). Steps run in order, each followed by its own wait, then extraction starts.
Waits are deliberately strict durations, not "wait until visible" (see Design Decisions).
5. Hidden WebView and authenticated sessions
- Hidden background WebView: for JavaScript-driven sites, the app creates an invisible Android WebView inside a background service, lets the page render and run its scripts, then runs the extraction.
- Keep Auth Session: preserves the logged-in state between runs for sites that require login. A status indicator shows whether a session exists (for example No Session), and Login to Site lets you sign in from within the app.
- JSON session import: log in on a desktop browser (handling 2FA/CAPTCHA there), export the cookies as a JSON array, and paste them into the app. The WebView starts already authenticated, with no brittle automated login flow.
6. Scheduling and jitter
- Interval (min): base time between runs.
- Jitter (min): a random offset added to each run, so requests do not land at perfectly regular moments and a fleet of monitors does not fire simultaneously.
- Each monitor has an independent on/off toggle on the dashboard.
7. Change detection and alert conditions
- Stateful delta tracking: the last extracted value is stored per monitor and compared on every run.
- Trigger rule: for example Contains Target, with an optional case-sensitive switch.
- Ignore duplicate alerts: if the value has not changed, no notification is sent. This also prevents repeated alerts for an unchanged message.
8. Notification templating
The Notification Message field accepts free text plus the $s placeholder, which is replaced with the cleaned extracted text. For example:
Parcel update: $s
9. History log and export
- Every detected change is stored with a timestamp in a per-monitor history dialog (with a Visit Website shortcut and copy-to-clipboard).
- The log can be exported as plain text (format documented below). The export is intentionally raw; encrypt it separately if you need to share it.
10. Webhook / API call
Enable Trigger Webhook / API Call on a monitor to forward each detected change:
| Field | Description |
|---|---|
| API URL (POST) | Endpoint that receives the data |
| Headers | One Key: Value per line (for example Authorization, X-Monitor) |
| Payload / Body | Template; $s is replaced by the extracted text. Sent as plain UTF-8 text |
| Encrypt Payload | Optional AES-256-GCM encryption (see below) |
Sending the body as plain text means no character in the extracted value can break a JSON structure.
11. Payload encryption
When Encrypt Payload is enabled, the body is encrypted before it leaves the device.
| Parameter | Value |
|---|---|
| Cipher | AES-256-GCM (authenticated encryption) |
| Key derivation | PBKDF2-HMAC-SHA256, 10,000 iterations, 32-byte key |
| Salt / nonce | Random, generated for every payload |
| Wire format | Base64(Salt)]Base64(Nonce)]Base64(Ciphertext+Tag) |
| Transport | Sent as text; the backend treats it as a base64 string |
The receiver needs the same passphrase to decrypt. An in-app Decryption Guides screen provides ready-to-copy snippets for Python, Node.js, and PHP.
12. Encrypted backup (export / import)
- Export asks for a password and writes an encrypted file.
- Import requires the exact same password; a wrong password or a modified file fails authentication and nothing is imported.
πΈ UI Walkthrough
1. Main Orchestration Dashboard

The central hub. Lists every monitor with its name, most recent extracted value, interval, and an on/off toggle, plus a button to add a new monitor.
2. Task Initialization

Entry point for a new pipeline: task name, target URL, auth-session and hidden-WebView options, and a **Test & Download Rendered HTML** button to inspect what the engine sees after rendering.
3. Multi-Modal Extraction Rules

The extraction-rule dropdown: parent traversal, custom CSS selector, custom XPath, regular expression, or JavaScript code.
4. Granular Searching Scope

Choose Whole Page, CSS Selector Scope, XPath Scope, or JavaScript Code Scope to limit the search to one region and remove ambiguity on busy, deeply nested pages.
5. Advanced JavaScript Injection

Inject a custom JavaScript routine that iterates nodes, extracts nested elements, and returns a clean, pre-formatted string.
6. Auto-Click Sequences

Define ordered clicks (CSS selectors) with a wait after each, so the page reaches the right state before extraction.
7. Alert Conditions, Jitter & Scheduling

Base interval, jitter, trigger rule, duplicate suppression, and the notification template using `$s`.
8. Cross-Environment Session Import

Paste a cookie JSON array exported from a desktop browser to start an authenticated session without automating the login.
9. Authenticated Hidden WebView

The result of session import: a persistent, logged-in WebView session ready for background extraction.
10. Extraction History Log

Chronological history of detected changes per monitor, with timestamps and a link back to the page.
11. Android Notifications

The end-user output: a native notification containing the formatted extracted value.
12. Webhook / API Configuration

API URL, multi-line headers, body template with `$s`, and the **Encrypt Payload** option.
13. In-App Decryption Guides

Format description and copy-ready decryption code for Python, Node.js, and PHP, so a backend can be wired up quickly.
14. Session, Hidden WebView & Auto-Click (latest editor)

URL, **Keep Auth Session** with a status indicator and **Login to Site**, **Use Hidden WebView** with its **Wait (s)** field, the optional **Auto-Click Sequence** (**Add Click Action**), and **Test & Download Rendered HTML**.
15. Search Scope Options

The **Base Element** dropdown (Whole Page, CSS Selector Scope, XPath Scope, JavaScript Code Scope), followed by the extraction rule, selector with index, and output format.
16. Search Target & Extraction Rules

**Target Keyword** with index, the **Extraction Rule** dropdown (parent traversal, CSS selector, XPath, regex, JavaScript), and the alert **Trigger Rule** with the case-sensitivity option.
π§© Custom JavaScript Guide
The JavaScript rule runs inside the loaded page. The last evaluated expression is the result, and it should be a string.
Conventions
- Return a string. It becomes
$sin the notification, the log entry, and the webhook body. - Return an empty string for "nothing to report", which simply produces no alert.
- Handle failures inside the script. Because every site is different, the script decides what counts as a problem and what the alert should say (for example
'Layout changed: tracker container not found'). The app does not impose a generic error policy. - Normalize whitespace yourself when needed (
.replace(/\s+/g, ' ').trim()).
Example: latest delivery milestone from a tracking page
var tempElement = document.querySelector('#progressTracker-container');
var outputstring = tempElement
? [...tempElement.querySelectorAll('.milestone')]
.reverse()
.map(el => {
var status = el.querySelector('.milestone-primaryMessage')?.innerText
.replace(/\s+/g, ' ').trim() || '';
var details = el.querySelector('.milestone-explanation')?.innerText
.replace(/\s+/g, ' ').trim() || '';
return details ? `${status} - ${details}` : '';
})
.find(text => text.includes(' - ')) || ''
: '';
outputstring;
This is an illustration of the technique. Class names on third-party sites change, so selectors in examples may need updating.
Example: explicit "layout changed" reporting
var box = document.querySelector('#results');
if (!box) {
'Layout changed: #results not found';
} else {
box.innerText.replace(/\s+/g, ' ').trim();
}
π Backend Integration (Webhook + Encryption)
Request shape
POST https://your-server.example/events
Content-Type: text/plain; charset=utf-8
Authorization: Bearer <token>
X-Monitor: Amazon Tracking
<body: extracted text, or the encrypted string when Encrypt Payload is on>
Keep header values ASCII-only. Put free-form text in the body.
Decryption snippets
Python
import base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
def decrypt_payload(encrypted_str, password):
parts = encrypted_str.strip().split(']')
if len(parts) != 3:
raise ValueError("unexpected payload format")
salt = base64.b64decode(parts[0])
nonce = base64.b64decode(parts[1])
ciphertext = base64.b64decode(parts[2]) # includes the 16-byte tag
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=10000)
secret_key = kdf.derive(password.encode('utf-8'))
return AESGCM(secret_key).decrypt(nonce, ciphertext, None).decode('utf-8')
Node.js
const crypto = require('crypto');
function decryptPayload(encryptedStr, password) {
const parts = encryptedStr.trim().split(']');
const salt = Buffer.from(parts[0], 'base64');
const nonce = Buffer.from(parts[1], 'base64');
const ciphertextBuffer = Buffer.from(parts[2], 'base64');
const derivedKey = crypto.pbkdf2Sync(password, salt, 10000, 32, 'sha256');
const authTag = ciphertextBuffer.slice(-16);
const actualCiphertext = ciphertextBuffer.slice(0, -16);
const decipher = crypto.createDecipheriv('aes-256-gcm', derivedKey, nonce);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(actualCiphertext, undefined, 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
PHP
function decryptPayload($encryptedStr, $password) {
$parts = explode(']', trim($encryptedStr));
$salt = base64_decode($parts[0]);
$nonce = base64_decode($parts[1]);
$ciphertext = base64_decode($parts[2]);
$derivedKey = hash_pbkdf2('sha256', $password, $salt, 10000, 32, true);
$authTag = substr($ciphertext, -16);
$actualCiphertext = substr($ciphertext, 0, -16);
return openssl_decrypt($actualCiphertext, 'aes-256-gcm', $derivedKey,
OPENSSL_RAW_DATA, $nonce, $authTag);
}
Example receiver: FastAPI + PostgreSQL
A minimal reference implementation that accepts the webhook, decrypts, and stores each event.
CREATE TABLE monitor_events (
id BIGSERIAL PRIMARY KEY,
monitor TEXT NOT NULL,
value TEXT NOT NULL,
received_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
import os
import psycopg
from fastapi import FastAPI, Request, Header, HTTPException
app = FastAPI()
PASSWORD = os.environ["PAYLOAD_PASSPHRASE"]
DSN = os.environ["DATABASE_URL"]
@app.post("/events")
async def ingest(request: Request, x_monitor: str = Header(default="unknown")):
body = (await request.body()).decode("utf-8").strip()
try:
text = decrypt_payload(body, PASSWORD) # from the Python snippet above
except Exception:
raise HTTPException(status_code=400, detail="decryption failed")
text = text.replace("\x00", "") # PostgreSQL text cannot store NUL
with psycopg.connect(DSN) as conn:
conn.execute(
"INSERT INTO monitor_events (monitor, value) VALUES (%s, %s)",
(x_monitor, text),
)
return {"status": "ok"}
Receiver checklist: read the raw body (do not parse as JSON), trim whitespace before splitting, validate there are three parts, and treat decryption failure as a normal 400 response.
π Log Export Format
The text export is human-readable and consistent, so a single parser works for every file.
=== Log Export for Amazon Tracking ===
URL: https://example.eu/d/00V
[Oct 21, 2026 - 03:41 pm]
Package arrived at Delhi Airport at 4:15 am Wednesday, 21 October
--------------------------------------------------
Each entry starts with a [timestamp] line, followed by the extracted value, followed by a dashed separator. If the format ever changes, a version marker will be added to the header so parsers can tell the formats apart; files without one are the original format.
Parse it with Python
import re
text = open("export.txt", encoding="utf-8").read()
entries = re.findall(r"^\[(.+?)\]\n(.*?)(?=^\[|\Z)", text, re.S | re.M)
rows = [(ts, val.replace("-" * 50, "").strip()) for ts, val in entries]
π Security Model
| Asset | Protection |
|---|---|
| Webhook payload | Optional AES-256-GCM with a PBKDF2-derived key; tampering is detected by the authentication tag |
| Transport | Intended to be used over HTTPS, which also protects headers such as API tokens |
| Backup file | Password-protected, encrypted export; import requires the exact password |
| Log export | Plain text by design; encrypt separately before sharing if it contains sensitive values |
| Session cookies | Imported by the user for their own accounts and used only by the app's WebView |
Notes:
- The encryption passphrase is shared between the app and the receiver; keep it out of source control and load it from an environment variable or secret store on the server.
- Treat exports and backups like any file that may contain API URLs, tokens, and extracted data.
π§ Design Decisions
- Selectors over screen positions. Targets are described by what the element is (CSS/XPath/keyword), not where it appears, so extraction survives layout shifts.
- Strict time waits over "wait until visible". If a target element has been removed from a page, a visibility wait could hang forever. Fixed waits give every run a known maximum duration, which matters for a battery-conscious background job.
- Failure handling lives in the script. Different monitors need different definitions of failure (some do not care about outages at all). Letting each JavaScript routine return its own message avoids forcing hard-coded "site unavailable" alerts on every monitor.
- Plain-text webhook body. Sending raw UTF-8 text avoids escaping problems entirely; no character in scraped content can invalidate the body.
- Raw text log export. Logs are not always tabular; exporting text and shaping it with JavaScript or downstream scripts keeps the app flexible.
- Local-first. Scheduling, rendering, extraction, and storage all happen on the device; no cloud service is required.
β οΈ Known Limitations
- Selector dependence. Extraction depends on the target site's markup. If a site restructures its page, the selector or script must be updated.
- Polling, not push. Updates are detected on the configured interval; there is no real-time subscription.
- Android background scheduling. Timing can be affected by device battery optimization and vendor-specific restrictions, so very short intervals are not guaranteed.
- Login-protected sites rely on a valid imported session; sessions expire and must be refreshed.
- Third-party sites may restrict automated access. Behavior depends on each site's own policies and defenses.
π€ Responsible Use
This tool is built for personal monitoring of information you are entitled to see, such as your own parcels, public notices, and prices.
- Respect each website's terms of service and
robots.txt. - Use reasonable intervals; do not hammer servers.
- Use session import only with your own accounts.
- Do not use the tool to circumvent access controls or to collect data you are not permitted to access.
πΊοΈ Roadmap
Ideas under consideration:
- Send test payload button to verify URL, headers, and decryption in one step
- Versioned payload header (including the key-derivation iteration count) for future-proof decryption
- JSON Lines log export for direct database ingestion
- Retry queue for webhook deliveries that fail while offline
- "Simple" vs "Advanced" configuration modes to shorten the form for basic selector monitors
- Per-step failure messages for click sequences ("step 2: element not found")
π οΈ Engineering Workflow & Role
Role: Product and system design, feature specification, data flow, and orchestration logic.
Approach: The feature set comes directly from problems encountered while scraping and monitoring real sites. The system behavior (extraction modes, timing model, change detection, webhook and encryption design, backup format) was specified and tested by me; AI-assisted workflows were used to accelerate the native Android implementation.
Testing: Features were verified on a physical device against live sites, including webhook delivery, payload encryption and decryption, encrypted backup round-trips, and log export.
π¬ Source Code & Contact
The source code is currently private. This repository documents the architecture, features, and integration formats.
I am happy to provide a live technical walkthrough of the Android background services, extraction engine, and encryption design over a screen share.
- Name: Soumyadip Ghosh
- Email: ***
- LinkedIn: www.linkedin.com/in/SamDip
- GitHub: https://github.com/rotten-repo