作为一名爬取过电商、资讯、社交等多平台数据的开发者,我踩过的反爬坑没有100也有80——刚入门时因不懂User-Agent被直接拒接,后来因高频请求被封IP,甚至遇到过接口签名参数导致爬虫直接瘫痪的情况。
反爬本质是网站与爬虫的“攻防博弈”,但大多数开发者只停留在“换IP、加延迟”的基础操作,面对动态签名、Cookie验证、设备指纹等进阶反爬就束手无策。本文结合我近两年的实战经验,拆解10个高频反爬场景的解决方案,每个技巧都附代码片段和踩坑总结,帮你从“爬虫新手”进阶为“反爬克星”。
在讲技巧前,先明确网站反爬的核心逻辑——识别“非人类行为”或“异常行为”。常见识别维度包括:
静态标识:固定User-Agent、缺失Referer/Origin请求头;行为特征:请求频率过高、IP地址集中、访问路径固定(如只爬数据页不逛首页);动态验证:需要登录Cookie、接口签名参数、验证码;环境特征:浏览器指纹(Canvas、WebGL)、设备信息、JS执行能力。知道了识别逻辑,反爬的核心思路就清晰了:让爬虫的请求“看起来像人类”,让行为“符合正常用户习惯”。
很多新手爬虫只加一个User-Agent就敢爬,结果被网站直接拉黑——现代网站会校验多个请求头字段,缺失或异常都会被标记为爬虫。
import requests
from fake_useragent import UserAgent
import random
# 1. 构建真实请求头池(关键字段缺一不可)
UA_POOL = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Firefox/129.0"
]
def get_random_headers(referer="https://www.baidu.com"):
"""生成随机请求头"""
ua = random.choice(UA_POOL)
return {
"User-Agent": ua,
"Referer": referer, # 动态设置Referer
"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",
"Upgrade-Insecure-Requests": "1"
}
# 使用示例
url = "https://target-website.com/data"
headers = get_random_headers(referer="https://target-website.com/list") # Referer设为目标网站列表页
response = requests.get(url, headers=headers, timeout=10)
Sec-Fetch-*系列字段(如
Sec-Fetch-Mode: navigate),可直接从浏览器开发者工具复制完整请求头。
网站最容易识别的异常行为就是“高频次、无间隔”请求——人类浏览网页会有点击、滚动、停顿,而爬虫默认是“秒级爬取”,很容易触发IP封禁。
import time
import random
def crawl_with_delay(url, headers):
"""带随机延迟的请求函数"""
try:
# 随机延迟1-5秒,模拟人类思考时间
delay = random.uniform(1.2, 4.8)
time.sleep(delay)
print(f"延迟{delay:.2f}秒后请求:{url}")
response = requests.get(url, headers=headers, timeout=10)
return response
except Exception as e:
print(f"请求失败:{e}")
return None
# 连续爬取时,延迟递增
def batch_crawl(url_list, headers):
base_delay = 1.0
for i, url in enumerate(url_list):
# 每爬5个页面,延迟增加0.5秒
if i % 5 == 0 and i != 0:
base_delay += 0.5
time.sleep(random.uniform(base_delay, base_delay + 1.5))
crawl_with_delay(url, headers)
当请求频率控制后仍被封IP,说明网站已对IP进行了限制——此时需要用IP池切换IP,让网站无法识别你的真实地址。
import requests
class ProxyPool:
def __init__(self, proxy_api):
self.proxy_api = proxy_api # 第三方IP池API
self.proxies = [] # 缓存的代理IP列表
def fetch_proxies(self):
"""从API获取代理IP列表"""
try:
response = requests.get(self.proxy_api, timeout=5)
self.proxies = response.text.split("
") # 假设API返回格式为每行一个IP:端口
print(f"获取到{len(self.proxies)}个代理IP")
except Exception as e:
print(f"获取IP池失败:{e}")
def get_random_proxy(self):
"""随机获取一个代理IP"""
if not self.proxies:
self.fetch_proxies()
return random.choice(self.proxies) if self.proxies else None
# 使用示例
proxy_pool = ProxyPool("http://your-proxy-api.com/get_proxies")
def crawl_with_proxy(url, headers):
proxy = proxy_pool.get_random_proxy()
if not proxy:
print("无可用代理,使用本机IP")
return requests.get(url, headers=headers, timeout=10)
proxies = {
"http": f"http://{proxy}",
"https": f"https://{proxy}"
}
try:
response = requests.get(url, headers=headers, proxies=proxies, timeout=10)
return response
except Exception as e:
print(f"代理{proxy}失效,切换IP")
proxy_pool.proxies.remove(proxy) # 移除失效代理
return crawl_with_proxy(url, headers) # 递归重试
很多网站的核心数据(如用户评价、历史价格)需要登录后才能访问——此时需要管理Cookie,让爬虫维持登录状态,避免每次请求都被要求登录。
import requests
import json
# 1. 保存Cookie到文件(首次登录后执行)
def save_cookies(session, filename="cookies.json"):
cookies = session.cookies.get_dict()
with open(filename, "w", encoding="utf-8") as f:
json.dump(cookies, f, ensure_ascii=False, indent=2)
print("Cookie保存成功")
# 2. 从文件加载Cookie
def load_cookies(session, filename="cookies.json"):
try:
with open(filename, "r", encoding="utf-8") as f:
cookies = json.load(f)
session.cookies.update(cookies)
print("Cookie加载成功")
return True
except FileNotFoundError:
print("未找到Cookie文件,请先登录")
return False
# 3. 登录获取Cookie(以模拟登录为例)
def login_and_get_cookies(username, password):
session = requests.Session()
login_url = "https://target-website.com/login"
data = {
"username": username,
"password": password
}
response = session.post(login_url, data=data, headers=get_random_headers())
if response.status_code == 200 and "登录成功" in response.text:
save_cookies(session)
return session
else:
print("登录失败")
return None
# 使用示例
def crawl_with_login(url):
session = requests.Session()
# 尝试加载已保存的Cookie
if not load_cookies(session):
# 加载失败则重新登录
session = login_and_get_cookies("your-username", "your-password")
if not session:
return None
headers = get_random_headers(referer="https://target-website.com")
response = session.get(url, headers=headers, timeout=10)
# 检查Cookie是否有效(如返回登录页则重新登录)
if "登录" in response.text and "登录成功" not in response.text:
print("Cookie失效,重新登录")
session = login_and_get_cookies("your-username", "your-password")
response = session.get(url, headers=headers, timeout=10)
return response
很多现代网站用Vue、React等框架开发,数据通过JS动态加载(如滚动加载、点击“加载更多”)——此时requests无法获取动态数据,需要模拟浏览器执行JS。
from playwright.sync_api import sync_playwright
import time
def crawl_dynamic_page(url):
with sync_playwright() as p:
# 启动Chrome浏览器,添加伪装(避免被识别为无头浏览器)
browser = p.chromium.launch(
headless=True,
args=[
"--disable-blink-features=AutomationControlled", # 禁用自动化标识
"--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
]
)
context = browser.new_context(
viewport={"width": 1920, "height": 1080}, # 模拟桌面浏览器窗口
geolocation={"longitude": 116.403874, "latitude": 39.914885}, # 模拟地理位置
permissions=["geolocation"]
)
page = context.new_page()
# 屏蔽webdriver标识
page.add_init_script("""
delete window.navigator.webdriver;
window.navigator.languages = ["zh-CN", "zh"];
""")
# 访问页面
page.goto(url, wait_until="networkidle") # 等待网络空闲(确保JS加载完成)
time.sleep(random.uniform(1.5, 2.5))
# 模拟滚动加载(滚动3次,每次滚动后等待数据加载)
for _ in range(3):
page.mouse.wheel(0, 1000) # 向下滚动1000像素
page.wait_for_timeout(random.uniform(1000, 2000)) # 等待1-2秒
# 获取页面HTML(此时已包含动态加载的数据)
html = page.content()
browser.close()
return html
window.navigator.webdriver标识容易被检测,而Playwright默认隐藏该标识;避免过度模拟:如连续点击、快速滚动,容易触发网站的行为验证(如滑块验证码);优先捕获AJAX接口:如果能通过开发者工具找到动态加载的AJAX接口,用requests直接请求比Playwright更高效。
很多API接口会对请求参数进行加密(如添加sign、timestamp、nonce等字段),直接拼接参数会返回403——破解签名是爬虫进阶的关键,核心是“还原网站的加密逻辑”。
假设网站接口参数为:
?id=123×tamp=1699999999&nonce=abc123&sign=xxx,其中sign是MD5加密后的字符串。
// 网站JS签名逻辑
function generateSign(params, secret) {
// 1. 参数按key排序
let sortedKeys = Object.keys(params).sort();
// 2. 拼接参数:key1=value1&key2=value2+secret
let signStr = sortedKeys.map(key => `${key}=${params[key]}`).join("&") + secret;
// 3. MD5加密并转大写
return md5(signStr).toUpperCase();
}
确定secret(密钥):可能是固定值(如“abcdef123”),或从页面HTML、Cookie中获取;用Python还原逻辑:
import hashlib
import time
import random
import string
def generate_nonce(length=6):
"""生成随机nonce字符串(大小写字母+数字)"""
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
def generate_sign(params, secret="abcdef123"):
"""还原JS签名逻辑:MD5加密"""
# 1. 参数按key排序
sorted_keys = sorted(params.keys())
# 2. 拼接参数字符串
sign_str = "&".join([f"{key}={params[key]}" for key in sorted_keys]) + secret
# 3. MD5加密并转大写
md5 = hashlib.md5()
md5.update(sign_str.encode("utf-8"))
return md5.hexdigest().upper()
def crawl_signed_api():
api_url = "https://target-website.com/api/data"
# 1. 构造基础参数
params = {
"id": 123,
"timestamp": int(time.time()), # 时间戳(秒级)
"nonce": generate_nonce() # 随机字符串
}
# 2. 生成签名
params["sign"] = generate_sign(params, secret="abcdef123") # 替换为真实secret
# 3. 发送请求
headers = get_random_headers(referer="https://target-website.com")
response = requests.get(api_url, params=params, headers=headers, timeout=10)
print(f"接口响应:{response.json()}")
return response
当爬虫触发高强度反爬时,网站会弹出验证码(如滑块验证码、图文验证码、短信验证码)——此时需要破解验证码才能继续爬取。
from PIL import Image
import pytesseract
import requests
# 注意:需先安装Tesseract-OCR,并配置环境变量
pytesseract.pytesseract.tesseract_cmd = r"C:Program FilesTesseract-OCR esseract.exe"
def recognize_captcha(captcha_url):
"""下载验证码并识别"""
# 1. 下载验证码图片
response = requests.get(captcha_url, stream=True, timeout=10)
with open("captcha.png", "wb") as f:
f.write(response.content)
# 2. 预处理图片(灰度化、二值化,提高识别率)
img = Image.open("captcha.png").convert("L") # 灰度化
threshold = 127 # 二值化阈值
img = img.point(lambda p: p > threshold and 255) # 二值化
# 3. OCR识别
captcha_text = pytesseract.image_to_string(img).strip()
print(f"识别到验证码:{captcha_text}")
return captcha_text
# 复杂验证码:使用打码平台(以超级鹰为例)
def recognize_captcha_with_platform(captcha_path):
"""调用超级鹰打码平台识别验证码"""
import requests
from hashlib import md5
username = "your-username"
password = md5("your-password".encode()).hexdigest()
appid = "123456" # 替换为你的appid
appkey = "your-appkey"
url = "http://upload.chaojiying.net/Upload/Processing.php"
data = {
"user": username,
"pass2": password,
"softid": appid,
"codetype": "1004", # 验证码类型(1004为4位数字字母)
}
files = {"userfile": open(captcha_path, "rb")}
response = requests.post(url, data=data, files=files)
result = response.json()
if result["err_no"] == 0:
return result["pic_str"]
else:
print(f"打码失败:{result['err_str']}")
return None
现代网站会通过浏览器指纹(Canvas、WebGL、UserAgent、屏幕分辨率等)识别设备——即使你换了IP和Cookie,同一设备的指纹仍会被识别为爬虫。
from playwright.sync_api import sync_playwright
def fake_browser_fingerprint():
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
viewport={"width": random.randint(1366, 1920), "height": random.randint(768, 1080)},
user_agent=random.choice(UA_POOL),
locale="zh-CN",
timezone_id="Asia/Shanghai"
)
page = context.new_page()
# 伪装Canvas指纹
page.add_init_script("""
// 重写Canvas绘制方法,修改指纹结果
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function(type) {
const canvas = document.createElement('canvas');
canvas.width = this.width;
canvas.height = this.height;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
return originalToDataURL.call(canvas, type);
};
// 伪装WebGL指纹
const originalGetParameter = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(pname) {
if (pname === 37445) return 'Intel(R) UHD Graphics'; // 伪装显卡型号
if (pname === 37446) return '4.6.0 - Build 27.20.100.9664'; // 伪装WebGL版本
return originalGetParameter.call(this, pname);
};
""")
page.goto("https://target-website.com", wait_until="networkidle")
# 验证指纹伪装是否成功(访问指纹检测网站)
# page.goto("https://browserleaks.com/canvas", wait_until="networkidle")
# time.sleep(5)
# page.screenshot(path="fingerprint.png")
browser.close()
当爬取数据量过大(如百万级数据)时,单机爬虫效率低、易被封IP——分布式爬取可将任务分配到多个节点,同时爬取并共享进度,突破单机限制。
# settings.py
from scrapy_redis.spiders import RedisSpider
from scrapy_redis.connection import RedisConnection
# 启用Redis调度器(任务队列)
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 启用Redis去重(避免重复爬取)
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# Redis连接配置
REDIS_URL = "redis://localhost:6379/0"
# 任务队列持久化(避免爬虫重启后任务丢失)
SCHEDULER_PERSIST = True
# 任务优先级(默认按FIFO排序)
SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.PriorityQueue"
# 自定义分布式爬虫
class DistributedSpider(RedisSpider):
name = "distributed_spider"
redis_key = "spider:start_urls" # Redis中任务队列的key
def parse(self, response):
# 解析数据(与普通Scrapy爬虫一致)
yield {"data": response.text}
# 提取下一页URL,加入任务队列
next_url = response.css("a.next::attr(href)").get()
if next_url:
yield scrapy.Request(next_url, callback=self.parse)
反爬不仅是技术问题,还涉及法律风险——未经允许爬取网站数据,可能违反《网络安全法》《反不正当竞争法》,甚至面临诉讼。
单一反爬技巧难以应对复杂场景,实际爬取时需组合使用——以下是电商平台爬取的组合策略:
基础层:完整请求头伪装 + 随机延迟(1-3秒);核心层:IP池(每5页切换一次IP) + Cookie管理(维持登录状态);进阶层:Playwright模拟浏览器(爬动态价格数据) + 接口签名破解(爬评价数据);保障层:分布式爬取(百万级商品) + 合规检查(不爬用户隐私)。
def ecommerce_crawler(product_url):
# 1. 初始化Session和IP池
session = requests.Session()
proxy_pool = ProxyPool("http://your-proxy-api.com/get_proxies")
load_cookies(session) # 加载登录Cookie
# 2. 构造请求头
headers = get_random_headers(referer="https://ecommerce-platform.com/list")
# 3. 切换IP
proxy = proxy_pool.get_random_proxy()
proxies = {"http": f"http://{proxy}", "https": f"https://{proxy}"} if proxy else None
# 4. 随机延迟
time.sleep(random.uniform(1.5, 3.5))
try:
# 5. 爬取商品详情页(动态数据用Playwright)
if "dynamic" in product_url:
html = crawl_dynamic_page(product_url)
# 解析动态价格数据
else:
response = session.get(product_url, headers=headers, proxies=proxies, timeout=10)
# 解析静态数据
# 6. 爬取评价接口(需要签名)
comment_api = "https://ecommerce-platform.com/api/comments"
params = generate_comment_params(product_id=123) # 生成带签名的参数
comment_response = session.get(comment_api, params=params, headers=headers, proxies=proxies)
return {"product_data": response.text, "comments": comment_response.json()}
except Exception as e:
print(f"爬取失败:{e}")
proxy_pool.proxies.remove(proxy) # 移除失效IP
return ecommerce_crawler(product_url) # 重试
反爬没有万能公式,核心是“灵活应变”——不同网站的反爬策略不同,需要针对性调整。但掌握了以上10个技巧,你就能应对90%以上的爬取场景。
如果在实战中遇到具体的反爬难题(如某网站的签名参数、滑块验证码),欢迎在评论区交流,我会尽力帮你解答!
¥11.80
2022春全新升级 人教版RJ版 六年级下册 每天100道口算题卡 双减基础课课练 6年级下册口算+专项 老师好帮手家长好助手学生好练手
¥11.80
2022春全新升级 人教版RJ版 一年级下册 每天100道口算题卡 双减基础课课练 1年级下册口算+专项 老师好帮手家长好助手学生好练手
¥28.00
边条包边裁剪器边条助手安装KT板相框辅助卡边条工具广告展板包边
¥22.30
小学生多功能拼音卡片 最新版 小学生拼音识字认识卡片发音书写拼音教与学助手 小学生一年级二年级拼音认识卡片识字助手
¥19.50
儿童可水洗汉语拼音学习神器卡片幼小衔接一年级教具拼读训练助手
¥14.88
手动可调剥线皮神器快速去皮剥线抽芯电工助手废旧电线铜线剥皮器