← All posts

Tor for Anonymous Scraping: Does It Work, and How to Avoid DNS Leaks

Quick answer: Tor gives you free IP anonymity, but it is a poor tool for scraping. Exit-node IPs are published and heavily blocked, throughput is slow, and a careless setup leaks DNS queries that de-anonymize you anyway. Route requests through the SOCKS proxy Tor exposes on 127.0.0.1:9050, force DNS through that same tunnel, and rotate circuits between requests. For most real workloads, residential proxies or a managed fetch API get you further with less pain.

I want to be fair to Tor here. It is a genuinely impressive piece of privacy engineering, and for its actual purpose (protecting the identity of people who need it) it works. But "anonymous browsing for humans at risk" and "high-volume automated data collection" are different problems, and the tool that solves the first is usually wrong for the second.

Let me walk through what you actually get, what it costs you, and how to do it safely if you decide you must.

What Tor gives you

Tor routes your traffic through three volunteer-run relays before it reaches the destination. The site sees the IP of the final relay (the exit node), not yours. You get:

  • Free egress IPs. No proxy bill.
  • Real IP anonymity from the perspective of the target site.
  • Automatic rotation if you request new circuits.

That is a real feature set. If your only goal were "the target must not learn my real IP," Tor delivers.

What it costs you

Here is the part the "scrape anonymously with Tor" tutorials skip.

Exit-node IPs are public and heavily filtered. The full list of Tor exit nodes is published and updated constantly. Anti-bot vendors ingest it. Many sites simply return a block page or a CAPTCHA to every Tor exit. So the anonymity is real, but the access often is not. You are anonymous and locked out.

It is slow. Three hops of onion routing across volunteer bandwidth means high latency and modest throughput. For a crawl of thousands of pages, this alone can be a dealbreaker.

The exit-node pool is small and shared. Thousands of people share the same few thousand exits. That concentration means rate limits and reputation scores get poisoned fast, and you inherit whatever the last person did from that IP.

DNS leaks can undo the whole point. If your scraper resolves hostnames using your local DNS resolver instead of routing DNS through Tor, your ISP (and anyone watching) sees exactly which domains you are visiting. The IP is hidden, the intent is not.

Property Tor Residential proxy Managed fetch API
Cost Free Per GB or per IP Per request or credit
Speed Slow Fast Fast
IP reputation Poor (published exits) Good (real ISP IPs) Handled for you
DNS leak risk High if misconfigured Low None (server side)
Setup effort Moderate Low Very low
Good for scraping? Rarely Often Usually

How to route through Tor safely, if you must

If you have a legitimate reason (say, you are collecting public data and genuinely need to hide the origin, not just rotate IPs), here is a setup that avoids the common footguns.

First, run the Tor daemon. On most systems the tor package exposes a SOCKS5 proxy on port 9050.

# Debian/Ubuntu
sudo apt-get install tor
sudo systemctl start tor
# Tor now listens on 127.0.0.1:9050 (SOCKS5)

The critical detail is DNS. You must send DNS resolution through the SOCKS proxy, not resolve locally. With requests and PySocks, use the socks5h:// scheme (the h means "resolve hostnames remotely").

import requests

proxies = {
    "http": "socks5h://127.0.0.1:9050",
    "https": "socks5h://127.0.0.1:9050",
}

# socks5h routes DNS through Tor. Plain socks5:// would leak DNS locally.
r = requests.get("https://example.com/public-page", proxies=proxies, timeout=30)
print(r.status_code, len(r.text))

That single h is the difference between a real DNS leak and a clean tunnel. If you use curl, the equivalent is --socks5-hostname (not --socks5).

curl --socks5-hostname 127.0.0.1:9050 https://example.com/public-page

To rotate circuits, enable the control port and send a NEWNYM signal between batches. Add ControlPort 9051 and a hashed control password to your torrc, then:

from stem import Signal
from stem.control import Controller

def new_circuit():
    with Controller.from_port(port=9051) as c:
        c.authenticate(password="your-control-password")
        c.signal(Signal.NEWNYM)  # request a fresh exit

Do not slam NEWNYM on every request. Tor rate-limits it internally, and hammering it just gives you the same small pool of exits faster. Rotate every few minutes or every N requests, and always add jitter and per-host rate limiting so you are not overloading any target. If you want a full pattern for that, see how to build a polite scraper.

Verify your setup leaks nothing. Before trusting it, hit a service that reports the exit IP and confirm it is a Tor exit, and watch your local DNS traffic to confirm no plaintext lookups escape. A tunnel you have not verified is a tunnel you should assume is leaking.

When Tor actually makes sense for scraping

Rarely, but not never:

  • Low-volume collection where origin-hiding is the point, not IP rotation. Think research on adversarial infrastructure where you genuinely do not want your source IP linked to the query.
  • You are budget-constrained to zero and the target does not block Tor exits (test first).
  • You are testing how a site treats Tor traffic as part of the work itself.

For everything else (product data, price monitoring, search results, content ingestion at any real scale) Tor's blocked exits and low throughput make it the wrong tool. This is not a moral judgment on Tor; it is just the wrong shape for the job.

Why residential or a fetch API usually wins

Residential proxies route through real consumer ISP IPs, so they carry the reputation of ordinary home connections rather than a published block list. They are fast, they are not pre-filtered, and a decent provider handles rotation for you. The tradeoff is cost and the ethics of the IP source, which I cover in best proxies for web scraping.

The step beyond that is not managing any of this yourself. With link.sc, the proxy selection, rotation, rendering, and DNS all happen server side. You send a URL and get clean markdown back, with no SOCKS config to leak through.

curl https://link.sc/v1/fetch \
  -H "Authorization: Bearer lsc_..." \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/public-page", "format": "markdown"}'

There is no socks5h versus socks5 trap to fall into, because the resolution never touches your machine. For most teams that is the honest recommendation: spend your attention on the data, not on whether a stray DNS query just undid your anonymity.

A note on ethics and legality

Anonymity is not permission. Route only requests you are allowed to make in the first place: public pages, within the site's rate limits, respecting robots.txt, and never to evade a login or a paywall. Tor's ability to hide your origin does not expand what you are entitled to access, and hiding your identity to do something you could not do openly is the wrong side of the line. Collect public data respectfully, identify your intent where you can, and back off when a site signals it wants you to.


Skip the SOCKS config and the DNS-leak checklist: link.sc handles proxies, rotation, and rendering server side. Start free.