入门级反爬(User-Agent、IP 封禁、请求频率限制)好解决,但到了进阶阶段,会遇到更多花样。这篇讲三个最实用的反爬对抗技术。
一、IP 代理池------突破封 IP
爬得稍微快一点,网站就封 IP。解决方案是维护一个代理池,IP 被封了自动换。
1. 免费代理获取(思路)
python
import requests
from bs4 import BeautifulSoup
import time
def fetch_free_proxies():
"""从免费代理网站采集代理IP(示例)"""
proxies = []
url = "https://www.free-proxy-list.com/"
resp = requests.get(url, headers={"User-Agent": "Mozilla/5.0"})
soup = BeautifulSoup(resp.text, "html.parser")
for row in soup.select("table tr")[1:]: # 跳过表头
cols = row.select("td")
if len(cols) >= 2:
ip = cols[0].text.strip()
port = cols[1].text.strip()
proxies.append(f"{ip}:{port}")
return proxies
需要注意的是: 免费代理质量很差(90% 不可用,速度慢),小规模采集凑合用,大规模采集建议买付费代理。
2. 验证代理是否可用
python
import threading
def check_proxy(proxy):
"""测试代理是否可用"""
try:
resp = requests.get(
"http://httpbin.org/ip",
proxies={"http": proxy, "https": proxy},
timeout=5
)
if resp.status_code == 200:
print(f"✅ {proxy} 可用 - {resp.text.strip()}")
return True
except:
pass
return False
# 多线程验证所有代理
def validate_all(proxies):
valid = []
threads = []
lock = threading.Lock()
def check(p):
if check_proxy(p):
with lock:
valid.append(p)
for p in proxies:
t = threading.Thread(target=check, args=(p,))
t.start()
threads.append(t)
for t in threads:
t.join()
return valid
3. 代理池管理器
python
import random
import time
import requests
class ProxyPool:
"""简单的代理池管理器"""
def __init__(self):
self.proxies = []
self.blacklist = set()
def add_proxy(self, proxy):
if proxy not in self.blacklist:
self.proxies.append(proxy)
def get_proxy(self):
"""随机返回一个代理"""
if not self.proxies:
return None
return random.choice(self.proxies)
def mark_bad(self, proxy):
"""标记代理不可用"""
if proxy in self.proxies:
self.proxies.remove(proxy)
self.blacklist.add(proxy)
print(f"💀 代理 {proxy} 已移除,剩余 {len(self.proxies)} 个")
def request_with_retry(self, url, max_retries=5):
"""带代理重试的请求"""
for i in range(max_retries):
proxy = self.get_proxy()
if not proxy:
print("代理池已空,等待补充...")
return None
try:
resp = requests.get(
url,
proxies={"http": proxy, "https": proxy},
timeout=10,
headers={"User-Agent": "Mozilla/5.0"}
)
if resp.status_code == 200:
return resp
else:
self.mark_bad(proxy)
except:
self.mark_bad(proxy)
time.sleep(0.5)
print(f"重试 {max_retries} 次全部失败: {url}")
return None
4. 付费代理推荐
免费代理不稳定,真干活建议买:
- 快代理:国内最大的代理服务商,动态代理按量计费
- 芝麻代理:价格便宜,几块钱能用一天
- 站大爷:支持动态按需提取
一般几十块钱能用几个月,省心很多。
二、请求指纹------伪装浏览器特征
现在的反爬不仅看 IP 和 UA,还会检测TLS 指纹、Headers 顺序、WebDriver 等。
1. 完整的请求头伪装
python
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Referer": "https://www.google.com/",
"Sec-Ch-Ua": '"Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": '"Windows"',
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
}
2. 随机切换
python
import random
from fake_useragent import UserAgent
ua = UserAgent()
class RandomHeaders:
"""随机生成请求头"""
@staticmethod
def get_headers():
user_agents = [
ua.chrome,
ua.edge,
ua.firefox,
]
return {
"User-Agent": random.choice(user_agents),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": random.choice([
"zh-CN,zh;q=0.9,en;q=0.8",
"en-US,en;q=0.9,zh-CN;q=0.8",
"zh-CN,zh;q=0.9",
]),
"Referer": random.choice([
"https://www.baidu.com/",
"https://www.google.com/",
"https://www.bing.com/",
]),
}
3. 使用 requests 会话重用
python
# 每次都新建会话,容易被识别
# 正确的做法是复用 Session
session = requests.Session()
session.headers.update(RandomHeaders.get_headers())
# 连续请求,保持同一指纹
resp1 = session.get("https://example.com/page1")
resp2 = session.get("https://example.com/page2")
三、字体反爬------最常见的反爬手段
很多网站(大众点评、猫眼电影、58同城)用自定义字体来加密数字,你爬下来看到的是乱码。
原理
正常数字:0 1 2 3 4 5 6 7 8 9
加密后: ➀ ➁ ➂ ➃ ➄ ➅ ➆ ➇ ➈ ➉
网页通过 @font-face 加载一个自定义字体文件(.woff 或 .ttf)
字体文件里把字符映射关系打乱了
所以浏览器能正确显示,但你爬到的文本是乱码
解决方案
python
from fontTools.ttLib import TTFont
import re
import requests
def parse_font_anti_spider(html_text, font_url):
"""解析字体反爬"""
# 1. 下载字体文件
resp = requests.get(font_url)
with open("temp.woff", "wb") as f:
f.write(resp.content)
# 2. 解析字体映射关系
font = TTFont("temp.woff")
cmap = font.getBestCmap()
# 3. 建立映射关系
# cmap 返回的是 {unicode编码: 字形名称}
# 比如 {0xe001: 'one', 0xe002: 'two', ...}
# 需要根据字形名称找到对应的数字
unicode_to_digit = {}
num_map = {
"one": "1", "two": "2", "three": "3", "four": "4", "five": "5",
"six": "6", "seven": "7", "eight": "8", "nine": "9", "zero": "0",
}
for unicode_val, glyph_name in cmap.items():
# 字形名称可能类似 "uniE001" 或 "one"
glyph_name = glyph_name.lower()
for eng, digit in num_map.items():
if eng in glyph_name:
unicode_to_digit[chr(unicode_val)] = digit
# 4. 替换加密字符
for enc_char, digit in unicode_to_digit.items():
html_text = html_text.replace(enc_char, digit)
return html_text
在线字体反爬(动态加载更麻烦)
很多网站已经升级到了动态字体------每次请求都生成新的字体文件,映射关系每次都不一样:
python
# 解决方案思路:
# 1. 每次请求都下载最新的字体文件
# 2. 通过字形轮廓计算来识别数字
# 更简单的方法:用 OCR 识别
# from PIL import Image
# import pytesseract
# 但准确率不如直接解析字体文件
四、Selenium 防检测
用 Selenium 时,网站可以通过 window.navigator.webdriver 检测到你是自动化工具。
1. 隐藏 WebDriver 特征
python
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
driver = webdriver.Chrome(options=options)
# 注入 JS 修改 navigator 属性
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
Object.defineProperty(navigator, 'plugins', {
get: () => [1, 2, 3, 4, 5]
});
Object.defineProperty(navigator, 'languages', {
get: () => ['zh-CN', 'zh']
});
"""
})
2. 使用 undetected-chromedriver
有个专门的库解决了大部分检测问题:
bash
pip install undetected-chromedriver
python
import undetected_chromedriver as uc
driver = uc.Chrome()
driver.get("https://example.com")
# 自动隐藏了 WebDriver 特征,比手动配置稳定得多
五、实际工作流:如何选择反爬策略
| 反爬级别 | 特征 | 应对方法 |
|---|---|---|
| ⭐ 入门级 | 封 IP、限频率 | 加延时 + 轮换 UA |
| ⭐⭐ 初级 | 检测请求头、验证码 | Session 复用 + 代理池 |
| ⭐⭐⭐ 中级 | 字体反爬、动态加载 | 下载字体解析 + 抓接口 |
| ⭐⭐⭐⭐ 高级 | WebDriver 检测、风控系统 | undetected-chromedriver + 行为模拟 |
| ⭐⭐⭐⭐⭐ 顶级 | 滑块验证、人机识别 | 打码平台 + 行为轨迹模拟 |
核心原则: 反爬对抗不是越强越好,够用就行。加 3 秒延迟 + 轮换 UA 能解决 80% 的问题,没必要一上来就上分布式代理池。
💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。