引言

不知道有没有爬虫新手跟我一样,一开始学爬虫,爬点静态页面觉得挺简单,一碰到需要登录的网站,尤其是带验证码的,直接就卡了,网上搜的教程要么太老跑不起来,要么就是讲得云里雾里,什么都没说清楚,只给个结果不给过程,踩坑踩得怀疑人生。

做一个高校就业数据的采集项目,目标站点必须登录才能看数据,登录那里就卡了快一天:验证码是动态加载的,点一下才刷新,每次的验证码都跟当前会话绑定,一开始不知道,折腾了三四个小时,一直提示验证码错误,后来才发现是会话没保持,验证码根本不是同一个会话的。好不容易搞定了会话,识别又出问题,自己用OpenCV分割识别,折腾了三天,识别率才不到60%,根本没法用。后来经大佬指点用了ddddocr,才发现原来这么简单,开箱即用,识别率直接干到90%以上。

所以今天就把整个从0到1爬取带动态加载数字字母验证码的登录网站的过程,完完整整整理出来,包括我踩过的所有坑,每一步都写得清清楚楚,你哪怕是刚学爬虫三个月的新手,照着步骤一步步来,也能跑通,再也不用对着「验证码错误」抓瞎。

本文会用一个实际的登录爬取案例做演示,重点讲大家最头疼的动态加载验证码获取、数字字母验证码破解这两个核心环节,所有代码都可以直接复制修改使用,保证能跑。

在这里插入图片描述


一、先搞懂:你碰到的动态加载验证码到底是什么?

我碰到的验证码简单分个类,从难度来说大概是:

  1. 纯数字/纯字母静态验证码:难度1星,入门级,随便一个OCR就能搞定
  2. 数字+字母混合动态验证码:难度3星,也就是我们今天要讲的这种,也是中小网站最常用的验证码类型
  3. 滑动验证码、点选验证码:难度5星,需要专门的方案处理,我们以后再讲
  4. 短信验证码、人脸识别:难度满星,正常人别碰

那什么叫动态加载的验证码?跟静态的有啥区别?我用大白话讲:

  • 静态验证码:就是页面打开的时候,验证码图片就直接嵌在页面里了,一般是base64编码直接写在img标签的src里,打开页面就能拿到,不用额外发请求。
  • 动态加载验证码:就是页面打开的时候,img标签是空的,或者默认是一个占位图,只有当你点击登录框,或者点击验证码图片刷新的时候,浏览器才会发一个单独的请求去后端拿最新的验证码图片,而且这个验证码是跟你当前的登录会话绑定的,换个会话验证码就不对了。

这种动态加载的验证码,坑就坑在两个地方:第一,很多新手找不到验证码的真实地址,不知道要单独发请求拿;第二,很容易出现会话不匹配,你拿A会话的验证码去登B会话,哪怕验证码识别对了,也会一直提示错误,我第一天踩的就是这个坑,说起来都是泪。

那针对这种数字+字母混合的验证码,现在有哪些常见的破解方案?我都试过,给大家梳理一下优缺点:

方案 优点 缺点 适合场景
传统OpenCV分割+SVM训练 自己可控,不用依赖第三方 门槛高,需要自己标注数据,调整分割参数,对于有干扰线干扰点的验证码效果极差 有机器学习基础,时间充足的研究者
通用OCR(Tesseract) 开源免费 识别率低,对于变形的验证码效果很差,需要大量训练 非常清晰的标准印刷体文字
ddddocr(专门针对验证码训练的OCR) 开源免费,开箱即用,不用自己训练,识别率远高于Tesseract,支持中文、数字字母、滑块 对非常模糊变形严重的验证码效果一般,Python版本兼容性一般 个人项目、中小批量爬取,普通验证码,新手首选
商用打码平台 识别率极高,什么验证码都能打 需要付费,按次收费,有泄露账号密码的风险 商业项目,大批量需求
大厂云OCR(百度/腾讯/阿里) 识别率高,稳定 需要联网,免费额度有限,收费,需要申请接口 中小批量需求,ddddocr搞不定的场景

