Python爬虫实战:Scrapy框架抓取研招网数据并存入SQLite
简介网络爬虫是一种自动化程序用于从互联网上抓取和提取结构化数据。其核心原理是模拟浏览器行为发送HTTP请求解析返回的HTML或JSON响应并通过选择器定位目标信息。这项技术对于数据采集、市场分析和研究具有重要价值广泛应用于舆情监控、价格追踪和教育信息聚合等场景。以Python的Scrapy框架为例它提供了高效的异步请求处理和管道机制能有效应对动态加载页面。本教程聚焦于Scrapy框架和SQLite数据库的工程实践通过分析Ajax接口和构建数据管道完整演示了从研招网抓取招生专业目录并存储分析的全过程为处理类似结构化数据源提供了可复用的解决方案。1. 项目概述为什么选择研招网作为爬虫实战目标研招网全称“中国研究生招生信息网”是每一位考研学子必经的官方信息门户。上面汇聚了全国各大高校、科研院所的招生简章、专业目录、导师信息、历年分数线等海量关键数据。对于考生而言手动查询和对比这些信息无异于大海捞针效率极低。而对于数据分析、教育咨询甚至是一些学术研究来说这些结构化数据又蕴含着巨大的价值。这就是我们这个爬虫项目的起点将公开、分散的网页信息自动化地、结构化地采集下来变成一份可以随时查询、分析和利用的本地数据库。我选择研招网作为实战案例主要基于几个考量。首先它的数据结构相对规整页面遵循一定的模板非常适合用来讲解爬虫的核心逻辑——如何从看似复杂的网页中精准定位并提取出我们需要的信息。其次它涉及了静态页面和动态加载两种常见的数据呈现方式能覆盖爬虫技术中requests和Scrapy框架的基础与进阶应用。最后将数据存入SQLite数据库并进行分析形成了一个从“数据获取”到“数据存储”再到“初步分析”的完整闭环这对于理解数据爬取项目的全流程非常有帮助。无论你是刚接触Python爬虫想找一个有实际意义又不至于太复杂的项目练手还是已经有一定基础想系统学习Scrapy框架和数据处理这个项目都能提供一条清晰的路径。接下来我会带你一步步拆解这个项目从环境搭建、页面分析、代码编写到数据存储和简单分析分享其中每一个关键决策背后的思考以及我踩过的一些坑。2. 核心思路与技术选型解析2.1 目标分析与策略制定动手写代码之前我们必须先想清楚要爬什么、怎么爬。研招网的信息层级很深从院校列表到具体专业的考试科目我们需要划定一个明确的范围。为了控制项目复杂度和演示的完整性我决定将本次爬取的目标定为获取指定年份例如2024年全国硕士研究生招生中所有招生单位的“招生专业目录”核心信息。这通常包括招生单位名称、院系所、专业代码、专业名称、研究方向、拟招生人数、考试科目等。这些信息分布在两个主要页面上院校列表页一个静态页面列出了所有招生单位的名称和链接。专业目录详情页点击某个院校后进入的页面其数据很可能是通过JavaScript动态加载的Ajax请求并非直接写在HTML源码里。基于这个分析我们的爬虫策略需要分两步走第一步使用requests库或Scrapy的Spider抓取院校列表页解析出所有院校的详情页链接。第二步处理动态加载的数据。这里有两种主流方案一是分析页面背后的Ajax接口直接请求JSON数据更高效、更推荐二是使用Selenium或Playwright这类浏览器自动化工具模拟点击获取渲染后的页面。考虑到效率和资源占用本项目优先采用分析Ajax接口的方式。2.2 技术栈选型与理由确定了策略接下来是选择趁手的工具。技术选型没有绝对的好坏只有是否适合当前场景。爬虫框架Scrapy vs. 纯 RequestsScrapy是一个为爬虫而生的异步框架它内置了请求调度、去重、管道Pipeline处理等强大功能。如果你的项目需要爬取大量页面、结构复杂、需要良好的扩展性和可维护性Scrapy是不二之选。它就像一套完整的“自动化流水线”。纯requests库搭配BeautifulSoup或lxml解析更加轻量灵活学习曲线平缓适合快速验证想法或爬取数据量不大的简单任务。本项目选择为了展示一个更工程化、更接近企业级应用的爬虫项目我选择使用Scrapy作为主框架。它能更好地演示项目结构、中间件、管道等概念并且其异步特性在爬取大量院校数据时优势明显。动态页面处理Ajax分析 vs. Playwright分析Ajax接口通过浏览器的开发者工具F12切换到Network网络选项卡过滤XHR/Fetch请求找到真正返回数据的API地址。直接请求这个API获得结构清晰的JSON数据效率极高是首选方案。使用Playwright当页面数据加密复杂、接口难以逆向或者交互逻辑极其繁琐时使用scrapy-playwright这样的中间件来驱动真实浏览器进行渲染和抓取是可靠的备选方案。但代价是资源消耗大、速度慢。本项目选择研招网的专业目录数据通常有清晰的Ajax接口因此优先采用分析Ajax接口的方式。我会在实操部分详细演示如何找到并利用这个接口。数据存储SQLite为什么是SQLite因为它是一个无需单独服务器进程的、自包含的、零配置的SQL数据库引擎。整个数据库就是一个.db或.sqlite文件非常适合本地存储、小型项目或作为中间数据缓存。对于爬虫项目来说它轻便、易用与Python集成度极高内置sqlite3模块。相较于MySQL或PostgreSQL它省去了安装和配置数据库服务的步骤。相较于CSV或JSON文件它提供了强大的SQL查询能力便于后续的数据筛选和分析。可视化工具为了更直观地查看和管理爬取的数据我会推荐使用DB Browser for SQLite (DB4S)这个图形化工具。它免费、开源、跨平台可以方便地浏览表结构、执行SQL查询、导出数据。其他辅助工具解析库Scrapy内置了基于lxml的强大的选择器response.xpath()或response.css()性能优于BeautifulSoup因此本项目直接使用Scrapy Selector。开发环境VS Code或PyCharm均可配合Python虚拟环境venv或conda管理项目依赖是必须的好习惯。注意在开始爬取任何网站前请务必阅读并遵守该网站的robots.txt协议通常位于网站根目录如https://yz.chsi.com.cn/robots.txt。尊重网站的爬虫规则合理设置请求间隔DOWNLOAD_DELAY避免对目标网站服务器造成过大压力这是爬虫工程师的基本素养。3. 环境搭建与Scrapy项目初始化3.1 创建虚拟环境与安装依赖隔离的项目环境是专业开发的起点。打开你的终端命令行跟着我一步步操作。# 1. 创建一个新的项目目录并进入 mkdir yzw_spider cd yzw_spider # 2. 创建Python虚拟环境这里使用venv如果你用conda请用conda create python -m venv venv # 3. 激活虚拟环境 # 在 Windows 上 venv\Scripts\activate # 在 macOS/Linux 上 source venv/bin/activate # 激活后命令行提示符前通常会显示 (venv) # 4. 安装核心依赖Scrapy pip install scrapy -i https://pypi.tuna.tsinghua.edu.cn/simple # 5. 可选但推荐安装用于处理潜在动态内容的库以备不时之需 pip install scrapy-playwright playwright install chromium # 安装Playwright的Chromium浏览器驱动安装完成后你的虚拟环境里就有了一个功能完整的Scrapy框架。使用虚拟环境的好处是每个项目的依赖互不干扰便于管理和部署。3.2 创建Scrapy项目与爬虫Scrapy有一套标准的项目结构使用命令行工具可以快速生成。# 在项目根目录 (yzw_spider) 下创建Scrapy项目项目名也是 yzw_spider scrapy startproject yzw_spider . # 注意最后的点(.)表示在当前目录创建。完成后你会看到多了一个 yzw_spider 文件夹和 scrapy.cfg 文件。 # 进入项目文件夹并创建一个爬虫 cd yzw_spider/spiders scrapy genspider yz_chsi yz.chsi.com.cn解释一下上面的命令scrapy startproject yzw_spider .创建了一个名为yzw_spider的Scrapy项目。项目核心文件都在yzw_spider/目录下。scrapy genspider yz_chsi yz.chsi.com.cn在spiders目录下生成一个名为yz_chsi的爬虫文件并指定了允许爬取的域名allowed_domains为yz.chsi.com.cn。现在你的目录结构应该大致如下yzw_spider/ # 项目根目录 ├── venv/ # 虚拟环境目录 ├── scrapy.cfg # Scrapy部署配置文件 └── yzw_spider/ # 项目Python模块 ├── __init__.py ├── items.py # 定义数据模型Item ├── middlewares.py # 中间件 ├── pipelines.py # 数据管道如存入数据库 ├── settings.py # 项目设置 └── spiders/ # 爬虫文件存放目录 ├── __init__.py └── yz_chsi.py # 刚刚生成的爬虫3.3 配置基础设置 (settings.py)在编写爬虫逻辑前我们需要对项目进行一些基础配置打开yzw_spider/settings.py文件。# yzw_spider/settings.py BOT_NAME yzw_spider SPIDER_MODULES [yzw_spider.spiders] NEWSPIDER_MODULE yzw_spider.spiders # 遵守robots协议初期调试可设为False正式爬取建议设为True ROBOTSTXT_OBEY False # 配置并发请求数根据目标网站承受能力调整不宜过高 CONCURRENT_REQUESTS 16 # 下载延迟秒礼貌爬取避免被封IP DOWNLOAD_DELAY 1 # 启用或禁用中间件 # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html DOWNLOADER_MIDDLEWARES { # yzw_spider.middlewares.YzwSpiderDownloaderMiddleware: 543, } # 启用或禁用扩展 # See https://docs.scrapy.org/en/latest/topics/extensions.html EXTENSIONS { # scrapy.extensions.telnet.TelnetConsole: None, } # 配置Item Pipeline数字代表执行顺序越小优先级越高 ITEM_PIPELINES { yzw_spider.pipelines.YzwSpiderPipeline: 300, } # 日志级别DEBUG会显示所有信息INFO及以上更简洁 LOG_LEVEL INFO # 设置请求头模拟真实浏览器 DEFAULT_REQUEST_HEADERS { Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36, }关键设置解读DOWNLOAD_DELAY 1每两次请求之间至少间隔1秒这是最基本的礼貌爬虫设置。对于重要网站间隔可以更长。USER_AGENT将自己伪装成一个普通的Chrome浏览器这是绕过基础反爬机制的第一步。你可以从网上搜索最新的常用User-Agent字符串。ITEM_PIPELINES我们稍后会在pipelines.py中编写将数据存入SQLite的管道这里需要先启用它。4. 页面分析与爬虫核心逻辑实现4.1 第一步解析院校列表页静态页面首先我们需要找到研招网列出所有招生单位的页面。通过访问研招网并导航我们可以找到类似https://yz.chsi.com.cn/zsml/queryAction.do的入口但更直接的方式是找到专业目录查询的初始页它往往包含一个院校下拉框其选项数据来自一个初始化接口。经过实际探查研招网通常通过一个Ajax请求来加载院校数据。我们打开浏览器开发者工具F12进入研招网专业目录查询页面观察网络请求。实操发现在页面加载时会发起一个POST请求到类似https://yz.chsi.com.cn/zsml/pages/getSs.jsp的地址它返回一个包含所有招生单位学校列表的JSON数据格式大致为[{dm:学校代码, mc:学校名称}, ...]。这比解析HTML页面更简单因此我们爬虫的第一步不再是抓取HTML而是直接请求这个JSON接口获取学校列表。让我们修改spiders/yz_chsi.py文件# yzw_spider/spiders/yz_chsi.py import scrapy import json class YzChsiSpider(scrapy.Spider): name yz_chsi allowed_domains [yz.chsi.com.cn] # 起始URL不再是HTML页面而是直接请求院校JSON数据的接口 def start_requests(self): # 这个URL可能需要根据实际情况调整务必通过浏览器开发者工具核实 url https://yz.chsi.com.cn/zsml/pages/getSs.jsp # 通常这是一个POST请求可能需要特定的表单数据或请求头 headers { Content-Type: application/x-www-form-urlencoded; charsetUTF-8, X-Requested-With: XMLHttpRequest, # 表明是Ajax请求 } # 有些接口可能需要POST数据这里先尝试空数据或简单数据 formdata { # 可能的参数需根据实际请求分析 # mldm: zyxw, // 门类代码如‘zyxw’代表专业学位 } yield scrapy.FormRequest(urlurl, methodPOST, headersheaders, formdataformdata, callbackself.parse_school_list) def parse_school_list(self, response): # 解析返回的JSON数据 try: school_list json.loads(response.text) # 假设返回格式是 [{dm:10001,mc:北京大学}, ...] for school in school_list: school_code school.get(dm) school_name school.get(mc) if school_code and school_name: # 构造每个学校专业目录查询的请求 # 需要分析点击学校后查询专业目录的Ajax接口 # 这里先打印出来验证数据获取成功 self.logger.info(f获取到学校: {school_name} ({school_code})) # 接下来需要构造查询专业目录的请求 # 通常需要学校代码(school_code)、年份(ssdm)等参数 # 我们假设找到了查询专业目录的接口 URL 和所需参数 query_url https://yz.chsi.com.cn/zsml/querySchAction.do form_data { ssdm: school_code, # 学校代码 dwmc: school_name, mldm: , # 门类代码可先留空获取全部 yjxkdm: , # 一级学科代码可先留空 xxfs: , # 学习方式 # ... 其他必要参数需从页面分析 } # 将请求交给下一个回调函数处理并传递学校信息 yield scrapy.FormRequest(urlquery_url, methodPOST, formdataform_data, callbackself.parse_major_list, meta{school_name: school_name, school_code: school_code}) except json.JSONDecodeError as e: self.logger.error(f解析学校列表JSON失败: {e}, 响应内容: {response.text[:200]})关键点解析start_requests方法我们重写了这个方法直接向Ajax接口发起POST请求而不是传统的GET一个HTML页面。FormRequest用于模拟表单提交。请求头(Headers)我们添加了X-Requested-With头这是许多网站判断请求是否为Ajax的标准方式有时不加这个头服务器可能返回不同的内容。parse_school_list方法解析接口返回的JSON提取学校代码和名称。然后最关键的一步是构造查询该校专业目录的请求。这里我用了querySchAction.do作为示例实际URL和参数必须通过分析研招网页面上的实际请求来确定。meta参数在yield新的Request时我们通过meta字典将学校名称和代码传递给下一个回调函数parse_major_list这样在解析专业详情时就知道数据属于哪个学校。4.2 第二步解析专业目录详情页动态Ajax数据接下来我们需要分析在选择了学校后专业列表是如何加载的。同样使用浏览器开发者工具。操作流程在研招网专业目录查询页面选择一所学校点击“查询”。在开发者工具的Network选项卡中筛选XHR/Fetch请求。你会看到一个新的请求例如https://yz.chsi.com.cn/zsml/queryAction.do方法为POST里面包含了学校代码、门类等一大堆参数。这个请求的响应就是专业目录的HTML片段或JSON数据。实际情况分析研招网的这个接口通常返回的是HTML片段。服务器根据你的查询条件直接生成一个包含专业列表的表格HTML然后前端JavaScript将这个HTML插入到页面中。这意味着我们不需要处理复杂的JSON而是需要从返回的HTML中解析数据。让我们完善parse_major_list方法# 接上面的 yz_chsi.py def parse_major_list(self, response): school_name response.meta[school_name] school_code response.meta[school_code] self.logger.info(f正在解析 {school_name} 的专业目录...) # 使用XPath解析返回的HTML表格 # 首先定位到专业表格的行tr。需要分析返回的HTML结构。 # 假设表格结构如下简化 # table ... # tbody # tr td专业代码/tdtd专业名称/tdtd研究方向/tdtd拟招生人数/tdtd考试科目/tdtda href详情链接查看/a/td /tr # ... # /tbody # /table # 获取所有专业行跳过可能的表头行 major_rows response.xpath(//table[contains(class, ch-table)]//tr[position()1]) # 注意上面的XPath是示例必须根据实际HTML结构调整。contains(class, ch-table)是研招网常用的表格类名。 if not major_rows: self.logger.warning(f未在 {school_name} 的响应中找到专业数据表格响应URL: {response.url}) # 可以尝试打印部分HTML来调试 # self.logger.debug(response.text[:1000]) return for row in major_rows: item {} item[school_name] school_name item[school_code] school_code # 使用相对XPath从当前行(row)中提取各个单元格的数据 # 单元格索引可能需要调整 item[major_code] row.xpath(./td[1]/text()).get().strip() # 专业代码 item[major_name] row.xpath(./td[2]/text()).get().strip() # 专业名称 item[research_direction] row.xpath(./td[3]/text()).get().strip() # 研究方向 item[planned_enrollment] row.xpath(./td[4]/text()).get().strip() # 拟招生人数 item[exam_subjects] row.xpath(./td[5]/text()).get().strip() # 考试科目 # 可能还有详情页链接如果需要可以进一步抓取 detail_link row.xpath(./td[6]/a/href).get() if detail_link: item[detail_link] response.urljoin(detail_link) # 构建绝对URL # 将提取的数据项 yield 出去交给 Item Pipeline 处理 yield item # 分页处理如果存在 # 查找“下一页”的链接或分析分页参数 # next_page_url response.xpath(//a[contains(text(),下一页)]/href).get() # if next_page_url: # yield scrapy.Request(urlresponse.urljoin(next_page_url), # callbackself.parse_major_list, # metaresponse.meta)XPath调试技巧 在编写XPath时最可靠的方法是在Scrapy Shell中实时测试。在项目根目录运行scrapy shell https://yz.chsi.com.cn/zsml/queryAction.do然后在Shell中你可以使用view(response)在浏览器中打开响应或者直接使用response.xpath(你的表达式)来测试直到能准确提取到数据。4.3 定义数据模型 (items.py)为了让数据更结构化我们在items.py中定义一个Item类。这类似于定义数据库的表结构。# yzw_spider/items.py import scrapy class YzwSpiderItem(scrapy.Item): # define the fields for your item here like: school_name scrapy.Field() # 招生单位名称 school_code scrapy.Field() # 招生单位代码 major_code scrapy.Field() # 专业代码 major_name scrapy.Field() # 专业名称 research_direction scrapy.Field() # 研究方向 planned_enrollment scrapy.Field() # 拟招生人数 exam_subjects scrapy.Field() # 考试科目 detail_link scrapy.Field() # 详情页链接 crawl_time scrapy.Field() # 爬取时间然后在爬虫文件中导入并使用它# 在 yz_chsi.py 顶部添加 from yzw_spider.items import YzwSpiderItem # 在 parse_major_list 循环中创建 Item 实例 for row in major_rows: item YzwSpiderItem() # 使用定义的Item类 item[school_name] school_name # ... 其他字段赋值 item[crawl_time] datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S) # 记录爬取时间 yield item5. 数据存储SQLite管道与去重策略数据抓取到了接下来需要持久化存储。我们将通过Scrapy的Item Pipeline来实现。5.1 创建SQLite数据库与表首先我们设计一张表来存储信息。打开pipelines.py文件。# yzw_spider/pipelines.py import sqlite3 import datetime class YzwSpiderPipeline: def open_spider(self, spider): # 爬虫启动时连接数据库创建表 self.conn sqlite3.connect(yzw_majors.db) # 数据库文件名为 yzw_majors.db self.cursor self.conn.cursor() # 创建表。这里以 (school_code, major_code) 作为联合主键防止重复插入。 create_table_sql CREATE TABLE IF NOT EXISTS majors ( id INTEGER PRIMARY KEY AUTOINCREMENT, school_name TEXT NOT NULL, school_code TEXT NOT NULL, major_code TEXT NOT NULL, major_name TEXT, research_direction TEXT, planned_enrollment TEXT, exam_subjects TEXT, detail_link TEXT, crawl_time TEXT, UNIQUE(school_code, major_code) ON CONFLICT REPLACE ) self.cursor.execute(create_table_sql) self.conn.commit() spider.logger.info(SQLite数据库连接已建立表已准备就绪。) def close_spider(self, spider): # 爬虫关闭时关闭数据库连接 self.conn.close() spider.logger.info(SQLite数据库连接已关闭。) def process_item(self, item, spider): # 处理每一个yield过来的item将其插入数据库 insert_sql INSERT OR REPLACE INTO majors (school_name, school_code, major_code, major_name, research_direction, planned_enrollment, exam_subjects, detail_link, crawl_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) data ( item.get(school_name), item.get(school_code), item.get(major_code), item.get(major_name), item.get(research_direction), item.get(planned_enrollment), item.get(exam_subjects), item.get(detail_link), item.get(crawl_time, datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S)) ) try: self.cursor.execute(insert_sql, data) self.conn.commit() spider.logger.debug(f成功插入/更新数据: {item.get(school_name)} - {item.get(major_code)}) except sqlite3.Error as e: spider.logger.error(f插入数据时出错: {e}, 数据: {data}) return item关键点解析open_spider和close_spider是Scrapy Pipeline的生命周期方法分别在爬虫开始和结束时自动调用非常适合做数据库的连接和关闭。UNIQUE(school_code, major_code) ON CONFLICT REPLACE这是SQLite的语法为(school_code, major_code)组合创建唯一约束。如果尝试插入重复的学校-专业组合INSERT OR REPLACE会替换掉旧记录。这是一种简单的去重策略。你也可以使用IGNORE来忽略重复项。process_item每个Item都会经过这里。我们使用参数化查询?占位符来插入数据这比拼接字符串更安全能有效防止SQL注入。日志记录使用spider.logger来记录信息便于调试和监控运行状态。5.2 启用Pipeline并运行爬虫确保settings.py中已经启用了这个Pipeline我们在3.3节已经配置过ITEM_PIPELINES { yzw_spider.pipelines.YzwSpiderPipeline: 300, }现在可以在项目根目录运行爬虫了scrapy crawl yz_chsi如果一切顺利你会看到日志滚动爬虫开始请求学校列表然后逐个请求各学校的专业目录并将数据存入yzw_majors.db文件。实操心得首次运行很可能因为XPath路径不对、请求参数不全、网站结构微调而失败。不要灰心爬虫开发就是一个“分析-调试-再分析”的过程。多使用scrapy shell和浏览器的开发者工具仔细观察请求和响应。将LOG_LEVEL设置为DEBUG可以查看更详细的请求和响应信息但日志会非常长。6. 反爬虫策略应对与调试技巧研招网作为重要网站具备一定的反爬机制。直接运行上述爬虫可能会遇到访问限制或返回错误页面。以下是常见的挑战和应对策略。6.1 请求头与Cookie管理User-Agent我们已经设置了常见的浏览器UA但可以准备一个列表轮流使用避免单一UA被识别。Referer有些接口会检查Referer头确保请求来自正确的上一页面。在Request中加上headers{Referer: 来源页面URL}。Cookie对于需要登录或会话保持的页面需要处理Cookie。Scrapy默认会自动处理Cookie。如果遇到问题可以尝试先在浏览器中手动完成一次查询操作然后从开发者工具的请求头中复制完整的Cookie字符串在start_requests的请求头中设置。使用scrapy.Request的cookies参数传递一个字典。更复杂的情况可以使用scrapy.downloadermiddlewares.cookies.CookiesMiddleware进行配置。6.2 频率控制与IP代理DOWNLOAD_DELAY在settings.py中设置这是最基本的频率控制。对于研招网建议设置在2-5秒以上。自动限速扩展启用AUTOTHROTTLE扩展让Scrapy根据服务器响应速度自动调整请求间隔。# settings.py AUTOTHROTTLE_ENABLED True AUTOTHROTTLE_START_DELAY 3.0 # 初始延迟 AUTOTHROTTLE_MAX_DELAY 60.0 # 最大延迟 AUTOTHROTTLE_TARGET_CONCURRENCY 1.0 # 平均并发请求数IP代理如果单个IP被封锁需要使用代理IP池。可以在middlewares.py中编写一个下载器中间件为每个请求随机分配代理。这涉及购买或获取代理IP服务是进阶内容。6.3 处理动态参数与加密研招网的查询接口可能会携带动态生成的参数如token、timestamp等。这些参数可能出现在表单数据里也可能在URL中。应对方法仔细分析网络请求在开发者工具中对比多次请求的参数差异找到规律。有时这些参数是固定的有时需要从之前的HTML页面中提取例如一个隐藏的input标签。使用scrapy-splash或scrapy-playwright如果参数生成逻辑在前端JavaScript中且难以逆向可以动用浏览器渲染引擎来执行JS获取渲染后的页面或直接获取JS计算出的参数。这是最后的手段因为效率最低。6.4 调试与日志分析当爬虫不按预期工作时系统的调试方法至关重要。Scrapy Shell你的最佳朋友。用它来快速测试URL、XPath/CSS选择器、请求响应。scrapy shell https://yz.chsi.com.cn/zsml/queryAction.do # 在shell中 view(response) # 在浏览器打开如果支持 response.xpath(//table) # 测试XPath保存响应到文件在爬虫代码中可以将有问题的响应保存为HTML文件方便仔细查看。with open(debug_page.html, wb) as f: f.write(response.body) self.logger.info(已保存响应到 debug_page.html)检查日志将LOG_LEVEL设为DEBUG查看每一个请求和响应的详细信息包括状态码、URL、请求头等。限制请求范围在开发阶段可以在parse_school_list中只yield前2-3个学校的请求快速验证流程是否正确。7. 数据初步分析与可视化探索数据成功爬取并存入SQLite后我们就可以进行一些简单的分析了。这里使用Python的sqlite3和pandas库结合matplotlib进行可视化。7.1 使用DB Browser for SQLite查看数据首先你可以直接使用图形化工具DB Browser for SQLite (DB4S)打开yzw_majors.db文件。下载安装从其官网下载对应操作系统的版本。打开数据库启动DB4S点击“打开数据库”选择你的.db文件。浏览数据在“浏览数据”选项卡中选择majors表你可以看到所有爬取的数据支持排序、筛选。执行SQL在“执行SQL”选项卡中可以运行任意SQL查询语句非常方便。7.2 使用Python进行数据分析安装必要的库pip install pandas matplotlib然后创建一个新的Python脚本analyze_data.py# analyze_data.py import sqlite3 import pandas as pd import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties # 解决中文显示问题 plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] # 用来正常显示中文标签 plt.rcParams[axes.unicode_minus] False # 用来正常显示负号 # 1. 连接数据库读取数据到DataFrame conn sqlite3.connect(yzw_majors.db) query SELECT * FROM majors df pd.read_sql_query(query, conn) conn.close() print(f共爬取到 {len(df)} 条专业记录。) print(df.head()) # 查看前几行 print(df.info()) # 查看数据概览和类型 # 2. 数据清洗与转换 # 将‘拟招生人数’转换为数值类型有些可能是‘未公布’或范围‘5-10’ # 这里简单处理只提取数字部分对于范围取平均值或第一个值 def parse_enrollment(text): if not text or pd.isna(text): return None # 移除空格和中文 text str(text).strip().replace(人, ).replace(, ().replace(, )) # 尝试匹配数字 import re numbers re.findall(r\d, text) if numbers: # 如果有多个数字如范围取第一个 return int(numbers[0]) else: return None df[planned_enrollment_num] df[planned_enrollment].apply(parse_enrollment) # 3. 简单的统计分析 # 3.1 招生单位学校数量 school_count df[school_name].nunique() print(f\n共涉及 {school_count} 所招生单位。) # 3.2 招生人数最多的专业Top10 (按专业名称聚合) top_majors df.groupby(major_name)[planned_enrollment_num].sum().sort_values(ascendingFalse).head(10) print(\n拟招生人数最多的十大专业) print(top_majors) # 3.3 招生人数最多的学校Top10 top_schools df.groupby(school_name)[planned_enrollment_num].sum().sort_values(ascendingFalse).head(10) print(\n拟招生人数最多的十大招生单位) print(top_schools) # 4. 数据可视化 fig, axes plt.subplots(2, 2, figsize(16, 12)) # 4.1 各专业大类通过专业代码前两位粗略划分招生人数分布 df[major_category] df[major_code].str[:2] # 假设前两位是学科门类代码 category_enrollment df.groupby(major_category)[planned_enrollment_num].sum().sort_values(ascendingFalse) axes[0, 0].bar(category_enrollment.index.astype(str), category_enrollment.values) axes[0, 0].set_title(各学科门类拟招生人数分布) axes[0, 0].set_xlabel(学科门类代码) axes[0, 0].set_ylabel(拟招生人数) axes[0, 0].tick_params(axisx, rotation45) # 4.2 招生人数最多的十大专业条形图 axes[0, 1].barh(top_majors.index, top_majors.values) axes[0, 1].set_title(拟招生人数最多的十大专业) axes[0, 1].set_xlabel(拟招生人数) axes[0, 1].invert_yaxis() # 让最高的在最上面 # 4.3 招生人数最多的十大学校条形图 axes[1, 0].barh(top_schools.index, top_schools.values) axes[1, 0].set_title(拟招生人数最多的十大招生单位) axes[1, 0].set_xlabel(拟招生人数) axes[1, 0].invert_yaxis() # 4.4 各学校专业数量分布箱线图或直方图 major_count_per_school df.groupby(school_name).size() axes[1, 1].hist(major_count_per_school, bins30, edgecolorblack) axes[1, 1].set_title(各招生单位开设专业数量分布) axes[1, 1].set_xlabel(专业数量) axes[1, 1].set_ylabel(招生单位数量) plt.tight_layout() plt.savefig(yzw_analysis.png, dpi300, bbox_inchestight) plt.show() print(\n分析图表已保存为 yzw_analysis.png。)分析脚本解读数据读取使用pandas的read_sql_query函数可以非常方便地将SQL查询结果直接读入DataFrame。数据清洗原始数据中的“拟招生人数”字段可能是文本如“5”、“10-15”、“未公布”。我们写了一个简单的函数parse_enrollment使用正则表达式提取数字为后续的数值分析做准备。这是爬虫数据分析中非常关键且常见的一步。聚合分析使用groupby进行分组统计例如按专业名称、按学校汇总招生人数。可视化使用matplotlib生成多子图直观展示学科分布、热门专业/学校等信息。运行这个脚本你就能得到一份关于研招专业数据的初步分析报告和可视化图表。这仅仅是开始你可以基于这份数据做更深入的分析比如结合院校层次985/211、地域等因素进行交叉分析。8. 项目优化与扩展思路一个基础的爬虫完成后可以从工程化和功能深度上进行优化。8.1 工程化优化配置化管理将数据库路径、请求头、起始URL、关键XPath等配置项提取到settings.py或单独的配置文件中便于维护和部署。中间件开发随机User-Agent中间件自动为每个请求从预定义的UA列表中随机选取一个。代理IP中间件集成代理IP池自动切换IP应对反爬。重试中间件对失败的请求如超时、状态码非200进行智能重试。增量爬取我们的管道使用了INSERT OR REPLACE实现了简单的全量更新式增量爬取。更复杂的增量爬取需要记录数据版本或更新时间只爬取发生变化的数据。分布式爬取使用scrapy-redis组件可以将爬虫改造成分布式利用多台机器提升爬取速度。这对于超大规模数据采集是必要的。8.2 功能扩展爬取更多字段当前只爬取了专业目录的核心字段。你可以进一步爬取每个专业的“详情页”获取导师信息、参考书目、学制学费、联系方式等。历史数据对比定期如每月运行爬虫将数据按时间分区存储。通过对比不同时间点的数据可以分析招生计划的变化趋势。构建简单查询系统使用Flask或Streamlit等轻量级Web框架将SQLite数据库包装成一个简单的本地Web查询页面方便自己或他人按学校、专业等条件检索。数据异常监控在Pipeline中加入简单的校验逻辑比如检查关键字段是否为空、招生人数是否为异常值如负数或极大值并记录日志或发出告警。8.3 部署与定时任务开发完成后你可能希望爬虫能定期自动运行。服务器部署将代码部署到Linux服务器上。使用Cron定时任务最简单的定时执行方式。# 编辑crontab crontab -e # 添加一行例如每天凌晨2点运行一次 0 2 * * * cd /path/to/your/yzw_spider /path/to/your/venv/bin/scrapy crawl yz_chsi /path/to/log/scrapy.log 21使用ScrapydScrapy官方推荐的部署工具可以管理多个爬虫项目提供API接口来启动、停止、监控爬虫。使用更强大的调度器如Apache Airflow可以构建复杂的数据管道处理依赖、失败重试、报警等。在整个项目从零到一的构建过程中最深的体会是爬虫项目三分在代码七分在分析。耐心地使用浏览器开发者工具去观察每一个网络请求理解网站数据加载的逻辑是成功的关键。而写出健壮的、可维护的、对目标网站友好的爬虫代码则体现了工程师的专业素养。这个研招网爬虫项目麻雀虽小五脏俱全希望它能成为你爬虫学习路上一个扎实的里程碑。本文还有配套的精品资源点击获取

相关新闻

最新新闻

日新闻

周新闻

月新闻