作为写了4年爬虫的老鸟,我从最开始的“爬3页就封IP”“验证码卡一下午”“数据乱码没法用”,到现在“爬10万条无异常”“验证码自动识别”“数据精准提取”,前前后后踩了足足50个坑——有免费代理的坑、有编码格式的坑、有滑块验证码的坑,甚至还有因为没加随机延时被平台拉黑宽带的坑。

这些坑踩得多了,也总结出了一套“对症解决方案”:针对IP封禁,搭建高可用IP池+行为伪装;针对验证码,按类型用自动化工具秒过;针对数据乱码,编码自动识别+精准提取。这篇文章就把这些实战经验整理成手册,每个方案都带“踩坑经历+可复用代码+避坑技巧”,新手照着做就能避开90%的爬虫问题。

一、IP不封:从“免费代理秒封”到“高可用IP池+行为伪装”

IP封禁是爬虫最常见的问题,也是我踩过最多坑的地方——最开始用自己的IP硬爬,封;换免费代理,爬10页就封;甚至买了低价代理,还是频繁被检测。后来才明白,IP不封的核心是“IP干净+行为像人”。

1. 新手必踩3个IP坑

  • 坑1:用免费代理。免费代理基本是“共享黑名单”,几百人共用一个IP,平台早就标记为恶意IP,用了必封;
  • 坑2:代理数量太少。就3-5个代理轮换,切换频率不够,单一IP仍高频访问;
  • 坑3:只换IP不伪装行为。IP换了,但请求头固定、延时固定,还是被一眼认出是爬虫。

2. 解决方案:高可用IP池+全链路行为伪装

(1)搭建高可用IP池(亲测稳定)

核心是“收费高匿代理+自动验证+动态切换”,确保每次用的IP都是干净可用的:

import requests
import random
from datetime import datetime, timedelta
from typing import List, Dict

class StableProxyPool:
    def __init__(self, proxy_list: List[Dict], validate_interval: int = 300):
        self.proxy_list = proxy_list  # 收费高匿代理列表
        self.validate_interval = validate_interval  # 5分钟验证一次
        self.valid_proxies = []
        self.last_validate = datetime.min

    def _validate_proxy(self, proxy: Dict) -> bool:
        """验证代理:访问目标平台同源网站,避免误判"""
        test_url = "https://www.baidu.com/"  # 爬淘宝就换https://www.taobao.com/
        try:
            resp = requests.get(
                test_url,
                proxies=proxy,
                headers=self._get_random_header(),
                timeout=5,
                verify=False
            )
            # 状态码200且响应时间<3秒,视为可用
            return resp.status_code == 200 and resp.elapsed.total_seconds() < 3
        except Exception:
            return False

    def _get_random_header(self):
        """随机请求头(配合IP池使用,双重伪装)"""
        HEADERS_POOL = [
            {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/130.0.0.0 Safari/537.36",
                "Referer": "https://www.baidu.com/",
                "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
            },
            {
                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Safari/605.1.15",
                "Referer": "https://www.baidu.com/",
                "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
            }
        ]
        return random.choice(HEADERS_POOL)

    def validate_all(self):
        """批量验证代理,更新可用池"""
        print("开始验证代理...")
        self.valid_proxies = [p for p in self.proxy_list if self._validate_proxy(p)]
        self.last_validate = datetime.now()
        print(f"可用代理数:{len(self.valid_proxies)}/{len(self.proxy_list)}")

    def get_proxy(self) -> Dict:
        """获取可用代理,超时自动重新验证"""
        if (datetime.now() - self.last_validate) > timedelta(seconds=self.validate_interval):
            self.validate_all()
        if not self.valid_proxies:
            raise Exception("无可用代理,请更换代理提供商")
        return random.choice(self.valid_proxies)

# 初始化IP池(收费代理格式:http://账号:密码@IP:端口)
PAID_PROXIES = [
    {"http": "http://user1:pass1@111.222.333.444:8080", "https": "https://user1:pass1@111.222.333.444:8080"},
    {"http": "http://user2:pass2@111.222.333.445:8080", "https": "https://user2:pass2@111.222.333.445:8080"},
    # 至少准备10个以上,越多越稳定
]
proxy_pool = StableProxyPool(PAID_PROXIES)
(2)行为伪装:避免“机器特征”

