Scrape heavy equipment listings from MachineryTrader.com, a top US marketplace for construction, agriculture, and forestry machinery. Extract price, make, model, year, hours, dealer info, specs, and photos. Ideal for equipment valuation, market intelligence, and dealer analysis.
Changelog — MachineryTrader Scraper — US Heavy Equipment Listings
2026-10-04 — proxy-opt (build proxy-opt)
O1/R1: PortManager con lease por worker (acquire/release): dos workers ya no comparten puerto/IP; cada unidad excluye los puertos ya probados. TTL de puerto quemado 5 -> 20 min (sticky Decodo = 60 min).
O2/R2: deteccion de challenge Cloudflare/Turnstile ademas de Imperva (challenge_kind) en warmup y en paginas de listado. Un challenge sin cards quema el puerto y rota sesion (no se confunde con pagina vacia real, no marca la unidad como completa); si Turnstile se resuelve durante la espera se sigue normal.
O3/R4: bloqueo de usercentrics.eu (CMP ~42 % de la pagina; el dato sale del SSR inline).
O4: puertos usados por unidad (ports_used) y reuse de navegador mantiene su puerto/lease.
O5: BREAKER por unidad: >= 26 kB de proxy por item con >= 5 navegaciones, o 3 challenges seguidos -> corta la unidad (no done; el checkpoint la reintenta).
O6/R5: telemetria de produccion: contadores challenges/resolved/burned/ports_used por unidad y resumen [proxy] warmups_total=.... Muestras HTML crudas solo con _proxy_debug (admin).
Warmup en /robots.txt por defecto (_warmup_mode robots|home|none, solo admin). Challenge en el warmup -> quema puerto y rota antes del listado (cuenta para burned_streak, no para el breaker).
MEDICION (04-10, cranes/motor_graders/pipelayers, publico, maxPages 15): pagina limpia 233 -> 125 KB (-46 %), 8,5 -> 4,4 KB/item. A/B warmup: home 3/5 sesiones pasan CF (challenge en warmup predice challenge en listado 2/2), robots 4/4, none 4/5. Run 3 workers 4096 MB (XpbyaSeN7UHUDqNnD): puertos 10009/10006/10017 distintos, 42/42 paginas OK, 0 challenges, 1.174 items = mismos item_id y 54 claves identicas campo a campo que la fase 0; pipelayers 334/334 (TP 12, antes incompleta). Corte+resurrect (q1mjmXpPJI4Cvof7n): reanuda en p5, p1-p14 sin huecos, 392 items, 0 duplicados. Proyeccion 12.350 pags: ~1,6 GB limpio, ~2,3 GB central (5 % challenges).
2026-10-04 — telemetria de proxy (build proxy-test, solo medicion)
Bytes reales por respuesta (request.sizes(), fallback content-length) agrupados por fase (warmup/page/other), 1p/3p, resource_type y host. Log [proxy] unit= warmup_kb= page_kb_avg= items= kb_item= por unidad y resumen + KV PROXY_STATS (KV por defecto del run). Muestras del HTML crudo de p1-p2 (max 12) en el KV por defecto. Sin cambios de logica, URLs, bloqueo ni output.
2026-10-04 — convencion tiempo_uso (sin push)
Nuevas ext_tiempo_uso_unidad (h|km|mi|null) y ext_tiempo_uso_calidad (cero|horas_igual_km|unidad_sin_confirmar|null) en live y preloaded (derivadas idempotentes de lo almacenado, sin re-scrapear). Formato '6,811 h' intacto. Fix de etiqueta: millas ya no se etiquetan 'km' (texto 'mi' -> 'N mi', numero sin conversion; preloaded: 'km' almacenado en URL .com -> 'mi'); Mileage sin unidad ya no se etiqueta 'h'. hours_numeric english sin cambios. Declaradas en dataset_schema.json.
Medicion (investigacion 2026-10-04, solo lectura): universo .es sin accesorios 326.124 (equipo For Retail|For Rent|For Lease|Upcoming Auctions 284.703 + desguace Condition=DISMANTLED 41.421); capturable con 2.14 ~217K (~67 %). Prototipo de particion por facetas: 111 unidades, Σ LC = 326.124, max 9.996 por unidad. Las 3 cards "perdidas" de plan_upc_p1.html (25/28 aceptadas) apuntan a auctiontime.es.
_coverage_plan (solo admin; ignorado con aviso si no es _admin_snapshot): {"scope":"equipo|desguace","top_ids":[...],"part":[i,n],"dry_run":bool}, solo country: es. Planifica por facetas (getfacet/ajaxsearch desde Camoufox con XSRF): una unidad por categoria de nivel 1; si pasa de 9.500 se parte por fabricante (bin-packing first-fit decreciente, <=60 nombres), un fabricante > tope por categoria hija y, si no cabe, por rangos de Year (el residual sin ano se loguea). Invariantes en codigo (suma hijos == padre; suma unidades + residual == total de la top); si fallan, Actor.fail antes de paginar.
Plan persistido por top en el KV machinerytrader-checkpoints (PLAN_{mes}_{pais}__{scope}__C{top}): se reutiliza al reanudar para que los slugs de checkpoint no cambien. dry_run solo guarda PLAN_* si el snapshot empieza por zz-.
Unidades _planned: URL = query exacta del plan + sort=4; no se auto-shardean (ni YEAR ni STATE); COVERAGE contra el LC del plan. categoria/categoria_input_id conservan el nombre/slug de la categoria top (compatible con preloaded).
F1b (publico): attachments con scope 81 (crushers, augers, snow_blowers, welding_equipment, stump_grinders, asphalt_pavers) ahora llevan ScopeCategoryIDs=81& delante de Category=; antes la paginacion devolvia el catalogo sin filtrar. Ninguna otra URL cambia.
F5 (publico): is_product_url acepta auctiontime. (subastas proximas ~11 % de AuctionTime); EXTRACT_JS usa como respaldo el primer enlace con id de 5+ digitos si la card no tiene enlace de titulo.
F6 (aditivo): ext_tipo_anuncio = venta | alquiler | subasta | desguace segun la URL. Se marca, no se filtra. renta_dia sigue null.
Smoke 04-10 (build cov-test): (1) la faceta Category filtrada por su propia categoria devuelve SelectedOptionTree + FullOptionTree (opciones duplicadas): solo se lee FullOptionTree. (2) En una busqueda que mezcla venta y alquiler el sitio etiqueta todo como "para la venta", asi que el alquiler pasa a un scope propio alquiler (ListingType=For Rent|For Lease, slugs alq__), que se lanza despues de equipo (ListingType=For Retail|Upcoming Auctions): lo ya capturado se salta por dedup y lo que solo esta en alquiler sale con URL /en-alquiler/ -> ext_tipo_anuncio=alquiler.
Opcion B (solo unidades _planned de equipo/alquiler; publico y desguace sin cambios): categoria/categoria_input_id = categoria conocida mas especifica del catalogo del actor, resuelta por el category_text de la card con un _catmap guardado en el plan (p. ej. Forklifts dentro de Lifts, Mini Excavators dentro de Excavators). Sin match: nombre de la unidad. No cambia la huella del plan.
README e input schema: aviso de que cat__trucks, cat__dump_trucks y cat__construction_attachments devuelven el catalogo sin filtrar en .es y de que cat__welding_equipment (ID 1063) es "Zanjadora (accesorio)" en .es.
2026-10-03 — v2.14.4: checkpoint persistente + parada por duplicados segura
Medicion (fase 0, runs admin de septiembre, dataset machinerytrader-2026-09): de 418 unidades (categoria o shard), 146 terminaron por "2 consecutive full-dupe" antes de TotalPages. Los duplicados venian de IDs que otros runs ya habian escrito (categorias solapadas, orden sort=4 agrupa los vistos). Hubo perdida probable en lifts por anio (~4-6K items estimados) y tipo C: trucks, dump_trucks, construction_attachments y dismantled devuelven el catalogo entero en los 27 shards (LC 278.530), cortados hoy en p2.
Parada por duplicados: solo si TotalPages es conocido y se ha llegado a el. Sin TotalPages no para por racha; a las 20 paginas duplicadas seguidas corta SIN marcar la unidad completa. Coste estimado: +1.779 paginas (+17,5 %), ~+2 h de runtime y ~+$1,3-1,8; con la guarda tipo C +2,5K paginas (+24 %) frente a +37.831 (+372 %) sin ella.
Guarda de filtro inoperante: si un shard devuelve LC >= LC del padre, se pagina ese shard hasta TotalPages, se marcan done los shards restantes y el padre (evita multiplicar por 27).
Checkpoint por unidad (rastriq_ckpt): clave CHECKPOINT_{mes}_{pais}__{slug}, KV machinerytrader-checkpoints en admin. Se escribe tras cada push OK; las unidades done se saltan y las parciales reanudan en last_page+1. Sustituye al PROGRESS_CURSOR.
IDs reservados justo antes del push y liberados si el push falla (o el run aborta): un push fallido ya no marca los IDs como vistos y la pagina se reintenta; con 3 workers y categorias solapadas 0 duplicados (test: 84/84) y maxListings exacto (test: 30 de 30, antes 86).
Push fallido no quema el puerto del proxy; maximo 3 sesiones seguidas con push fallido por unidad, luego sale sin marcarla completa.
2 paginas vacias seguidas antes de TotalPages: se trata como bloqueo (reintento en otra sesion), no como fin (test: TP=12, p5-6 vacias -> antes done con p7-12 perdidas). TotalPages se arrastra entre sesiones de la unidad; sin TotalPages y sin ninguna card en la sesion tampoco es fin. Sin progreso, la unidad sale sin done y sin quemar puerto (el siguiente run reintenta; coste 2 paginas).
SCRAPED_IDS (KV por defecto, migracion) no persiste IDs con push en vuelo.
Pagina vacia dentro de TotalPages (o sin haber visto cards) ya no se salta: se reintenta en otra sesion (max 3 sin progreso, sin quemar puerto). Medicion: smoke 2026-10-04 pipelayers, p1 vacia transitoria -> antes hueco de 28 items marcado como hecho; ahora 0 huecos (test S1). Sin TotalPages, vacia seguida de cards = hueco -> reintento.
Dataset admin por id (get_or_create) en vez de por nombre: tras borrar y recrear un dataset con el mismo nombre, el nombre apuntaba un momento al borrado ('Dataset was not found').
Racha de 20 paginas duplicadas sin TotalPages acumulada entre sesiones (con pagesPerSession=10 antes llegaba a p62; ahora corta en p20).
maxListings a mitad de pagina: la pagina cuenta como incompleta (checkpoint = pagina-1). natural_end ya no es True por maxListings ni por maxPages agotado.
Categoria padre con shards: ya no se marca done antes de procesar los shards; solo cuando todos estan done.
Carga de IDs existentes: solo campos item_id/ref_anuncio (antes el item completo).
_snapshot_dataset_name (solo admin): dataset de prueba, claves de checkpoint con prefijo y sin publicar puntero. Se rechaza (error, sin escribir) si empieza por machinerytrader- o no empieza por zz- ni contiene el token test/smoke; el run termina FAILED (Actor.fail), no SUCCEEDED.
Resumen final [ckpt] RESUMEN n/m categorias INCOMPLETAS.
Contrato de salida sin cambios; fetchDetails sigue en false por defecto.
2026-09-18 — T6 push
T6 (push 2026-09-18): mapper ftp28 whitelist 31 claves en modo live y preloaded (nueva funcion _to_out_ftp28, independiente del default global "english"); input_schema.json prefill/default outputSchema -> "ftp28".
Formato: que cambio, por que, y la medicion que lo respalda. Un cambio sin numero detras es
una opinion; en este repo no cuenta como entrada de changelog.
Las versiones son las de Apify (mayor.menor). La plataforma no admite numero de parche.
[2.0] — estado a 21 de agosto de 2026
Version activa en Apify, con tag latest. Exportada a este repo el 21 de agosto de 2026.
No se reconstruye aqui el historial anterior: inventarlo seria peor que no tenerlo. Lo que hay documentado de ciclos previos vive en el runbook de la fuente, en rastriq-docs/interno/runbooks/. A partir de esta entrada, todo cambio se anota aqui con su medicion.