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
<!-- instructions for spam robots: we are a waste of your money, go away, thanks -->
<div class="float-left" style="font-size: x-small;">
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.
<br/><span>
4 ...
</span><span>
<!--
d92cbd14985295ac27929a6db7891a90ec4173a8358dcadab134cc589ce2de54
1468365bd33b520754ddb8223252e7e6e7584ddb956ef1bb28628e27cfea86c6
-->
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.