上个月爬某电商平台的商品数据,前3天被封了3次IP、2次账号——一开始卡在JS加密的sign参数,好不容易破解后又遇到滑块验证,过了滑块又被指纹识别打回原形。折腾一周后,把JS逆向、滑块轨迹、指纹修改这三板斧吃透,现在爬同类网站成功率稳定在95%以上。这篇文章把我踩过的坑、破解的思路和可直接复用的代码全分享出来,新手也能跟着突破大部分网站的反爬防御。

一、JS加密逆向:破解API参数的“密码锁”

现在90%的网站都会对API请求参数做加密(比如sign、token),直接传明文参数会返回403或400。破解JS加密的核心不是“看懂所有JS代码”,而是“找到加密函数、提取密钥、复现逻辑”。

实战案例:破解某短视频平台的sign参数

以某短视频平台的商品列表API为例,抓包发现请求参数里有个sign字段,每次请求都不一样,其他参数(timestamp、page)是明文。

步骤1:定位加密函数
  1. 打开Chrome开发者工具(F12)→ Network,找到目标API请求,复制sign的值(比如abc123def456);
  2. 切换到Sources → 按Ctrl+Shift+F,粘贴sign或关键词getSign/sign=搜索,找到加密相关的JS代码;
  3. 发现一段混淆后的代码(变量名是a/b/c,典型的混淆特征):
    function e(t) {
        var e = "doubao_secret_2025"; // 密钥藏在这里
        return md5(t + e)
    }
    var a = Date.parse(new Date()) / 1000; // timestamp
    var c = e(a + "page=" + b); // sign = md5(timestamp+"page="+page+密钥)
    
步骤2:复现加密逻辑(Python实现)

不管JS多混淆,只要找到加密算法(MD5/SHA256/AES)和密钥,就能用Python复现:

import requests
import time
import hashlib

def generate_sign(timestamp, page):
    """复现JS加密逻辑:sign = md5(timestamp+"page="+page+密钥)"""
    secret = "doubao_secret_2025"  # 从JS中提取的密钥
    sign_str = f"{timestamp}page={page}{secret}"
    # MD5加密,转小写(JS里md5默认返回小写)
    sign = hashlib.md5(sign_str.encode()).hexdigest().lower()
    return sign

def crawl_encrypted_api():
    url = "https://api.example.com/goods/list"
    page = 1
    timestamp = int(time.time())  # 与JS一致的时间戳(秒级)
    sign = generate_sign(timestamp, page)
    
    params = {
        "timestamp": timestamp,
        "page": page,
        "sign": sign,
        "size": 20
    }
    
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
    }
    
    response = requests.get(url, headers=headers, params=params)
    if response.status_code == 200:
        print("JS加密破解成功!返回数据:")
        print(response.json()["data"][0]["title"])
    else:
        print(f"破解失败,状态码:{response.status_code}")

if __name__ == "__main__":
    crawl_encrypted_api()
踩坑记录:
  • 混淆JS看不懂? 用Chrome的“Pretty Print”({}按钮)格式化代码,再用断点调试(在加密函数前打√),一步步看参数传递;
  • 密钥藏在其他JS文件? 搜索secret/key/token等关键词,或跟踪e函数的调用链;
  • 时间戳不一致? JS里用Date.parse(new Date())/1000(秒级),Python别用毫秒级的time.time()

二、滑块验证突破:模拟真人轨迹,骗过“机器检测”

滑块验证(比如极验、腾讯防水墙)是网站防爬虫的“守门员”,核心难点不是找缺口,而是模拟真人的滑动轨迹——匀速滑动、直线滑动都会被识别为爬虫。

实战案例:突破极验4.0滑块验证

步骤1:找缺口位置(OpenCV图像识别)

先用OpenCV对比原图和缺口图,定位缺口的x坐标:

import cv2
import numpy as np

def find_gap(img_path, gap_img_path):
    """
    找缺口位置:返回缺口的x坐标
    img_path: 原图路径
    gap_img_path: 缺口图路径
    """
    # 读取图片
    img = cv2.imread(img_path, 0)
    gap_img = cv2.imread(gap_img_path, 0)
    
    # 用模板匹配找缺口
    res = cv2.matchTemplate(img, gap_img, cv2.TM_CCOEFF_NORMED)
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
    
    # 缺口的x坐标(max_loc是匹配到的左上角坐标)
    gap_x = max_loc[0]
    return gap_x
步骤2:模拟真人滑动轨迹(关键!)

真人滑动的特点:先加速、再匀速、最后减速,轨迹有微小偏移(不是绝对直线):

import random
from playwright.sync_api import sync_playwright

