法律案例数据库搭建实战:爬虫、数据清洗与文本挖掘全流程解析
2026/9/15 10:25:22 网站建设 项目流程

做法律案例数据库这个事,其实是被逼出来的。我平时要频繁检索裁判文书,公共平台的查询体验大家心里都有数,翻页慢、筛选不灵活、复制还受限制,更别提想做点统计分析了。既然手头有Python这个趁手工具,为什么不自己动手抓一份干净、结构化的案例数据下来,顺便跑一跑文本挖掘?断断续续折腾了快一个月,我把从爬虫搭建、数据清洗、入库建模到基础挖掘的全流程跑通了,这篇文章就是完整复盘,把能落地的细节和踩过的坑都写出来。适合有一定Python基础、想拿真实数据练手爬虫和文本分析的人,也适合法律信息化相关从业者参考。

1. 项目概述与整体设计思路

1.1 核心需求解析:为什么非要自己建库

公共法律案例数据的痛点,做实务的人都懂:第一是检索效率低,平台按关键词搜出来的结果动辄几千条,但筛选维度单一,只能一页页翻;第二是数据格式封闭,你想把案由、法院层级、判决年份、地域这些字段拉出来做透视,几乎不可能;第三是无法二次加工,比如做类案对比、统计某个法官对某类案件的态度倾向,这些需求在现有平台上都实现不了。

我自己最直接的触发点是做一次劳动争议判决结果的统计分析。手动翻了一百多份文书,复制粘贴到Excel里整理了一天,才意识到这种工作完全可以交给爬虫和数据库来做。项目目标很明确:抓取公开渠道的法律案例数据,存入本地数据库,形成一套可查询、可筛选、可分析的私有数据资产。数据量级按万级设计,单机即可完成。

这个项目本质上是一个垂直领域的爬虫+数据工程项目。网上爬虫教程遍地都是,但绝大多数拿豆瓣、电商练手,数据结构简单、反爬宽松,跟真实场景差距很大。法律案例网站文本密度高、页面结构相对规整但细节坑多,更贴近工业级数据采集的实际情况,做完一趟下来,爬虫、清洗、入库、挖掘的完整链路都能打通。

1.2 技术选型与方案对比

选型这事我在动工前专门做过对比。爬虫层我试过Scrapy,也用过纯requests,最后选了requests + BeautifulSoup的组合。Scrapy性能确实强,自带并发、去重、中间件,但学习曲线陡,对新手不友好,而且法律案例这种单站点规模可控的项目,Scrapy的很多功能用不上。requests写起来直观,配合concurrent.futures做并发完全够用,出了问题也好排查。

数据解析在BeautifulSoup和lxml+xpath之间纠结过一阵。BeautifulSoup的API对新手极其友好,但是在大规模解析时速度会比lxml慢。实测单页解析差距在几十毫秒量级,对每天万级数据的采集任务影响不大,所以最终选BeautifulSoup保开发效率。如果以后数据量上到百万级,再考虑切lxml不迟。

存储方面我直接选了SQLite。理由很现实:单机项目、单文件存储、不需要安装服务端,Python自带sqlite3库零依赖就能跑。MySQL和PostgreSQL当然更强大,但对这个量级属于杀鸡用牛刀。真到了需要多人并发访问或者数据量爆炸的那天,SQLite的库文件可以平滑迁移到PostgreSQL,不影响已有数据处理流程。

文本挖掘层用了jieba分词加基础统计方法。本来想上更重的深度学习模型做文书语义分析,但实践下来发现法律文本的很多信息用规则加统计就能挖出不错的效果,比如词频、关键词抽取、判决结果倾向判断。先把基础版本跑通,后续再迭代模型。

2. 环境准备与关键工具链搭建

2.1 Python环境配置与虚拟环境隔离

这个项目的Python版本我建议用3.9以上。3.8及以下版本在类型注解和某些新特性上有差异,虽然不影响核心功能,但新版本更省心。环境管理用conda或者python自带venv都行,重点是要建独立虚拟环境,别跟系统Python搅在一起,不然依赖冲突会让人怀疑人生。

我习惯用venv,命令很简单:

python -m venv law_crawler_env source law_crawler_env/bin/activate # Windows下是 law_crawler_env\Scripts\activate

进入虚拟环境后安装依赖。这个项目核心库其实就那么几个:

