
简介本资源是一套基于Python Scrapy框架实现的校园集市数据采集系统源码面向Python初学者、爬虫入门学习者及高校信息分析实践者解决校园社区平台结构化数据自动化获取难题适用于课程设计、科研数据预处理与轻量级舆情分析等场景。压缩包共48个文件120KB含24个Python脚本涵盖spiders爬虫逻辑、pipelines数据管道、database数据库交互、utils工具函数等核心模块、11个文本说明文档含环境配置、使用指南与结构说明、2个SQL建表脚本mysql_struct.sql与mysql_create.sql及必要配置与许可文件目录分层清晰模块职责明确便于理解Scrapy工程化开发范式。目前已有51人学习下载提供完整可运行项目骨架、多策略热度计算如pop_value_cal.py、calculate_hot.py、情感评分sentiment_Rating.py与词向量/语义相关性分析correlation_cal_w2c.py、correlation_cal_bert.py等扩展能力是掌握Scrapy实战开发与校园数据挖掘的良好范例。1. 项目缘起为什么需要“赞噢校园集市”爬虫做校园二手交易平台的数据分析最头疼的就是数据从哪里来。直接找平台要API对于绝大多数校园集市类应用来说这几乎不可能。它们要么没有开放接口要么接口权限申请流程繁琐对于学生团队或个人开发者而言门槛太高。于是自己动手写一个爬虫就成了获取一手数据最直接、也最可控的方式。“赞噢校园集市”作为一个典型的校园内信息聚合平台上面沉淀了大量有价值的非结构化数据商品品类分布、价格区间、发布时间规律、用户活跃时段、热门关键词等等。这些数据对于分析校园内的消费趋势、优化商品发布策略、甚至作为课程设计或毕业设计的素材都有着极高的价值。但网页上的数据是给人看的不是给程序直接用的这就需要爬虫来扮演“数据搬运工”的角色。选择Python的Scrapy框架来做这件事几乎是业内共识。它不是一个简单的requestsBeautifulSoup脚本而是一个为大规模、结构化数据抓取而生的“工业级”框架。这意味着从项目一开始你考虑的就不仅仅是“怎么把这一页数据扒下来”而是“如何高效、稳定、可维护地抓取成千上万条商品信息并处理可能遇到的翻页、反爬、数据清洗等一系列问题”。Scrapy提供了一套完整的解决方案包括请求调度、数据解析、管道处理、中间件扩展等组件让你能像搭积木一样构建一个健壮的爬虫系统而不是在几百行的脚本里和一堆try...except与正则表达式搏斗。2. 核心架构设计Scrapy项目是如何组织的一个标准的Scrapy项目其目录结构本身就体现了清晰的分层设计思想。当你执行scrapy startproject zanao_market命令后会生成如下骨架zanao_market/ ├── scrapy.cfg # 项目部署配置文件 └── zanao_market/ # 项目Python模块 ├── __init__.py ├── items.py # 定义要抓取的数据结构 ├── middlewares.py # 下载器中间件和爬虫中间件 ├── pipelines.py # 数据后处理管道清洗、去重、存储 ├── settings.py # 项目全局设置并发、延迟、中间件开关等 └── spiders/ # 爬虫文件存放目录 ├── __init__.py └── zanao_spider.py # 我们即将编写的核心爬虫这个结构的关键在于“各司其职”items.py定义你的“数据容器”。这就像数据库的表结构提前声明好你要抓取哪些字段如商品标题、价格、描述、发布时间、卖家信息等确保后续处理的数据格式统一。spiders/这里是爬虫的“大脑”。每个爬虫文件负责定义如何发起请求起始URL、如何解析响应提取数据、以及如何跟进链接翻页或深入详情页。pipelines.py数据离开爬虫后的“加工流水线”。在这里你可以对抓取到的Item进行清洗比如去除价格字符串中的“元”字转为浮点数、去重基于商品ID、验证检查关键字段是否为空并最终存储到文件如JSON、CSV或数据库如MySQL、MongoDB中。settings.py项目的“控制中心”。几乎所有行为都可以在这里配置例如CONCURRENT_REQUESTS并发请求数控制爬取速度避免对目标服务器造成过大压力。DOWNLOAD_DELAY下载延迟两次请求之间的等待时间是应对反爬的初级但有效手段。USER_AGENT用户代理模拟真实浏览器访问。ITEM_PIPELINES启用哪些数据管道及其执行顺序。middlewares.py提供“插件”能力。你可以在这里编写自定义中间件在请求发出前或响应返回后插入逻辑例如自动更换代理IP、处理Cookie、解析动态加载的内容需结合Selenium或Playwright等。对于“赞噢校园集市”这类页面结构相对规整的网站我们的核心工作将集中在spiders/和items.py并辅以简单的pipelines进行数据清洗和存储。settings.py中的基础反爬配置是必须的而middlewares.py在初期可以保持默认待遇到更复杂的反爬策略时再启用。3. 爬虫实战从页面解析到数据抓取假设“赞噢校园集市”的商品列表页URL模式为https://market.zanao.com/list?page{page}商品详情页为https://market.zanao.com/item/{item_id}。我们的爬虫目标是抓取所有列表页上的商品基本信息并尽可能进入详情页抓取更完整的描述和卖家信息。3.1 定义数据模型items.py首先在items.py中定义我们想要抓取的数据字段。这步很关键它让后续的数据处理有据可依。import scrapy class ZanaoMarketItem(scrapy.Item): # 从列表页即可获取的基础信息 item_id scrapy.Field() # 商品ID通常可作为唯一标识 title scrapy.Field() # 商品标题 price scrapy.Field() # 价格字符串如“50元” main_image_url scrapy.Field() # 主图链接 list_page_url scrapy.Field() # 列表页来源URL publish_time scrapy.Field() # 发布时间列表页显示 category scrapy.Field() # 商品分类 # 需要进入详情页才能获取的扩展信息 detail_url scrapy.Field() # 详情页URL description scrapy.Field() # 商品详细描述 seller_name scrapy.Field() # 卖家昵称 contact_info scrapy.Field() # 联系方式需注意隐私合规 view_count scrapy.Field() # 浏览量 location scrapy.Field() # 交易地点 status scrapy.Field() # 商品状态已售/在售3.2 编写核心爬虫spiders/zanao_spider.py接下来是重头戏。我们将创建一个名为ZanaoSpider的爬虫类。import scrapy from urllib.parse import urljoin from zanao_market.items import ZanaoMarketItem class ZanaoSpider(scrapy.Spider): name zanao # 爬虫的唯一标识用于命令行启动 allowed_domains [market.zanao.com] # 限制爬虫只爬取该域名下的链接 start_urls [https://market.zanao.com/list?page1] # 起始URL # 基础反爬配置也可以在settings.py中统一设置 custom_settings { DOWNLOAD_DELAY: 1, # 每次请求间隔1秒礼貌爬取 CONCURRENT_REQUESTS_PER_DOMAIN: 2, # 对同一域名并发数 USER_AGENT: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., # 模拟Chrome } def parse(self, response): 解析商品列表页。 1. 提取当前页所有商品条目。 2. 对于每个商品提取基础信息并构造详情页请求。 3. 寻找并跟进“下一页”链接。 # 假设每个商品在页面上由一个 classitem 的 div 包裹 item_nodes response.css(div.item) for item_node in item_nodes: # 初始化一个Item对象 market_item ZanaoMarketItem() # 从列表页节点提取基础信息这里以CSS选择器为例XPath同样强大 market_item[item_id] item_node.css(::attr(data-id)).get() market_item[title] item_node.css(h3.title::text).get().strip() price_text item_node.css(span.price::text).get() market_item[price] price_text.strip() if price_text else None market_item[main_image_url] item_node.css(img.thumb::attr(src)).get() market_item[list_page_url] response.url market_item[publish_time] item_node.css(span.time::text).get() market_item[category] item_node.css(span.category::text).get() # 构建详情页URL detail_path item_node.css(a::attr(href)).get() if detail_path: detail_url urljoin(response.url, detail_path) market_item[detail_url] detail_url # 关键步骤生成一个对详情页的新请求并将初步填充的item通过meta传递过去 # callback指定详情页的解析函数 yield scrapy.Request( urldetail_url, callbackself.parse_detail, meta{market_item: market_item} ) else: # 如果没有详情页链接直接yield当前的基础信息item yield market_item # 翻页处理查找“下一页”按钮的链接 next_page_url response.css(a.next-page::attr(href)).get() if next_page_url: # 将相对URL转为绝对URL next_page_absolute_url urljoin(response.url, next_page_url) # 将下一页的请求交回给parse函数自身处理实现递归爬取 yield scrapy.Request(urlnext_page_absolute_url, callbackself.parse) def parse_detail(self, response): 解析商品详情页补充Item的扩展信息。 # 从请求的meta中取出之前传递过来的item对象 market_item response.meta[market_item] # 使用更精确的选择器提取详情页信息 market_item[description] \n.join(response.css(div.detail-content ::text).getall()).strip() market_item[seller_name] response.css(div.seller-info span.name::text).get() # 注意联系方式可能被保护需要特殊处理或遵守robots.txt market_item[contact_info] response.css(div.contact::text).get() market_item[view_count] response.css(span.view-count::text).re_first(r\d) # 用正则提取数字 market_item[location] response.css(span.location::text).get() market_item[status] 已售 if response.css(span.sold-out) else 在售 # 详情页信息补充完毕返回完整的item给引擎引擎会将其送入pipelines yield market_item代码要点解析parse与parse_detail的分工这是Scrapy处理分页详情页抓取的经典模式。parse负责“广度”遍历列表与翻页parse_detail负责“深度”挖掘单个商品详情。通过yield scrapy.Request并指定callback实现了请求的调度。meta参数的使用在从列表页跳转到详情页时我们将已经提取到的部分数据market_item通过Request的meta字典传递下去。这样在详情页解析函数中可以拿到同一个item对象进行信息补充避免了数据割裂。选择器的使用示例中主要使用了CSS选择器response.css它比XPath更易读尤其在处理class、id时。::text用于提取节点文本::attr(attr_name)用于提取属性值。get()获取第一个匹配结果getall()获取所有匹配结果的列表。翻页逻辑通过查找并跟进“下一页”链接并将回调函数指向自身callbackself.parse爬虫可以自动遍历所有列表页直到找不到下一页链接为止。3.3 配置与优化settings.py在settings.py中我们需要开启一些关键配置以支持爬虫运行和数据持久化。# 遵守robots协议对于正规项目建议设为True但某些情况下可能需要关闭以抓取数据 ROBOTSTXT_OBEY False # 并发与延迟设置根据目标网站承受能力和自身需求调整 CONCURRENT_REQUESTS 16 # 全局并发请求数 CONCURRENT_REQUESTS_PER_DOMAIN 4 # 对单个域名的并发数 DOWNLOAD_DELAY 0.5 # 下载延迟(秒)0.5秒/请求 # 用户代理池可选更高级的反爬策略 # USER_AGENT ... # 如果只用一个在这里设置 # 或者安装scrapy-fake-useragent库实现自动轮换 # 启用并调整内置的去重过滤器 DUPEFILTER_CLASS scrapy.dupefilters.RFPDupeFilter # 配置Item Pipelines数字代表优先级(1-1000)越小越先执行 ITEM_PIPELINES { zanao_market.pipelines.ZanaoMarketPipeline: 300, } # 日志级别开发调试时可设为DEBUG生产环境设为INFO或WARNING LOG_LEVEL INFO4. 数据处理与持久化Pipeline的实战应用爬虫抓取到的原始数据往往是“脏”的需要清洗后才能使用。同时我们需要将数据保存下来。这些工作都在pipelines.py中完成。4.1 数据清洗管道import re from itemadapter import ItemAdapter class ZanaoMarketPipeline: def process_item(self, item, spider): 对每一个yield出来的item进行处理。 adapter ItemAdapter(item) # 1. 价格清洗将“50元”、“面议”、“免费送”等转换为标准格式 price_field adapter.get(price) if price_field: # 尝试提取数字 num_match re.search(r(\d(\.\d)?), price_field) if num_match: adapter[price_num] float(num_match.group(1)) # 新增一个清洗后的数字字段 adapter[price_unit] 元 elif 面议 in price_field or 议价 in price_field: adapter[price_num] None adapter[price_unit] 面议 elif 免费 in price_field or 送 in price_field: adapter[price_num] 0.0 adapter[price_unit] 免费 else: adapter[price_num] None adapter[price_unit] 未知 # 2. 描述信息去空白和冗余字符 description adapter.get(description) if description: # 替换多个换行和空格为单个空格 cleaned_desc re.sub(r\s, , description).strip() adapter[description] cleaned_desc # 3. 关键字段空值检查可选可记录日志或丢弃 required_fields [title, item_id] for field in required_fields: if not adapter.get(field): spider.logger.warning(fItem {adapter.get(item_id)} missing required field: {field}) # 可以选择在此处丢弃该item: raise DropItem(fMissing {field}) return item4.2 数据存储管道清洗后的数据需要存储。我们可以写多个管道分别存储到不同地方。这里以存储为JSON行文件JSON Lines和MySQL数据库为例。JSON文件存储管道import json from scrapy.exporters import JsonItemExporter class JsonExportPipeline: def open_spider(self, spider): # 爬虫启动时打开文件 self.file open(zanao_items.jl, wb) # .jl 表示 JSON Lines格式 self.exporter JsonItemExporter(self.file, encodingutf-8, ensure_asciiFalse) self.exporter.start_exporting() def close_spider(self, spider): # 爬虫关闭时结束导出并关闭文件 self.exporter.finish_exporting() self.file.close() def process_item(self, item, spider): self.exporter.export_item(item) return itemMySQL存储管道需要安装pymysql或mysqlclientimport pymysql class MySQLPipeline: def __init__(self, mysql_settings): self.mysql_settings mysql_settings self.conn None self.cursor None classmethod def from_crawler(cls, crawler): # 从settings.py中读取数据库配置 return cls( mysql_settingscrawler.settings.get(MYSQL_SETTINGS) ) def open_spider(self, spider): # 建立数据库连接 self.conn pymysql.connect(**self.mysql_settings) self.cursor self.conn.cursor() # 创建表如果不存在 self._create_table() def close_spider(self, spider): # 关闭连接 if self.cursor: self.cursor.close() if self.conn: self.conn.close() def _create_table(self): create_table_sql CREATE TABLE IF NOT EXISTS zanao_items ( id INT AUTO_INCREMENT PRIMARY KEY, item_id VARCHAR(50) UNIQUE, title TEXT, price_num FLOAT, price_unit VARCHAR(10), description LONGTEXT, seller_name VARCHAR(100), category VARCHAR(50), publish_time DATETIME, location VARCHAR(100), status VARCHAR(20), detail_url TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) CHARSETutf8mb4; self.cursor.execute(create_table_sql) self.conn.commit() def process_item(self, item, spider): # 构造插入或更新语句 sql INSERT INTO zanao_items (item_id, title, price_num, price_unit, description, seller_name, category, publish_time, location, status, detail_url) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE titleVALUES(title), price_numVALUES(price_num), descriptionVALUES(description), statusVALUES(status); values ( item.get(item_id), item.get(title), item.get(price_num), item.get(price_unit), item.get(description), item.get(seller_name), item.get(category), item.get(publish_time), item.get(location), item.get(status), item.get(detail_url) ) try: self.cursor.execute(sql, values) self.conn.commit() except pymysql.Error as e: spider.logger.error(fError inserting item {item.get(item_id)}: {e}) self.conn.rollback() return item在settings.py中启用这两个管道并配置MySQL连接信息ITEM_PIPELINES { zanao_market.pipelines.ZanaoMarketPipeline: 100, # 先清洗 zanao_market.pipelines.JsonExportPipeline: 200, # 再导出JSON zanao_market.pipelines.MySQLPipeline: 300, # 最后存入数据库 } MYSQL_SETTINGS { host: localhost, port: 3306, user: your_username, password: your_password, db: scrapy_data, charset: utf8mb4, }5. 进阶应对反爬策略与提升爬虫健壮性校园网站的反爬措施可能相对简单但养成良好的爬虫习惯至关重要。以下是一些实战中必须考虑的要点5.1 基础反爬措施设置合理的DOWNLOAD_DELAY这是最基本的礼貌。DOWNLOAD_DELAY 1意味着每秒最多一个请求能极大降低被封IP的风险。可以通过RANDOMIZE_DOWNLOAD_DELAY True让延迟在一个区间内随机波动模拟真人操作。使用真实的User-Agent在settings.py中设置一个常见的浏览器UA字符串。更高级的做法是使用中间件轮换多个UA。启用CookiesCOOKIES_ENABLED True。有些网站会通过会话Cookie来跟踪访问行为保持Cookies有助于维持会话状态。遵守robots.txt对于公开数据抓取建议ROBOTSTXT_OBEY True。但需注意robots.txt是君子协议并非所有网站都严格遵循或定义清晰。5.2 处理动态加载内容如果“赞噢校园集市”的商品列表是通过JavaScript动态加载的比如滚动翻页直接请求HTML将获取不到数据。这时有几种方案方案A分析Ajax接口使用浏览器的开发者工具F12 - Network - XHR/Fetch观察滚动时浏览器向后台发送了哪些请求。往往能找到一个返回JSON数据的API接口。直接模拟请求这个接口效率更高数据也更干净。优势高效、数据结构化。挑战接口参数可能加密需要逆向分析。方案B使用Selenium或Playwright中间件让Scrapy驱动一个真实的浏览器来渲染页面再提取渲染后的HTML。优势能应对几乎所有前端渲染的页面。劣势速度极慢资源消耗大不稳定。仅作为最后手段。Scrapy-Playwright集成这是目前更流行的方案。安装scrapy-playwright库并在爬虫中启用Playwright。# 在爬虫的custom_settings中启用 custom_settings { DOWNLOAD_HANDLERS: { http: scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler, https: scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler, }, TWISTED_REACTOR: twisted.internet.asyncioreactor.AsyncioSelectorReactor, PLAYWRIGHT_BROWSER_TYPE: chromium, }然后在发起请求时通过meta字典指定使用Playwrightyield scrapy.Request(urlurl, callbackself.parse, meta{playwright: True})5.3 错误处理与重试网络请求充满不确定性。Scrapy内置了强大的重试和错误处理机制。自动重试在settings.py中设置RETRY_ENABLED TrueRETRY_TIMES 2重试次数RETRY_HTTP_CODES [500, 502, 503, 504, 522, 524, 408, 429]对哪些HTTP状态码进行重试。429状态码Too Many Requests尤其重要遇到时应增加延迟。自定义错误处理在爬虫中重写start_requests方法或使用中间件可以更精细地控制请求失败后的逻辑比如更换代理IP。5.4 分布式爬取与增量抓取当数据量巨大时单机爬取可能力不从心。分布式爬虫结合Scrapy-Redis组件可以轻松将爬虫改造成分布式。所有爬虫实例共享一个Redis队列中的请求并利用Redis的集合进行去重实现多机协同工作。增量抓取每次全量抓取浪费资源。核心思路是识别数据的“更新标志”。对于商品可以用item_id和publish_time组合判断。在管道中只存储publish_time晚于上次抓取时间的商品或者设计一个版本号字段。更复杂的增量抓取需要设计专门的数据同步逻辑。6. 项目运行与调试6.1 启动爬虫在项目根目录scrapy.cfg所在目录下执行scrapy crawl zanao -o output.jsoncrawl zanao启动名为zanao的爬虫即ZanaoSpider中定义的name。-o output.json将抓取到的item输出到output.json文件Scrapy会自动根据后缀选择导出器。6.2 常用调试命令Shell交互调试这是最强大的调试工具。在命令行输入scrapy shell https://market.zanao.com/list?page1会进入一个交互式环境变量response包含了请求的响应。你可以直接在这里测试选择器response.css(div.item).get() response.xpath(//h3[classtitle]/text()).get()查看爬取状态运行爬虫时控制台会实时输出日志。关注scraped items count已抓取条目数和日志中的WARNING/ERROR信息。限制抓取数量调试时可以使用-s CLOSESPIDER_ITEMCOUNT10参数让爬虫在抓取10个item后自动停止。6.3 日志与监控日志级别在settings.py中设置LOG_LEVEL DEBUG可以查看最详细的日志包括每个请求和响应。生产环境建议设为INFO或WARNING。日志文件通过-s LOG_FILEscrapy.log将日志输出到文件。数据统计Scrapy内置了数据收集器可以通过扩展Extensions将统计信息如请求数、成功数、item数发送到监控系统。7. 伦理、法律与最佳实践在享受爬虫带来的数据便利时必须时刻绷紧合规这根弦。尊重robots.txt这是网站所有者表达爬虫抓取意愿的首要文件。务必先检查并遵守。对于明确禁止爬取的目录不要强行抓取。控制访问频率DOWNLOAD_DELAY不仅是技术需要更是道德要求。不要对目标网站发起DDOS攻击般的请求。识别并遵守网站条款许多网站的“使用条款”或“服务协议”中会明确禁止未经授权的数据抓取。在法律层面这比robots.txt更具约束力。数据用途限制抓取到的数据应用于个人学习、研究或公益目的。绝对禁止用于商业牟利、骚扰用户、侵犯隐私或任何非法活动。对于“赞噢校园集市”这类包含用户联系方式的平台对联系信息的处理要格外谨慎。版权与隐私商品描述、图片可能涉及用户原创内容或版权。公开传播或商用需获得授权。用户昵称、联系方式属于个人隐私信息收集和使用必须符合相关法律法规。设置清晰的User-Agent在你的User-Agent中留下联系方式例如YourProjectBot/1.0 (https://yourproject.com/bot-info)方便网站管理员在必要时联系你。这是一种负责任的体现。写一个能跑的爬虫不难但写一个稳定、高效、可维护、且负责任的爬虫才是区分新手和资深开发者的关键。基于Scrapy框架你拥有了实现后者的坚实基础。从明确需求、设计数据结构开始到编写解析逻辑、处理反爬、数据清洗入库每一步都需要仔细考量。希望这份针对“赞噢校园集市”的爬虫设计与实现详解能为你提供一个从零到一的完整蓝本并在你未来面对更复杂的数据抓取任务时带来有价值的参考。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。