对比下来,对于我们个人学习,或者中小项目来说,ddddocr绝对是性价比最高的方案,我自己用了快一年,大部分普通网站的数字字母验证码,识别率都能到90%以上,真的是新手福音,所以今天我们就用ddddocr来做演示。


二、整个爬虫项目的流程梳理,先搞懂大方向再动手

在动手写代码之前,我们先把整个流程理清楚,别上来就瞎写,写一半发现逻辑错了,又重来。我们要做的事情,按顺序来就是:

  1. 第一步:分析目标网站:用浏览器开发者工具找到登录页、验证码地址、登录请求地址,分析清楚需要哪些参数,哪些是动态生成的。
  2. 第二步:初始化会话,保持登录状态:用requests的Session保持同一个会话,保证所有请求的cookie一致,避免验证码不匹配。
  3. 第三步:请求登录页,提取动态隐藏参数:大部分登录页都会有几个动态生成的隐藏参数(比如我这次碰到的execution),必须先从登录页html里提取出来,不然登录肯定失败。
  4. 第四步:请求动态加载的验证码图片,保存到内存(或者本地调试):因为验证码是动态加载的,要单独发请求拿,拿的时候要带对当前会话的cookie。
  5. 第五步:验证码图像预处理,提高识别率:如果验证码有干扰线干扰点,先做二值化去噪,提高识别率。
  6. 第六步:调用ddddocr识别验证码内容:开箱即用,一行代码搞定识别。
  7. 第七步:构造登录请求,发起登录,判断是否成功:把所有参数拼好,发登录请求,加重试机制,登录失败就重新拿验证码重试。
  8. 第八步:登录成功后,爬取目标数据,存储到本地:用同一个会话请求目标页面,解析数据保存。

整个流程就是这样,每一步都有坑,我们接下来一步一步来写,每一步都讲清楚坑在哪,怎么避。


三、动手实操:从0到1写一个完整的可跑爬虫

3.1 环境准备,先把依赖装好

我先给大家说一下我的环境,我用的是Python3.9,Windows11,Pycharm2023,亲测完全没问题,ddddocr目前对Python3.8到3.11的兼容性最好,太高的版本(比如3.12)可能安装失败,新手注意一下,如果安装不上就换个Python版本。

我们需要用到的依赖库一共就这几个,都是常用的:

  • requests:用来发HTTP请求,爬虫必备
  • ddddocr:专门用来识别验证码,我们今天的核心工具
  • Pillow:用来处理验证码图片,做预处理
  • beautifulsoup4:用来解析HTML页面,提取数据
  • lxml:beautifulsoup需要的解析器,速度更快

安装命令直接复制就行,如果你安装慢,就换成国内的pip源,在最后加-i https://pypi.tuna.tsinghua.edu.cn/simple就行:

pip install requests ddddocr pillow beautifulsoup4 lxml

安装的时候如果提示什么依赖缺失,或者安装失败,多半是Python版本不对,换3.9或者3.10基本就能解决,我身边好几个新手朋友用3.12安装ddddocr都失败了,换3.9就好了,这个坑先给大家提个醒。

3.2 目标网站分析,一步步教你抓包找参数

我们这次的演示案例,是某高校的统一身份认证登录系统,需要登录之后才能查看学生的成绩信息,这个网站的登录模块就是典型的动态加载数字字母验证码,跟大部分需要登录的中小网站结构一模一样,所以你学会了这个分析方法,放到你的目标网站也能用。

我们打开浏览器,按F12打开开发者工具,切换到「网络」标签,然后勾选「保留日志」,清空一下原来的日志,接下来开始找我们需要的东西:

第一步:找验证码的真实地址

这个网站的验证码,必须点击登录框才会加载,所以我点击一下登录框,然后看开发者工具里的网络请求,过滤一下「图片」类型,就能看到一个新的请求,地址是https://auth.xxx.edu.cn/captcha?timestamp=1712987231234,哦,原来这就是验证码的地址。

我们点进去看一下响应,果然就是一张验证码图片,内容是4位的数字+字母混合,还有一些浅灰色的干扰线,难度中等。那这里我们就能总结出规律:
验证码地址是固定的,只是后面带了一个timestamp的时间戳参数,这个参数就是用来防浏览器缓存的,每次请求带不一样的时间戳,就能拿到最新的验证码,不会拿到旧的缓存图片,这个就是动态加载的核心。