pip install requests beautifulsoup4 lxml jieba pandas openpyxl

数据库部分用Python自带的sqlite3,不需要额外安装。pandas和openpyxl是用来做数据导出和快速统计的,最后写报告的时候非常有用。整个依赖清单不超过10个包,相比动辄几十个依赖的框架型项目,轻量很多,出问题的概率也低。

2.2 目标网站结构与数据入口分析

正式写爬虫之前,花时间做结构分析是必须的。我先用浏览器的开发者工具手动访问目标网站,观察列表页和详情页的URL规律、翻页参数、内容渲染方式。法律案例类网站一般不会用太复杂的JS渲染,数据直接写在HTML里,这对爬虫来说是最友好的情况。

列表页URL结构可能有几种模式:有的用页码参数,比如 /list/案件类型/页码.html;有的用POST请求发查询条件。如果是POST,就要用requests的data参数带上表单字段。详情页一般是 /judgment/detail/案号.html 这种静态地址,参数规律比较规整。

需要注意的是robots.txt。虽然它没有法律强制力,但遵守它是一个爬虫工程师基本的职业素养。我会先看一下目标站点的robots.txt,确认哪些路径不允许抓取。如果对方明确禁止,就应该换个数据源或者重新评估方案。

我把页面结构梳理出来的字段清单大致是这样:

字段来源位置说明
案件名称详情页标题全文标题,包含当事人和案由
案号标题或正文首段格式如(2023)京01民终1234号
法院名称详情页正文/头部对应案件审理法院
审理日期正文结尾或头部格式不统一,需要清洗
案由列表页或详情页标签如劳动争议、合同纠纷
判决结果正文尾部结构差异大,需人工验证

字段梳理清楚后再写爬虫,代码会很有章法,不会写着写着发现缺字段又回去改。

3. 爬虫设计与核心实现

3.1 请求策略:请求头、延迟与重试机制

请求策略是爬虫能否稳定运行的基石。我会在请求头里带上完整的User-Agent信息,模拟正常浏览器的访问。单纯用默认的python-requests标识很容易被识别拦截。推荐的请求头至少包括User-Agent、Accept、Accept-Language这几个字段。

headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", "Connection": "keep-alive" }

延迟策略上,两个连续的请求之间至少间隔1到2秒。有些人喜欢把延迟设到0.1秒抢速度,这种做法很不可取。一方面给目标服务器造成不必要的压力,另一方面被识别后封IP,后续整个项目就推进不下去了。我做这个项目时平均每秒请求不超过0.8次,总数据量也就几万页,跑下来效率完全够。

重试机制必须做。网络请求受各种因素影响,超时、断连、服务器临时返回500都很常见。我封装了一个带重试的请求函数,最多重试3次,每次重试的间隔按指数退避策略递增:

import time import requests def fetch_url(url, max_retries=3, timeout=15): for attempt in range(max_retries): try: resp = requests.get(url, headers=headers, timeout=timeout) if resp.status_code == 200: return resp elif resp.status_code == 403: time.sleep(5) continue else: time.sleep(2 * (attempt + 1)) except requests.RequestException: time.sleep(2 * (attempt + 1)) return None

3.2 解析逻辑:从HTML到结构化字段

页面请求成功后的解析环节,基本功是定位HTML节点。BeautifulSoup定位节点有几种方式:find、find_all按标签和属性找,select按CSS选择器找。我实践经验是先用select写CSS选择器效率最高,一眼就能看出层级关系。比如案号所在的div结构是<div class="case-number">(2023)京01民终1234号</div>,直接:

soup.select_one("div.case-number").text.strip()

正文里字段缺失的情况非常常见。一个案由为“买卖合同纠纷”的案件,判决结果里的措辞和“劳动争议”完全不同,所以写提取函数时要做空值兜底。我用get_text获取文本后统一做正则补提,比如从正文最后200字里找“判决如下”之后的内容作为判决结果字段的备选来源。

解析模块最好设计成独立函数,输入soup对象,输出字典格式的字段数据,这样后续加字段或调整逻辑都方便。我还会在每个字段抓取位置后面加日志输出,采集几页后人工抽样检查字段是否对得上,发现问题及时调整。

3.3 并发设计:单线程、线程池还是协程

这是爬虫里最值得聊的部分。很多教程直接上多线程,但并没有说清楚为什么需要并发、并发度怎么设。我实际对比过三种方案的差异:

