第四章:数据采集与清洗——爬虫基础、法律网站数据抓取、去重与格式清洗

做法律大模型,数据是命根子。但说实话,光有数据还不够,你得知道怎么把数据「请」回来,还得把它「洗」干净。这一章,我就带你走一遍从爬虫到清洗的全流程。都是我在项目里踩过的坑,你听听看。

4.1 爬虫基础:别把法律网站搞崩了

爬虫这东西,说白了就是个自动化下载器。你手动复制粘贴,它帮你自动干。但法律网站不是你家后院,爬得太猛,人家服务器会报警的。

⚠️ 注意: 法律网站的数据受版权保护。我个人习惯是只爬公开的裁判文书、法律法规,绝不碰需要登录或付费的内容。合规第一,别给自己找麻烦。

我常用的工具是 Python 的 requestsBeautifulSoup。简单说,requests 负责发请求拿网页,BeautifulSoup 负责解析 HTML 提取内容。嗯,这里要注意:很多法律网站有反爬机制,你得加个 User-Agent 伪装成浏览器。

import requests
from bs4 import BeautifulSoup

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
url = 'https://example.com/case/123'
resp = requests.get(url, headers=headers)
soup = BeautifulSoup(resp.text, 'html.parser')
title = soup.find('h1', class_='case-title').text.strip()
print(title)

你看,核心代码就这么几行。但实际项目中,你可能会遇到动态加载的页面。这时候 requests 就拿不到了,得用 Selenium 模拟浏览器。我曾经在一个地方法院的网站上折腾了两天,最后发现人家用的是 Vue 异步渲染……嗯,从那以后我学乖了,先看页面源码再动手。

4.2 法律网站数据抓取:实战案例

咱们拿「中国裁判文书网」举个例子。这个网站数据量大,但结构乱。我建议你先抓一个页面看看结构,再写循环批量抓取。

为什么会这样?因为法律网站的 HTML 结构经常变。你写死一个选择器,过两周可能就失效了。我的做法是:

  1. 先手动分析:打开浏览器开发者工具,找到目标数据的 CSS 选择器或 XPath。
  2. 写一个测试脚本:只抓一页,验证数据对不对。
  3. 加延时和重试:每抓一页等 1-3 秒,遇到 503 错误自动重试 3 次。
💡 小技巧: 我习惯用 time.sleep(random.uniform(1, 3)) 随机延时,这样更像人类行为,不容易被封 IP。

下面是一个批量抓取裁判文书标题和案号的示例:

import time
import random

def fetch_case_list(page_num):
    url = f'https://example.com/list?page={page_num}'
    resp = requests.get(url, headers=headers)
    soup = BeautifulSoup(resp.text, 'html.parser')
    cases = []
    for item in soup.select('div.case-item'):
        title = item.find('a').text.strip()
        case_id = item.find('span', class_='case-id').text.strip()
        cases.append({'title': title, 'case_id': case_id})
    return cases

for page in range(1, 101):
    data = fetch_case_list(page)
    # 保存到本地
    with open(f'cases_page_{page}.json', 'w') as f:
        json.dump(data, f, ensure_ascii=False)
    time.sleep(random.uniform(1, 3))

你想想看,如果不用随机延时,100 页可能 5 分钟就抓完了,但你的 IP 大概率会被封。我刚开始做的时候不懂,一口气抓了 5000 页,结果第二天 IP 就被拉黑了……

4.3 去重:别让模型学重复的东西

法律数据里重复率特别高。同一个案子,可能在不同网站、不同时间被收录多次。如果不去重,模型会学到「同一个意思有不同说法」,反而降低准确率。

我的去重策略分三步:

  1. 精确去重:用 MD5 或 SHA256 对全文做哈希,哈希值一样的就是重复。
  2. 模糊去重:用 SimHash 或 MinHash,计算文本相似度,超过 90% 就视为重复。
  3. 业务去重:法律数据有唯一标识,比如案号、文书编号。同一个案号只保留一份。
📌 重点: 我建议优先用业务去重,因为案号是法律文书的「身份证」,准确率最高。哈希去重适合内容完全一样的情况,模糊去重则用来处理「同一案件不同版本」的问题。

下面是一个简单的 MD5 去重示例:

import hashlib

seen_hashes = set()
deduped_data = []

for item in raw_data:
    text = item['content']
    hash_val = hashlib.md5(text.encode()).hexdigest()
    if hash_val not in seen_hashes:
        seen_hashes.add(hash_val)
        deduped_data.append(item)

嗯,这里要注意:MD5 去重对「只改了一个标点符号」的情况无效。所以我在项目中通常先用 MD5 去重,再用 SimHash 做第二轮过滤。

4.4 格式清洗:把数据变成模型能吃的样子

法律数据格式乱得让人头疼。有的用全角标点,有的用半角;有的段落之间空三行,有的连空格都没有。模型可不管这些,它只认干净的数据。

我总结了一套清洗流程,你直接拿去用:

步骤 操作 示例
1 统一编码 全部转成 UTF-8,避免乱码
2 去除空白字符 删除多余空格、换行、制表符
3 统一标点 全角逗号转半角,中文句号保留
4 去除噪声 删除页眉页脚、广告、版权声明
5 标准化日期 「2023年1月1日」统一为「2023-01-01」

下面是一个清洗函数的示例:

import re

def clean_text(text):
    # 去除多余空白
    text = re.sub(r'\s+', ' ', text)
    # 统一标点(中文保留全角,英文用半角)
    text = text.replace(',', ',').replace('。', '.')
    # 去除页眉页脚(假设以「本院认为」开头)
    if text.startswith('本院认为'):
        text = text[4:]
    # 标准化日期
    text = re.sub(r'(\d{4})年(\d{1,2})月(\d{1,2})日', r'\1-\2-\3', text)
    return text.strip()

我曾经遇到过一个案子,判决书里混了法官的批注和排版标记。清洗的时候没注意,结果模型把「【注:此处删除】」也当成了正文……从那以后,我每次清洗完都会随机抽 100 条人工检查一遍。

4.5 知识体系总览

下面这张图是我自己画的,把数据采集和清洗的整个流程串起来了。你一看就明白。

法律大模型数据采集与清洗流程 第一阶段:数据采集 爬虫基础 法律网站抓取 反爬处理 数据存储 第二阶段:数据清洗 去重处理 格式清洗 标准化 质量检查 第三阶段:输出高质量法律数据集 用于大模型训练 / 微调 / 评估

你看,整个流程分三个阶段:采集、清洗、输出。每个阶段都有对应的技术要点。我个人习惯在清洗阶段花最多时间,因为数据质量直接决定模型效果。你想想看,喂给模型一堆脏数据,它学出来的东西能靠谱吗?

💡 我的经验: 数据清洗的时间至少占整个项目的一半。别急着训练模型,先把数据搞干净。我曾经因为偷懒少做了模糊去重,结果模型在测试集上准确率掉了 5 个点……后来老老实实补上,效果立竿见影。

好了,这一章的内容就这些。爬虫、去重、清洗,每一步都有坑,但只要你按流程走,基本不会出大问题。记住:法律数据是敏感数据,合规永远是第一位的。

专注资料整理