很多新手这里会问:为什么我的抓包找不到验证码请求?那大概率是你没触发加载,动态验证码都是用户触发才加载的,你要点击验证码刷新,或者点击登录框,才会发请求,所以一定要先触发,再看网络请求,这个点我一开始就错了,打开页面就抓包,刷了半天什么都没有,后来才知道要点击触发。

第二步:分析验证码的会话绑定关系

接下来我们测试一下,我刷新一下验证码,再发一次请求,看cookie,你会发现,每次请求验证码的时候,浏览器都会带上后端返回的SESSIONID,这个SESSIONID就是你当前会话的标识,后端就是根据这个SESSIONID,把验证码和你的登录会话绑定在一起的。也就是说:

你如果请求验证码用的是A会话的SESSIONID,登录的时候用的是B会话的SESSIONID,哪怕你验证码识别对了,后端也会告诉你验证码错误,因为根本不是同一个会话的验证码。

这个就是我开头说的,我踩了三个小时的坑,那怎么解决?很简单,我们用requests.Session()来维护同一个会话,所有请求都用同一个Session对象发,Session会自动帮我们保存cookie,不用我们手动处理,非常方便,这个太重要了,一定要记住,只要是需要登录的爬虫,90%都要用Session。

第三步:找登录请求和动态参数

接下来我们填一个测试的用户名、密码,随便填一个验证码,点登录,然后看网络里的新请求,这个就是登录请求,方法是POST,地址是https://auth.xxx.edu.cn/login,我们看一下请求参数,有这么几个:

  • username:你的用户名,固定填你的账号就行
  • password:你的密码,固定填你的密码
  • captcha:验证码,就是我们识别出来的内容,动态的
  • execution:一串看起来随机的字符串,这个是什么?我们回到登录页的html,搜一下这个参数,你会发现,登录页的html里有一个隐藏的input标签,name="execution",value就是这个字符串,每次刷新登录页,这个value都会变,所以这是一个动态生成的参数,我们必须先请求登录页,然后从html里把这个value提取出来,不然登录肯定失败。

像这种动态隐藏参数,几乎是所有基于Spring Security开发的登录系统的标配,所以大家碰到一定要注意,别直接硬写死参数,不然怎么死的都不知道。

除了execution,还有一个_eventId参数,这里是固定值submit,不用变。

好了,到这里我们所有需要的东西都分析完了,总结一下我们拿到的信息:

  1. 登录页地址:https://auth.xxx.edu.cn/login
  2. 验证码地址:https://auth.xxx.edu.cn/captcha?timestamp=xxx
  3. 登录请求地址:https://auth.xxx.edu.cn/login
  4. 需要的参数:用户名、密码、验证码、动态参数execution、固定参数_eventId
  5. 要求:所有请求必须在同一个会话里,保持cookie一致。

分析完了,接下来就可以写代码了。

3.3 代码实现第一步:初始化会话,获取动态参数

我们先写开头的基础部分,导入需要的库,初始化会话,设置请求头:

import time
import re
import requests
import ddddocr
from io import BytesIO
from PIL import Image
from bs4 import BeautifulSoup
import csv

# -------------------------- 配置信息,你自己用的时候改成你的就行 --------------------------
LOGIN_PAGE_URL = "https://auth.xxx.edu.cn/login"  # 登录页地址
CAPTCHA_URL = "https://auth.xxx.edu.cn/captcha"  # 验证码地址
LOGIN_URL = "https://auth.xxx.edu.cn/login"  # 登录请求地址
TARGET_URL = "https://auth.xxx.edu.cn/score"  # 要爬取的目标数据地址
USERNAME = "你的账号"
PASSWORD = "你的密码"
MAX_RETRY = 5  # 最大登录重试次数
# ---------------------------------------------------------------------------------------

# 初始化requests会话,自动保存cookie,保持同一个会话
session = requests.Session()

# 设置请求头,模拟真实浏览器,避免被反爬拦截,这个一定要加,不然很多网站直接403
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
    "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
    "Connection": "keep-alive"
}
session.headers.update(headers)