单线程串行最简单,代码好写、逻辑清晰,对服务器最友好。缺点是慢。假如要采集5万个详情页,每页请求加解析平均花2秒,串行需要27个小时,时间成本太高。

线程池方案用concurrent.futures.ThreadPoolExecutor实现,代码量不大,提速明显。我测试过不同并发数的差异:

并发数完成500页耗时平均单页耗时
11100秒2.2秒
5260秒0.52秒
10180秒0.36秒
20150秒0.30秒

从10到20的提升已经很小,但服务器压力翻倍。综合考量后我把并发数定在8到12之间,既能保证速度,又不至于太激进。

协程方案我也试过,用httpx的AsyncClient加asyncio,性能上限更高,但代码复杂度上升明显。对requests+BeautifulSoup这套同步方案,线程池加信号量控制是最合适的平衡点。核心逻辑:

from concurrent.futures import ThreadPoolExecutor, as_completed import threading semaphore = threading.Semaphore(10) def fetch_detail(url): with semaphore: resp = fetch_url(url) if resp is None: return None return parse_detail(resp.text) with ThreadPoolExecutor(max_workers=10) as executor: futures = [executor.submit(fetch_detail, url) for url in detail_urls] for future in as_completed(futures): data = future.result() if data: save_to_database(data)

代理IP的问题也值得提一句。如果目标站点封IP,解决方案无非两个方向:降低频率,或者用代理池。代理池水很深,免费代理质量差、稳定性没保证,付费代理也需要甄别。我的建议是先把频率控制做好,能不依赖代理就不要上代理。

3.4 分布式爬虫与其他延伸方向

很多人做完单机爬虫后会想要不要上分布式。我的看法是,数据量级在百万以内根本不需要分布式。单机多线程就够跑了。分布式引入的消息队列、任务调度、状态同步这些复杂度,对小项目是纯消耗。

如果确实需要扩展,可以基于Redis做URL队列,多个爬虫节点消费任务。但这是另一个量级的工程,普通场景用不上。我见过很多人爬虫写着写着就跑偏到架构上,反而把核心的数据质量忽略了。

可视化方面,我用pandas处理完数据后,用matplotlib和pyecharts画过案由分布、年份趋势、地域分布这些图。对法律数据分析来说,可视化的意义在于快速发现规律,比如某类案件在不同年份的增长趋势、不同地域的判决差异,这些信息在Excel里看不出来,画成图就很直观。

3.5 数据清洗与法律文本预处理

抓下来的原始数据不能直接入库。我踩过的最大的坑是编码问题,有些网页用GBK编码,直接用UTF-8解析会乱码。所以requests拿到响应后要先确认网页声明的charset,再决定解码方式:

if resp.encoding and resp.encoding.lower() not in ("utf-8", "utf8"): resp.encoding = resp.apparent_encoding

清洗阶段主要处理几类脏数据:HTML标签残留、全角半角混杂、多余空白和换行、日期格式不统一、金额格式不一致。我把清洗逻辑写成一个pipeline函数,依次处理:

import re def clean_text(text): # 去掉HTML标签 text = re.sub(r"<[^>]+>", "", text) # 全角转半角 text = text.replace("\u3000", " ") # 合并多余空白 text = re.sub(r"\s+", " ", text) # 剔除特殊字符 text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9,。!?;:“”()()【】《》、%年月日号字第]", "", text) return text.strip()

日期字段的格式问题特别多,“2023年5月6日”“2023-05-06”“2023/5/6”这些都要统一。我用正则分别提取年月日,再拼成ISO标准格式:

def normalize_date(text): matcher = re.search(r"(\d{4})年(\d{1,2})月(\d{1,2})日", text) if matcher: y, m, d = matcher.groups() return f"{y}-{int(m):02d}-{int(d):02d}" return ""

法律文本预处理里还涉及分词。jieba对法律术语的支持一般,所以需要加载自定义词典,把“驳回上诉”“维持原判”“解除劳动合同”“经济补偿金”这些专业词汇提前加入词典,保证分词的完整性:

import jieba jieba.load_userdict("legal_dict.txt") # legal_dict.txt 每行一个词,格式:词 词频 词性 # 解除劳动合同 500 n # 经济补偿金 500 n # 劳动仲裁 500 n

