【爬虫】05 - 爬虫攻防

爬虫05 - 爬虫攻防

文章目录

一:随机User-Agent爬虫

1:fake-useragent

当爬虫请求头(User-Agent)暴露规律时,目标网站的反爬系统会在‌5秒内‌识别并封锁IP。2023年AlexTop百万网站统计显示,‌68.7%的反爬策略会检测User-Agent特征‌。

检测项 检测原理 典型案例
固定特征值 持续相同User-Agent触发阈值告警 某电商平台连续10次相同UA即封禁
非常用浏览器 识别非常规浏览器版本(如过时Chrome 85) 政府网站拒绝服务古董浏览器
设备类型冲突 移动端UA访问PC端网页触发异常 新闻APP接口校验设备一致性
协议完整性 缺失Accept-Encoding/Connection等标准头 金融数据接口强制校验完整协议头
shell 复制代码
pip install fake-useragent --upgrade # 添加upgrade是为了防止旧版数据源失效的问题
python 复制代码
from fake_useragent import UserAgent
import requests

# 创建UserAgent对象, 下面将使用ua.random 获取随机的 UserAgent
ua = UserAgent(browsers=['chrome', 'firefox', 'edge'], os=['windows', 'mac'])
header = {
    'User-Agent': ua.random, # 随机获取一个UserAgent
    'Accept-Encoding': 'gzip, deflate, br', # 告诉服务器,我们接受gzip压缩
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', # 告诉服务器,我们接受中文
    'Connection': 'keep-alive' # 告诉服务器,我们保持连接
}

requests.get('https://www.baidu.com', headers=header)

可以封装设备的一致性

python 复制代码
from fake_useragent import UserAgent
import requests

# 可以封装设备一致性
def generate_user_agent(device_type="pc"):
    ua = UserAgent()
    base_headers = {
        'Accept-Encoding': 'gzip, deflate, br',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
    }
    if device_type == 'mobile':
        return {
            **base_headers,
            'User-Agent': ua.firefox, # a
            'X-Requested-With': 'com.android.browser'
        }
    else:
        return {
            **base_headers,
            'User-Agent': ua.chrome, # b
            'Sec-CH-UA-Platform': '"Windows"'
        }


for page in range(1, 11):
    headers = generate_user_agent('mobile' if page % 2 else 'pc')
    response = requests.get(f'https://www.zhipin.com/job_detail/?query=python&city=101010100&industry=&position=', headers=headers)
    while True:
        if response.status_code == 200:
            print(response.text)
            break
    print("=" * 20)

2:高级反反爬策略

方案一:动态版本更新‌(解决版本过时检测)

python 复制代码
# 强制使用最新Chrome版本  
ua = UserAgent(min_version=120)  # Chrome 120+  
headers = {'User-Agent': ua.chrome}  

方案二:混合真实浏览器指纹‌

python 复制代码
# 从真实浏览器捕获指纹注入  
real_fingerprint = {  
    'Sec-CH-UA': '"Chromium";v="118", "Google Chrome";v="118", "Not=A?Brand";v="8"',  
    'Sec-CH-UA-Mobile': '?0',  
    'Sec-CH-UA-Platform': '"Windows"'  
}  
headers = {‌**generate_context_headers(), **‌real_fingerprint}  

失败重试熔断机制

python 复制代码
from tenacity import retry, stop_after_attempt, wait_exponential  

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1))  
def safe_request(url):  
    try:  
        return requests.get(url, headers=generate_context_headers())  
    except requests.exceptions.RequestException as e:  
        if e.response.status_code == 403:  
            # 触发UA刷新熔断  
            UserAgent().update()  
        raise  

safe_request('https://target.com/api')  

3:生产环境建议

定时更新UA数据库

shell 复制代码
# 每天自动更新UA数据库  
0 3 * * * /usr/bin/python3 -c "from fake_useragent import UserAgent; UserAgent().update()"  

可以配置些监控和报警

python 复制代码
# 当连续5次403错误时触发警报  
if error_count > 5:  
    send_alert(f"UA策略失效!当前拦截率:{error_count/request_count*100:.2f}%")  
    switch_to_backup_proxy()  

在生产环境中最好使用多库备用

python 复制代码
# 当fake_useragent失效时切换至browser_useragent  
try:  
    from fake_useragent import UserAgent  
except ImportError:  
    from browswer_useragent import BrowserUserAgent as UserAgent

