fix(scrapers): avito listing_date from sortTimeStamp JSON (#726) #729
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#729
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/726-avito-listing-date"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Investigation (real capture)
Captured a live SERP via the scraper's own curl_cffi path (
impersonate=chrome120) — HTTP 200, 46 cards (my dev IP isn't firewalled like the prod box). Findings:data-marker="item-date"marker was not renamed — it's present, but only on the top ~10 above-the-fold cards. Avito lazy-renders the SERP: ~79% of cards (below the fold) have noitem-datein the raw HTML curl_cffi receives (no JS hydration). DOM-only parsing → ~21% coverage.sortTimeStamp(epoch-ms) in the embedded per-item JSON (56 occ/page). The_parse_htmlline-266 note "Avito state давно пустой" was stale — the date is right there.avito_houses_sample.htmlis a different scraper's fixture (avito_houses.py/__preloadedState__), not this apartment SERP — left untouched.Fix
_build_sort_timestamp_map(html):item_id → sortTimeStamp → datevia"id":<8-12d> … "sortTimeStamp":<ms>._dom_card_to_lot: primary =ts_map[card.data-item-id]; fallback = existing DOMitem-date/item-date-info+_parse_relative_date(keeps the top-card path)._parse_relative_datereused unchanged.Verified
_parse_html→ 42/46 = 91%listing_datecoverage (was ~21% DOM-only) — clears the >80% DoD bar.avito_serp_sample.html(distilled from real structure) +test_avito_serp_date.py(3 tests): JSON path on a lazy card, DOM path on a top card, map builder.ruffclean; existingtest_avito_relative_date/test_avito_houses_parsestill green (35 passed).DoD
sortTimeStampJSON (primary) + DOMitem-date(fallback, top cards).listing_datefrom it;ruff check app/services/scrapers/avito.pyclean.listing_date != None.SELECT count(listing_date)*100.0/count(*) FROM listings WHERE source='avito'→ expect >80% for newly scraped rows. NB: old NULL rows aren't backfilled (out of scope); coverage climbs as re-scrape runs. Also: the prod scraper IP is currently firewalled by Avito (sessionavito-raw.log= block page) — re-scrape needs a working IP/proxy, separate from this code fix.Refs #726
✅ APPROVE
Хорошо отинвестигаченный fix #726 с верификацией на реальном захвате.
Correctness
_SORT_TS_RE:"id":\d{8,12}отсекает короткие вложенные id (category.id:24);.{0,1500}?non-greedy +re.Sограничивает поиск телом одного item-объекта, bounded → нет ReDoS. First-occurrence-wins dedup корректен (item раньше recommendations-дублей).datetime.fromtimestamp(ms/1000, tz=UTC).date()— верная конверсия epoch-ms. Primary JSON-путь + сохранён DOMitem-datefallback;_parse_relative_dateпереиспользован без изменений.data-item-idключует map к DOM-карточкам — согласовано с извлечениемsource_id.Scope/judgment — worker корректно установил, что предположение issue про
avito_houses_sample.htmlошибочно (та фикстура принадлежит другому скраперу,__preloadedState__), оставил её нетронутой и создал новуюavito_serp_sample.html. cian/yandex/SQL/base.py не тронуты.Tests — 3 offline-теста (JSON-путь на lazy-карточке, DOM-fallback на top, map builder), без сети/БД. Real-capture 42/46=91% берёт планку >80%. Post-deploy coverage + firewalled-IP caveat корректно отданы в QA.
Minor (non-blocking) — большой кусок диффа = ruff-format churn (dict по строке, разбивка log-вызовов); косметика, на логику не влияет. Advisory: UTC
.date()может дать ±1 день у поздневечерних постов vs MSK — несущественно для оконного матчинга поwindow_days, follow-up не завожу.Нет security/data-loss; аддитивно с безопасным fallback. Дифф scraper-only — нет self-extending триггера, нет secrets. Coverage-smoke → qa.