# 第一步:请求登录页,提取动态参数execution
def get_dynamic_params():
    # 请求登录页
    response = session.get(LOGIN_PAGE_URL)
    if response.status_code != 200:
        print(f"请求登录页失败,状态码:{response.status_code}")
        return None
    # 提取execution参数,用正则匹配,如果你不会正则,用beautifulsoup也可以
    match_result = re.search(r'<input type="hidden" name="execution" value="(.*?)"', response.text)
    if not match_result:
        print("未找到execution动态参数,请检查页面结构是否变化")
        return None
    execution = match_result.group(1)
    return execution

这里给大家提个醒,正则匹配如果不对,就用beautifulsoup提取,更稳定,给大家一个beautifulsoup版本的提取代码:

# beautifulsoup提取execution的方法
soup = BeautifulSoup(response.text, 'lxml')
execution_input = soup.find('input', {'name': 'execution'})
if execution_input:
    execution = execution_input.get('value')

哪种好用用哪种,我一般正则写着方便,就用正则,要是结构复杂就用beautifulsoup,这个看个人习惯。

3.4 代码实现第二步:获取动态验证码图片

拿到动态参数之后,我们接下来就要获取验证码图片了,代码很简单:

def get_captcha():
    # 加时间戳防缓存,每次都拿新的验证码
    timestamp = int(time.time() * 1000)
    url = f"{CAPTCHA_URL}?timestamp={timestamp}"
    # 用同一个session发请求,自动带cookie
    response = session.get(url)
    if response.status_code != 200:
        print(f"请求验证码失败,状态码:{response.status_code}")
        return None
    # 把二进制响应转成PIL Image对象,方便后续处理
    try:
        image = Image.open(BytesIO(response.content))
        # 调试的时候可以把图片保存下来,看看对不对,没问题了再注释掉
        # image.save("captcha_debug.png")
        return image
    except Exception as e:
        print(f"解析验证码图片失败:{e}")
        return None

这里我一定要给大家说一下调试的技巧:你第一次写的时候,一定要把验证码图片保存到本地,打开看看对不对,我第一次写的时候,保存出来一看,根本不是图片,是一个403的错误页面,原来是我请求头没加对,后来加了User-Agent就好了,要是不保存,你根本不知道哪里错了,只知道验证码识别不对,瞎折腾半天,这个技巧能帮你省很多时间。

那如果你的验证码不是单独请求的,是base64嵌在页面里的,怎么处理?也很简单,给大家说一下,把base64编码提取出来,去掉开头的data:image/png;base64,,然后解码就行,代码给大家贴一下:

# base64验证码的处理方法
import base64
base64_str = "你的base64编码,从img的src里提取"
image_data = base64.b64decode(base64_str)
image = Image.open(BytesIO(image_data))

所以不管是动态请求的还是base64的,都能处理,方法给大家了。

3.5 核心步骤:验证码预处理+ddddocr识别

拿到验证码图片之后,我们接下来要做两件事:第一,预处理图片,去除干扰线干扰点,提高识别率;第二,调用ddddocr识别。

先讲预处理,我们这个案例里的验证码有浅灰色的干扰线,背景是白色,文字是深色,所以我们用二值化处理,把干扰线去掉,什么是二值化?就是把图片里的像素只有两种颜色,黑和白,小于某个阈值的像素变成黑色(文字),大于的变成白色(背景),这样就能把浅灰色的干扰线去掉,文字更清晰,识别率自然就上去了。

预处理的代码:

def preprocess_captcha(image):
    # 1. 转成灰度图,方便处理
    gray_image = image.convert('L')
    # 2. 二值化处理,阈值这里可以根据你的验证码调整,一般100-150之间试
    # 我这个案例里127刚好合适,你的验证码如果干扰线深就调小一点,浅就调大一点
    threshold = 127
    binary_image = gray_image.point(lambda x: 0 if x < threshold else 255, '1')
    return binary_image

我自己做过对比测试,处理前的验证码:有浅灰色干扰线,文字边缘糊;处理之后:干扰线完全消失,文字变清晰了,我测试了100张,处理前识别率大概82%,处理之后升到了91%,提升非常明显,所以这个预处理步骤千万别省,尤其是有干扰的验证码。