加上自定义词典后,分词准确率有明显提升“劳动仲裁”这个词在默认词典下可能被拆成“劳动”和“仲裁”,加载词典后就作为一个整体被识别,后续统计词频时效果差异很大。

4. 数据库设计、入库与查询优化

4.1 表结构设计:如何兼顾灵活性与查询效率

法律案例数据入库前,表结构必须想清楚。我设计了三张核心表,既保证查询效率,又留出扩展空间:

案件主表(cases)存放案件的静态属性,包括案件ID、案件名称、案号、法院、审级、案由、审理日期、判决结果、全文文本。当事人表(parties)单独拆出来,因为一个案件可能有多个当事人,当事人类型也不一样(原告、被告、第三人),一对多关系必须拆表。文书关联表能支持按当事人反查案件的需求。

CREATE TABLE IF NOT EXISTS cases ( id INTEGER PRIMARY KEY AUTOINCREMENT, case_number TEXT UNIQUE, case_name TEXT, court TEXT, judge_level TEXT, cause TEXT, trial_date TEXT, result_summary TEXT, full_text TEXT, source_url TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS parties ( id INTEGER PRIMARY KEY AUTOINCREMENT, case_id INTEGER, party_name TEXT, party_role TEXT, FOREIGN KEY (case_id) REFERENCES cases(id) ); CREATE INDEX idx_cases_cause ON cases(cause); CREATE INDEX idx_cases_trial_date ON cases(trial_date);

案号这个字段我加了唯一约束,这是天然的业务主键。同一个案件不会出现两份完全相同的案号,用案号做去重比用标题去重靠谱得多。

4.2 数据批量入库:事务与冲突策略

数据入库看起来简单,但处理不好性能差异很大。我最开始是一条一条插入,抓5000条数据要跑很久。后来改成批量提交,速度提升了几十倍。核心思路是用事务包裹批量插入:

import sqlite3 def save_batch(cases_data): conn = sqlite3.connect("law_cases.db") cursor = conn.cursor() rows = [ (c["case_number"], c["case_name"], c["court"], c["judge_level"], c["cause"], c["trial_date"], c["result_summary"], c["full_text"], c["source_url"]) for c in cases_data ] cursor.executemany( "INSERT OR IGNORE INTO cases (case_number, case_name, court, judge_level, cause, trial_date, result_summary, full_text, source_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", rows ) conn.commit() conn.close()

INSERT OR IGNORE的作用是当案号已存在时跳过插入,这个冲突策略正好实现增量采集。每次跑爬虫只需要抓新增部分,不用重新爬全量数据。

4.3 数据库同步与工具选择

数据量大了或者换设备之后,会考虑数据库同步的问题。SQLite单文件特性让同步变得很简单,直接用文件同步工具就能搞定。我试过几种方案,简单场景用云盘同步最省事,稍微专业一点可以用支持定时同步的工具。需要说明的是,SQLite不适合多设备同时写同一个文件,只适合单点写入后同步副本,这个限制要清楚。

如果团队协作或数据量上百万级,就该迁到真正的数据库服务了。PostgreSQL是首选,支持并发访问、全文检索更强,迁移方案成熟。还有向量数据库这个方向,做法律文书的语义检索时可以把案件文本向量化后存入向量库,支持相似度检索。这个属于进阶玩法,先不展开。

5. 法律文本挖掘的实践细节

5.1 裁判结果倾向分析的关键代码

文本挖掘是这个项目最亮的部分。第一部分做裁判结果倾向分析,本质是从判决文本里提取倾向性信息。劳动争议案件的结果可以粗分类为“支持劳动者诉求”“部分支持”“不支持”这三类,特征就藏在“判决如下”后面的表述里。我用规则匹配实现了一个基础版本:

def analyze_labor_judgment(full_text): result_keywords = { "support": ["支持原告", "应予支持", "支付", "撤销", "确认"], "partial": ["部分支持", "酌情", "部分予以支持"], "reject": ["驳回", "不予支持", "无法律依据"] } summary = "" scores = {"support": 0, "partial": 0, "reject": 0} for category, keywords in result_keywords.items(): for kw in keywords: scores[category] += full_text.count(kw) if scores["reject"] > scores["support"] and scores["reject"] > scores["partial"]: summary = "不支持" elif scores["support"] > scores["reject"] and scores["support"] > scores["partial"]: summary = "支持" else: summary = "部分支持" return summary

5.2 案由词频统计与关键词提取

文本挖掘最常见的需求是统计某个时间段内高频案由或者争议焦点。先用jieba分词,再过滤停用词,然后统计词频。法律文本的停用词表跟通用文本不一样,“原告”“被告”“本院认为”这些虽然在普通文本里是名词,但在法律文本里出现在每份文书中,没有区分度,必须加入停用词表。

统计完词频后,我习惯用pandas把结果存成DataFrame再导出Excel,方便后续画图:

import pandas as pd from collections import Counter def get_freq(text_list, stopwords): word_counter = Counter() for text in text_list: words = jieba.cut(text) word_counter.update(w for w in words if w not in stopwords and len(w) > 1) return word_counter top_words = get_freq(documents, stopwords).most_common(50) df = pd.DataFrame(top_words, columns=["keyword", "count"]) df.to_excel("高频词统计.xlsx", index=False)

5.3 类案检索的初步实现

类案检索是法律实务里最有价值的需求。传统做法是打标签和关键词匹配,现在也可以用文本相似度做初筛。我把每份判决书的分词结果转成词频向量,再用余弦相似度计算案件间的相似程度,返回与目标案件最相似的Top N份文书。

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity def get_similar_cases(query_text, case_texts, case_ids, top_n=5): vectorizer = TfidfVectorizer(tokenizer=jieba.lcut, max_features=5000) tfidf_matrix = vectorizer.fit_transform([query_text] + case_texts) sim_scores = cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:]).flatten() top_indices = sim_scores.argsort()[-top_n:][::-1] return [(case_ids[i], sim_scores[i]) for i in top_indices]