二:代理IP爬虫

当爬虫请求频率超过‌5次/秒‌时,目标网站的反爬系统将在‌10秒内‌封锁当前IP。据2024年全球反爬技术报告,‌83%的网站采用IP指纹检测‌作为核心防御手段

动态代理IP池‌,结合智能路由与熔断机制实现反爬突围,实测将IP封禁率从‌72%降至3%‌

检测维度 反爬策略 典型案例
请求频率阈值 单IP单位时间内请求次数超限触发封禁 某社交平台限制单IP每秒3次请求
IP黑名单库 识别代理服务器IP段并全局封禁 新闻网站屏蔽已知数据中心IP
地理位置异常 短时间跨国IP跳跃触发风控 电商平台拦截中美IP交替访问行为

1:获取代理IP

可以使用https://free-proxy-list.net/zh-cn/获取到免费的代理IP

python 复制代码
import requests
import random
from bs4 import BeautifulSoup

# 获取免费代理 -> https://free-proxy-list.net/zh-cn/
def scrape_free_proxies():
    url = "https://free-proxy-list.net/" # 获取免费的ip代理
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    proxies = [] # 创建一个空列表, 存储获取到的代理
    for row in soup.select("table.table tbody tr"):
        cols = row.find_all('td')
        if len(cols) < 7:
            continue
        if cols[4].text == 'elite proxy' and cols[6].text == 'yes':
            proxies.append(f"{cols[0].text}:{cols[1].text}")
    return proxies


# 请求, 如果失败了就换一个代理IP, 最多尝试5次
def do_request(proxy):
    for i in range(5):
        print(f"Trying proxy: {proxy}")
        try:
            response = requests.get('http://www.baidu.com', proxies=proxy)
            print(response.json())
            return
        except:
            print(f"Failed to get IP, trying again...")
            proxy = {'http': f'http://{random.choice(proxy_list)}',
                             'https': f'http://{random.choice(proxy_list)}'}


if __name__ == '__main__':
    proxy_list = scrape_free_proxies()
    print(proxy_list)
    # 随机选择代理
    current_proxy = {'http': f'http://{random.choice(proxy_list)}',
                     'https': f'http://{random.choice(proxy_list)}'}

    do_request(current_proxy)

还可以添加代理IP的智能容错机制

python 复制代码
import requests
import random
from bs4 import BeautifulSoup
from tenacity import retry, stop_after_attempt, wait_fixed


# 获取免费代理 -> https://free-proxy-list.net/zh-cn/
def scrape_free_proxies():
    url = "https://free-proxy-list.net/" # 获取免费的ip代理
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    proxies = [] # 创建一个空列表, 存储获取到的代理
    for row in soup.select("table.table tbody tr"):
        cols = row.find_all('td')
        if len(cols) < 7:
            continue
        if cols[4].text == 'elite proxy' and cols[6].text == 'yes':
            proxies.append(f"{cols[0].text}:{cols[1].text}")
    return proxies


# 代理IP的智能容错机制
@retry(stop=stop_after_attempt(3), wait=wait_fixed(2))
def robust_request(url, proxy_pool):
    proxy = random.choice(proxy_pool)
    try:
        return requests.get(url,
                            proxies={'http': f'http://{proxy}', 'https': f'http://{proxy}'},
                            timeout=10)
    except (requests.ProxyError, requests.ConnectTimeout):
        proxy_pool.remove(proxy)  # 移除失效代理
        raise


if __name__ == '__main__':
    proxy_list = scrape_free_proxies()
    url = "http://www.baidu.com"
    response = robust_request(url, proxy_list)
    print(response.status_code)
    print(response.text)

2:高阶攻防

四类代理IP的选型

代理类型 优势 劣势 适用场景
数据中心代理 高速度、低延迟 易被识别封禁 快速抓取非敏感数据
住宅代理 高匿名性、真实用户IP 成本高、速度波动 对抗严格反爬(如Cloudflare)
移动4G代理 极高匿名性、IP池庞大 稳定性差、管理复杂 抓取APP接口数据
Socks5代理 支持UDP、加密传输 配置复杂、兼容性要求 需要深度匿名场景

由此演化出对应IP黑名单的三种策略:

策略一:协议混淆,将HTTP流量伪装成Socks5

python 复制代码
import socks  
import socket  