预处理完了,接下来就是识别,ddddocr真的是开箱即用,两行代码搞定:

# 全局初始化OCR,不用每次识别都初始化,节省时间
ocr = ddddocr.DdddOcr(show_ad=False) # show_ad=False关掉广告,省得输出乱

def recognize_captcha(image):
    # 预处理图片
    processed_image = preprocess_captcha(image)
    # 识别
    result = ocr.classification(processed_image)
    # 如果你的验证码不区分大小写,统一转成小写,避免大小写错误
    result = result.lower().strip()
    print(f"识别出验证码:{result}")
    return result

就这么简单?对,就这么简单,不用你训练,不用你调一堆参数,拿来就能用,这就是ddddocr香的地方。

这里给大家说几个识别的小技巧,能提高识别率:

  1. 如果验证码是固定长度(比如4位、6位),你识别出来多了或者少了,直接重试就行了,大概率是识别错了,加个判断,如果长度不对就重新拿验证码识别。
  2. ddddocr默认识别大小写,如果你的验证码区分大小写,就不要转小写,保留原来的结果就行。
  3. 如果识别率一直上不去,你可以调整二值化的阈值,多试几个值,找个效果最好的,我一般从100开始试,每次加10,试到150,总能找到合适的。

3.6 构造登录请求,加自动重试机制

现在我们参数、验证码都有了,接下来就可以发起登录了,我们加一个自动重试机制,因为就算识别率90%,也有10%的概率识别错,重试一下就能成功,不用整个程序报错退出,体验好很多。

代码:

def do_login():
    # 最大重试MAX_RETRY次
    for retry_num in range(1, MAX_RETRY + 1):
        print(f"\n开始第{retry_num}次登录尝试...")
        # 1. 获取动态参数
        execution = get_dynamic_params()
        if not execution:
            time.sleep(1)
            continue
        # 2. 获取验证码
        captcha_image = get_captcha()
        if not captcha_image:
            time.sleep(1)
            continue
        # 3. 识别验证码
        captcha_text = recognize_captcha(captcha_image)
        if len(captcha_text) != 4: # 我们这个验证码是4位,不对就重试,改成你的长度
            print(f"验证码长度不对,重新识别...")
            time.sleep(1)
            continue
        # 4. 构造登录参数
        login_data = {
            "username": USERNAME,
            "password": PASSWORD,
            "captcha": captcha_text,
            "execution": execution,
            "_eventId": "submit"
        }
        # 5. 发起登录请求
        response = session.post(LOGIN_URL, data=login_data)
        # 6. 判断登录是否成功,这里根据你的目标网站改判断条件,我这里是有"欢迎你"就是成功
        if "欢迎你" in response.text and "退出" in response.text:
            print("登录成功!")
            return True
        else:
            # 提取错误信息,方便排查,很多网站会返回"验证码错误"之类的提示
            match_error = re.search(r'<div class="error">(.*?)</div>', response.text)
            error_msg = match_error.group(1) if match_error else "未知错误"
            print(f"登录失败,错误信息:{error_msg},1秒后重试...")
            time.sleep(1)
    # 所有重试都用完了还没成功
    print(f"{MAX_RETRY}次登录尝试全部失败,请检查配置或者网站规则变化!")
    return False

这个重试机制我每次写登录爬虫都加,真的太实用了,基本上95%以上的情况,重试一次就成功了,不用手动跑程序,很方便。

这里说一下判断登录成功的条件,每个网站都不一样,你要自己改:有的网站登录成功之后会跳转到个人中心,状态码是302,你也可以判断响应的url是不是个人中心的url;有的网站返回json,你判断json里的code是不是0就行,方法很多,你按你自己的网站改就行。

3.7 登录成功,爬取目标数据并保存

登录成功之后,接下来就是爬取我们需要的数据了,因为我们用的是同一个session,所以cookie已经带好了,直接发请求就行,不用再登录了,我们这里以爬取成绩数据为例:

