eBextractor Extension Config
Remote eBay selectors for the eBextractor Chrome extension. Static files on Cloudflare Pages — no Workers, no code, data only.
Published files
| File | Purpose |
|---|---|
v1/version.json |
Checked by the extension for a newer rev · no-cache |
v1/selectors-r1.json |
The selectors (schema 1, rev 1) · cached 1 year, immutable |
Structure
ebextractor-extension-config/ (source repo)
├─ selectors/v1.json source of truth for schema 1 — edit this
├─ SELECTORS.md what every key targets (this page is built from it)
├─ scripts/build.mjs validate → dist/ (fails if a key is undocumented)
├─ scripts/devtools.mjs paste-in DevTools checker
└─ dist/ what's served here
├─ index.html this page
├─ _headers cache rules
└─ v1/
├─ version.json {schema, rev, file}
└─ selectors-r<rev>.json the config
How the extension uses it
- eBay pages read a copy saved in the browser — never this site, so it adds no page-load time.
- In the background, at most every 5 minutes while the user is on eBay, the extension fetches
v1/version.json(~60 bytes). It downloadsselectors-r<rev>.jsononly whenrevis higher than what it has. - If a page parses to nothing, it checks right away (max once a minute), retries with the new file, then falls back to the PythonAnywhere API.
- A malformed file, a different schema or a lower
revis ignored; the last good copy stays. The extension also ships a bundled copy for first install / offline.
Fix a breakage
- Find the broken key: run
pnpm devtoolsand pastedevtools-check.jsinto the DevTools console on the broken eBay page. - Edit
selectors/v1.json— new selector first, keep the old ones. - Bump
"rev"by 1, thenpnpm release. Users pick it up within ~5 minutes of browsing eBay.
What each key in selectors/v1.json targets, where to find that HTML, and what breaks when it stops matching. Not published — the build only uploads the JSON, but it fails if a key in the JSON isn't documented here.
When something breaks
- Open the page type below in Chrome (signed in to eBay).
- Run
pnpm devtools, paste the printed file's contents into the DevTools console. It lists every key for that page with how many elements each selector matches;✖= nothing matched. - For a
✖key: right-click the element on the page → Inspect, find a stable class/attribute, test it withdocument.querySelectorAll('…').length. - Add the new selector to
selectors/v1.json(first in the list for priority keys; anywhere for any keys), keep the old ones, bumprev,pnpm release.
Match modes (fixed in the extension code):
- any — all selectors are combined into one query; order doesn't matter.
- priority — tried in list order, first one that matches wins; put the newest selector first.
Pages
| Section | Page | Example URL |
|---|---|---|
search | Search results | https://www.ebay.com/sch/i.html?_nkw=shoes (also sold: &LH_Sold=1&LH_Complete=1) |
itemPage | Listing page | https://www.ebay.com/itm/<item id> |
purchaseHistory | Sold / offer history (needs sign-in) | https://www.ebay.com/bin/purchaseHistory?item=<item id> |
Check list and grid view (&_dmd=2) and at least one non-US site (ebay.de, ebay.pl) — eBay rolls markup out per site. Per-site lists go under "sites": { "www.ebay.de": { "search": { … } } }.
search — search results page
The search page has two consumers: the parser (reads a copy of the page HTML to build the price analysis sidebar) and the live page features (hide items, per-card buttons) that work on the real DOM.
search.resultsContainer
ul.srp-results.srp-list.clearfixul.srp-results.srp-grid.clearfixul.su-grid.su-grid--is-listul.su-grid- Targets: the
<ul>that holds every result card. - Mode: any (first in document order).
- Used by: parser (
src/lib/utils/getListings.ts). - If broken: sidebar shows 0 listings; the extension retries with a newer config, then falls back to the PythonAnywhere API.
- Look for: the list right under "Results" — currently
ul.srp-resultsorul.su-grid.
search.card
li.s-item.s-item__dsa-on-bottomli.s-item.s-item__pl-on-bottomli.s-item.s-item__before-answer.s-item__pl-on-bottomli.s-card.s-card--horizontalli.s-card.s-card--verticalli.su-grid__item- Targets: each listing
<li>inside the results container. - Mode: any (all matches).
- Used by: parser.
- If broken: 0 listings (same fallback as above), or some listings missing from the analysis.
- Look for: the repeating
<li>per listing — currentlyli.s-card,li.su-grid__item,li.s-item.
search.skipCard
li.srp-river-answer.srp-river-answer--SPECTRUM_OF_VALUE_CAROUSELli>div.srp-river-answer--START_LISTING_BANNERli>div.srp-river-answer--LIVE_EVENTS_CAROUSELli>div.srp-river-answer--NAVIGATION_ANSWER_COLLAPSIBLE_CAROUSELli>div.srp-river-answer--RIGHT_ALIGNED_MESSAGEli>div.srp-river-answer--BASIC_PAGINATION_V2li>div.srp-river-answer--REWRITE_STARTli>script- Targets: an element inside a card that marks it as not a real listing (banners, carousels, pagination, "start listing" ads).
- Mode: any.
- Used by: parser — a card containing any match is ignored.
- If broken: junk rows in the analysis (wrong prices/keywords), "Link element not found" errors in the console.
- Look for:
srp-river-answer--*classes on promo blocks between listings.
search.link
a.s-item__linka.su-linka.s-card__linka.su-link.su-item-card__title- Targets: the listing's
<a>to/itm/<id>inside a card. - Mode: any.
- Used by: parser — required: a card without it is skipped; also used to de-duplicate and build the image URL.
- If broken: listings missing or 0 listings.
- Look for: the title link — currently
a.su-link,a.s-card__link,a.s-item__link.
search.price
span.s-item__pricespan.s-card__pricespan.su-item-card__price- Targets: the price text element inside a card (e.g.
$12.99or$7.99 to $8.99). - Mode: any.
- Used by: parser — price stats, price frequencies, CSV.
- If broken: prices show
N/A/—; listings are excluded from price stats. - Look for:
span.su-item-card__price,span.s-card__price,span.s-item__price.
search.imageContainer
div.s-item__imagediv.su-media__imagediv.su-image- Targets: the element wrapping the listing image inside a card.
- Mode: any.
- Used by: parser — its
<img alt>is the 2nd fallback for the title. - If broken: only matters when
search.titlealso fails.
search.title
div.s-card__titlediv>a.su-item-card__titlediv>a- Targets: the element whose text is the listing title, inside a card.
- Mode: any.
- Used by: parser — titles, top keywords, CSV. Fallbacks: image
alt, then link text. - If broken: titles become "Opens in a new window…"/
Unknown, keyword counts get noisy.
search.category
li.srp-refine__category__item- Targets: category items in the left filter column.
- Mode: any (all matches).
- Used by: parser — "Categories" section of the CSV only.
- If broken: CSV categories empty; nothing else.
search.pageCards
ul.srp-results>li.s-item__pl-on-bottom:not(.srp-river-answer--ITEMS_CAROUSEL_WITH_COLOR)ul.srp-results>li.s-card.s-card--horizontal:not(.srp-river-answer--ITEMS_CAROUSEL_WITH_COLOR)ul.srp-results>li.s-card.s-card--vertical:not(.srp-river-answer--ITEMS_CAROUSEL_WITH_COLOR)ul.su-grid>li.su-grid__item:not(.srp-river-answer--ITEMS_CAROUSEL_WITH_COLOR)- Targets: the listing
<li>cards on the live page. Each must carrydata-listingid(on the<li>or a childdiv). - Mode: priority (all matches of the first selector that matches anything). Keep
:not(.srp-river-answer--ITEMS_CAROUSEL_WITH_COLOR)to skip carousels. - Used by: hide/unhide items (
src/lib/utils/itemVisibility.ts), re-hiding saved hidden items on load (src/features/exclude-checker/), the per-card buttons (src/features/ebay-search/components/CreateSoldHistoryAndOtherFeatures.tsx). - If broken: no Sold History / Quick Actions buttons under listings; hidden items reappear and are counted again.
search.cardActionsMount
div.s-item__info.clearfixdiv.su-card-container__content- Targets: the element inside a live card where the Sold History / Quick Actions buttons are appended.
- Mode: priority.
- If broken: buttons don't appear (cards found, nowhere to put them).
- Look for: the card's text/info column — currently
div.su-card-container__content,div.s-item__info.
search.cardTitle
.s-card__title>span.primary.default.s-item__title.s-card__title.su-item-card__header>a- Targets: the title text inside a live card.
- Mode: priority.
- Used by: per-card actions (product analysis, "check on other sites", hidden-item names).
- If broken: those actions get an empty title.
search.cardImage
.s-card__imagediv.su-image img- Targets: the listing
<img>inside a live card. - Mode: priority.
- Used by: per-card "save image" / search by image, hidden-item thumbnails.
- If broken: missing thumbnails, image actions do nothing.
itemPage — listing page
itemPage.quantityAvailability
#qtyAvailabilitydiv.x-quantity__availability- Targets: the block with "N available · M sold" on
/itm/<id>. - Mode: priority. The code reads the 2nd
<span>inside it and takes its first word as the sold count. - Used by: the sold count shown in the per-card Sold History panel on the search page (it fetches the item page in the background).
- If broken: sold count shows blank/
Error. - Look for: the quantity row near the Buy button — currently
#qtyAvailability,div.x-quantity__availability.
purchaseHistory — /bin/purchaseHistory?item=<id>
The extension fetches this page with the user's own eBay session. Signed out → eBay redirects to sign-in and the extension asks the user to sign in.
purchaseHistory.soldTable
div.app-table.fixed-price table.app-table__table- Targets: the
<table>of purchases (buyer, price, quantity, date). First row =<th>headers, other rows =<td>. - Mode: priority.
- Used by: sold history (
src/lib/utils/getItemHistoryById.ts), on the item page and in the per-card panel. - If broken: "0 sold" / empty sold history while eBay shows sales — the top user complaint. If neither table matches, the extension retries with a newer config, then falls back to the PythonAnywhere API.
- Also check: the header texts. Date filters match column names per language (
src/lib/utils/getSoldHistoryFieldsByLanguage.ts, e.g. "Date of purchase", "Kaufdatum") — a renamed column breaks the 7/30/60/365-day filters even when the table is found.
purchaseHistory.offerTable
div.app-table.offer table.app-table__table- Targets: the
<table>of Best Offer history (same row layout). - Mode: priority.
- If broken: offer history empty.
purchaseHistory.itemDetails
dl.app-item-card__details- Targets: the
<dl>of item details at the top (<dt>label /<dd>value pairs). - Mode: priority.
- If broken: item details missing in the history view; tables still work.