I solved this in a cute way a couple of years ago.
I had an "email me" form on a small business website that I wanted to be maximally accessible; spam bots found it and started overwhelming the legitimate messages. From reading the server logs, I learned that bots were submitting the form without re-fetching it first -- somebody had cached my form and was simply sending a POST whenever they had some garbage for me to read. A hidden form input would help for a few days, but then some bot's owner would figure out the right input, cache it, and the deluge would begin again.
I didn't have any backend where I could add session information to the form, and didn't want to add any. Instead, between the "Type your message here" box and the hidden element, I inserted the output of a script that writes
There will be a short delay before you may submit the form. If you
have been typing in your information, the delay may already have
ended.
4 ...
The garbage block is randomly generated to make it hard to compress. I experimented with how long the blocks of garbage needed to be. When I got the form size up to about 200K, the spam messages stopped.
This is actually not a lot of extra data, about like adding a few extra images to the page. Even for a hypothetical customer on dialup, the delay between rendering the text box and rendering the submit button is shorter than the time it would probably take to actually compose a message.