光有IP池不够,还要让访问行为像真人,核心是“三随机”:

import time

def random_delay(min_delay: float = 1.5, max_delay: float = 3.5):
    """随机延时,模拟真人浏览间隔"""
    delay = random.uniform(min_delay, max_delay)
    time.sleep(delay)
    return delay

def dynamic_delay(status_code: int):
    """根据响应状态码动态调整,主动避坑"""
    if status_code == 200:
        random_delay(1.5, 3.5)
    elif status_code == 429:  # 请求过多
        print("触发限流,延长延时...")
        random_delay(5, 8)
    elif status_code == 403:  # 疑似封禁
        print("疑似被检测,切换IP+长延时...")
        random_delay(10, 15)
        proxy_pool.validate_all()  # 重新验证代理池,切换IP

def random_page_crawl(total_pages: int = 1000):
    """随机顺序爬取,打破机器规律"""
    pages = list(range(1, total_pages + 1))
    random.shuffle(pages)
    return pages

# 完整使用示例
def crawl_with_anti_block(url_template: str, total_pages: int):
    random_pages = random_page_crawl(total_pages)
    for page in random_pages:
        try:
            proxy = proxy_pool.get_proxy()
            headers = proxy_pool._get_random_header()
            resp = requests.get(
                url_template.format(page=page),
                proxies=proxy,
                headers=headers,
                timeout=10,
                verify=False
            )
            print(f"爬取第{page}页,状态码:{resp.status_code}")
            
            # 动态延时
            dynamic_delay(resp.status_code)
            
            # 处理数据...
        except Exception as e:
            print(f"第{page}页失败:{str(e)}")
            random_delay(5, 8)  # 失败后长延时,避免频繁重试
            continue

3. IP不封避坑技巧

  • 代理必须选“高匿”:透明代理会暴露真实IP,等于白用;
  • 代理和Cookie绑定:一个Cookie搭配1-2个代理,模拟同一用户固定设备登录;
  • 避免高频重试:爬取失败先延时,再重试,别同一IP短时间内多次请求;
  • 定期换代理池:同一批代理用1-2周就换,避免被平台标记。

二、验证码秒过:从“手动输入”到“按类型自动化破解”

验证码是爬虫的“拦路虎”,我踩过的坑包括:用OCR识别图形验证码准确率低、滑块验证码轨迹太规律被检测、短信验证码没法自动化。后来发现,不同类型的验证码有不同的破解思路,不用死磕一种方法。

1. 常见验证码类型及对应坑点

验证码类型 新手坑点 解决方案
图形验证码(数字/字母/汉字) 用pytesseract识别率低,复杂图形直接失效 用ddddocr(免费、准确率高)
滑块验证码(拼图解谜) 轨迹直线移动、速度均匀,被秒检测 Selenium+轨迹模拟(加速+减速+波动)
短信验证码 手动接收太麻烦,接码平台怕违规 仅用于个人学习,用合规接码平台
行为验证码(滑动/点击) 固定点击位置,被识别为机器 随机点击偏移+模拟真人操作节奏

2. 实战代码:3种常见验证码自动化破解

(1)图形验证码:ddddocr免费秒解
import ddddocr
from PIL import Image
import requests

def solve_graph_captcha(captcha_url: str, proxy: Dict) -> str:
    """破解图形验证码(数字/字母/简单汉字)"""
    # 下载验证码图片
    resp = requests.get(captcha_url, proxies=proxy, timeout=10)
    with open("captcha.png", "wb") as f:
        f.write(resp.content)
    
    # 用ddddocr识别(免费,准确率80%+)
    ocr = ddddocr.DdddOcr(show_ad=False)  # 关闭广告
    with open("captcha.png", "rb") as f:
        img_bytes = f.read()
    result = ocr.classification(img_bytes)
    print(f"图形验证码识别结果:{result}")
    return result

# 使用示例
captcha_url = "https://example.com/captcha"  # 替换为实际验证码URL
proxy = proxy_pool.get_proxy()
captcha_code = solve_graph_captcha(captcha_url, proxy)
(2)滑块验证码:Selenium+轨迹模拟
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
import random
import time