def human_slide_track(distance):
    """生成真人滑动轨迹:返回[(x1,y1), (x2,y2), ...]"""
    track = []
    current_x = 0
    # 总时间控制在1-1.5秒,步数20-30步
    steps = random.randint(20, 30)
    for step in range(steps):
        # 加速阶段(前1/3):x增量大
        if step < steps/3:
            x = random.uniform(1, 3) * (distance/steps)
        # 匀速阶段(中间1/3):x增量稳定
        elif step < steps*2/3:
            x = random.uniform(0.8, 1.2) * (distance/steps)
        # 减速阶段(最后1/3):x增量小
        else:
            x = random.uniform(0.3, 0.8) * (distance/steps)
        
        current_x += x
        # y坐标加微小偏移(真人不会绝对水平)
        y = random.uniform(-1, 1)
        track.append((current_x, y))
    # 最后补到目标距离,避免差一点
    track.append((distance, random.uniform(-1, 1)))
    return track

def slide_verification():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()
        page.goto("https://www.geetest.com/demo/slide-float.html")  # 极验测试页
        
        # 等待滑块加载
        page.wait_for_selector(".geetest_slider_button")
        
        # 保存原图和缺口图(实际爬取时从页面获取图片URL)
        page.screenshot(path="full.png")  # 原图
        page.click(".geetest_slider_button")  # 点击滑块显示缺口
        page.wait_for_timeout(1000)
        page.screenshot(path="gap.png")  # 缺口图
        
        # 找缺口位置
        gap_x = find_gap("full.png", "gap.png")
        print(f"缺口x坐标:{gap_x}")
        
        # 生成轨迹
        track = human_slide_track(gap_x - 10)  # 减10是滑块本身的宽度
        
        # 模拟滑动
        slider = page.locator(".geetest_slider_button")
        slider.hover()
        page.mouse.down()  # 按下鼠标
        
        for x, y in track:
            page.mouse.move(x, y + 50)  # y固定50(滑块高度),加微小偏移
            page.wait_for_timeout(random.randint(10, 30))  # 每步间隔10-30ms
        
        page.mouse.up()  # 松开鼠标
        page.wait_for_timeout(2000)
        
        # 验证是否成功
        if page.locator(".geetest_success_radar").is_visible():
            print("滑块验证成功!")
        else:
            print("滑块验证失败,可能轨迹太规律")
        
        browser.close()

if __name__ == "__main__":
    slide_verification()
踩坑记录:
  • 轨迹太规律被检测? 加随机偏移(x/y都加±1),总时间控制在1-1.5秒(太快像机器);
  • 缺口识别不准?cv2.TM_CCOEFF_NORMED匹配算法,或对图片做灰度化/降噪处理;
  • Playwright被识别?--disable-blink-features=AutomationControlled参数(禁用自动化标记)。

三、指纹识别绕过:修改浏览器“身份证”,避免被标记

浏览器指纹是网站识别爬虫的“终极手段”——即使换IP、换Cookie,指纹相同还是会被封。常见的指纹类型有:Canvas指纹、WebGL指纹、User-Agent组合、字体列表等。

实战案例:修改Canvas+WebGL指纹

步骤1:固定Canvas指纹

Canvas指纹是通过绘制图形生成的唯一哈希值,修改toDataURL方法固定返回值:

from playwright.sync_api import sync_playwright

def modify_canvas_fingerprint():
    with sync_playwright() as p:
        # 启动浏览器,禁用自动化检测
        browser = p.chromium.launch(
            headless=False,
            args=[
                "--disable-blink-features=AutomationControlled",
                "--disable-webgl",  # 禁用WebGL指纹(简单粗暴)
                "--lang=zh-CN"
            ]
        )
        page = browser.new_page()
        
        # 注入JS修改Canvas指纹
        page.add_init_script('''
            // 重写Canvas的toDataURL和getImageData方法
            Object.defineProperty(HTMLCanvasElement.prototype, 'toDataURL', {
                value: function() {
                    return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
                }
            });
            
            Object.defineProperty(HTMLCanvasElement.prototype, 'getImageData', {
                value: function() {
                    return { data: new Uint8ClampedArray([255, 255, 255, 255]) };
                }
            });
        ''')
        
        # 访问指纹检测网站验证
        page.goto("https://browserleaks.com/canvas")
        print("请查看Canvas指纹是否固定(哈希值不变),按回车关闭...")
        input()
        
        browser.close()
步骤2:搭建指纹池(进阶)

如果需要多个不同指纹,用指纹池随机选择User-Agent+Canvas值:

# 指纹池:User-Agent + Canvas哈希值
FINGERPRINT_POOL = [
    {
        "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "canvas_hash": "abc123def456"
    },
    {
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
        "canvas_hash": "789xyz012abc"
    }
]

