ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Selenium的裁判文书网爬虫:登录、反爬与框架搭建

基于Selenium的裁判文书网爬虫:登录、反爬与框架搭建 简介基于Selenium的裁判文书网爬虫项目面向计算机相关专业学生、教师及企业开发者解决裁判文书网登录与数据采集难题。项目已获导师指导认可答辩评审分达95分代码经完整测试、功能可靠可直接用于毕业设计、课程设计、作业或作为爬虫进阶学习的参照。压缩包共19个文件以Python脚本为核心包含登录模块、文书爬取脚本及参数配置脚本辅以JS脚本和多个txt参数文件用于维护登录状态、请求头与docid等关键信息并提供了地域、页码等参数配置说明另含README文档与项目授权码便于快速部署和规范使用。包体仅233KB结构清晰、轻量易读覆盖从模拟登录到分页采集的完整链路。已有182人学习下载有基础的开发者可在此基础上扩展其他法律数据采集功能初学者也能对照源码和注释理解Selenium模拟登录、请求头构造、分页抓取与数据解析的完整实现思路。1. 基于 Selenium 裁判文书网爬虫为什么我劝你先别急着写代码看到“基于 Selenium 裁判文书网爬虫”这个标题很多人的第一反应是“又是个反爬练手项目”但真正动手之后才发现裁判文书网和普通门户网站的难度不在一个量级。它的登录、搜索、翻页、字段解析每一步都有历史包袱单纯靠 requests 硬拉页面大概率连登录态都撑不过十分钟。Selenium 的优势在于它模拟的是真实浏览器行为能帮你把登录、点击、滚动、等待这些环节串成一条稳定链路适合做规模不大但要求数据完整的定向采集比如某个地区、某个案由的公开裁判文书。这篇笔记按我实际搭过的方案来写先讲清楚裁判文书网登录和页面结构上的三块硬骨头再给出可复现的 Selenium 代码框架最后把翻车最多的参数和坑点列出来。适合刚入门的 Python 爬虫用户也适合想拿公开文书数据做计量、做文本分析的从业者。如果你是冲着违法绕过验证码去的那这个标题不适合你因为合法合规地控制频率、处理登录才是一条能长期走通的路。2. 裁判文书网的登录与页面逻辑先弄清楚你要打交道的三块硬骨头2.1 登录态为什么绕不开验证码、会话与 Cookie裁判文书网早期可以匿名访问后来逐步收紧了查询权限很多列表页和详情页都要登录态才能完整加载。所谓“文书网登录资料齐全”通常指两样东西一套可用的账号信息和一份记录了登录流程的说明文档。账号信息可能是你通过正规渠道申请的也可能是项目组内部测试用的但不管来源如何Selenium 爬虫要做的只是把登录流程自动化而不是去破解验证码。验证码是第一个变量。常见的有滑块、点选、文字输入三类Selenium 都能感知到元素但自动通过验证码的技术风险和平台风险都很高。我一般建议把验证码设计成“人工介入点”爬虫检测到验证码弹出时发一个通知到钉钉或微信人工在浏览器里滑一下爬虫继续跑。这样既不需要引入第三方识别服务也不会因为接打码平台而触碰灰色地带。会话和 Cookie 是第二个变量。Selenium 登录成功后浏览器上下文里会保存一套有效的 Cookie这套 Cookie 可以序列化到本地文件后续新开浏览器时直接加载减少重复登录的次数。但 Cookie 有有效期也可能被服务端主动失效所以爬虫里一定要有一套“检测到登录态失效就自动重新登录”的逻辑而不是简单地把 Cookie 写死在配置文件里。2.2 用 Selenium 管理登录信息资料齐全的“资料”到底指什么标题里说的“登录资料齐全”展开来看至少包含四个部分账号密码、登录 URL、验证码出现位置的截图说明、以及登录成功后的判断标志。很多爬虫新手只拿到账号密码就以为万事大吉结果发现登录页还有动态参数、隐藏输入框甚至登录按钮的 id 每隔一段时间就变。所以我在工程里习惯把登录信息整理成一个配置文件避免把账号密码硬编码进源码。配置文件的写法大致如下{ login_url: https://wenshu.court.gov.cn/user/login, username_selector: #username, password_selector: #password, login_button: #loginButton, success_flag: #navBar, cookie_file: ./cookies.json, timeout: 20 }这里的关键不是 URL 和选择器本身而是success_flag和cookie_file两个字段。success_flag是登录成功后页面必现的元素选择器用来判断登录是否真的完成cookie_file是本地 Cookie 持久化路径避免每次启动都重新走一遍登录。实际项目中我发现很多站点会在登录后把 URL 改掉所以判断登录成功不能只看 URL一定要配合元素是否存在来判断。配置好之后登录模块的伪代码只需做三件事打开登录页、填入账号密码、点击登录。验证码出现时爬虫暂停并等待人工操作。整个流程的核心思路是“把不稳定的人工验证抽离出来把稳定的输入和点击自动化”。账号密码这类敏感信息建议通过环境变量注入不要提交进 Git 仓库这点放到后面的源码规范里一起说。2.3 页面结构分析搜索页、列表页、详情页的三层模型裁判文书网的前端是一个典型的 SPA单页应用列表和详情都由 JavaScript 动态渲染。用 Selenium 看页面不能像 requests 那样直接拿 HTML需要等待渲染完成而且列表页和详情页的元素结构差异很大。爬虫工程上我把整个站点抽象成三层搜索页负责输入关键字和筛选条件列表页负责翻页和摘取文书链接详情页负责解析正文和元数据。搜索页的输入框和搜索按钮相对稳定但筛选条件比如案由、法院、年份、文书类型这些下拉选项往往是用自定义组件实现的Selenium 的原生select操作未必管用。常见做法是直接点击下拉框再点击选项文本或者用execute_script调用前端的角标逻辑。而列表页的翻页按钮通常是一个带page参数的链接但 Selenium 点击时经常会误触到隐藏元素需要先滚动到目标元素再点击。详情页是爬虫的终点也最容易踩坑。裁判文书正文的排版在不同年份、不同法院之间差异巨大有些是纯文本有些嵌套表格有些还带图片。解析详情页时我会先用 XPath 提取一个“正文容器”元素再取里面的纯文本而不是只找单独的p标签。因为这样能最大限度保留段落结构后续做文本分析时也方便切分句子。3. 搭建基于 Selenium 的爬虫框架从驱动配置到第一份可用数据3.1 环境准备与依赖版本Python、Selenium、WebDriver 的最小组合Selenium 爬虫的环境坑非常多版本不匹配是最常见的翻车原因。我用的一套稳定组合是 Python 3.10、Selenium 4.x、以及对应浏览器的 WebDriver。注意Selenium 4 已经内置了部分浏览器驱动管理能力但你如果用的浏览器是 Chrome建议还是手动把 chromedriver 下载到本地并在启动时指定路径避免 Selenium Manager 自动下载带来的网络和版本问题。安装依赖的命令pip install selenium4.15.0 pip install webdriver-manager4.0.1webdriver-manager不是必须的但它能帮你自动匹配 Chrome 版本减少本地驱动版本不对的烦恼。如果你在公司内网环境WebDriver 下载可能被防火墙拦这时要么手动放一个驱动文件到项目目录要么用webdriver-manager的本地缓存模式。我在生产环境里一般选择手动指定路径因为可控性更高。启动浏览器的核心参数from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() options.add_argument(--window-size1400,900) options.add_argument(--disable-blink-featuresAutomationControlled) options.add_argument(--no-sandbox) driver webdriver.Chrome(optionsoptions)这里的disable-blink-featuresAutomationControlled是为了去掉 Selenium 注入的自动化标记但不是万能的。很多站点还能通过navigator.webdriver属性、浏览器指纹、行为轨迹来判断你是不是机器人。不过这已经超出了“登录资料齐全”这个标题的范畴我一般不会在这个层面深挖因为正常的访问频率下站点根本不会把你当爬虫处理。3.2 核心代码登录模块、搜索模块、详情解析模块怎么拆把爬虫拆成三个模块是为了出错时能单独调试。登录模块负责建立会话搜索模块负责拿列表页的链接详情解析模块负责抽数据。三个模块之间通过一个driver对象串联。登录模块的代码import json import time from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def login(driver, config): if load_cookie(driver, config[cookie_file]): return True driver.get(config[login_url]) WebDriverWait(driver, config[timeout]).until( EC.presence_of_element_located((By.CSS_SELECTOR, config[username_selector])) ) driver.find_element(By.CSS_SELECTOR, config[username_selector]).send_keys(username) driver.find_element(By.CSS_SELECTOR, config[password_selector]).send_keys(password) driver.find_element(By.CSS_SELECTOR, config[login_button]).click() time.sleep(3) if driver.find_elements(By.CSS_SELECTOR, config[success_flag]): save_cookie(driver, config[cookie_file]) return True return False这段代码的核心是“先尝试加载 Cookie不行再走登录流程”。注意username和password这里我留的是变量实际工程中建议从环境变量读取。还有一个细节save_cookie不要只存 Cookie 的 name 和 value还要带上 domain、path 和 expiry否则下次加载时部分 Cookie 会失效。搜索模块的代码重点是输入关键字和执行搜索def search(driver, keyword): search_input WebDriverWait(driver, 20).until( EC.element_to_be_clickable((By.CSS_SELECTOR, #searchBox)) ) search_input.clear() search_input.send_keys(keyword) driver.find_element(By.CSS_SELECTOR, #searchBtn).click() time.sleep(2) WebDriverWait(driver, 20).until( EC.presence_of_element_located((By.CSS_SELECTOR, .list-item)) )这里我加了两个等待一个是等搜索按钮可点击一个是等搜索结果列表出现。有些人喜欢上来就time.sleep(10)这会让每次搜索都固定等待 10 秒白白浪费了速度。显式等待的性价比更高因为它只等到元素出现就继续执行。需要注意的是element_to_be_clickable和presence_of_element_located的语义完全不同前者要求元素可见且可点后者只要在 DOM 里就算找到。搜索框建议用前者结果列表用后者即可。详情解析模块用 XPath 提取正文def parse_detail(driver, url): driver.get(url) WebDriverWait(driver, 20).until( EC.presence_of_element_located((By.XPATH, //div[classcontent])) ) paragraphs driver.find_elements(By.XPATH, //div[classcontent]//p) text \n.join([p.text.strip() for p in paragraphs if p.text.strip()]) return text这里的.content是常见正文容器 class不同法院生成的页面可能略有差异。我一般会先写一个小脚本把当前页面的所有div的 class 列出来再人工确认正文容器是哪一层。确认之后XPath 里优先用//div[classcontent]//p这样相对较稳定的路径尽量不要从根节点一长串往下写因为中间任何一层标签调整都会让整个 XPath 失效。3.3 数据落盘与断点续爬不把鸡蛋放在一个进程里裁判文书网的数据量很大单次爬取可能要跑几个小时甚至几天。如果进程崩了没有断点续爬之前的成果全部白费。我的做法是每抓完一条详情页立即把文书链接和标题追加到一个本地 CSV 里同时把要抓的链接队列持久化到硬盘而不是放在内存里。落地代码可以这么写import csv import os from queue import Queue def write_record(writer, record): writer.writerow(record) def load_done_set(path): if not os.path.exists(path): return set() with open(path, r, encodingutf-8) as f: return {line.strip() for line in f if line.strip()} done load_done_set(done.txt) task_queue Queue() # 每次从队列取链接前先检查是否已在 done 集合里done.txt就是“后悔药”每成功抓完一条就写一条。这样即使程序中途崩了重启时也能跳过已完成的数据不用从头再来。我还建议给详情页的数据单独建一个data/目录按日期分文件夹存储原始 HTML 和解析后的文本方便回溯。原始 HTML 至少保留一个月因为解析代码一旦要改字段逻辑没有原始页面就没办法重新解析。4. 参数调优与效率平衡把速度、稳定性、封禁风险调到同一条线上4.1 显式等待与页面加载策略别用 sleep 硬扛Selenium 的implicitly_wait是全局等待会让所有查找元素的操作都等待一个固定时间这看起来很省事实际会让爬虫慢很多。显式等待只针对某个元素等待能显著提升速度。裁判文书网的关键路径就是登录成功标志、搜索结果列表、详情正文容器这三个地方分别设置 20 秒左右的超时比较合适。页面加载策略也值得一提。Selenium 默认的page_load_strategy是normal要等整个页面所有资源加载完成才返回。但裁判文书网详情页里有很多 JS 和 CSS 资源有些还会加载超时。我一般会改成eager也就是等 DOM 就绪就继续不等待图片和样式表。这样能省下不少时间但要注意某些详情页的正文是异步接口返回的eager模式下正文容器可能还没渲染出来所以还是需要显式等待兜底。options.page_load_strategy eager这个参数改动看似小实际上在批量爬取时能把单页耗时从 4 秒压到 2 秒左右翻倍提升。代价是偶发性的元素找不到处理方式就是抛异常后重试一次重试时把页面加载策略改回normal或者直接强制刷新。4.2 异常重试与超时设置反爬阴影下的生存法则Selenium 爬虫最怕的不是登录失败而是运行到一半时页面报错。常见异常有三类元素未找到、页面超时、浏览器崩溃。元素未找到可以通过重试解决页面超时可以换加载策略解决浏览器崩溃就只能靠监控进程来自动拉起。重试逻辑我一般这样写import time from selenium.common.exceptions import TimeoutException, NoSuchElementException def retry_call(func, retries3, delay5): for i in range(retries): try: return func() except (TimeoutException, NoSuchElementException) as e: if i retries - 1: raise e time.sleep(delay) print(fretry {i 1} after error: {e})这段包装函数可以用在任何需要等待元素或点击操作的场景。注意重试次数不要太多3 次足够。重试间隔从 5 秒到 10 秒之间随机化避免出现固定节奏这比任何 UA 伪装都有效。很多新手会忽略随机延时结果每分钟访问节奏完全一致很容易被识别出来。我一般会在每次请求详情页前加一句time.sleep(random.uniform(2, 5))代码里不写固定的time.sleep(3)而是随机间隔是爬虫的常识。random.uniform(2, 5)会让两次请求之间的间隔在 2 到 5 秒内波动既不会太快也不会因为完全无间隔而触发服务端防护。4.3 请求频率与数据量权衡多久访问一次才算“有分寸”裁判文书网虽然有反爬机制但正常的检索访问是被允许的。你一天抓几百条文书和几分钟内抓几千条服务端的感知是完全不同的。我的经验是单进程跑的话详情页请求间隔保持在 3 秒以上列表页翻页间隔保持在 5 秒以上这样基本不会触发验证码或封禁。如果你要抓的数据量在十万级别纯 Selenium 单线程会非常慢。这时候的常见做法是换一个思路先用 Selenium 拿到搜索结果的 cookie再把这些 cookie 导入到 requests 会话里用 requests 去请求详情接口这样可以省去浏览器渲染的开销。但这就偏离了“基于 Selenium”这个标题方向我在这里只提一下不展开。正因为存在这种升级路径Selenium 爬虫最适合的依然是中小规模、稳定可控的采集任务。另外每天要控制总量。比如今天跑完明天再跑不要 24 小时不间断。长时间把浏览器窗口挂着即使频率很低也会被服务端盯上。我在实际项目中是每天凌晨跑一次每次最多 2000 条跑完自动关浏览器清空临时文件。5. Selenium 爬裁判文书网的避坑指南五个亲测翻车的现场5.1 元素定位时灵时不灵XPath 与 iframe 的恩怨现象同一个 XPath上午能定位到下午就报找不到元素或者在一台电脑上能跑换台电脑就不行。原因裁判文书网的登录框和某些弹窗是放在 iframe 里的。Selenium 默认操作的是顶层文档如果目标元素在 iframe 内直接find_element肯定找不到。另外页面上可能有多套元素结构比如搜索结果为空时列表容器的 class 会变化导致 XPath 失效。解决先判断元素是否在 iframe 内。如果确实在用driver.switch_to.frame()切换进去操作完再切回来。定位策略上优先用稳定的 id 和 name其次再用 XPath 包含文本的方式比如//span[contains(text(),搜索)]。绝对路径的 XPath 不要用/html/body/div[1]/div[2]/...这种任何一层改动都会崩。用相对路径加属性组合才是稳定的前提。5.2 验证码弹窗打乱节奏识别与人工介入的边界现象爬虫运行一段时间后突然弹出滑块验证码程序傻等超时后面的任务全部堆积。原因访问频率过快或浏览器特征被识别触发了验证码机制。验证码出现的位置时间不固定靠代码硬等是不现实的。解决我后来在循环里加了一段验证码检测逻辑每执行 30 个详情页就检查一下页面是否存在验证码元素存在就播放一个声音提示并暂停 30 秒留出人工处理的时间。人工完成验证后爬虫继续跑。这里的关键是“检测代码要写在主循环里”而不是只在登录时检测。对于验证码识别服务我强烈不建议接因为成本高且法律风险明显人工 降频反而是最稳妥的解法。5.3 列表页翻页失效隐藏参数与动态加载现象点击下一页URL 变了但列表内容没有变或者直接跳回了第一页。原因裁判文书网的列表页翻页是通过 JavaScript 动态请求数据的点击按钮后页面可能重新加载而搜索条件没有正确地传给下一页。还有一个常见原因是翻页按钮在页面滚动之后才可见没有滚动就直接点击点击落在了透明遮罩层上。解决点击翻页前先用ActionChains把元素滚动到视野中央再做真实点击。不要用execute_script直接触发点击事件那会绕过前端的事件绑定。点击后要验证列表的第一条记录是否变化如果没变说明翻页操作没有生效再做一次重试。5.4 登录态被踢下线会话保持与定时刷新现象爬虫跑到一半突然所有的详情页都跳转到登录页正文全部拿不到。原因服务端会定期校验会话有效性特别是长时间没有操作或者 Cookie 过期时登录态会失效。Selenium 虽然保存了浏览器上下文但服务端可以通过心跳接口来判断会话是否活跃。解决每处理 50 条数据就主动访问一次搜索页并检查登录标志元素是否还在。如果不在就重新走登录流程。还有一个技巧是在详情页的请求头里带上 Referer 和 Accept-Language模拟正常用户从列表页点击进入详情页的行为。这能显著降低被踢下线的概率。5.5 数据清洗反转车判决结果字段的 N 种写法现象好不容易抓下来的文书正文里有大量空白字符、全角半角混用、法院名称写法不一甚至同一类型的案件判决结果的表述完全不同直接导致后续统计结果不可信。原因裁判文书网的数据由各个法院上传字体和排版规格并不统一。对于文本分析来说原始文本还算可以但如果你要从中提取案件类型、判决日期、金额等结构化字段就非常考验清洗逻辑。解决正文清洗脚本要单独建不要和爬虫代码耦合在一起。常规清洗步骤包括统一换行符、去除多余空白、把全角标点转为半角、按特定规则截取“判决如下”之后的段落。这些清洗规则要反复验证建议拿 100 条人工标注好的文书做测试集每次改完清洗逻辑都跑一遍确保不破坏原有字段。6. 收尾把爬虫变成可持续运行的小系统不止于源码这个标题的最终交付其实不是一份源码而是一套能自己跑起来、能自愈、能输出可信数据的小系统。我建议你拿到源码后先不要急着全量爬取而是做一次 10 条数据的冒烟测试。跑通之后再把数据量调到 100观察登录态保持和重试逻辑的表现。确认稳定后再设置每天定时执行的计划任务比如 Windows 的任务计划程序或 Linux 的 crontab。如果你要把数据落库推荐 SQLite 起步零配置、单文件、好备份。表结构可以按这个思路建CREATE TABLE wenshu ( id INTEGER PRIMARY KEY AUTOINCREMENT, doc_id TEXT UNIQUE, title TEXT, court TEXT, case_number TEXT, doc_type TEXT, publish_date TEXT, content TEXT, crawl_time TEXT ); CREATE INDEX idx_court ON wenshu(court); CREATE INDEX idx_publish_date ON wenshu(publish_date);其中doc_id必须是源站对应的唯一标识用来去重。加索引是为了后续按法院、按日期筛选时不至于全表扫描。爬虫每写入一条前先按doc_id查一下是否已存在存在就跳过这是最基础的去重手段。我自己的教训是拿到任何爬虫项目源码先看日志目录和配置分离这两点。如果一份代码日志乱打、账号密码写死在源码里那它只适合练习不适合直接上生产。你手上那份“登录资料齐全详细文档源码”的压缩包最应该先翻的是文档里关于登录流程的描述其次是依赖清单最后才是主爬虫文件。按照这个顺序读你会比直接跑python main.py少踩一半的坑。如果你把上面的避坑点都提前看一遍再动手改参数这个项目大概率能在半天内跑通。希望帮到你。本文还有配套的精品资源点击获取
返回列表