def simulate_human_trace(driver, slider, target_x: int):
    """模拟真人滑块轨迹:加速→匀速→减速,加入随机波动"""
    action = ActionChains(driver)
    # 按住滑块
    action.click_and_hold(slider).perform()
    time.sleep(random.uniform(0.1, 0.3))  # 按住后停顿一下
    
    current_x = 0
    # 加速阶段
    while current_x < target_x * 0.6:
        step = random.randint(3, 8)
        action.move_by_offset(step, random.randint(-1, 1))  # 上下波动
        current_x += step
        time.sleep(random.uniform(0.01, 0.03))
    
    # 匀速阶段
    while current_x < target_x * 0.9:
        step = random.randint(2, 5)
        action.move_by_offset(step, random.randint(-1, 1))
        current_x += step
        time.sleep(random.uniform(0.02, 0.04))
    
    # 减速阶段
    while current_x < target_x:
        step = random.randint(1, 3)
        action.move_by_offset(step, 0)
        current_x += step
        time.sleep(random.uniform(0.03, 0.06))
    
    # 微调(避免超过目标)
    if current_x > target_x:
        action.move_by_offset(current_x - target_x, 0)
        time.sleep(random.uniform(0.05, 0.1))
    
    # 松开滑块
    action.release().perform()

def solve_slider_captcha(driver, slider_selector: str, target_x: int):
    """破解滑块验证码"""
    slider = driver.find_element(By.CSS_SELECTOR, slider_selector)
    simulate_human_trace(driver, slider, target_x)
    time.sleep(2)  # 等待验证结果
    print("滑块验证码破解完成")

# 使用示例
driver = webdriver.Chrome()
driver.get("https://example.com/slider-captcha")  # 替换为滑块验证码页面
solve_slider_captcha(driver, ".slider", 300)  # 第二个参数是滑块选择器,第三个是目标距离
(3)短信验证码:合规接码平台(仅学习用)
import requests

def get_sms_code(api_key: str, phone_num: str) -> str:
    """通过接码平台获取短信验证码(仅用于个人学习)"""
    # 接码平台API(需自行注册,选择合规平台)
    api_url = f"https://api.sms-platform.com/get_code?key={api_key}&phone={phone_num}"
    resp = requests.get(api_url, timeout=60)
    if resp.json()["success"]:
        return resp.json()["code"]
    else:
        print("获取短信验证码失败")
        return ""

# 使用示例(仅学习,禁止用于非法用途)
api_key = "your_api_key"
phone_num = "138xxxx8888"
sms_code = get_sms_code(api_key, phone_num)

3. 验证码破解避坑技巧

  • 图形验证码:如果准确率低,先对图片做预处理(灰度化、二值化),再用ddddocr识别;
  • 滑块验证码:别用固定轨迹,一定要加入随机波动,速度别太快(模拟真人手速);
  • 合规第一:接码平台仅用于个人学习,严禁用于爬取敏感数据、违规操作;
  • 优先绕过:如果能找到接口直接请求(不带验证码),或用Cookie跳过登录,优先选择绕过,而非破解。

三、数据不乱码:从“乱码一堆”到“编码自动识别+精准提取”

数据乱码是爬虫的“隐形坑”——爬了半天数据,打开全是“平台”“浏览器”,白忙活一场。我踩过的坑包括:忽略Content-Type、直接用utf-8解码、动态渲染数据提取不到。

1. 数据乱码3大核心原因

  • 原因1:编码格式判断错误(比如页面是gbk,却用utf-8解码);
  • 原因2:忽略响应头的Content-Type(没指定charset,默认用ISO-8859-1解码);
  • 原因3:动态渲染页面(JS加载数据),直接爬HTML导致数据缺失或乱码。

2. 解决方案:编码自动识别+动态数据提取

(1)编码自动识别:再也不用猜编码
import requests
from chardet import detect
from requests.exceptions import RequestException

def get_correct_encoding(resp: requests.Response) -> str:
    """自动识别响应编码,优先用响应头指定的charset"""
    # 1. 从响应头获取编码
    encoding = resp.encoding
    if encoding and encoding.lower() != "iso-8859-1":
        return encoding
    
    # 2. 用chardet检测编码
    content = resp.content
    detected = detect(content)
    encoding = detected["encoding"]
    # 常见编码修正(chardet可能误判)
    if encoding == "GB2312":
        return "GBK"  # GB2312是GBK的子集,用GBK解码更全
    return encoding or "utf-8"

