
简介本资源是一款面向法学研究者、法律数据分析人员及Python初学者的裁判文书网自动化采集工具解决公开司法文书获取效率低、验证码拦截频繁、地域与时间筛选困难等实际问题。压缩包共22个文件79KB含2个核心爬虫脚本.py、2个前端交互逻辑.js、11张验证码训练样本.jpg、2份说明文档.txt/.md、1份Word格式附赠资源.docx及HTML页面等覆盖验证码识别模型训练、法院地域切割、DocID精准下载、时间条件过滤等关键模块。已有118人学习下载适合开展区域司法实践对比、类案检索、判决趋势分析等研究。用户可直接运行checkcode.py完成验证码自动识别调用court.py按省/市/法院级别批量抓取结合content.html与vl5x.js理解反爬机制配套说明文件.txt和README.md提供清晰的环境配置与使用路径是兼具实用性与教学参考价值的轻量级法律数据采集方案。 先说明白这个项目不是要把裁判文书网整个搬空而是一个面向法律研究和数据分析的小批量采集工具。核心就三件事——按条件搜索拿列表、通过DocID精确定位文书、验证码识别配合限速下载。项目基于Python3搭建源码拆成了可配置的模块体验上比一次性脚本灵活得多。如果你是需要批量获取裁判文书的人比如在读法学硕士做实证分析、律所助理建案件样本库、或者数据分析师要按年份/法院/案由拉数据做统计那么这个工具可以帮你把“手工逐篇下载”变成“按条件自动拉取”。但它不等于无节制抓取网站robots和服务条款必须遵守项目里默认的请求间隔、超时、重试策略也建议保留别为了提速把账号和IP都搭进去。1. 为什么做这个爬虫场景拆解与合规边界先说场景。裁判文书网的数据价值不在于单篇文书而在于“可筛选、可统计、可对比”。比如研究“近三年某省民间借贷纠纷的利率支持情况”手工一篇篇翻是灾难写脚本按法院地域、时间范围、案由筛选能把几周的工作量压缩到几个小时。另一个典型场景是做数据更新lawtech团队维护本地文书库每天增量拉取新发布的文书而不是重复全量。这套工具在设计时就没按“全量抓取”来写。原因很简单裁判文书网有严格的访问频率控制全量抓的代价是封IP、封账号甚至触发更严格的验证码最后反而什么都拿不到。小批量、低频、按需取才是能长期跑下去的方式。合规上我只建议抓取公开可检索的信息并在本地标注数据来源和抓取日期不要拿来做任何敏感的个人画像或牟利转售。项目结构上我分了几个独立模块网络请求、验证码处理、搜索器、详情下载、数据清洗、落库存储。这样一旦网站接口变动只需要替换对应的请求层。Docs文件夹里放了接口抓包说明和字段字典帮助二次开发的人快速上手。为了让不同基础的人都能用我还加了命令行入口和简单的配置文件直接把参数填进去就能跑。1.1 目标用户与使用边界法学院学生、研究机构做裁判文书实证研究需要按条件抽样工具可以帮助记录样本来源。数据团队搭建涉法数据仓库需要定期增量入库工具可以做增量更新而非重复全量拉取。个人开发者学习爬虫和验证码识别项目代码注释比较详细适合用来了解一个完整爬虫的工程结构。使用边界我强调一句不要把采集能力用来规避网站的反爬机制比如大规模验证码打码、代理池轮换、半夜偷跑等。我项目里特意把并发默认值设为1限制QPS就是希望使用者在“能拿到数据”和“不给对方服务造成压力”之间找到平衡。1.2 项目模块总览模块化是这套系统最核心的设计决策。刚开始我也写过单文件爬虫但随着需求增加——验证码识别率不稳定、接口参数变化、数据清洗规则调整——单文件越来越难维护。后来重构成下面几个模块各司其职spider_core.py统一的请求入口负责会话保持、UA管理、重试与超时。search.py封装搜索接口处理分页和参数拼接。detail.py根据DocID请求文书详情页解析正文。captcha.py验证码图片预处理与OCR识别以及人工兜底逻辑。storage.py存储层支持JSON落盘和SQLite入库。filter.py时间、法院、案由条件的过滤规则独立出来方便复用。每个模块通过配置文件连接起来比如修改config.yaml就能切换存储方式、请求延迟策略和验证码模式。对于想学习爬虫工程化的朋友这个拆分方式可能是比爬虫代码本身更有价值的参考。2. 整体架构与数据模型设计在设计这套工具时我参考了不少开源爬虫项目的做法但针对裁判文书网的特点做了调整。裁判文书网的难点在于搜索接口是异步的返回的是JSON而详情页又依赖JS渲染和动态加载。两个技术栈不同必须分开处理。2.1 请求流程设计整个采集流程分两层第一层是搜索列表页。通过搜索接口按关键词、案件类型、时间范围、法院地域等条件发起查询返回分页的文书摘要。这一层拿到的关键是每个文书的docid、标题、案号、法院、日期等元数据。第二层才是正文下载。拿着docid去请求详情接口解析出完整的文书正文和附件链接。为什么不用搜索列表返回的正文摘要因为列表页的摘要经常缺字而且不是完整的裁判文书结构。对分析和训练来说正文必须完整。所以DocID是系统里最重要的关联键所有上下游操作都围绕它展开。请求流程简化如下session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..., Referer: https://wenshu.court.gov.cn/ }) # 1. 搜索获取docid列表 search_results search_api(session, keyword民间借贷, start_time2023-01-01) # 2. 详情下载带上docid doc_content detail_api(session, docidsearch_results[0][docid])每一层都有独立的重试和验证码状态机。如果搜索列表页遭遇验证码不会影响已经拿到的docid详情下载的验证码单独处理。这样即使验证码识别率不高也能保证已获取的元数据不丢失。2.2 数据表与文件结构存储设计遵循“原始数据不可变、清洗数据可追溯”的原则。原始HTML存档和结构化字段分开放方便回查。SQLite表结构示意CREATE TABLE doc_meta ( docid TEXT PRIMARY KEY, title TEXT, case_number TEXT, court TEXT, case_type TEXT, cause TEXT, judge_date TEXT, local_path TEXT, created_at TEXT ); CREATE TABLE doc_parsed ( docid TEXT PRIMARY KEY, doc_type TEXT, parties TEXT, judge_text TEXT, meta_json TEXT, FOREIGN KEY (docid) REFERENCES doc_meta(docid) );文件目录按年份/法院代码/案由分类分层。比如data/ 2024/ 苏01/ 民事/ docid_xxx.html这样设计的好处有两个一是按时间、地域切割后统计分析直接对文件夹做聚合就行二是增量更新时只处理新出现的docid老数据不动。2.3 为什么DocID比案号更可靠案号虽然对法律人来说更直观但在数据抓取里不是一个稳定的主键。案号格式在不同年份、不同法院有变化同一个案件可能有多篇文书判决书、裁定书、调解书共用同一案号而且案号在列表页有时返回为空。DocID是网站内部生成的唯一标识可以理解为数据库里的自增主键有docid再拉详情页不会拿错文书。实践中我通常先把搜索列表获得的docid批量存入一个pending_import表再由另一个线程消费这些docid去下载详情。这样做的好处是即使中间断网重启后只需要从pending_import里取出未完成的docid继续跑不会重复解析列表页。3. 核心实现分类检索、时间过滤和法院地域切割标题里提到的三个筛选维度——分类、时间、法院地域——看似简单实际实现时各有坑。我一个个说。3.1 分类检索不只是案由字符串裁判文书网的分类体系很复杂有“案件类型”刑事、民事、行政、赔偿、执行和“案由”两级概念。简单地把案由当作字符串去过滤会出现“合同纠纷”和“买卖合同纠纷”都匹配到同一个案件的混乱。我的做法是维护一份案由层级映射表把高频案由映射到对应分类。比如case_type_map { 刑事: [危险驾驶, 盗窃, 诈骗, 故意伤害], 民事: [民间借贷, 买卖合同, 离婚纠纷, 侵权责任], 行政: [行政处罚, 行政强制, 行政许可] }搜索时优先选“案件类型”再在关键词里加入二级案由这样可以大幅减少噪声。分类检索的真正价值不是把所有文书塞进一个大池子而是为后续分析提供可聚合的维度。另外分类结果建议保存为标准化枚举值而不是原始字符串。因为网页上显示的是“民初”、“刑初”这类简称入库时要转成标准名称。我在filter.py里写了一套正则和映射规则专门处理这种简称差异。3.2 时间条件过滤边界条件最容易搞错时间条件在搜索接口里通常是起止日期字符串但有几个坑第一时间格式。网站用YYYY-MM-DD但有的接口接收YYYY/MM/DD拼错一个分隔符整条查询报错。第二起止日期的闭合性。到底含不含边界不同接口不一样我实测下来裁判文书网通常是闭区间但保险起见我会在请求后把返回的裁判日期与条件比对一遍超出范围的直接丢弃。第三裁判日期不等于发布日期。如果你想做“每日增量”应该用发布日期而不是裁判日期来过滤否则会有大批已经上网的历史文书重复返回。实现上时间过滤我写成了一个独立函数def filter_by_date(doc_date: str, start: str, end: str) - bool: d datetime.strptime(doc_date, %Y-%m-%d).date() s datetime.strptime(start, %Y-%m-%d).date() e datetime.strptime(end, %Y-%m-%d).date() return s d e在详情下载完成后再执行一次这个过滤等于多一层保险。宁可在写入前多耗一点CPU也不要放脏数据进库。3.3 法院地域切割按省份、地市、法院三级抓取法院地域是法律数据分析很关键的维度。比如研究“浙江地区民间借贷案件数量变化”如果不按地域过滤就需要自己从法院名称里提取区域信息工作量很大。裁判文书网的法院参数通常是一串代码比如“浙江省高级人民法院”对应某个courtId。这个对应关系可以手工整理也可以从网站上抓法院列表接口获取。我整理了一份法院代码表并做了三级切割省/直辖市浙、京、苏等。地市杭州、苏州、南京等对应“浙01”这类代码。具体法院杭州市中级人民法院、杭州市上城区人民法院用全称匹配。目录切割就按这个三级结构来。比如data/2024/浙01/杭州市中级人民法院/。这样后续做地域统计时按文件夹名聚合即可不用重新解析文书。需要注意有些案件是跨地域的比如最高院提审的案件法院地域字段可能是“最高法”不能归到某个省。数据清洗时我会单独建一个最高法目录避免污染省级统计。4. 验证码识别与反爬应对不追求100%但求稳定可用裁判文书网的验证码是很多爬虫的拦路虎。我的经验是不要试图暴力破解验证码而是设计一个“OCR优先、人工兜底、频率控制兜底”的三级方案。4.1 验证码形态与预处理常见形态是4位数字或数字加字母字符有轻微变形和干扰线。直接送进OCR识别率可能只有三成。预处理流程很关键灰度化消除颜色干扰。二值化选全局阈值或Otsu。降噪用中值滤波去掉孤立的噪点。字符分割如果验证码字符间距均匀可以用垂直投影切割如果粘连严重就整张图直接进OCR。Pillow实现预处理代码from PIL import Image, ImageFilter import pytesseract def preprocess_captcha(img_path): img Image.open(img_path).convert(L) img img.point(lambda x: 255 if x 128 else 0) img img.filter(ImageFilter.MedianFilter(size3)) return img def recognize(img): processed preprocess_captcha(img) text pytesseract.image_to_string(processed, config--psm 7 -c tessedit_char_whitelist0123456789) return text.strip()--psm 7是告诉Tesseract把图片当作一行文本去识别对4位验证码比较合适。白名单限制字符集能显著提升准确率。4.2 提高识别率的经验光靠通用OCR准确率还是有限。我试过几种改进用数百张验证码图片训练Tesseract的专用字库识别率能提升到七成以上。但不是所有人都有耐心标注所以作为可选模块。对粘连字符做形态学处理比如用cv2.dilate和erode调整字符粗细让Tesseract更容易分离字符。不同时间段的验证码风格会变化所以我在captcha.py里加了缓存机制识别失败的图片统一保存到captcha_failed/目录供人工标注和后续训练使用。不管用哪种方法识别率都不可能100%。所以项目里设计了一个硬性规则连续3次验证码识别失败自动停止请求并通知人工而不是无限重试。无限重试只会让IP很快被网站风控盯上。4.3 请求频率与反爬策略我觉得“反爬应对”的核心不在破解而在自律。项目默认配置如下每次请求之间随机睡眠2-5秒。同一Session执行连续请求超过20次后强制睡眠60秒。失败请求自动重试最多3次超过后进入退避状态。默认并发线程数为1开启多线程前需要配置thread_num但我不建议超过3。用这套配置调用稳定性比原先“高并发代理池”好很多。有些人对爬虫性能有误解认为并发越高越好但对这种严格限流的网站一个稳定、低频的抓取进程反而更能长期跑。4.4 手动打码兜底模块当OCR连续失败时项目会把验证码图片保存到本地并弹出一个简单的Tkinter窗口让人工识别填入后继续流程。别觉得“人工”丢人实际工程里这是最高效的兜底方案。对于偶尔几十个验证码的情况人工点几秒就完事比训练模型划算得多。另外也支持把一个失败的验证码目录挂在FastAPI服务上手机扫码查看、填写验证码并发回给主进程。这适合需要远程盯任务的场景。但我还是那句话能控制频率就不需要频繁用到这个功能。5. 数据清洗与存储从HTML到结构化字段爬下来的文书是HTML直接分析肯定不行。清洗层要做的事比想象中多。5.1 HTML正文解析详情页的正文结构在不同法院、不同文书类型之间不太一致但大体包含标题、案号、当事人信息、委托诉讼代理人、案件由来、审理经过、本院查明、本院认为、裁判结果、审判人员、日期等段落。解析思路是先按HTML标签切分段落去掉script、style、注释。识别标题、案号、日期这三个稳定字段用正则或CSS选择器提取。剩余正文按段落拆分存入judge_text字段。在解析时我会用beautifulsoup4和lxml结合。lxml速度快但对格式很差的HTML容易崩bs4更容错适合这种数据结构不完全规范的场景。解析前先把编码统一转成UTF-8否则会出现大量乱码。判断编码时用requests返回的apparent_encoding比直接猜更可靠。5.2 段落合并与政治敏感词检测无这个环节我这里不做敏感词过滤因为数据是公开的主要是做格式标准化。比如对连续的空白字符进行压缩、把全角括号转半角、把多个换行合并成一个。做完这些再存库后续用Python做文本分析时分词、统计都会顺畅很多。清洗的结果同时写两份一份是原始HTML的存档另一份是清洗后的JSON和SQLite。原始HTML存档的好处是如果后续想重新解析比如网站改版后需要恢复更多字段有原始数据可以回滚。这个习惯也适用于其他爬虫项目强烈建议保留原始响应。5.3 SQLite vs CSV vs JSON存储选择的思考CSV适合一次性分析和Excel打开但字段里有换行和逗号时容易错位而且不擅长存正文。JSON适合调试和程序间交换但不适合大范围筛选。SQLite单文件、无服务、支持SQL是我这个项目的默认选择。对于几十万条文书来说SQLite完全够用也方便其他同事直接拿到库文件做分析。所以我的落库策略是元数据存SQLite全文存JSON文件原始HTML存目录。这样兼顾了查询效率和文本分析需求。6. 数据分析示例时间、地域、案由三个维度的统计工具做完数据采集下一步是分析。标题里既然写了“分析系统”那就不能只停留在爬虫层。我把采集后的数据做成了几个简单的交叉统计这部分也是读者可以复用到自己项目里的。6.1 按年份统计案件数量变化从SQLite取出裁判日期字段按年group by就能得到案件数量趋势。比如“浙江地区危险驾驶罪案件数量在近五年的变化”直接反映出法律政策或执法力度的影响。画个折线图或柱状图比看表格直观得多。sql模板SELECT substr(judge_date, 1, 4) AS year, COUNT(*) AS cnt FROM doc_parsed WHERE court LIKE 浙江% AND case_type 刑事 GROUP BY year;6.2 案由词频与热点分析把judge_text丢给jieba分词统计高频词可以快速看出某个时间段里案由分布。比如2024年“民间借贷”相关文书里高频词可能有“利息”“担保”“违约金”形成一个初步的争议焦点清单。不过这里要注意分词的词典要补充法律术语比如“审限”、“保全”、“上诉”否则会被切成奇怪的字。我整理了一份legal_terms.txt导入jieba后效果提升很明显。6.3 地域间裁判金额差异分析如果文书里有金额字段可以用正则提取“人民币XXXX元”再转成数值。这个字段在实际文书中格式多样有中文大写、阿拉伯数字、带币种等所以清洗规则要写得很细。提取金额后按省份聚合求均值或中位数可以做出有洞察力的区域对比图。这种分析虽然简单但在法律实证领域已经很能说明问题。工具的价值在于把原本爬取到的“死数据”变成可以支撑观点和报告的“活证据”。7. 常见问题与排查技巧实录写爬虫最耗时间的不是写代码而是调试各种不可预期的问题。这里整理几个我在这个项目中遇到的高频问题以及解决思路。7.1 搜索接口返回空结果最常见原因是参数格式不对尤其是时间条件和分页页码。裁判文书网搜索接口的分页参数是pageId和pageSize不是传统的page1。如果照搬别的网站分页方式请求结果一定不为空但解析不到数据。排查步骤在浏览器开发者工具里手动发起一次查询查看请求头里的参数和返回的JSON结构。对比本地代码的请求体和浏览器请求体定位差异。确认关键字是否被URL编码中文必须用urllib.parse.quote。7.2 验证码识别率骤降如果脚本连续跑了几天后验证码识别率突然下降不要急着换OCR引擎先检查是不是验证码风格变了。裁判文书网的验证码会不定期改版字符变长、加干扰线、或者变成空心字。这种情况最有效的办法是收集新一批识别失败的图片重新做预处理参数调优。再一个原因是Tesseract的训练数据版本过低。新验证码字体如果不在训练集里识别率自然差。可以换用tesseract_best语言包试试或者用fastai-captcha类似的开源库训练专属模型。但训练模型对大多数用户来说太重了我建议还是靠频率控制降低验证码出现概率。7.3 下载详情页时被重定向到登录页这基本是会话过期或Cookie失效。列表页能请求通不代表详情页也能通因为详情页可能需要更完整的Cookie。解决办法是启动时先用requests创建会话并访问一次首页拿到初始Cookie搜索过程中注意更新Cookie如果某次详情请求返回登录页特征就放弃当前批重置会话并增加等待时间。7.4 编码乱码我遇到过几次页面声明是UTF-8但请求回来后text属性显示乱码。原因是服务器返回的响应头里没指定charsetrequests默认走ISO-8859-1。解决方法是统一使用resp.content加上apparent_encoding来解码resp requests.get(url, headersheaders) if resp.status_code 200: encoding resp.apparent_encoding or utf-8 html resp.content.decode(encoding, errorsignore)7.5 增量更新时重复导入数据增量更新的核心是维护一个已导入docid集合。我用SQLite里doc_meta表做主键去重每次导入前先和库中已有docid做对比只处理新出现的。此外建议记录最近一次成功抓取的截止时间以免漏抓更新期间发布的文书。如果断点续跑逻辑没做好很可能出现“今天跑一遍、明天又重复跑一遍”的情况。解决办法是设计一个pending_import表将所有待下载docid先写入状态标记为pending成功下载后更新为done。8. 实操过程中的经验总结与扩展建议最后聊几个比较常见的实操心得可能会对想二次开发的人有帮助。第一关于DocID的长期价值。即便暂时不需要下载全文也可以先把docid和元数据存下来。因为裁判文书网的检索功能虽然强大但搜索关键词的覆盖度并不总尽如人意自己维护一份docid索引后续做补采会有很强的前瞻性。第二存档原始页面。很多爬虫只保存解析后的字段省了磁盘空间却丢了后路。我遇到过两次网站前端结构升级原来解析的字段全乱套了幸好有原始HTML存档重新写一遍解析规则就能恢复。第三选型上不要迷信最新框架。有些朋友一上来就上Scrapy、Playwright但对这种小批量的场景用requests加少量协程可能更轻量、更可控。Playwright虽然能处理JS渲染但浏览器实例开销大而且更容易触发网站风控。在裁判文书网这个具体案例里轻量请求配合手动Cookie管理已经是够用的方案。第四把代码当成产品来写而不是一次性脚本。模块拆分、配置文件、日志体系这些“看起来多余”的东西在实际运行时会帮你省很多时间。我这次重构后日志记录到文件和控制台定位问题比之前只靠print高效很多。扩展方向上如果你熟悉数据处理可以接入Pandas、Matplotlib实现对已采集数据的自动报表生成也可以结合大模型做摘要提炼把长文书的争议焦点自动整理成结构化列表。但基础还是这份干净、可追溯、带完整元数据的数据集。做这套工具最大的体会是爬虫最难的不是绕过验证码也不是解析HTML而是设计出稳定、克制、可持续运行的采集流程。数据是公开的技术是合法的但使用数据的方式必须可靠、可解释、可追溯。守住这个底线工具才能真正帮到你的研究和工作。本文还有配套的精品资源点击获取