# 强制使用Socks5协议  
socks.set_default_proxy(socks.SOCKS5, "proxy_ip", 1080)  
socket.socket = socks.socksocket  

# 发送请求(网站识别为普通Socks流量)  
requests.get("https://target.com")  

策略二:IP冷启动‌:新代理首次访问仅采集低风险页面

策略三:流量染色‌:在代理请求中注入真实浏览器指纹(如TLS指纹)

3:企业级的代理实战

redis自建代理池系统

python 复制代码
import redis
import json
import requests

class ProxyPool:
    def __init__(self):
        self.redis = redis.Redis(host='127.0.0.1', port=6379, db=0)

    def add_proxy(self, proxy:str, score:float=100):
        self.redis.zadd('proxies', {proxy: score})


    def get_best_proxy(self):
        return self.redis.zrange('proxies', 0, 0)[0].decode()

    def refresh_proxy(self, proxy:str, penalty:float=20):
        self.redis.zincrby('proxies', -penalty, proxy)

if __name__ == '__main__':
    # 添加代理
    pool = ProxyPool()
    pool.add_proxy('127.0.0.1:8080')
    pool.add_proxy('127.0.0.1:8081')
    pool.add_proxy('127.0.0.1:8082')
    # 获取代理
    best_proxy = pool.get_best_proxy()

    try:
        # 请求, 如果失败了就换一个代理IP, 最多尝试5次
        requests.get("https://target.com", proxies={'http': best_proxy})
    except Exception:
        pool.refresh_proxy(best_proxy)  

商业代理集成

python 复制代码
import hashlib  
import time  

def gen_mogu_proxy():  
    # 生成动态签名  
    timestamp = str(int(time.time()))  
    secret = "your_api_secret"  
    sign = hashlib.md5(f"timestamp={timestamp}&secret={secret}".encode()).hexdigest()  

    # 获取独享代理(按需切换IP)  
    api_url = f"http://piping.mogumiao.com/proxy/api/get_ip?count=1&timestamp={timestamp}&sign={sign}"  
    result = requests.get(api_url).json()  
    return f"{result['msg'][0]['ip']}:{result['msg'][0]['port']}"  

# 获取高匿名IP  
mogu_proxy = gen_mogu_proxy()  
requests.get("https://target.com", proxies={'http': f'http://{mogu_proxy}'})  

三:动态数据的抓取

当传统爬虫遭遇‌React/Vue单页应用‌时,‌83%的数据请求‌通过Ajax/WebSocket动态加载,直接获取HTML源码的成功率不足15%。

而如果结合‌逆向工程‌与‌无头浏览器控制技术‌,构建覆盖SPA(单页应用)、SSR(服务端渲染)、CSR(客户端渲染)的全场景解决方案,实现动态数据抓取成功率从‌12%到98%‌的技术跃迁

1:动态页面技术全景

技术类型 核心原理 典型场景
Ajax/XHR XMLHttpRequest异步获取数据 电商商品分页加载
WebSocket 全双工通信实时更新 股票行情/在线聊天
SSR 服务端生成动态HTML(如Next.js) 新闻门户首屏渲染
CSR 客户端JS动态构建DOM(如React/Vue) 后台管理系统
JSONP 跨域数据获取(逐渐被CORS替代) 老旧天气预报接口

爬虫 浏览器 CDN API服务器 React 访问https://shop.com 获取基础HTML框架 返回包含React Root的HTML 发送XHR请求GET /api/products 返回JSON数据 执行hydrate渲染DOM 生成完整商品列表DOM 爬虫 浏览器 CDN API服务器 React

2:动态页面逆向工程

2.1:XHR请求追踪与解析
  1. 打开‌Network面板‌并筛选XHR/Fetch请求
  2. 定位目标数据的API端点(如/graphql)
  3. 解析请求头认证参数(Authorization/X-API-Key)
  4. 复制为Python代码(Copy as cURL → 转换为requests代码)
python 复制代码
import requests
from urllib.parse import urlencode

# 设置请求头,包含API版本和授权信息
headers = {
    'x-api-version': '3.2',
    'authorization': 'Bearer eyJhbGciOiJIUzI1Ni...',
}

# 定义请求参数,包括类别ID、排序方式、页码和平台信息
params = {
    'categoryId': 305,
    'sort': 'sales_desc',
    'page': 1,
    'platform': 'web'
}