def get_correct_content(url: str, proxy: Dict) -> str:
    """获取正确编码的页面内容,避免乱码"""
    try:
        resp = requests.get(url, proxies=proxy, timeout=10, verify=False)
        encoding = get_correct_encoding(resp)
        # 用正确编码解码
        resp.encoding = encoding
        return resp.text
    except RequestException as e:
        print(f"获取内容失败:{str(e)}")
        return ""

# 使用示例
url = "https://example.com/gbk-page"  # GBK编码的页面
proxy = proxy_pool.get_proxy()
content = get_correct_content(url, proxy)
print("无乱码内容:", content[:100])  # 打印前100字符验证
(2)动态数据提取:用Playwright获取JS渲染后的数据
from undetected_playwright import sync_playwright
import random

def extract_dynamic_data(url: str) -> str:
    """提取动态渲染页面的数据(避免数据缺失/乱码)"""
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page(
            viewport={"width": 1920, "height": 1080},
            extra_http_headers={"Accept-Language": "zh-CN,zh;q=0.9"}
        )
        # 禁用图片加载,提高速度
        page.route("**/*.{png,jpg,jpeg,gif}", lambda route: route.abort())
        # 访问页面,等待JS渲染完成
        page.goto(url, wait_until="networkidle", timeout=60000)
        random_delay(1, 2)  # 等待数据完全渲染
        
        # 提取数据(用CSS选择器,精准提取,避免HTML乱码干扰)
        # 示例:提取商品价格
        price = page.query_selector(".price").inner_text().strip()
        # 示例:提取JSON数据(如果数据在script标签中)
        script_content = page.query_selector("script#init-data").inner_text()
        # 解析JSON(避免JSON乱码)
        import json
        data = json.loads(script_content.encode("utf-8").decode("unicode_escape"))
        
        browser.close()
        print("动态数据提取成功,无乱码")
        return {"price": price, "data": data}

# 使用示例
dynamic_url = "https://example.com/dynamic-page"  # 动态渲染页面
data = extract_dynamic_data(dynamic_url)

3. 数据不乱码避坑技巧

  • 优先用resp.text:requests的resp.text会自动根据响应头解码,比resp.content手动解码更靠谱;
  • 处理JSON乱码:如果JSON字符串有unicode转义(比如\u54c8\u54c8),用json.loads(content.encode("utf-8").decode("unicode_escape"))
  • 避免直接保存HTML:尽量用选择器精准提取数据(比如价格、标题),再保存为JSON/CSV,减少乱码概率;
  • 动态页面别用requests:直接爬HTML会缺失JS加载的数据,或导致乱码,优先用Playwright/Selenium。

四、终极避坑:50个坑总结的5个通用原则

  1. 模拟真人,而非对抗平台:反爬的核心不是“破解”,而是让爬虫的行为、请求特征和真人一致——平台的反爬系统只是筛选异常,不是针对爬虫;
  2. 不贪快,求稳定:新手总想着“爬得越快越好”,结果高频请求被封,反而更慢。稳定的低速爬取(1.5-3.5秒/页),整体效率更高;
  3. 定期更新配置:请求头、代理池、选择器要定期更新——浏览器版本更新了,User-Agent就要换;页面结构变了,选择器就要改;
  4. 做好异常处理:每个请求都要加超时、重试、异常捕获,避免一个页面爬取失败导致整个爬虫停摆;
  5. 遵守合规底线:不爬敏感数据(个人信息、隐私),遵守robots.txt协议,不用爬虫做非法用途(刷量、薅羊毛)。

五、总结

爬虫反爬不是“玄学”,而是“经验+技巧”——IP不封的核心是“干净IP+真人行为”,验证码秒过的核心是“按类型选对工具”,数据不乱码的核心是“正确编码+精准提取”。

我踩过的50个坑,本质上都是因为“想当然”:以为免费代理能用、以为固定延时没事、以为utf-8能解所有编码。后来照着本文的方法改造后,爬虫的稳定性从30%提升到98%,再也没遇到过“爬一半被封”“数据乱码没法用”的问题。

如果你在爬取过程中遇到具体的反爬问题(比如某平台的特殊验证码、奇怪的乱码),欢迎在评论区留言,我会针对性给出解决方案~ 觉得有用的话,别忘了点赞收藏,下次写爬虫直接套用这份手册!

Logo

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

更多推荐