Implements full Mercari scraping support for the trust-scoring pipeline: - `app/platforms/mercari/` — new MercariAdapter (scraper-based), scraper (parse_search_html / parse_listing_html), and __init__ - `app/platforms/__init__.py` — adds "mercari" to SUPPORTED_PLATFORMS - `api/main.py` — platform routing: _make_adapter, OR-group guard, seller lookup, BTF/Trading API guards all parameterised by platform - `web/src/views/SearchView.vue` — enables Mercari tab in platform picker BrowserPool stability fixes (browser_pool.py): - Add -ac flag to Xvfb (disables X11 auth requirement in Docker containers) - Shift display counter from :100-:199 to :200-:399 (avoids ghost kernel socket conflicts with low-numbered displays) - Add wait_for_selector / wait_for_timeout_ms params to fetch_html, _fetch_with_slot, _fetch_fresh - Add time.sleep(0.3) in _fetch_fresh after Xvfb start (was missing) Mercari scraper fix: - Remove sortBy=SORT_SCORE from build_search_url — that param is deprecated on Mercari and causes an empty 85KB response instead of search results Probe + debug scripts in scripts/: - probe_mercari.py — standalone Cloudflare bypass test - debug_fetch_fresh.py — pool simulation diagnostic Trust signal coverage: feedback_count, feedback_ratio partial score (account_age_days, category_history absent = score_is_partial=True). get_completed_sales stubbed for Phase 3. Tracks: snipe#53 (pool thread-safety fix, follow-up)
39 lines
1.5 KiB
Python
39 lines
1.5 KiB
Python
"""PlatformAdapter abstract base and shared types."""
|
|
from __future__ import annotations
|
|
|
|
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
|
|
from app.db.models import Listing, Seller
|
|
|
|
# Single source of truth for platform validation.
|
|
# Phase 2 will extend this set as new adapters are implemented.
|
|
SUPPORTED_PLATFORMS: frozenset[str] = frozenset({"ebay", "mercari"})
|
|
|
|
|
|
@dataclass
|
|
class SearchFilters:
|
|
max_price: Optional[float] = None
|
|
min_price: Optional[float] = None
|
|
condition: Optional[list[str]] = field(default_factory=list)
|
|
location_radius_km: Optional[int] = None
|
|
pages: int = 1 # number of result pages to fetch (48 listings/page)
|
|
must_include: list[str] = field(default_factory=list) # client-side title filter
|
|
must_exclude: list[str] = field(default_factory=list) # forwarded to eBay -term AND client-side
|
|
category_id: Optional[str] = None # eBay category ID (e.g. "27386" = GPUs)
|
|
must_include_mode: str = "all" # "all" | "any" | "groups"
|
|
adapter: str = "auto" # "auto" | "api" | "scraper"
|
|
|
|
|
|
class PlatformAdapter(ABC):
|
|
@abstractmethod
|
|
def search(self, query: str, filters: SearchFilters) -> list[Listing]: ...
|
|
|
|
@abstractmethod
|
|
def get_seller(self, seller_platform_id: str) -> Optional[Seller]: ...
|
|
|
|
@abstractmethod
|
|
def get_completed_sales(self, query: str) -> list[Listing]:
|
|
"""Fetch recently completed/sold listings for price comp data."""
|
|
...
|