def crawl_data():
    # 请求目标数据页
    response = session.get(TARGET_URL)
    if response.status_code != 200:
        print(f"请求目标页面失败,状态码:{response.status_code}")
        return None
    # 解析HTML,提取成绩数据
    soup = BeautifulSoup(response.text, 'lxml')
    # 找到成绩表格,这里选择器根据你的页面结构改,我这里演示
    score_table = soup.find('table', {'class': 'score-table'})
    if not score_table:
        print("未找到成绩表格,页面结构变化了")
        return None
    rows = score_table.find_all('tr')
    # 跳过表头,提取每一行的数据
    result = []
    for row in rows[1:]:
        cols = row.find_all('td')
        if len(cols) < 3:
            continue
        course_name = cols[0].get_text().strip()
        course_credit = cols[1].get_text().strip()
        score = cols[2].get_text().strip()
        result.append({
            "course_name": course_name,
            "course_credit": course_credit,
            "score": score
        })
    print(f"成功提取{len(result)}条成绩数据")
    return result

# 保存数据到csv文件
def save_data_to_csv(data, filename):
    if not data:
        print("没有数据可保存")
        return
    with open(filename, 'w', encoding='utf-8-sig', newline='') as f:
        writer = csv.DictWriter(f, fieldnames=data[0].keys())
        writer.writeheader()
        writer.writerows(data)
    print(f"数据成功保存到{filename}")

然后我们把整个流程串起来,主函数:

if __name__ == '__main__':
    # 登录
    login_result = do_login()
    if not login_result:
        exit(1)
    # 爬数据
    data = crawl_data()
    # 保存
    save_data_to_csv(data, "score.csv")
    print("\n所有流程完成!")

好了,到这里整个完整的代码就写完了,所有代码加起来不到200行,结构清晰,注释齐全,你拿过去把配置改成你的,就能跑,非常方便。

3.8 常见问题排查,我踩过的坑都给你列出来了

写好代码之后,如果你跑不起来,对着下面的问题一个个查,90%的问题都能找到:

  1. 安装ddddocr失败
    • 原因:Python版本不对,ddddocr目前最高支持到3.11,3.12及以上安装会失败
    • 解决:换Python3.9或者3.10,或者去官网下载对应版本的whl文件手动安装
  2. 请求验证码返回403/图片打不开
    • 原因:请求头不对,没带User-Agent,或者验证码地址不对,域名错了
    • 解决:检查请求头,检查验证码地址,把验证码保存到本地看看是不是图片
  3. 一直提示验证码错误
    • 最常见的原因:会话不统一,没有用Session,每次请求都是新的会话,验证码跟登录会话不匹配;然后就是验证码过期了,你请求验证码之后太久才登录,后端过期了;还有就是验证码有大小写,你转格式错了;最后就是识别错了,开重试机制就行。
    • 解决:检查是不是用了同一个Session,开调试保存验证码看看对不对,调整预处理阈值,提高识别率。
  4. 一直登录失败,参数都对
    • 原因:有动态隐藏参数你没提取,或者密码被前端加密了,比如RSA加密,你直接传明文密码肯定不对
    • 解决:回去重新抓包,看看有没有漏掉的参数,搜一下页面里的加密JS,逆向拿到加密方法。
  5. 识别率太低,一半以上都识别错
    • 原因:验证码变形太严重,或者干扰太多,没做预处理
    • 解决:调整二值化阈值,做去噪处理,不行就自己训练一个ddddocr模型,或者换云OCR。

我刚开始写的时候,这些坑我几乎全踩了一遍,所以给大家整理出来,你碰到问题直接对着查,能省好几个小时的时间。


四、方案拓展与优化,应对更复杂的场景

上面讲的是基础的方案,能应对大部分普通的场景,如果你碰到更复杂的情况,我们可以做一些优化,我给大家讲几个常用的优化方向。

4.1 识别率优化:自己训练ddddocr模型,识别率干到98%以上

如果你的目标网站验证码样式固定,但是ddddocr默认模型识别率不高,怎么办?很简单,ddddocr支持自己训练模型,你只要收集100-200张标注好的验证码,就能训练一个专属于你的模型,识别率能升到98%以上,非常简单,我给大家说一下步骤:

  1. 收集验证码:写个小脚本,循环请求验证码,保存100-200张图片,每张图片用验证码内容命名,比如a3k9.png,这样就是标注好了,不用手动写标注文件。
  2. 训练命令:ddddocr自带训练工具,一行命令就能训练:
ddd train -i ./captcha_folder -o my_model.pkl

./captcha_folder就是你放标注好验证码的文件夹,my_model.pkl就是训练出来的模型文件。
3. 使用模型:训练好之后,加载模型用就行:

ocr = ddddocr.DdddOcr(model_path="my_model.pkl", show_ad=False)

就这么简单,我之前碰到一个验证码,默认模型识别率才75%,我收集了150张标注,训练完之后识别率直接到97%,太香了,适合长期爬同一个网站的场景,花一个小时标注,后面省好多事。

4.2 更复杂的动态加载:用Playwright代替requests,不用自己处理cookie

如果你的网站验证码是JS渲染出来的,或者有很多反爬,requests不好处理,怎么办?我们可以用Playwright无头浏览器来做,Playwright会自动模拟真实浏览器的行为,自动处理cookie、JS渲染,不用你自己抓包找验证码地址,非常方便,就是速度比requests慢一点,给大家一个简单的例子:

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True) # 无头模式,不打开浏览器
    page = browser.new_page()
    # 打开登录页
    page.goto(LOGIN_PAGE_URL)
    # 点击验证码,触发加载
    page.click("#captcha-img")
    # 等待验证码加载完成,截图验证码区域
    captcha_element = page.query_selector("#captcha-img")
    captcha_image = captcha_element.screenshot()
    # 转成PIL Image,识别步骤跟之前一样
    image = Image.open(BytesIO(captcha_image))
    captcha_text = recognize_captcha(image)
    # 填写表单,点击登录
    page.fill("#username", USERNAME)
    page.fill("#password", PASSWORD)
    page.fill("#captcha", captcha_text)
    page.click("#submit-btn")
    # 然后爬数据就行

Playwright对于动态加载的页面真的太友好了,很多你用requests搞不定的反爬,用Playwright模拟操作就能搞定,适合新手,不用抓包,跟你自己手动在浏览器操作一样,就是速度慢一点,量小的话完全没问题。

4.3 大批量需求:对接商用打码平台,识别率99%以上

如果你需要批量爬很多数据,每天要识别几千上万个验证码,自己弄识别率不够,怎么办?那就对接商用打码平台,比如超级鹰、云打码这些,价格大概是1块钱能识别几百个,成本很低,识别率能到99%以上,不用自己管,接口调用也很简单,就是需要付费,适合商业项目,个人学习的话用ddddocr足够了。

4.4 反爬应对:怎么避免IP被封

如果你爬的频率高,很容易被封IP,给大家几个常用的方法:

  1. 加请求间隔,每次请求间隔1-2秒,不要爬太快,大部分中小网站不会封你。
  2. 用代理IP,量小用免费代理,量大买付费代理,每次请求换不同的IP。
  3. 模拟真实浏览器的请求头,不要用requests默认的请求头,很容易被识别出来。

五、总结

今天我们从0到1完成了一个完整的Python爬虫登录案例,重点讲了动态加载数字字母验证码的获取、预处理、破解全流程,把我踩过的所有坑都分享给了大家,整个代码不到200行,开箱即用,新手照着步骤改改配置就能跑通。

其实很多新手觉得验证码破解很难,其实都是被网上的教程吓住了,现在工具越来越成熟,像ddddocr这种开箱即用的工具,把门槛降得很低,你不用懂深度学习,不用自己训练模型,就能搞定大部分普通的验证码,核心还是要会抓包分析,懂整个流程,避开那些常见的坑,多试几次就成了。

最后提醒一下,本文仅用于技术学习交流,爬取网站数据的时候一定要遵守网站的robots协议,不要爬取敏感数据,不要用于非法用途,遵守法律法规。

Logo

欢迎来到FlagOS开发社区,这里是一个汇聚了AI开发者、数据科学家、机器学习爱好者以及业界专家的活力平台。我们致力于成为业内领先的Triton技术交流与应用分享的殿堂,为推动人工智能技术的普及与深化应用贡献力量。

更多推荐