# 直接请求数据接口
response = requests.get(
    'https://api.shop.com/graphql',
    headers=headers,
    params=urlencode(params, doseq=True)
)

# 解析JSON数据
products = response.json()['data']['products']
2.2:websocket实时数据捕获
python 复制代码
import asyncio
import websockets
import json

async def fetch_danmu():
    uri = "wss://live-api.example.com/ws"  # 替换为实际的 WebSocket 地址
    while True:
        try:
            async with websockets.connect(uri) as websocket:
                print("成功连接到直播间!")

                # 可选:发送认证信息
                auth_message = json.dumps({
                    "user": "test_user",
                    "token": "your_token"
                })
                await websocket.send(auth_message)
                print("认证信息已发送")

                while True:
                    try:
                        # 接收服务器发送的弹幕消息
                        message = await websocket.recv()
                        danmu_data = json.loads(message)
                        print(f"收到弹幕: {danmu_data.get('content', '未知内容')}")
                    except websockets.exceptions.ConnectionClosed:
                        print("连接断开,重新连接中...")
                        break
        except Exception as e:
            print(f"发生错误: {e}")
            break

# 运行异步任务
asyncio.get_event_loop().run_until_complete(fetch_danmu())

3:无头浏览器控制技术

无头浏览器(Headless Browser)是指没有图形用户界面的浏览器,可以通过编程方式控制,模拟用户操作,执行JavaScript渲染,是现代爬虫技术中的重要工具。

  1. Puppeteer - Google开发的Node库,控制Chromium/Chrome
  2. Playwright - Microsoft开发的多浏览器控制工具
  3. Selenium - 传统的浏览器自动化框架(后面介绍)
  4. Pyppeteer - Puppeteer的Python版本
3.1:Playwright详解

Playwright是由Microsoft开发的跨浏览器自动化测试工具,支持Chromium、WebKit和Firefox

Playwright有如下的特性:

  • 多浏览器支持:Chromium (Chrome, Edge)、WebKit (Safari)、Firefox
  • 跨平台能力:Windows、macOS、Linux全平台支持 & 可本地运行也可CI/CD集成
  • 多语言绑定:JavaScript/TypeScript、Python、Java、.NET
  • 现代化架构:基于WebSocket的通信协议、自动等待机制、强大的选择器引擎
bash 复制代码
pip install playwright
playwright install  # 安装浏览器

基本页面操作

python 复制代码
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    # 启动浏览器(无头模式)
    browser = p.chromium.launch(headless=False)
    
    # 创建新页面
    page = browser.new_page()
    
    # 导航到URL
    page.goto("https://example.com")
    
    # 获取页面标题
    print(page.title())
    
    # 截图
    page.screenshot(path="example.png")
    
    # 关闭浏览器
    browser.close()

元素定位与交互 - Playwright提供多种强大的选择器:

python 复制代码
# CSS选择器
page.click("button.submit")

# 文本选择器
page.click("text=Login")

# XPath
page.click("//button[@id='submit']")

# 组合选择器
page.click("article:has-text('Playwright') >> button")

自动等待机制 -> Playwright内置智能等待,无需手动添加sleep

python 复制代码
# 等待元素出现(最多10秒)
page.wait_for_selector("#dynamic-element", timeout=10000)

# 等待导航完成
page.click("text=Navigate")
page.wait_for_url("**/new-page")

# 等待网络请求完成
with page.expect_response("**/api/data") as response_info:
    page.click("button.load-data")
response = response_info.value

网络请求拦截

python 复制代码
# 路由拦截
def handle_route(route):
    if "ads" in route.request.url:
        route.abort()  # 阻止广告请求
    else:
        route.continue_()

page.route("**/*", handle_route)

文件下载处理

python 复制代码
# 等待下载开始
with page.expect_download() as download_info:
    page.click("a#download-link")
download = download_info.value

# 保存下载文件
path = download.path()
download.save_as("/path/to/save")

iframe处理

python 复制代码
# 定位iframe
frame = page.frame(name="embedded")

# 在iframe内操作
frame.fill("#username", "testuser")
frame.click("#submit")

爬虫实战应用:

动态内容抓取

