- Reset master to upstream/main (16,697 commits) - Overlay 2,271 local-only files (skills, tools, workspace, configs, apps) - Restore IDENTITY.md and USER.md templates - Build verified, gateway running, Discord working Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
111 lines
4.0 KiB
Python
111 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
DBA.dk søge-tool — henter listings og returnerer JSON.
|
|
Bruges af dba-scout agenten til at finde billige ting.
|
|
|
|
Brug:
|
|
python3 dba-search.py "rtx 3090" [--max-price 5000] [--location herning] [--limit 10]
|
|
"""
|
|
import sys, re, json, argparse
|
|
|
|
def search_dba(query: str, max_price: int = None, location: str = None, limit: int = 10) -> list[dict]:
|
|
try:
|
|
from scrapling import StealthyFetcher
|
|
except ImportError:
|
|
print(json.dumps({"error": "scrapling not installed"}))
|
|
sys.exit(1)
|
|
|
|
# DBA søge-URL (sort by price ascending)
|
|
url = f"https://www.dba.dk/recommerce/forsale/search?q={query.replace(' ','+')}&sort=PRICE_ASC"
|
|
if location:
|
|
# DBA bruger county-filter, vi filtrerer manuelt i stedet
|
|
pass
|
|
|
|
try:
|
|
page = StealthyFetcher.fetch(url, stealthy_headers=True)
|
|
except Exception as e:
|
|
return [{"error": str(e)}]
|
|
|
|
results = []
|
|
|
|
# Primær kilde: SEO structured data (JSON-LD)
|
|
for s in page.css('script[type="application/ld+json"]'):
|
|
try:
|
|
raw = s.get().extract()
|
|
data_str = raw.split('>', 1)[1].rsplit('<', 1)[0]
|
|
data = json.loads(data_str)
|
|
items = data.get('mainEntity', {}).get('itemListElement', [])
|
|
for item in items:
|
|
listing = item.get('item', item)
|
|
price_str = listing.get('offers', {}).get('price', '')
|
|
try:
|
|
price = int(float(str(price_str))) if price_str else 0
|
|
except Exception:
|
|
price = 0
|
|
|
|
if max_price and price > max_price:
|
|
continue
|
|
|
|
entry = {
|
|
'title': listing.get('name', ''),
|
|
'price': price,
|
|
'url': listing.get('url', ''),
|
|
'id': listing.get('sku', ''),
|
|
'description': (listing.get('description') or '')[:200],
|
|
'condition': listing.get('itemCondition', '').replace('https://schema.org/', ''),
|
|
}
|
|
|
|
# Lokation fra URL ikke mulig via structured data — flag hvis i location
|
|
if location:
|
|
combined = (entry['title'] + entry['description']).lower()
|
|
if location.lower() not in combined:
|
|
# Hent listing for at tjekke by — skip for hastighed, marker som unknown
|
|
entry['location'] = 'unknown'
|
|
else:
|
|
entry['location'] = location
|
|
|
|
if entry['title']:
|
|
results.append(entry)
|
|
|
|
if len(results) >= limit:
|
|
break
|
|
except Exception:
|
|
pass
|
|
|
|
return results
|
|
|
|
|
|
def format_result(r: dict) -> str:
|
|
price = f"{r['price']} kr" if r['price'] else "ukendt pris"
|
|
loc = f" [{r['location']}]" if r.get('location') else ""
|
|
cond = " (brugt)" if 'Used' in r.get('condition', '') else ""
|
|
url = r.get('url', '')
|
|
return f"• {r['title']}{cond} — {price}{loc}\n {url}"
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Søg på DBA.dk')
|
|
parser.add_argument('query', help='Søgeord')
|
|
parser.add_argument('--max-price', type=int, default=None, help='Max pris i kr')
|
|
parser.add_argument('--location', default=None, help='By/lokation')
|
|
parser.add_argument('--limit', type=int, default=10, help='Max antal resultater')
|
|
parser.add_argument('--json', action='store_true', help='Output som JSON')
|
|
args = parser.parse_args()
|
|
|
|
results = search_dba(args.query, args.max_price, args.location, args.limit)
|
|
|
|
if args.json or not sys.stdout.isatty():
|
|
print(json.dumps(results, ensure_ascii=False, indent=2))
|
|
else:
|
|
if not results:
|
|
print(f"Ingen resultater for '{args.query}'")
|
|
else:
|
|
print(f"\nFandt {len(results)} resultater for '{args.query}':\n")
|
|
for r in results:
|
|
print(format_result(r))
|
|
print()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|