这个方案效果虽然比不上现在大模型做的语义检索,但胜在轻量、可解释。想要更强效果,可以切换到面向法律领域的预训练模型做向量化,再搭配向量数据库做相似度检索。我实测过,在几千份文书的体量下,TF-IDF版类案检索的准确率也能达到六七成,作为初筛工具已经够用。

5.4 从规则到AI的进阶路线

现在很多人关心AI和大模型跟爬虫、文本挖掘的关系。在我看来,爬虫本质上是在做数据采集,而AI模型特别是大模型是在做数据理解和生成,二者是上下游关系。当数据规模足够大、规则方法效果触顶时,引入AI模型做实体识别、关系抽取、案情摘要,方向是完全正确的。

比如用大模型做判决书的摘要,自动提取案件争议焦点和裁判规则,这个思路可行,但要注意成本和结果的稳定性。小体量项目先用规则和统计方法保障基本效果,需要更强能力时再在关键环节引入模型,这样性价比最高。我的路径是:先用这套基础版本跑通,让数据结构化沉淀下来,等需要深入挖掘时,数据和流程都已经准备好了,加模型是水到渠成的事。

6. 常见问题与排查技巧实录

6.1 请求返回403/418状态码

这个问题的根源基本是请求被目标服务器识别为爬虫,触发拦截。排查思路按顺序来:先查看请求头是否完整,重点看User-Agent是否被识别;再检查请求频率是不是太快,尝试把并发数降低一半试试;然后检查Cookie,确保持久会话。

我遇到过一种情况,同一个IP短时间内请求同一个页面超过一定次数就触发临时封禁。解决办法是把采集数据均匀分散到不同时段,或者降低整体频率拉长耗时。如果只是临时封禁,等十几分钟再跑就好,不要一被拦就上代理,代理质量参差不齐反而引入更多变量。

6.2 BeautifulSoup解析结果为空

解析结果为空,九成是页面结构和预期不一致。最常见的是页面内容经过JS渲染,直接请求HTML拿不到数据,这时要检查响应文本里是否包含目标字段。还有一种情况是有多个页面模板,不同的案件类型对应不同的HTML结构。我的做法是在解析代码里加一个字段完整性校验,解析完判断必填字段是否为空,为空就记录异常URL,最后统一人工检查。

异常记录这块,建议在SQLite里单独建一张日志表,把失败的URL、失败原因、时间都记下来。排查问题时比看终端日志高效得多。

6.3 数据库插入报错或越来越慢

插入报错绝大多数是字段内容和表结构约束冲突。比如案号字段设了UNIQUE,但清洗后的数据里案号格式不统一,“(2023)京01民终1234号”和“(2023)京01民终1234号”看起来一样,实际存储时却是两条数据,等后面查重就失控了。所以在清洗阶段要统一案号格式,把左右括号统一成全角或者半角。

