ARTICLE DETAIL

资讯详情

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

从零构建裁判文书网分布式爬虫:Scrapy-Redis实战与反爬策略详解

从零构建裁判文书网分布式爬虫:Scrapy-Redis实战与反爬策略详解 简介这是一套面向法律科技研究者、法学数据分析师及Python爬虫开发者的分布式网络爬虫系统专为突破中国裁判文书网反爬机制、实现案件信息全量自动化采集与结构化存储而设计。资源包共17个文件含13个核心Python脚本覆盖生产者调度、详情页解析、反反爬策略、日志监控与命令行交互等模块、1份README.md说明文档、1份附赠资源.docx含环境配置与使用指南、1个配置文件txt及1个运行日志log整体仅42KB轻量但功能完整。已有106人学习下载适合具备基础Python与HTTP协议理解能力的中阶开发者快速上手。读者可直接复用其分布式任务队列架构、动态UA与请求头轮换逻辑、HTML文本清洗规则及裁判文书结构化解析模板显著降低法律大数据采集门槛并为后续司法统计、类案推荐或NLP建模提供高质量原始数据支撑。1. 项目概述从零构建一个能打的裁判文书网爬虫最近在帮一个做法律数据分析的朋友搞点数据目标很明确就是中国裁判文书网。这网站对法律从业者和研究者来说是个宝库但手动一个个下载文书那工作量简直不敢想。所以我们决定搞一个自动化采集系统不仅要能爬还要爬得稳、爬得快、数据还得干净。这活儿听起来简单但真干起来你会发现从登录认证、反爬对抗、到海量数据的分发存储每一步都是坑。今天我就把自己从零搭建这套“中国裁判文书网全量数据爬取系统”的完整思路、踩过的坑和最终跑通的方案详细拆解一遍。如果你也在为类似的大规模、高反爬网站的数据采集头疼那这篇经验应该能给你省下不少折腾的时间。简单说这个系统核心就干三件事自动化采集、智能化对抗反爬、结构化清洗存储。它不是一个简单的脚本而是一个基于Python的分布式爬虫框架能够7x24小时稳定运行自动翻页、解析详情、下载文书原文并把杂乱无章的网页信息变成规规矩矩、能直接导入数据库或用于分析的格式化数据。无论是用于学术研究、行业报告还是商业分析有了这套自动化工具数据获取的效率能提升好几个数量级。2. 核心需求与架构设计解析2.1 我们到底要爬什么需求拆解在动手写代码之前必须把目标网站和我们的需求摸透。裁判文书网这里我们以公开可访问的司法信息公开平台为参考的数据结构很有特点这直接决定了我们爬虫的设计。首先数据是分层级的。通常的访问路径是搜索列表页 - 文书详情页 - 文书全文PDF或HTML。列表页包含了案件的基本元信息比如案号、案件名称、审理法院、裁判日期等通常以分页形式展示。详情页则包含了更丰富的文本信息如当事人信息、案情摘要、裁判理由、法律依据等。最终的文书全文可能是嵌入页面的文本也可能是一个需要额外请求的PDF文档链接。其次网站具有较强的反爬机制。这是本项目最大的挑战之一。常见的反爬手段包括但不限于请求频率限制单位时间内访问次数过多会封IP、请求头校验检查User-Agent,Referer等、Cookie和Session验证特别是涉及搜索和翻页时、动态参数如__VIEWSTATE、__EVENTVALIDATION等ASP.NET表单令牌或是其他动态生成的token、甚至可能有的前端JavaScript混淆计算。我们的爬虫必须能妥善处理这些模拟一个“正常用户”的行为。最后数据需要清洗与结构化。网页上爬下来的原始数据是混杂的包含大量HTML标签、无关的空白字符、不规范的时间格式等。我们需要提取出核心字段并转换为统一、干净的格式例如将“2023年5月1日”转换为“2023-05-01”的日期格式将法院名称中的多余空格和字符去掉。因此系统需要具备以下核心能力分布式任务调度能够将海量的URL采集任务分发给多个爬虫节点并行执行极大提升采集速度。反反爬策略集成内置一套灵活、可扩展的反爬应对机制包括IP代理池、请求头轮换、请求间隔随机化、Cookie管理、动态参数解析等。精准数据解析针对列表页和详情页的不同结构编写健壮的解析规则XPath/CSS选择器/正则表达式并能应对网站小幅度的前端改版。数据清洗管道定义清晰的数据模型对提取的原始数据进行验证、转换、去重和格式化。可靠存储与容错支持将清洗后的数据持久化到数据库如MySQL、PostgreSQL或文件系统如JSON Lines、Parquet并具备断点续爬、错误重试等容错机制。2.2 技术栈选型与架构图基于以上需求我选择了以下技术栈它们经过了大量实战检验组合起来非常趁手爬虫框架Scrapy。这是Python生态中最强大、最成熟的爬虫框架没有之一。它提供了完整的项目结构、异步请求引擎、中间件扩展机制、数据管道等让我们能专注于核心的解析逻辑而不是重复造轮子处理网络IO和调度。它的CrawlSpider和Rule规则非常适合定义列表页的翻页和详情页的跟进。分布式与任务队列Scrapy-Redis。这是将Scrapy改造为分布式爬虫的经典方案。它利用Redis作为共享的请求队列和去重过滤器多个Scrapy爬虫节点可以从同一个Redis队列中领取任务实现了真正的分布式协同工作。Redis的持久化和高性能特性保证了任务调度的可靠性。反爬核心组件IP代理池自建或使用第三方服务。我们自建了一个从多个免费/付费代理源抓取IP并定时用裁判文书网进行校验将可用的高匿代理IP存入Redis供爬虫中间件按需取用。随机User-Agent准备一个包含几十种常见浏览器标识的列表每次请求随机选择。自动化Cookie处理利用Scrapy的CookiesMiddleware和自定义逻辑模拟登录态如果需要并维持会话。数据清洗与存储解析库lxml速度快或parselScrapy内置兼容性好用于XPath/CSS解析re用于正则匹配。数据清洗pandas虽然强大但在爬虫管道中可能过重。我更喜欢用Python原生的字符串方法和datetime库进行轻量级清洗复杂转换可以在数据入库后用SQL或pandas批量处理。存储MySQL用于存储结构化后的案件元数据和索引案号、标题、法院等便于复杂查询同时将文书全文PDF/HTML直接存储到文件服务器或对象存储如本地目录、MinIO并在数据库中记录文件路径。JSON格式作为中间缓存也很常用。部署与监控使用Docker容器化每个爬虫节点用Docker Compose或Kubernetes编排管理。用Scrapyd可以方便地部署和监控Scrapy项目但结合分布式后我们更关注Redis队列的状态和爬虫节点的日志。整个系统的架构可以简化为下图所示的数据流调度器一个单独的脚本或Scrapy的start_urls将初始搜索URL可能带有多维度筛选条件放入Redis请求队列。多个Scrapy爬虫节点Worker从Redis队列中获取待爬取的URL。爬虫节点发起请求前经过下载器中间件这里集成了一系列反爬策略从代理池取IP、更换请求头、设置随机延迟等。拿到响应后爬虫解析器根据URL类型列表页/详情页调用对应的解析函数。列表页解析提取当前页所有文书详情页的链接并生成新的Request对象放回Redis队列同时解析并清洗当前页的案件元数据交给数据管道。详情页解析提取详细的案件信息当事人、案情、裁判结果等并查找文书全文的下载链接。生成下载全文的Request如果需要并将详情数据交给管道。数据管道接收解析后的数据项进行进一步的清洗、验证然后写入MySQL数据库和文件系统。Redis同时负责请求去重防止同一页面被重复爬取。这个架构清晰、解耦每个环节都可以独立扩展和优化。3. 反反爬机制实战如何与网站“友好”相处和裁判文书网这类网站打交道硬闯肯定不行。我们的目标是“润物细无声”让服务器觉得我们是一个个来自不同地方、用着不同浏览器、行为正常的用户。下面是我实战中总结出的一套组合拳。3.1 IP代理池的搭建与维护这是应对IP封锁的基石。一个稳定的代理池至关重要。为什么不直接用免费代理免费代理可用率极低、速度慢、不稳定用于生产环境就是自找麻烦。我们的策略是以付费代理服务为主自建验证为辅。搭建一个简易高效的代理池代理源订阅1-2个可靠的付费代理服务API通常提供按量或按时计费的高匿HTTP/HTTPS代理。同时可以写个小脚本从几个知名的免费代理网站抓取IP作为补充但心里要对它们的质量有数。存储使用Redis的Sorted Set结构存储代理。成员是ip:port分数score是代理的“健康分”。初始分数为10。校验器编写一个异步校验脚本定时如每5分钟从池中取出部分代理用它们去访问裁判文书网的一个稳定、低负载的页面比如网站根目录或一个静态页面。根据响应状态码、响应时间来调整其健康分。成功且快速如2秒分数1不超过上限20。成功但慢分数不变。失败超时、连接错误、返回非200状态码分数-2。当分数低于阈值如3时从池中移除该代理。爬虫中间件集成在Scrapy的下载器中间件中每次请求前从Redis代理池中随机抽取一个健康分较高的代理。请求成功后可以给该代理加分失败后则减分并重试使用新代理。注意校验目标URL一定要选对。不要用搜索接口或详情页去校验这些页面本身可能有访问频率限制容易误杀好的代理。用一个几乎不会变且响应简单的页面最安全。3.2 请求头与请求行为的模拟光换IP不够你的“浏览器指纹”也得经常变。User-Agent轮换准备一个包含Chrome, Firefox, Safari, Edge等各版本的大量UA列表。在中间件中随机选取。注意UA的格式要完整正确。完善其他HeadersAccept,Accept-Language,Accept-Encoding,Connection,Upgrade-Insecure-Requests等头部都要模拟得像一个真正的浏览器。Referer头尤其重要在翻页或进入详情页时要正确设置为上一个页面的URL。Cookie管理如果网站需要登录或利用Cookie维持会话状态需要使用Scrapy的CookieMiddleware。对于复杂的登录流程如有验证码可能需要单独写一个登录脚本获取初始Cookie后将其注入到爬虫的start_requests方法或中间件中。务必确保同一会话的Cookie在系列请求中保持一致否则可能会被踢出。请求间隔随机化在DOWNLOAD_DELAYScrapy设置的基础上使用随机延迟。例如设置平均延迟为2秒但实际延迟在1.5秒到3秒之间随机浮动。这比固定延迟更难以被模式识别。模拟点击流对于某些网站连续、线性的翻页请求可能显得可疑。可以设计爬虫在爬取一定数量页面后随机“跳回”之前的页面或者模拟“浏览-后退-再浏览”的行为。这需要更精细的调度逻辑。3.3 动态参数与JavaScript渲染的应对这是反爬的深水区。如果网站的关键参数如翻页的token、查询的_csrf是通过前端JavaScript计算或加密的单纯的HTML下载就无能为力了。分析网络请求这是第一步也是最关键的一步。使用Chrome/Firefox的开发者工具切换到Network网络选项卡清空记录然后进行翻页或提交搜索操作。仔细观察浏览器实际发送的请求通常是XHR或Fetch请求而不是你看到的页面URL。查看请求的URL、方法GET/POST、Headers以及最重要的Form Data或Payload。这些参数里往往藏着玄机。逆向参数生成逻辑如果参数是加密的你需要找到生成它的JavaScript代码。在Sources源代码选项卡中搜索关键参数名或者使用“搜索所有文件”功能。一旦找到相关函数可以尝试用Python的execjs库来执行这段JS代码或者更彻底地将JS逻辑翻译成Python代码。这个过程需要一些耐心和JavaScript基础。终极方案Selenium/Splash当逆向工程过于复杂或者网站内容完全由JavaScript动态渲染时可以考虑使用浏览器自动化工具。Selenium可以控制一个真实的浏览器如Chrome能完美执行JS并获取渲染后的HTML。但它的代价是速度极慢、资源消耗大不适合大规模全量爬取。Splash是一个带有HTTP API的轻量级JavaScript渲染服务常与Scrapy配合通过scrapy-splash库比Selenium效率高一些但同样比直接请求慢。我的建议是除非万不得已否则优先尝试分析接口避免使用渲染方案。在裁判文书网的案例中经过仔细分析其核心的翻页和查询接口虽然携带了一些参数但大多可以通过解析前一个页面的HTML表单获得如__VIEWSTATE或是由简单的规则生成并未遇到强JS加密因此我们选择了直接请求接口参数解析的方案保证了效率。4. 爬虫核心实现与数据解析4.1 Scrapy项目结构与分布式配置首先创建一个标准的Scrapy项目。scrapy startproject wenshu_crawler cd wenshu_crawler scrapy genspider -t crawl case_spider “example.com” # 使用CrawlSpider模板关键文件结构wenshu_crawler/ ├── scrapy.cfg └── wenshu_crawler/ ├── __init__.py ├── items.py # 定义数据结构 ├── middlewares.py # 自定义中间件代理、UA等 ├── pipelines.py # 数据清洗和存储管道 ├── settings.py # 项目设置 └── spiders/ └── case_spider.py # 核心爬虫逻辑启用Scrapy-Redis进行分布式改造安装pip install scrapy-redis修改settings.py# 启用Redis调度器 SCHEDULER scrapy_redis.scheduler.Scheduler # 启用Redis去重过滤器 DUPEFILTER_CLASS scrapy_redis.dupefilter.RFPDupeFilter # 允许暂停/恢复支持断点续爬 SCHEDULER_PERSIST True # Redis连接信息 REDIS_HOST localhost REDIS_PORT 6379 # REDIS_PASSWORD your_password # 如果有密码 REDIS_PARAMS { db: 0, # 使用第0个数据库 } # 将爬取到的请求序列化后存入Redis SCHEDULER_QUEUE_CLASS scrapy_redis.queue.PriorityQueue # 优先级队列 # 最重要的将爬虫的起始URLs从start_urls移到Redis中 # 注释掉或删除spider中的start_urls改用redis_key # REDIS_START_URLS_KEY wenshu:start_urls # 默认可在spider中覆盖修改spiders/case_spider.py使其继承自RedisCrawlSpiderfrom scrapy_redis.spiders import RedisCrawlSpider from scrapy.spiders import Rule from scrapy.linkextractors import LinkExtractor class CaseSpider(RedisCrawlSpider): name case_distributed redis_key wenshu:start_urls # Redis中存储起始URL的键名 rules ( # 规则1: 匹配列表页的翻页链接并继续跟进无callback意味着只用于发现新请求 Rule(LinkExtractor(restrict_xpaths//div[classpagination]//a[contains(text(), 下一页) or contains(href, page)]), followTrue), # 规则2: 匹配详情页链接并交给parse_detail函数处理不跟进其内部的链接 Rule(LinkExtractor(restrict_xpaths//div[classcase-list]//a[classcase-title]), callbackparse_detail, followFalse), ) def parse_detail(self, response): # 详情页解析逻辑 pass现在你只需要将一个起始URL例如某个特定条件的搜索结果页通过lpush命令推送到Redis的wenshu:start_urls列表中所有在线的爬虫节点就会自动开始工作。4.2 列表页与详情页解析实战解析的核心是编写准确的XPath或CSS选择器。这里以假设的HTML结构为例实际开发中需要根据目标网站的实际结构进行调整。列表页解析 (parse或parse_start_url):列表页的主要任务是提取详情页链接并可能提取一些基础信息用于增量爬取或初步过滤。# 在 CrawlSpider 中通常用 Rule 处理翻页用 parse_start_url 处理起始页的数据提取 def parse_start_url(self, response): # 提取当前列表页所有案件条目 case_items response.xpath(//div[classcase-item]) for item in case_items: # 提取详情页链接 detail_url item.xpath(.//a[classtitle-link]/href).get() if detail_url: # 构建绝对URL detail_url response.urljoin(detail_url) # 可以在这里先提取一些列表页就有的信息通过meta传递给详情页解析函数 case_info { list_title: item.xpath(.//a[classtitle-link]/text()).get().strip(), court: item.xpath(.//span[classcourt]/text()).get(), publish_date: item.xpath(.//span[classdate]/text()).get(), } # 使用 yield scrapy.Request 会将新的请求加入调度队列 # 注意在RedisCrawlSpider中详情页请求通常由Rule生成这里演示另一种手动yield的方式 yield scrapy.Request(detail_url, callbackself.parse_detail, meta{pre_info: case_info})详情页解析 (parse_detail):这里是数据提取的主战场。需要仔细分析页面结构找出每个字段对应的选择器。def parse_detail(self, response): item {} # 1. 接收从列表页传递过来的信息 pre_info response.meta.get(pre_info, {}) item.update(pre_info) # 2. 提取详情页特有信息 # 案号 item[case_number] response.xpath(//div[contains(text(), 案号)]/following-sibling::div[1]/text()).get() # 案件类型 item[case_type] response.xpath(//div[contains(text(), 案件类型)]/following-sibling::div[1]/text()).get() # 当事人信息 (可能是一个列表) parties [] party_elements response.xpath(//table[idparty-table]//tr) for party_el in party_elements: party { role: party_el.xpath(./td[1]/text()).get(), # 角色原告、被告等 name: party_el.xpath(./td[2]/text()).get(), } parties.append(party) item[parties] parties # 存储为JSON字符串或列表取决于管道处理方式 # 3. 提取裁判文书全文链接 # 假设全文链接在一个id为“full-text-link”的a标签里 full_text_url response.xpath(//a[idfull-text-link]/href).get() if full_text_url: full_text_url response.urljoin(full_text_url) # 生成一个新的请求去下载文书全文并将当前item通过meta传递过去 yield scrapy.Request(full_text_url, callbackself.parse_full_text, meta{case_item: item}) else: # 如果没有单独的全文链接可能全文就在当前页面直接提取文本 item[full_text] .join(response.xpath(//div[classfull-text-content]//text()).getall()) yield item def parse_full_text(self, response): # 处理文书全文下载 case_item response.meta[case_item] # 判断响应内容类型 content_type response.headers.get(Content-Type, b).decode(utf-8).lower() if pdf in content_type: # 如果是PDF通常直接保存为二进制文件 file_path fpdfs/{case_item[case_number]}.pdf with open(file_path, wb) as f: f.write(response.body) case_item[full_text_file_path] file_path case_item[full_text_type] pdf else: # 假设是HTML或纯文本 case_item[full_text] response.text[:50000] # 截取一部分避免字段过大 case_item[full_text_type] html yield case_item实操心得XPath的编写非常依赖页面的稳定性。网站前端一旦改版选择器就可能失效。因此不要写过于脆弱、依赖过长路径或绝对位置的XPath。尽量使用具有唯一性的id、class属性或者利用文本内容进行相对定位如//div[contains(text(), 案号)]。同时做好异常处理对于可能缺失的字段使用.get()方法返回None比.extract_first()旧版方法或直接索引更安全。4.3 数据清洗管道设计爬取下来的原始数据是“脏”的需要在pipelines.py中进行清洗和存储。一个典型的管道可能包含多个处理阶段import pymysql from itemadapter import ItemAdapter from datetime import datetime import json class WenshuCrawlerPipeline: def process_item(self, item, spider): # 1. 基础清洗 # 去除字符串首尾空白 for field in [case_number, case_type, court, list_title]: if field in item and item[field]: item[field] item[field].strip() # 2. 日期格式化 date_str item.get(publish_date) if date_str: try: # 尝试解析多种可能的日期格式 for fmt in (%Y年%m月%d日, %Y-%m-%d, %Y/%m/%d): try: dt datetime.strptime(date_str, fmt) item[publish_date] dt.strftime(%Y-%m-%d) # 统一为ISO格式 break except ValueError: continue except Exception as e: spider.logger.warning(f日期解析失败: {date_str}, 错误: {e}) item[publish_date] None # 解析失败置空 # 3. 复杂字段处理如当事人列表 if parties in item and isinstance(item[parties], list): # 可以在这里进行进一步的清洗比如去除空条目规范化角色名称等 cleaned_parties [] for party in item[parties]: if party.get(name) and party[name].strip(): party[name] party[name].strip() cleaned_parties.append(party) item[parties] json.dumps(cleaned_parties, ensure_asciiFalse) # 序列化为JSON字符串存储 # 4. 字段验证与默认值 if not item.get(case_number): spider.logger.error(f缺失关键字段案号: {item}) # 可以选择丢弃该项或标记为无效 # raise DropItem(fMissing case number in {item}) return item class MySQLPipeline: def __init__(self, mysql_config): self.mysql_config mysql_config self.conn None self.cursor None classmethod def from_crawler(cls, crawler): # 从settings中读取数据库配置 return cls(mysql_config{ host: crawler.settings.get(MYSQL_HOST), user: crawler.settings.get(MYSQL_USER), password: crawler.settings.get(MYSQL_PASSWORD), database: crawler.settings.get(MYSQL_DATABASE), charset: utf8mb4, # 重要支持存储Emoji等四字节字符 }) def open_spider(self, spider): # 爬虫启动时连接数据库 self.conn pymysql.connect(**self.mysql_config) self.cursor self.conn.cursor() def close_spider(self, spider): # 爬虫关闭时关闭连接 if self.cursor: self.cursor.close() if self.conn: self.conn.close() def process_item(self, item, spider): # 将清洗后的item插入数据库 adapter ItemAdapter(item) sql INSERT INTO court_cases (case_number, case_type, court, publish_date, parties_json, full_text_file_path, full_text_type, created_at) VALUES (%s, %s, %s, %s, %s, %s, %s, NOW()) ON DUPLICATE KEY UPDATE case_typeVALUES(case_type), courtVALUES(court), publish_dateVALUES(publish_date), parties_jsonVALUES(parties_json), full_text_file_pathVALUES(full_text_file_path), full_text_typeVALUES(full_text_type), updated_atNOW() # 假设表结构中有唯一索引或主键是 case_number values ( adapter.get(case_number), adapter.get(case_type), adapter.get(court), adapter.get(publish_date), adapter.get(parties), # 这里已经是JSON字符串了 adapter.get(full_text_file_path), adapter.get(full_text_type), ) try: self.cursor.execute(sql, values) self.conn.commit() except pymysql.Error as e: spider.logger.error(f数据库插入失败: {e}, SQL: {sql}, Values: {values}) self.conn.rollback() return item在settings.py中启用并排序管道ITEM_PIPELINES { wenshu_crawler.pipelines.WenshuCrawlerPipeline: 300, # 先清洗 wenshu_crawler.pipelines.MySQLPipeline: 800, # 后存储 }5. 部署、监控与常见问题排查5.1 分布式部署与运行当你的爬虫代码在本地测试通过后就可以部署到生产环境了。我们使用Docker来保证环境一致性。编写DockerfileFROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple COPY . . CMD [scrapy, crawl, case_distributed]编写docker-compose.ymlversion: 3 services: redis: image: redis:alpine container_name: wenshu_redis ports: - 6379:6379 volumes: - redis_data:/data command: redis-server --appendonly yes crawler_worker_1: build: . container_name: crawler_1 depends_on: - redis environment: - REDIS_HOSTredis # 可以通过环境变量覆盖settings.py中的配置如并发数、延迟等 command: scrapy crawl case_distributed -s LOG_LEVELINFO crawler_worker_2: build: . container_name: crawler_2 depends_on: - redis environment: - REDIS_HOSTredis command: scrapy crawl case_distributed -s LOG_LEVELINFO # 可以按需扩展更多worker volumes: redis_data:运行与扩展在服务器上执行docker-compose up -d --scale crawler_worker5即可启动5个爬虫worker容器和一个Redis容器。将起始URL推送到Redis队列docker exec -it wenshu_redis redis-cli lpush wenshu:start_urls https://wenshu.court.gov.cn/website/...你的起始搜索URL爬虫们就会自动开始工作。你可以通过docker-compose logs -f crawler_worker_1来查看实时日志。5.2 监控与日志监控是保证长期稳定运行的眼睛。Scrapy日志合理设置LOG_LEVEL如INFO或WARNING将日志输出到文件并配合logrotate进行日志轮转避免磁盘爆满。Redis监控使用redis-cli命令查看队列长度LLEN wenshu:requests、去重集合大小等了解任务积压情况。系统监控监控服务器的CPU、内存、网络和磁盘IO。爬虫特别是使用代理时可能会消耗大量网络连接。数据质量监控定期检查数据库中新数据的数量、字段完整性如案号非空的比例可以写个简单的脚本每天跑一次。5.3 常见问题与排查技巧实录在长达数周的爬取过程中我遇到了各种各样的问题这里把最典型的几个和解决方法列出来希望能帮你避坑。问题1爬虫突然大量返回403或验证码页面。排查首先检查当前使用的IP代理是否大面积失效。登录代理池的管理界面或查看校验日志。其次检查请求头是否被识别特别是User-Agent和Referer。解决立即暂停爬虫避免进一步触发风控。更新代理池增加高质量代理的权重或临时切换代理源。调整爬取策略大幅增加请求延迟例如从2秒平均延迟增加到5秒并加入更随机的休眠时间。可以考虑让爬虫“休息”几个小时再继续。检查Cookie如果网站依赖登录态检查Cookie是否过期可能需要重新运行登录脚本。问题2解析数据大量为空或错位。排查这几乎肯定是网站前端HTML结构发生了变化。打开浏览器手动访问几个页面用开发者工具查看元素对比你代码中的XPath与现在的实际结构是否一致。解决更新解析规则这是最直接的解决办法。让你的XPath/CSS选择器更具鲁棒性。少用索引位置如div[1]多用具有语义化的class或id或者使用包含特定文本的节点进行定位。增加防御性代码在解析每个字段后立即进行简单的有效性检查如非空、长度范围。如果关键字段为空记录详细的错误信息包括当前URL和响应HTML片段便于事后分析。实现版本化解析器对于非常重要的爬虫可以设计一套机制当解析失败率达到阈值时自动触发告警并尝试切换到备用的解析规则如果事先准备了多套规则。问题3数据库写入速度跟不上爬取速度导致内存堆积。排查观察Scrapy的统计信息item_scraped_count和log_count/INFO中关于管道处理的信息检查MySQL的进程列表和慢查询日志。解决批量插入修改MySQLPipeline不要每条数据都执行一次INSERT而是将item缓存在一个列表中当积累到一定数量如100条或每隔几秒执行一次批量插入executemany。这能极大减少数据库的往返开销。异步数据库驱动考虑使用aiomysql或asyncpg对应PostgreSQL等异步驱动配合Scrapy的异步框架如Twisted适配器但复杂度较高。引入消息队列缓冲在爬虫管道和数据库之间加入一个消息队列如RabbitMQ、Kafka爬虫将item快速丢到队列里再由独立的消费者程序从队列中取出并写入数据库。这实现了彻底的解耦和削峰填谷。问题4Redis连接错误或任务重复。排查检查Redis服务是否正常运行网络是否通畅。检查SCHEDULER_PERSIST设置如果为True停止爬虫后再次启动它会从上次中断的地方继续。如果为False则每次都是全新的开始。解决确保Redis配置正确密码、数据库编号无误。理解scrapy-redis的去重机制。它基于请求的指纹fingerprint去重。如果两个请求的URL、方法和请求体完全相同则视为重复。如果你在请求中加入了随机参数如时间戳来绕过缓存这会导致指纹不同从而造成重复爬取。需要根据实际情况权衡要么接受可能的重复并在数据管道中根据业务主键如案号再次去重要么精心设计请求使其指纹对同一资源保持唯一。问题5法律与伦理风险。重要提示爬取公开数据也必须遵守法律法规和网站的robots.txt协议。务必控制爬取速度避免对目标网站服务器造成过大压力。仔细阅读网站的服务条款明确是否允许自动化访问。数据的使用应限于个人学习、研究或符合条款规定的合法目的切勿用于商业侵权或其他非法活动。对于敏感个人信息应进行匿名化处理。这个项目从设计到稳定运行前后迭代了多个版本。最大的体会是稳定性远比速度重要。一个每天能稳定爬取10万条数据的爬虫远胜于一个一天能爬100万但跑半天就被封的爬虫。耐心调试反爬策略精心设计错误处理和监控机制是这类项目成功的关键。最后记得定期备份你的数据和爬虫代码因为网站结构和反爬策略总是在变化今天的完美方案明天可能就需要调整。本文还有配套的精品资源点击获取
返回列表