def random_fingerprint():
    """随机选一个指纹"""
    return random.choice(FINGERPRINT_POOL)

# 在Playwright中使用随机指纹
def use_random_fingerprint():
    fingerprint = random_fingerprint()
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page(user_agent=fingerprint["user_agent"])
        
        # 注入对应Canvas指纹
        page.add_init_script(f'''
            Object.defineProperty(HTMLCanvasElement.prototype, 'toDataURL', {{
                value: function() {{
                    return 'data:image/png;base64,{fingerprint["canvas_hash"]}';
                }}
            }});
        ''')
        
        page.goto("https://browserleaks.com/canvas")
        input()
        browser.close()
踩坑记录:
  • 指纹修改不彻底? 除了Canvas,还要改WebGL(禁用或修改)、字体列表(注入假字体);
  • User-Agent和系统不匹配? 比如User-Agent是Windows,但页面检测到是Mac,会被识别——指纹池里要保证User-Agent和系统信息一致。

四、综合防御破解:组合拳突破90%网站

单一方法很难突破复杂反爬,需要用“代理+加密破解+滑块+指纹+延迟”的组合拳:

实战组合方案(可直接复用)

import requests
import time
import random
from playwright.sync_api import sync_playwright
import hashlib

# 1. 代理配置(隧道代理,自动换IP)
PROXY_HOST = "http-dyn.abuyun.com"
PROXY_PORT = "9020"
PROXY_USER = "你的代理账号"
PROXY_PASS = "你的代理密码"
proxies = {
    "http": f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}",
    "https": f"http://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}"
}

# 2. JS加密函数
def generate_sign(timestamp, page):
    secret = "site_secret_key"
    sign_str = f"{timestamp}page={page}{secret}"
    return hashlib.md5(sign_str.encode()).hexdigest()

# 3. 主爬取函数
def crawl_with_defense():
    # 第一步:用Playwright过滑块验证,获取Cookie
    cookie = None
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=False,
            args=["--disable-blink-features=AutomationControlled"]
        )
        page = browser.new_page()
        page.goto("https://www.target.com/login")
        
        # 过滑块验证(省略滑块代码,参考前面的slide_verification)
        # ...
        
        # 获取登录后的Cookie
        cookie = page.context.cookies()[0]["value"]
        browser.close()
    
    # 第二步:用requests+代理+加密参数爬取数据
    headers = {
        "User-Agent": random.choice([
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
        ]),
        "Cookie": f"session={cookie}",
        "Referer": "https://www.target.com/"
    }
    
    for page in range(1, 10):
        timestamp = int(time.time())
        sign = generate_sign(timestamp, page)
        params = {"timestamp": timestamp, "page": page, "sign": sign}
        
        try:
            response = requests.get(
                "https://api.target.com/data",
                headers=headers,
                params=params,
                proxies=proxies,
                timeout=10
            )
            
            if response.status_code == 200:
                print(f"第{page}页爬取成功,数据量:{len(response.json()['data'])}")
            else:
                print(f"第{page}页失败,状态码:{response.status_code}")
            
            # 随机延迟1-3秒,模拟真人
            time.sleep(random.uniform(1, 3))
        except Exception as e:
            print(f"第{page}页异常:{str(e)}")
            # 代理失效,重试一次
            time.sleep(5)
            continue

if __name__ == "__main__":
    crawl_with_defense()

五、避坑与合规提醒

  1. 别用免费代理:免费代理90%是爬虫IP,反而容易被封,练手用阿布云/快代理的试用流量;
  2. 频率控制:即使有代理,也别每秒爬10次——大部分网站的频率限制是“1分钟≤20次”;
  3. 合规第一
    • 不爬取隐私数据(身份证、手机号、银行卡);
    • 遵守robots.txt协议(虽然法律不强制,但能减少麻烦);
    • 不爬取付费内容或 copyrighted 数据(比如付费课程、电影);
    • 爬取前看网站的《用户协议》,明确禁止爬取的别碰。

总结

反爬的本质是“网站识别机器行为”,破解的核心就是“让爬虫行为无限接近真人”:

  • JS加密:逆向算法,复现参数;
  • 滑块验证:模拟真人轨迹,别太规律;
  • 指纹识别:修改或替换浏览器指纹,避免被标记;
  • 最后用代理+延迟+Cookie池做组合防御。

现在我用这套方法爬过电商、自媒体、招聘等平台,除了极少数用AI风控的网站(比如支付宝),90%的网站都能突破。如果你们遇到“JS混淆看不懂”“滑块一直失败”等问题,评论区留具体场景,我会用实战经验帮你拆解!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