爬虫与反爬的博弈——滑动验证码、请求参数加密、风控绕过

入门级反爬看 UA 和 IP,进阶反爬看验证码、参数加密、行为风控。这一篇讲目前最常见的三种进阶反爬手段怎么应对。

一、滑动验证码

滑块验证码是目前应用最广的人机验证方式,常见的产品有:

产品使用方难度
极验 Geetest知乎、B站、斗鱼⭐⭐⭐ 标准版
阿里云滑块淘宝、1688⭐⭐⭐⭐
腾讯防水墙微信、QQ⭐⭐⭐

滑动验证的校验逻辑

用户操作:
  拖动滑块 → 前端记录轨迹数据(坐标、速度、加速度)
        ↓
服务器校验:
  ① 轨迹是否像人类操作(不是直线)
  ② 拖动的距离是否正确(缺口位置)
  ③ 浏览器环境特征(WebDriver、Canvas指纹)

方案一:Selenium + 模拟拖动

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
import time

driver = webdriver.Chrome()
driver.get("https://example.com/login")

# 等待滑块出现
slider = driver.find_element(By.CLASS_NAME, "geetest_slider_button")

# 模拟人类拖动(关键:不是匀速直线!)
action = ActionChains(driver)
action.click_and_hold(slider).perform()

# 分段拖动:先慢后快再慢,模拟人类
track = [
    (0, 0),     # 起始
    (50, 3),    # 开始移动,轻微上下抖动
    (100, -2),
    (150, 5),
    (200, 1),
    (230, -3),
    (250, 0),   # 接近目标,放慢
    (258, 0),
    (262, 0),   # 到达
]

for x, y in track:
    action.move_by_offset(x, y).perform()
    time.sleep(0.01)  # 每步间隔10ms

action.release().perform()
time.sleep(2)

方案二:机器学习识别缺口位置

import cv2
import numpy as np

def find_gap_position(bg_image_path, slider_image_path):
    """识别滑块缺口位置(像素坐标)"""
    # 读取背景图和滑块图
    bg = cv2.imread(bg_image_path, 0)          # 灰度图
    slider = cv2.imread(slider_image_path, 0)

    # 模板匹配,找到滑块在背景中的位置
    result = cv2.matchTemplate(bg, slider, cv2.TM_CCOEFF_NORMED)
    _, _, _, max_loc = cv2.minMaxLoc(result)

    # 缺口左上角坐标
    x, y = max_loc
    return x

# 使用
gap_x = find_gap_position("bg.jpg", "slider.png")
print(f"缺口位置在 {gap_x} 像素处")

方案三:直接用打码平台(省事)

国内主流的打码平台都支持滑块验证码识别,价格通常 1-3 分钱/次:

# 以某打码平台为例
import requests

def solve_slider_captcha(bg_url, slider_url):
    """调用打码平台识别滑块"""

    api_key = "your_api_key"
    data = {
        "key": api_key,
        "method": "slide_captcha",
        "bg": bg_url,        # 背景图URL
        "slider": slider_url # 滑块图URL
    }

    resp = requests.post("http://api.damaplatform.com/solve", json=data)
    result = resp.json()

    if result["success"]:
        # 返回缺口 x 坐标,直接传给浏览器
        return result["data"]["x"]
    else:
        print(f"识别失败: {result.get('message')}")
        return None

建议: 免费折腾的时间成本往往比付费高。如果只是需要采集数据,打码平台几块钱就能解决大部分验证码。

二、请求参数加密

越来越多的 App 和 Web 端对请求参数做了加密,直接抓包看到的是一堆乱码。

1. 常见加密方式

① 参数签名:params + secret → MD5/SHA256 → sign
   服务端用同样的方式算一遍,不一致就拒绝

② 请求体加密:整个 body 用 AES/RSA 加密
   服务端解密后再处理

③ 请求头自定义:x-timestamp、x-nonce、x-sign
   防重放攻击

2. 逆向找到加密代码

# 通过抓包定位加密入口
# 方法:在 Fiddler/Charles 中找加密参数
# 然后在 JS 代码中搜索关键字

# 常见的搜索关键词:
search_keywords = [
    "sign", "signature", "token",
    "encrypt", "encode",
    "md5", "sha256", "aes", "rsa",
    "nonce", "timestamp",
]

3. 用 Python 复现加密逻辑

import hashlib
import time
import random
import string

def generate_sign(params, secret="your_secret_key"):
    """复现 App 的签名算法"""
    # 1. 参数按字典序排序
    sorted_keys = sorted(params.keys())

    # 2. 拼接成字符串
    raw_parts = []
    for k in sorted_keys:
        raw_parts.append(f"{k}={params[k]}")

    # 3. 拼接密钥
    raw_str = "&".join(raw_parts) + secret

    # 4. MD5 加密
    sign = hashlib.md5(raw_str.encode()).hexdigest()

    return sign

def generate_nonce(length=10):
    """生成随机字符串(防重放)"""
    return "".join(random.choices(string.ascii_letters + string.digits, k=length))

def make_request(url, params):
    """带签名的请求"""
    params["timestamp"] = int(time.time())
    params["nonce"] = generate_nonce()
    params["sign"] = generate_sign(params)

    resp = requests.get(url, params=params)
    return resp.json()

4. 逆向工具推荐

