Use a combination of timestamp, spinner, field names, and honeypots...
Read this article for the detailed example.
A timestamp is exactly what it sounds like. A hidden field that marks the time when the form was originally opened. This defeats playback bots that copy a form and replay back the submission process over and over with different data each time. If the timestamp isn't close enough in relation to when the form is submitted the submission is automatically rejected.
A spinner is a hash using data from the timestamp, IP address, entry ID of the article, and a secret.
Field names are all generated by applying the hash to the 'real' field names. By comparing the field names with the spinner during the post-processing stage you can verify whether the person/or bot is the same as the one who originally opened the form.
The Honeypot has already been explained by @D4V360. It's essentially a hidden field that acts as an invisible trap for bots. If it's filled in with data then the form is automatically rejected.