Python爬虫进阶实战笔记

Python爬虫进阶的核心在于突破限制架构设计 。这不仅仅是发送一个requests.get(),而是涉及到网络协议、浏览器渲染、数据流控制和反爬对抗。

1. 异步并发与速率控制(性能瓶颈突破)

当需要采集百万级数据时,同步IO会成为瓶颈。进阶需要使用asyncio + aiohttp,但直接暴力并发会瞬间被Ban,因此必须引入令牌桶算法控制速率。

python

复制代码
import asyncio
import aiohttp
from asyncio import Semaphore

class AsyncRateLimiter:
    """滑动窗口速率控制器(令牌桶变种)"""
    def __init__(self, rate, per):
        self.semaphore = Semaphore(rate)  # 最大并发数
        self.interval = per / rate        # 每次请求间隔
    
    async def acquire(self):
        await self.semaphore.acquire()
        asyncio.create_task(self._release())

    async def _release(self):
        await asyncio.sleep(self.interval)
        self.semaphore.release()

async def fetch(session, url, limiter):
    await limiter.acquire()
    async with session.get(url) as resp:
        return await resp.text()

# 运行逻辑:每秒最多50个请求,且严格间隔0.02秒

2. 应对复杂JavaScript渲染(Pyppeteer 底层控制)

对于SPA应用,Selenium太慢。进阶使用pyppeteer(无头Chrome的Python异步接口)。高级技巧是拦截请求注入JS来绕过检测。

python

复制代码
import asyncio
from pyppeteer import launch

async def smart_render():
    browser = await launch(headless=True, args=['--no-sandbox'])
    page = await browser.newPage()
    
    # 1. 拦截网络请求(阻塞图片和字体,大幅提速)
    await page.setRequestInterception(True)
    page.on('request', lambda req: asyncio.ensure_future(
        req.abort() if req.resourceType in ['image', 'font', 'media'] else req.continue_()
    ))

    # 2. 在页面上下文中覆写WebDriver检测属性
    await page.evaluateOnNewDocument('''
        Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
        window.chrome = { runtime: {} };
    ''')

    await page.goto('https://target.com', {'waitUntil': 'networkidle2'})
    
    # 3. 等待特定元素出现(比time.sleep智能)
    await page.waitForSelector('#data-loaded', {'timeout': 10000})
    
    content = await page.content()
    await browser.close()
    return content

3. 深度指纹对抗(TLS 指纹伪装)

服务端不仅看User-Agent,还通过JA3指纹检测Python的urllib3/requests库特征。进阶必须使用curl_cffi模拟真实浏览器的TLS握手。

python

复制代码
from curl_cffi import requests

# 冒充 Chrome 110 的 TLS 指纹和 HTTP/2 优先级设置
response = requests.get(
    'https://httpbin.io/headers',
    impersonate="chrome110",  # 这一行关键:伪造底层加密套件
    timeout=30
)

# 若结合代理,需额外处理代理头的转发
proxies = {"http": "socks5://127.0.0.1:1080", "https": "socks5://127.0.0.1:1080"}
response = requests.get('https://target.com', impersonate="edge101", proxies=proxies)

4. 增量式去重与布隆过滤器(大规模存储优化)

当数据量达到千万级,用Redis Set存储URL会耗尽内存。使用布隆过滤器(Scalable Bloom Filter)只存指纹,允许极低误判率,大幅节省空间。

python

复制代码
from pybloom_live import ScalableBloomFilter

# 自动扩容的布隆过滤器,误判率 0.001%
url_bloom = ScalableBloomFilter(mode=ScalableBloomFilter.SMALL_SET_GROWTH)

def is_duplicate(url):
    # 只存 URL 的哈希指纹,不存原文
    if url in url_bloom:
        return True
    url_bloom.add(url)
    return False

# 在异步管道中的使用
async def save_item(item):
    fingerprint = f"{item['title']}_{item['publish_date']}"
    if not is_duplicate(fingerprint):
        await db.insert(item)  # 异步写入数据库

5. 动态代理池与重试中间件(高可用架构)

进阶不是简单地用proxies参数,而是构建中间件,自动检测代理失效并切换。通过装饰器实现熔断重试。

python

复制代码
import random
from functools import wraps
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception

def proxy_retry_middleware(func):
    @wraps(func)
    @retry(
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=1, min=2, max=10),
        retry=retry_if_exception(lambda e: "403" in str(e) or "Timeout" in str(e))
    )
    async def wrapper(*args, **kwargs):
        # 从队列中取出可用代理(通过Redis维护高可用池)
        proxy = await get_available_proxy()
        kwargs['proxy'] = f"http://{proxy}"
        try:
            return await func(*args, **kwargs)
        except Exception as e:
            # 标记代理失效,从池中移除
            await mark_proxy_dead(proxy)
            raise e  # 抛出让 tenacity 重试
    return wrapper

6. 分布式调度器(去中心化协调)

使用Redis + Lua脚本实现原子性的任务分发,避免多台爬虫服务器抢占同一个任务导致重复。

lua

复制代码
-- acquire_task.lua
local key = KEYS[1]
local task = redis.call('rpop', key)  -- 从右端取
if task then
    redis.call('lpush', 'processing_queue', task)  -- 放入处理中队列
    redis.call('expire', 'processing_queue', 600) -- 超时回收
end
return task

python

复制代码
import redis
r = redis.Redis(host='localhost', decode_responses=True)

# Python 调用 Lua 实现原子操作
sha = r.script_load("""
    local task = redis.call('rpop', KEYS[1])
    if task then redis.call('lpush', 'processing', task) end
    return task
""")
task_data = r.evalsha(sha, 1, 'task_queue')

总结:进阶思维导图

层面 初级做法 进阶做法
并发 ThreadPoolExecutor asyncio + 令牌桶限流
渲染 time.sleep 硬等 waitForSelector + 请求拦截
反爬 换 UA/加延时 TLS指纹篡改 + JS注入
存储 MySQL 全量存储 布隆过滤器 + 增量更新
架构 单机运行 Lua原子任务 + 代理中间件
相关推荐
杨超越luckly2 小时前
Agent应用指南:获取12306官网全量站点及其编码信息
python·数据挖掘·数据分析·可视化·12306
16月6日-晴2 小时前
Java面向对象进阶—static
java·开发语言
爱跳舞的烤冷面2 小时前
自学嵌入式第16天(数据结构篇--链表进阶操作)
开发语言
持敬chijing2 小时前
PHP开发-环境搭建-安装phpstudy-vscode工具
开发语言·vscode·php
2401_868534782 小时前
MATLAB:车牌识别
python·django
卷无止境2 小时前
用 FastAPI 撑起大文件的上传下载:从流式处理到断点续传的完整实践
后端·python·fastapi
charlie1145141912 小时前
Cinux · 第一次跳进 Ring 3:用户态与特权隔离
开发语言·c++·操作系统·开源项目
躺不平的理查德3 小时前
Windows C++ 第三方库使用流程备忘录--OpenCV
开发语言·c++