Query per second or qps limits is a good start, but I quickly learned that a 1qps limit to "free" access meant that all of a sudden it was a 1000 unique IPs that were making one request each.
Lots of botnets were highlighted this way, when one address searches for 'joomla v2.3', and the next IP searches for 'joomla v2.3', page=2, and the next IP searches for 'joomla v2.3', page=3, etc.
It was annoying but an interesting problem. We could implement any arbitrary policy and then watch as the bots adjusted to come in just at that policy limit. We banned entire Ukrainian ISPs (they were a big source at the time) and have VPN providers become the big users. We put in limits per day, I tried a "thermal" system where IPs gained "heat" by queries and "cooled" by idle time. We built a server with a "broken" IP stack that we could send the initial TCP connect to, the server would accept the connection and then never respond. A "black hole" if you will. The trick was we didn't actually keep[ sockets open we just pretended like we it was the other end of a TCP connection. It did everything correctly except complete the connection. That would cause any client using off the shelf IP stacks to hang indefinitely.