← 所有项目

registry job autobidder

a windows app that catches registration jobs on a marketplace before other offices do, captcha and all

时间
2026年7月 – 2026年9月
对象
a judicial scrivener's office
角色
solo: reverse engineering, build, windows release, remote support
技术
Python, Chrome DevTools Protocol, Selenium, numpy CNN, tkinter, PyInstaller, GitHub Actions
规模
234 commits, 34k lines (21k app, 11k tests), 493 tests
接单作品,neoworks

这篇只有以下语言: english

what it is

a windows app for a judicial scrivener's office. there is a marketplace where scriveners pick up property registration jobs, and the good ones disappear in minutes, mostly in one short window around 11am. the office had an old macro that reloaded the page every five seconds. my job was to replace it: watch faster, solve the six-digit captcha, skip dates the office can't do, respect the site's limit of two cases per date, and go through the accept steps.

the old macro was a Delphi launcher that started a PyInstaller exe. i unpacked it, decompiled the python, and recovered its endpoints, selectors and captcha model weights. that was a starting point, not the truth.

four bugs only the live site could show

on july 22 i ran the program logged in as the customer, against a real posting. four separate defects came out, and any one of them was enough to catch nothing.

  • wrong tab. the previous version polled one market tab. it returned zero rows under every filter while the real posting sat on the other tab. now it polls both and merges by posting id.
  • locator kinds. selectors were stored with short names like "css", but Selenium wants "css selector". every css lookup threw, so the whole final accept chain had never run once.
  • captcha geometry. the code took a screenshot of the captcha element, which was a squashed, framed copy of a 250x72 image. the model read it as an empty string with 0.94 confidence, and the bundled test sample kept passing. now it reads the native image bytes.
  • a calendar read that took 111 seconds. the day-limit check called find_elements per calendar cell under a 3 second implicit wait, and every empty cell waited it out. one execute_script call brought it to 2 seconds.

the next days showed that the jobs this office could take used a different button than the one the old code knew, and that the leftover "captcha failures" were really the day limit. checked by hand, the reads were right.

the captcha, twice

per captcha, the inherited model was right 64% of the time. three cheap fixes all made it worse. i trained a fixed-length 6-head CNN on 150k rendered captchas plus 100 hand-labelled real ones. synthetic data alone gave 66 to 68%. with the real ones it reached 96% on a held-out 50. it runs in 18ms in plain numpy instead of about 480ms, and dropping TensorFlow cut about 436MB from the bundle.

on august 4 the site was replaced by a new single-page app on a JSON API. every old endpoint returned 404. the new captcha had the same digits on a bigger canvas (335x100), so the model scored 1%. the fix was to crop, not resize. the site checked the captcha before it looked at the case, so i could test reads against one of the office's own finished cases with no side effects. 300 live captchas through that check: 95%.

when the program did too much

one morning the program found a case in a city far outside the office's area and requested the approval code while nobody was at the desk. nothing was committed, since the code was never typed in, but it shouldn't have got that far. pressing start had been treated as consent for everything found afterwards.

where it ended up: a region filter (seoul plus four nearby cities by default) runs on the list data before any case is opened, so out-of-area cases cost zero requests. the program solves the captcha, ticks the terms and sends the approval code, then hands the browser window to a person, who types the six digits and picks how to pay the insurance fee. a test reads the source and fails if the program ever clicks the pay button.

i moved the browser side to Chrome driven over CDP, with polling done by the page's own fetch. a separate API login had given the account a second session and kept logging the customer out. a later live pass found four bugs the test fixtures had hidden by being kinder than the site. one: the token in session storage was wrapped in JSON, so the header was Bearer {"token":"..."} and the server called it expired.

some slowness was not mine to fix: at the 11am rush the site took about 49 seconds of server time from list to approval code. one failure was mine: my old workaround for the checkout popup later stopped the site from loading its payment script at all, once the site changed how it loaded it.

where it is now

two programs, one per market, because a mode switch left on the wrong setting looked identical. the bid program waits on the site's notification count once a minute instead of polling the list, so it looks less like a bot. an auto-updater rolls back if the new build doesn't start. last release v3.9.15. the open problem is Windows Smart App Control blocking the unsigned exe on one office PC, which only code signing will fix.