-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Add amazon/order-history domain skill #534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sfox100
wants to merge
2
commits into
browser-use:main
Choose a base branch
from
sfox100:amazon-order-history-skill
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+111
−0
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| # Amazon — Order History Extraction | ||
|
|
||
| Field-tested against amazon.co.uk on a 2010-2026 account (1,007 orders), first on | ||
| 2026-07-16 and re-verified end-to-end on 2026-07-29. Complements | ||
| [product-search.md](product-search.md), which covers the public catalogue; this file covers the | ||
| logged-in `/your-orders` pages. | ||
|
|
||
| ## Auth | ||
|
|
||
| Order history requires login. An unauthenticated visit to any orders URL redirects to | ||
| `/ap/signin` (title "Amazon Sign-In") — detect this and stop for a human login rather than | ||
| typing credentials: | ||
|
|
||
| ```python | ||
| if "/ap/signin" in page_info()["url"]: | ||
| raise RuntimeError("Amazon session not logged in — do a one-time headed login first") | ||
| ``` | ||
|
|
||
| A one-time headed login persists in the profile; no re-challenge was observed on later | ||
| headless runs from the same profile/IP. | ||
|
|
||
| ## Navigation | ||
|
|
||
| First visit uses `new_tab()`, the same gotcha documented in product-search.md; `goto_url()` | ||
| is fine for every page after that. | ||
|
|
||
| ```python | ||
| new_tab("https://www.amazon.co.uk/your-orders/orders?timeFilter=year-2025&startIndex=0") | ||
| wait_for_load() | ||
| wait(2) # the order list hydrates after readyState=complete | ||
| ``` | ||
|
|
||
| - `timeFilter` takes `last30`, `months-3`, then `year-YYYY` back to the account's first year. | ||
| Read the valid values off the page rather than guessing: | ||
| ```python | ||
| years = json.loads(js( | ||
| "JSON.stringify(Array.from(document.querySelectorAll('#time-filter option')).map(o => o.value))" | ||
| )) | ||
| ``` | ||
| - `startIndex` steps in 10s (`0, 10, 20, ...`) — 10 orders per page. | ||
|
|
||
| ## Know the expected count before you paginate | ||
|
|
||
| The page states its own total, so a scrape can verify itself instead of guessing when to | ||
| stop. This is the difference between a complete run and a short one that looks complete. | ||
|
|
||
| ```python | ||
| import re | ||
| count_text = js("document.querySelector('.num-orders')?.innerText") # '186 orders' / '1 order' | ||
| expected = int(re.sub(r"[^\d]", "", count_text or "0") or 0) | ||
| ``` | ||
|
|
||
| Stop on the pagination control, not on a short page — `.a-last` carries `a-disabled` on the | ||
| final page and the whole `.a-pagination` block is absent when a year fits on one page: | ||
|
|
||
| ```python | ||
| is_last_page = bool(js(""" | ||
| (function () { | ||
| const l = document.querySelector('.a-pagination .a-last'); | ||
| return !l || l.classList.contains('a-disabled'); | ||
| })() | ||
| """)) | ||
| ``` | ||
|
|
||
| Then assert what you collected against what the page promised, and fail loudly on a | ||
| mismatch rather than writing a truncated file: | ||
|
|
||
| ```python | ||
| if len(collected) != expected: | ||
| raise RuntimeError(f"year-{year}: got {len(collected)} of {expected} orders — investigate before trusting output") | ||
| ``` | ||
|
|
||
| Verified on `year-2026`: 57 expected, 6 pages, 57 collected, every order dated. | ||
|
|
||
| ## Extraction | ||
|
|
||
| Each order is an `.order-card`. Titles are the product links inside it; the order date is | ||
| loose text in the card. `js()` hands back whatever the expression evaluates to, so a | ||
| `JSON.stringify` payload needs `json.loads` on the Python side to become a list: | ||
|
|
||
| ```python | ||
| import json | ||
|
|
||
| page_orders = json.loads(js("""JSON.stringify(Array.from(document.querySelectorAll('.order-card')).map(c => { | ||
| const m = c.innerText.match(/(\\d{1,2}\\s+[A-Za-z]+\\s+\\d{4}|[A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})/); | ||
| const titles = Array.from(new Set(Array.from( | ||
| c.querySelectorAll('.yohtmlc-product-title, a.a-link-normal[href*="/dp/"], a.a-link-normal[href*="/gp/product/"]') | ||
| ).map(a => a.innerText.trim()).filter(t => t && t.length > 2))); | ||
| return {date: m ? m[1] : '', titles}; | ||
| }))""")) | ||
| ``` | ||
|
|
||
| The date alternation covers both `31 December 2024` (co.uk) and `December 31, 2024` (.com); | ||
| a UK-only pattern silently yields `date: ''` for every order on the US site. | ||
|
|
||
| ## Gotchas | ||
|
|
||
| - **Wait ~2s after `wait_for_load()`** before querying cards. `readyState=complete` fires | ||
| before the list renders, exactly as on search results. | ||
| - **Trust `.num-orders`, not an empty page.** A year returning zero cards is a signal to | ||
| inspect the DOM, never evidence of "no orders" — an earlier run on this account read | ||
| `year-2024`+ as empty and was wrong. Re-verified 2026-07-29: `year-2010` through | ||
| `year-2026` all extract with `.order-card`, 10 per page. | ||
| - **Don't stop on a short page alone.** The final page is legitimately short (7 of 10 on | ||
| `year-2026`), so a short-page-means-done rule is indistinguishable from a page that | ||
| under-rendered. Use `.a-last` plus the count assertion. | ||
| - **Digital items** (Kindle, Prime Video) sit alongside physical ones with no reliable | ||
| in-card type marker — classify by title downstream. | ||
| - **Long runs**: ~2-3s per page, and grocery-heavy years run 20+ pages. Checkpoint to disk | ||
| after each year and `print(..., flush=True)` — a killed run with buffered stdout loses | ||
| everything. | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: A missing
.num-orderselement is currently treated as a legitimate zero-order year:count_text or "0"makes the laterlen(collected) != expectedcheck pass for an empty scrape. That defeats the self-verification and can silently produce a trusted but empty result when Amazon has not rendered the page or changes the selector. Missing/invalid count text could instead fail explicitly (while handling an explicit “no orders” response as zero).Prompt for AI agents