工具用途难度
Fiddler + FiddlerScript打断点调试 JS⭐⭐
Chrome DevTools在 Sources 中搜索加密函数⭐⭐
AST(抽象语法树)反混淆加密 JS 代码⭐⭐⭐⭐
PyExecJS用 Python 执行 JS 代码⭐⭐⭐
JSDom模拟浏览器环境执行 JS⭐⭐⭐

最实用的方法:用 PyExecJS 直接调用网站的加密 JS:

import execjs
import requests

# 加载网站的加密 JS 文件(从 Chrome Sources 中复制出来)
with open("encrypt.js", "r", encoding="utf-8") as f:
    js_code = f.read()

# 编译 JS
ctx = execjs.compile(js_code)

# 调用 JS 中的加密函数
params = {"username": "test", "password": "123456"}
sign = ctx.call("encryptSign", params)
print(f"加密签名: {sign}")

三、风控绕过——不要像机器人一样访问

现在的大厂(淘宝、抖音、美团)都有风控系统,技术上的反爬只是一个层面,行为层面的风控往往更敏感。

1. 风控系统会检测什么

① 访问模式
   ❌ 每隔 3 秒整点访问 → 像定时任务
   ✅ 随机间隔 2-5 秒 → 像人类

② 浏览路径
   ❌ 直接访问目标页面 → 不经过首页
   ✅ 先首页 → 列表页 → 详情页 → 像正常用户

③ 鼠标/手势
   ❌ 无鼠标轨迹 → Selenium 特征
   ✅ 有曲线移动 → 人类行为

④ 账户行为
   ❌ 一个账号每天爬 10 万次
   ✅ 多个账号分散爬

2. 模拟人类行为

import time
import random
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()

def human_like_scroll(driver):
    """模拟人类滚动页面"""
    for _ in range(random.randint(3, 6)):
        scroll_distance = random.randint(200, 500)
        driver.execute_script(f"window.scrollBy(0, {scroll_distance});")
        time.sleep(random.uniform(0.5, 1.5))

def human_like_click(driver, element):
    """模拟人类点击"""
    # 先移动鼠标到元素
    action = webdriver.ActionChains(driver)
    action.move_to_element(element).perform()
    time.sleep(random.uniform(0.2, 0.8))
    element.click()

def random_delay():
    """随机延时,避免固定间隔"""
    time.sleep(random.uniform(1.5, 4.5))

# 使用
driver.get("https://example.com")
time.sleep(random.uniform(2, 4))  # 模拟页面加载延迟

# 模拟人类浏览
human_like_scroll(driver)
human_like_click(driver, driver.find_element(By.LINK_TEXT, "下一页"))
random_delay()

3. 多账号轮换

import itertools

class AccountPool:
    """账号池,轮流使用"""
    def __init__(self):
        self.accounts = [
            {"phone": "1380001", "token": "token_a"},
            {"phone": "1380002", "token": "token_b"},
            {"phone": "1380003", "token": "token_c"},
        ]
        self.pool = itertools.cycle(self.accounts)

    def get_account(self):
        return next(self.pool)

    def mark_banned(self, account):
        print(f"账号 {account['phone']} 被封,移除")
        self.accounts.remove(account)

pool = AccountPool()
for i in range(100):
    account = pool.get_account()
    headers = {"Authorization": f"Bearer {account['token']}"}
    resp = requests.get("https://api.example.com/data", headers=headers)
    # ...
    time.sleep(random.uniform(2, 5))

4. 风控降级策略

当爬虫触发风控时,不要硬刚,降低采集频率:

class AdaptiveCrawler:
    """自适应降级爬虫"""

    def __init__(self, initial_delay=1):
        self.delay = initial_delay
        self.min_delay = 0.5
        self.max_delay = 30
        self.success_count = 0
        self.fail_count = 0

    def request(self, url):
        """自适应请求"""
        time.sleep(self.delay)

        resp = requests.get(url, headers=headers)

        if resp.status_code == 200:
            self.success_count += 1
            self.fail_count = 0

            # 连续成功 10 次,适当加速
            if self.success_count >= 10:
                self.delay = max(self.min_delay, self.delay * 0.8)
                self.success_count = 0

        elif resp.status_code in [403, 429]:
            self.fail_count += 1
            self.success_count = 0

            # 触发风控,减速
            self.delay = min(self.max_delay, self.delay * 2)
            print(f"触发风控,降级到 {self.delay}s 间隔")

        return resp

crawler = AdaptiveCrawler(initial_delay=2)
data = crawler.request("https://example.com/api")

四、反爬对抗的真正原则

反爬对抗不是技术的较量,而是成本和收益的博弈

网站为什么反爬?
  怕你影响服务器性能、怕数据被竞对采集
如果你:
  爬得慢、像个人、不是大规模批量
  大多数网站根本就不管你

实用的反反爬策略优先级:

先试试不加任何伪装 → 被拦了再加UA
    → 被拦了加延时
    → 被拦了换代理IP
    → 被拦了处理验证码
    → 被拦了逆向加密参数
    → 被拦了模拟行为风控

从最简单的方法开始,够用就行,不要一上来就搞签名逆向和机器学习。


💡 觉得有用的话,点赞 + 关注【张老师技术栈】吧!每周更新 Java/Python/爬虫 实战干货,不让你白来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值