What is the HTTP GET actually returning?
The operator sees 54, then 55 a minute later, in a Planned Qty label on a Perspective view. The browser's developer tools show the same number in the Elements pane. A script that calls system.net.httpClient() and client.get(url) against that page never finds it, even though the same line-number approach works on conventional server-rendered pages.
The script and the browser are not reading the same document. A server-rendered page puts its values into the HTML body, so the value can sit on a predictable line (line 22 in a working example) that a script re-reads on each poll. A Perspective page arrives as a small application shell of script references and a mount point. The browser runs that JavaScript, opens a live session back to the Ignition gateway, receives the view definition and bound property values, and then builds the DOM. The Elements pane in the developer tools shows that built DOM. It is not the HTTP response. An HTTP client that does not execute JavaScript stops at the shell, so no line number ever holds the quantity.
Check before moving on:
- Fetch the page and dump the body. In Ignition the
get()call returns a response object, so read the body from its text property (response.text) rather than splitting the response object directly. - Search the body for the value currently on screen.
- Compare the body length with the size of the DOM in the Elements pane.
If the value is absent and the body is mostly script tags, the page is client-rendered. No parsing strategy on the raw GET will recover the value.
Where does the number on the screen come from?
Trace the label back to its source. The label's text property is bound to something on the gateway, usually a tag, a named query or database binding, or an expression that does the math. That binding is what you want to read. The screen is only a copy of it. Ask the team that owns the view to open it in the Designer, select the Planned Qty component, and report the binding type and path.
| Binding found on the label | Where the value lives | Cleanest way to read it |
|---|---|---|
| Tag binding | Tag provider on the hosting gateway | WebDev endpoint that reads the tag, or a remote tag provider over the gateway network |
| Query / named query binding | Database the hosting gateway queries | Run the same query against the same database, or wrap it in a WebDev endpoint |
| Expression doing the calculation | Only in the view | Move the math into an expression tag or script on the gateway, then expose that tag |
| Property set by a script | Session memory only | Refactor into a gateway tag first. Nothing outside the session can read it. |
Check: you have one concrete path, either a tag path or a query, that returns the same value the operator sees right now.
How do I expose the value with a WebDev endpoint?
A Perspective page is always served by an Ignition gateway, even when it is not the gateway your scraping script runs on. Install the WebDev module on the gateway that hosts the page and add a Python resource that returns the value as JSON. This is the most durable fix. The endpoint returns data, not presentation, so moving or restyling a component on the view does not break it.
- On the hosting gateway, install and license the WebDev module.
- In the Designer for the project that owns the view, create a Python resource under WebDev, for example
plannedQty. - Implement
doGetto read the source identified in the previous step and return JSON. - Save the project and note the resource URL. WebDev resources are served under the gateway's
/system/webdev/path followed by the project name and resource name.
# WebDev Python resource: doGet
# Tag path is a placeholder; use the path found on the label binding
def doGet(request, session):
qv = system.tag.readBlocking(['[default]Line1/PlannedQty'])[0]
return {'json': {
'plannedQty': qv.value,
'quality': str(qv.quality),
'timestamp': str(qv.timestamp)
}}
Return the quality and timestamp along with the value. A consumer that stores data blindly will record a stale or bad-quality number as if it were good.
Check: open the resource URL in a browser. You should get a small JSON object, and plannedQty should match the label on the Perspective view within one refresh.
What if WebDev cannot go on the hosting gateway?
Sometimes the other team will not add a module or an API this iteration. Pick the lowest-coupling path that still reads the data rather than the pixels.
| Option | Requires from the page owner | Breaks when |
|---|---|---|
| WebDev JSON endpoint | Module install plus one resource | The underlying tag or query is renamed |
| Remote tag provider over the gateway network | Gateway network connection and tag provider access | Tag paths change |
| Direct database read | Read-only DB credentials and the query | The schema changes |
| Headless browser rendering the view | Nothing, except a session login | Any layout, component, or style change. It also holds a live session on the gateway. |
When both gateways run Ignition, a remote tag provider and a WebDev endpoint both work. The remote tag provider is the better choice for a value that changes once a minute. You get tag change events and quality natively, with no polling code. Choose WebDev when the consumer is not an Ignition gateway or when the value comes from a query rather than a tag.
Treat a headless browser as a stopgap only. It executes the JavaScript and exposes the built DOM, but each instance is a real Perspective session, and component selectors change whenever the view is edited.
Check: the chosen path returns the live value with a quality indicator, from the machine that will run the logger.
How do I poll and store only on change?
On the consuming gateway, replace the line-number parser with a JSON read in a gateway timer event script. Keep the last stored value in a memory tag so a gateway restart does not reset the comparison.
Set the timer rate from how fast the value actually changes. For a value that increments about once a minute, a poll every few seconds catches every change without loading the hosting gateway. Keep the timeout shorter than the timer period so calls do not stack up.
Check: watch the gateway logs for the logger name. A clean run produces no warnings, and the insert table gains one row per change, not one row per poll.
How do I prove the stored value matches the screen?
- Open the Perspective view in a browser next to a query tool pointed at the insert table.
- Wait for the Planned Qty label to step, for example from
54to55. - Confirm a new row appears with
55and a source timestamp that matches when the label changed, within one poll period. - Confirm no duplicate row was written for the polls that ran while the value held steady.
- Force a bad read by pointing the tag path at a disabled or missing tag, or by stopping the endpoint. Confirm the logger warns and no row is inserted.
- Ask the view owner to move or restyle the label, then confirm logging continues unchanged. This proves the pipeline reads data, not layout.
FAQ
Can I read Perspective component values with system.net.httpClient?
No. The GET returns only the application shell, and the DOM holding the value is built in the browser by JavaScript over a live gateway session. Read the tag or query behind the component instead.
Does the WebDev module have to be on the gateway that hosts the Perspective page?
Yes, if the endpoint will read that gateway's tags or project resources directly. The consuming side only needs system.net.httpClient to call the resource URL and parse response.json.
Can I use a headless browser to scrape a Perspective view?
It works because it executes the JavaScript, but each instance holds a real Perspective session, and any edit to the view can break the element selector. Use it only until a WebDev endpoint, remote tag provider, or database read is available.