python 复制代码
async with async_playwright() as p:
    browser = await p.chromium.launch()
    page = await browser.new_page()
    
    await page.goto("https://dynamic-ecom-site.com")
    
    # 滚动加载所有商品
    while await page.locator("text=Load More").is_visible():
        await page.click("text=Load More")
        await page.wait_for_timeout(2000)  # 适当延迟
    
    # 提取所有商品数据
    products = await page.locator(".product").evaluate_all("""
        products => products.map(p => ({
            name: p.querySelector('.name').innerText,
            price: p.querySelector('.price').innerText
        }))
    """)
    
    print(products)
    await browser.close()

登录会话保持

python 复制代码
# 保存登录状态
context = browser.new_context()
page = context.new_page()
page.goto("login_url")
page.fill("#username", "user")
page.fill("#password", "pass")
page.click("#login")

# 保存cookies
context.storage_state(path="auth.json")

# 后续使用保存的状态
context = browser.new_context(storage_state="auth.json")
page = context.new_page()

性能优化技巧

浏览器上下文复用:

python 复制代码
context = browser.new_context()
page1 = context.new_page()
page2 = context.new_page()

请求过滤:

python 复制代码
await page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort())

并行处理:

python 复制代码
async with asyncio.TaskGroup() as tg:
    tg.create_task(scrape_page(page1, url1))
    tg.create_task(scrape_page(page2, url2))

常见问题解决方案

检测规避:

python 复制代码
# 修改WebGL供应商信息
await page.add_init_script("""
    const originalGetParameter = WebGLRenderingContext.prototype.getParameter;
    WebGLRenderingContext.prototype.getParameter = function(parameter) {
        if (parameter === 37445) return "Intel Open Source Technology Center";
        return originalGetParameter.call(this, parameter);
    };
""")

超时处理:

python 复制代码
try:
    await page.wait_for_selector(".element", timeout=5000)
except TimeoutError:
    print("元素加载超时")

元素点击问题:

python 复制代码
await page.locator("button").dispatch_event("click")  # 直接触发事件

与Puppeteer对比

特性 Playwright Puppeteer
浏览器支持 多引擎 仅Chromium
语言支持 多种 主要JS
自动等待 更智能 基础
选择器引擎 更强大 标准
移动设备模拟 完善 有限
社区生态 快速增长 成熟稳定
3.2:反反爬虫策略应对
  • 指纹伪装:修改浏览器指纹特征
  • 行为模拟:模拟人类操作模式(鼠标移动、随机延迟)
  • 代理轮换:结合代理IP池使用
  • WebGL/Canvas指纹处理:定制化渲染参数
3.3:高级技术应用

分布式无头浏览器集群
调度中心 浏览器节点1 浏览器节点2 浏览器节点3 代理IP池

智能渲染策略

  • 按需渲染:根据目标网站特点定制渲染策略
  • 资源加载控制:选择性加载CSS/JS/图片
  • 预渲染缓存:对常见页面进行预渲染

性能优化技术

  1. 浏览器实例复用:避免频繁启动关闭
  2. 页面池管理:维护多个页面实例
  3. 资源拦截:阻止不必要资源加载
  4. CDN缓存利用:合理设置缓存策略
挑战 解决方案
资源消耗大 浏览器实例池化、资源限制
检测风险高 指纹伪装、行为模拟
稳定性问题 心跳检测、自动恢复
性能瓶颈 分布式架构、智能调度
相关推荐
爬虫程序猿7 小时前
利用 Java 爬虫获取淘宝商品 SKU 详细信息实战指南
java·开发语言·爬虫
元Y亨H12 小时前
Playwright 下载文件时页面被 Chrome 过滤的问题
爬虫
q5673152318 小时前
手把手教你用Go打造带可视化的网络爬虫
开发语言·爬虫·信息可视化·golang
q567315231 天前
无需Python:Shell脚本如何成为你的自动化爬虫引擎?
开发语言·爬虫·python·自动化·scala
moonsheeper2 天前
用爬虫技术及各种技术使用场景
爬虫
是有头发的程序猿2 天前
电商开发日志:淘宝图片搜索商品列表(二)
数据库·爬虫·python
fyakm2 天前
python和java爬虫优劣对比
java·爬虫·python
华科云商xiao徐2 天前
手把手教你用Go打造带可视化的网络爬虫
前端·爬虫
华科云商xiao徐2 天前
Python爬虫数据清洗实战:从杂乱无章到整洁可用
爬虫·数据挖掘·数据分析
q567315232 天前
从开发到部署深度解析Go与Python爬虫利弊
爬虫·python·golang