数据库越来越慢,一般有两个原因。一是没有建索引,查询全表扫描;二是数据库文件在机械硬盘上碎片化。前者通过给查询字段建索引解决,后者可以考虑定期执行VACUUM压缩数据库文件。另外批处理时不要在一次事务里塞几千行,分段提交更稳定。

6.4 并发写库导致锁冲突

多线程爬虫跑起来后,如果有多个线程同时往SQLite写数据,很容易碰到database is locked错误。SQLite同一时刻只允许一个写连接,并发写入会引发锁竞争。解决方案有几种:最直接的是所有线程共用一个连接,写操作在同一连接里串行执行;或者用队列把数据集中到一个写入线程里处理,生产者爬数据,消费者写库,天然解耦。

我实际用的是第二种方案,主线程解析完字段后放入queue.Queue,后台写线程从队列取数据批量入库。这个模式下爬虫和写入互不阻塞,也不会锁冲突,瓶颈只在网络请求上。

7. 合规边界与数据使用伦理

这部分认真写一下,因为我见过太多人只知道爬虫怎么写,不知道边界在哪。做数据采集,第一要考虑的是目标数据的公开性。如果数据是公开的,不需要登录权限就能访问,采集行为本身的合规风险相对可控。需要登录才能看的数据,就要格外小心,因为这在法律上可能被认定为未经授权访问。

robots.txt这个东西虽然不是法律规定,但它是网站运营方明确表达的爬虫访问意愿,尊重它是行业底线。爬取频率也要尽量低,不要对目标服务器造成明显压力。我做这个项目时高频请求没有超过每秒两次,大部分时间是一秒一次以内。

第二个层面是数据的用途。爬取的数据如果只是自己分析学习,合规风险很低。但如果是转售、批量对外提供,就涉及数据权益问题了,特别是包含当事人个人信息的部分。文本挖掘做统计研究没问题,但输出任何报告时都要做脱敏处理,防止个人信息泄露。我在建库时就把当事人姓名单独拆表存储,查询输出时选择性地隐藏,方便做合规控制。

还有一个容易被忽略的点:数据源的选择。优先使用官方的公开数据发布平台,远离那种本身就在聚合转载别人数据的站点。后者数据质量没保证,还可能存在版权瑕疵。

8. 几个值得记住的优化建议

做完整个项目,有几个经验特别想分享。第一,爬虫工程里数据结构设计比爬虫本身更重要。核心数据模型没设计好,抓多少数据后面都得返工。案号作为业务主键、当事人单独建表、时间存标准格式,这几个决定在后期做分析时省了无数事。

第二,数据清洗不要追求一步到位,分阶段做更能控制质量。采集阶段做基础清洗,入库前做格式统一,分析前再做针对性的字段清洗。三个阶段各司其职,出了问题时能快速定位到底是哪一步出了问题。

第三,数据库选型要跟数据量匹配。刚开始不用纠结装MySQL还是PostgreSQL,SQLite单文件模式让我在开发、调试、备份各个环节都很快。如果一个项目卡在数据库安装配置上而不是爬虫本身,就说明选型不太对路。

第四,自动化运维部分不要忽略。爬虫跑在服务器上,断点续爬、失败重试、日志记录都必须有。每抓一批数据后,记录当前抓取位置,下次启动时从断点接着跑,这是生产级爬虫的基本功。代码里我加了一个简单的断点记录函数:

def save_checkpoint(last_id): with open("checkpoint.txt", "w") as f: f.write(str(last_id)) def load_checkpoint(): try: with open("checkpoint.txt", "r") as f: return int(f.read().strip()) except FileNotFoundError: return 0

最后再分享一个小技巧:正文全文入库后做分析时,尽量用SQL查出来后在内存里做处理,不要反复查库。几万条文本全量载入内存也就几百MB,一次性加载处理好过数据库查询几百次。文本挖掘的瓶颈很多时候不在算法,而在I/O设计上。这套系统跑起来之后,我的法律案例检索和分析效率比过去手动操作提升了不止一个量级,也让我对爬虫和数据工程的整个链路有了更完整的认知。如果你想练手或者有类似需求,按这个路线走,踩坑会少很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询