news 2026/10/30 5:40:30

Python定向爬虫做商品比价:Scrapy与SQLite完整源码剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python定向爬虫做商品比价:Scrapy与SQLite完整源码剖析

简介:一份基于Python和定向爬虫的商品比价系统毕业设计源码包,适合计算机专业学生作为毕设参考或爬虫入门实践。项目完整实现了从电商页面抓取商品信息、解析价格、存储数据到展示比价结果的流程,涵盖requests请求、页面解析、数据库操作与图形界面等关键技术。资源包共16个文件,其中10个Python脚本分别对应爬虫采集、数据清洗、数据持久化和交互界面等模块,另有3个编译生成的pyc文件、1个SQLite数据库、1个txt文档及1份README说明,整体压缩包仅26KB,结构紧凑、便于阅读。通过学习源码,可以理解定向爬虫的设计思路、模块划分以及应对网站反爬机制的常见策略,同时熟悉小型项目的完整开发流程。该资源已有146人下载学习,是一份高性价比的毕业设计参考资料,也适合课程设计与自学项目使用。

1. 毕设用 Python 定向爬虫做商品比价:这套源码解决什么问题?

做毕设的同学问我最多的一句话是:“用 Python 写爬虫抓电商平台的价格做比价,这个方向到底行不行?”我的回答是行,但别一上来就用通用爬虫的思路去抓全站数据。商品比价系统的核心不是爬虫本身,而是“定向”两个字——你要抓的不是全网,而是几十个确定商品在几个确定平台上的价格、标题和链接。这套基于 Python 和定向爬虫的商品比价系统源码,把 Scrapy 框架、价格清洗和 SQLite 落库串成一条完整链路,非常适合作为毕业设计骨架。它能解决三个问题:数据怎么来、价格怎么比、结果怎么展示。读完你就能照着跑通,再改成自己的版本。

2. 拆解比价系统源码:从 Scrapy 定向爬虫到价格比较核心

拿到压缩包第一步不是急着运行,而是先弄清楚每个文件存在的理由。很多同学把源码解压后直接scrapy crawl,跑出来一堆数据却不知道项目是怎么组织起来的,后面改需求时无从下手。这一章我会把比价系统的结构、选型依据和核心配置逐个讲清楚。

2.1 定向爬虫和通用爬虫的分水岭:抓什么、怎么抓

商品比价场景下,定向爬虫(也叫聚焦爬虫)比通用爬虫合适得多。通用爬虫的做法是从几个种子 URL 出发,不断抽取页面里的链接往外跳,靠广度优先把整个站点的网页抓下来,最后交给后续模块筛选。这种模式适合搜索引擎,不适合比价——你不需要一整个电商平台的数据,只要某几个商品在某个平台的价格字段。

定向爬虫事先约定好起点和边界:起点是平台的搜索 URL,边界是商品列表页里的详情链接。抓下来的数据也是预设好的结构化字段,比如平台名、标题、价格、详情页 URL。源码里用的 Scrapy 正好是为这种“窄而深”的抓取设计的,它自带请求调度、去重过滤和 Item Pipeline,比裸写requests加BeautifulSoup省掉大量基础设施代码。

常见的实现方案有两种,一是 Scrapy,二是 requests 配合 BeautifulSoup 手写循环。requests 方案在小规模抓取时更直观,但并发、重试、去重都要自己处理,代码量会膨胀到一两百行。Scrapy 把这些内建好了,所以毕设里多数人会选后者。源码里的price_spider.py就是定向爬虫的入口,它没有写遍历全网链接的逻辑,而是把目标平台的搜索请求一条条列出来,这就是“定向”二字的落地方式。

2.2 源码目录逐文件看:spiders、items、pipelines 各司其职

解压后你会看到典型的 Scrapy 工程布局,文件大致如下:

文件路径职责
scrapy.cfgScrapy 项目配置入口,部署和命令行都读它
requirements.txtPython 依赖清单
price_compare/settings.py项目配置,本系统的关键参数都在这里
price_compare/items.py定义商品数据结构,相当于字段模型
price_compare/middlewares.py下载中间件,处理请求与响应钩子
price_compare/pipelines.pyItem 清洗与落库管道
price_compare/db.pySQLite 连接与建表逻辑
price_compare/spiders/price_spider.py定向爬虫主体
compare.py比价查询脚本,读数据库输出最低价

items.py里定义的字段决定了后端比价能拿到什么数据。常见做法是定义五个字段:平台、标题、价格、URL、抓取时间。不要额外塞一堆用不上的页面信息,字段越多清洗越麻烦。

import scrapy class ProductItem(scrapy.Item): platform = scrapy.Field() # 平台标识,比如 a、b、c title = scrapy.Field() # 商品标题 price = scrapy.Field() # 原始价格字符串,比如 "¥2999" url = scrapy.Field() # 商品详情页 URL keyword = scrapy.Field() # 搜索关键词,比价时用来分组

字段定义得干净,后面管道清洗和 SQL 查询都会轻松很多。keyword这个字段容易被忽略,但同一个商品可能在不同平台有不同标题,后期比价要靠它把同一关键词下的商品横向比较,务必在爬虫里带上。

2.3 三个必调的 settings 参数:robots、延迟和 UA

把爬虫跑通容易,但很多人在第一步就被平台拦下来,原因集中在settings.py的三个配置上。第一个是ROBOTSTXT_OBEY,毕设场景建议设为True,让 Scrapy 自动读取目标站点根目录下的robots.txt,这是做技术验证时最稳妥的姿态。

# price_compare/settings.py 关键配置节选 BOT_NAME = 'price_compare' ROBOTSTXT_OBEY = True # 遵守 robots 协议,毕设图个合规稳妥 DOWNLOAD_DELAY = 2.0 # 请求间隔 2 秒,宁慢勿快 USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0 Safari/537.36' ITEM_PIPELINES = { 'price_compare.pipelines.PriceCleanPipeline': 300, }

DOWNLOAD_DELAY是最关键的稳定性参数。定向爬虫请求量不大,延迟设置在 1 到 3 秒之间,可以被视作低负载的普通访问,能避免很多不必要的拦截。USER_AGENT设成浏览器 UA 而不是 Scrapy 默认 UA,可以显著降低被拒概率。这两个参数属于“血泪经验”级别的教训——很多 403 错误不是平台针对你,而是默认 UA 太过明显的爬虫特征。

2.4 比价不止比大小:价格清洗与归一化逻辑

爬虫抓回来的价格是字符串,而且不同平台的格式还不一样:有的带“¥”,有的带“元”,有的是区间如“2999-3299”,还有的直接标“起”。如果直接丢进数据库,排序时会出现“999 元”排在“1000 元”前面的错乱。比价系统里必须有一层清洗逻辑,把字符串转成统一的浮点数。

import re def clean_price(raw_price): """把平台返回的价格字符串处理成可比对的浮点数""" if not raw_price: return 0.0 # 去掉货币符号、中文单位和空白 price_str = re.sub(r'[¥¥$元起\s]', '', raw_price) # 区间价格取最低值,防止列表页显示价高于详情页 if '-' in price_str: price_str = price_str.split('-')[0] try: return float(price_str) except ValueError: return 0.0

这段函数通常在pipelines.py里注册,Scrapy 会在每个 Item 进入管道时自动调用。clean_price的处理顺序有讲究:先去除符号,再处理区间,最后转浮点。如果把float()放在前面,遇到“¥”就会抛异常,所以正则清洗必须在前。这个函数是所有比价逻辑的地基,后面 SQL 里ORDER BY price才能得到正确排序。

3. 在本地把源码跑通:环境、命令与最低价输出

代码读懂是一回事,能在自己电脑上把数据跑出来是另一回事。这一章我按实际操作的顺序来,先准备 Python 环境,再运行定向爬虫,最后看比价结果。每一步我都会说明常见失败信号和对应的处理方法,免得你在命令行面前发呆。

3.1 安装 Python 与隔离环境:别把依赖装进系统目录

运行 Scrapy 项目之前,先确认 Python 版本。比价系统源码多数基于 Python 3,建议使用 3.8 以上,过老的 2.7 跑不了现代 Scrapy。安装 Python 时勾选“Add Python to PATH”,这是新版安装器默认不勾选但必须打开的选项,否则后续pip命令会提示找不到。

# 在项目根目录执行 python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate # 安装依赖 pip install -r requirements.txt

用虚拟环境而不是直接pip install到全局,是为了避免不同项目之间的依赖冲突,尤其是 Scrapy 依赖的 Twisted 版本很敏感。requirements.txt里至少包含scrapy、beautifulsoup4、requests这几个包,实际以源码压缩包里的清单为准。激活后命令行前面会出现(venv)标识,确认无误再继续。

3.2 定向爬虫最小实现:一个 Scrapy Spider 的完整代码

源码里的定向爬虫写在一个类里,核心思想是预设请求列表。下面是一个可以直接对照理解的简化版:

import scrapy from price_compare.items import ProductItem class PriceSpider(scrapy.Spider): name = 'price' def __init__(self, keyword='手机', *args, **kwargs): super().__init__(*args, **kwargs) self.keyword = keyword def start_requests(self): # 定向:只请求定义好的平台搜索页 platform_pages = [ {'platform': 'a', 'url': f'https://example-a.com/search?q={self.keyword}'}, {'platform': 'b', 'url': f'https://example-b.com/search?q={self.keyword}'}, ] for page in platform_pages: yield scrapy.Request( url=page['url'], meta={'platform': page['platform']}, callback=self.parse ) def parse(self, response): platform = response.meta['platform'] # 从列表页抽取商品节点,实际选择器按目标平台调整 for node in response.css('div.product'): item = ProductItem() item['platform'] = platform item['title'] = node.css('.title::text').get() item['price'] = node.css('.price::text').get() item['url'] = response.urljoin(node.css('a::attr(href)').get()) item['keyword'] = self.keyword yield item

start_requests和parse是 Scrapy 的两个核心方法。前者负责产生初始请求,后者处理响应并抽取字段。meta参数把平台标识传给回调函数,避免在 parse 里再去猜这个页面来自哪里。yield item会把数据交给管道,由管道执行清洗和落库。这里所有 URL 都用了占位的 example 域名,实际使用时要替换成目标平台真实的搜索 URL 结构。

3.3 启动爬虫并落库:命令、日志与常见失败信号

环境就绪后,运行下面的命令触发定向爬虫:

scrapy crawl price -a keyword=手机

-a keyword=手机会传一个参数给 spider 的__init__,这样同一个爬虫可以换个搜索词复用。日志里出现DEBUG: Crawled (200)表示请求成功;如果出现DEBUG: Crawled (403),大概率是第 2.3 节里说的 UA 或延迟问题,先回 settings 检查。出现DEBUG: Scraped from <200 ...>后面的字段字典,则说明解析成功,Item 正在进入管道。

数据落库时看终端输出有没有异常。管道里连接 SQLite 失败通常是因为没有初始化数据库表,源码里db.py的init_db()一般会在爬虫启动前执行一次。我的习惯是测试时不急着落库,先加一个-O prices.json参数把输出存成 JSON 文件:scrapy crawl price -a keyword=手机 -O prices.json。这样能最快确认解析逻辑对错,数据库结构等确认字段齐全后再接上,能少踩很多无谓的坑。

3.4 查比价结果:SQL 把最低价和差价算出来

数据入库后,比价查询脚本要做的事情很明确:找每个关键词下最低价的平台,以及两个平台之间的差价。下面是一段核心 SQL:

-- 找出某关键词下最低价的 5 条记录 SELECT platform, title, price, crawl_time FROM product_price WHERE keyword = '手机' ORDER BY price ASC LIMIT 5; -- 计算平台 a 与平台 b 之间同关键词的最新差价 SELECT keyword, MAX(CASE WHEN platform = 'a' THEN price END) AS price_a, MAX(CASE WHEN platform = 'b' THEN price END) AS price_b, MAX(CASE WHEN platform = 'a' THEN price END) - MAX(CASE WHEN platform = 'b' THEN price END) AS diff FROM product_price WHERE keyword = '手机' GROUP BY keyword;

第二条 SQL 用了MAX(CASE WHEN ... THEN ... END)的写法,把同一关键词在不同平台的价格拆成两列,直接相减得到差价。这里有个隐含前提:结果集里每个平台的记录只有一条,或者你只关注最新一次抓取。如果在同一关键词下同平台有多条历史价格,需要先按抓取时间取最新,否则差价会被旧数据污染。这个细节是比价系统的“黑匣子”之一,很多人以为 SQL 写错了,其实是数据表里有重复记录没处理。

4. 避坑排查:比价系统开发中的五个真实翻车点

这一章讲的都是实际开发里反复出现的故障。每条按“现象 → 原因 → 解决”的顺序梳理,你可以直接对照自己的日志排查。

4.1 平台返回 403,抓回来全是空列表

现象:Scrapy 日志里大量DEBUG: Crawled (403),parse方法里的代码根本没机会执行,数据库里一条记录都没有。

原因:403 有三个常见诱因。第一是 UA 太明显,默认 UA 在目标平台的黑名单里挂得上号。第二是请求频率过高,DOWNLOAD_DELAY没设置或者说设置成 0,平台判定为脚本访问。第三是目标页面必须登录才能查看,这在部分需要账号体系的平台上很常见。毕设场景下这都不是玄学,是请求特征不够“像人”。

解决:先在settings.py里把USER_AGENT改成浏览器 UA,再把DOWNLOAD_DELAY设成 2 秒以上。如果还是 403,把 URL 复制到浏览器里打开看一眼,手动访问都打不开或需要登录,说明定向爬虫选错了入口页面。我的做法是换成平台公开的商品分类页或搜索页,这类页面通常不需要登录态。还有一个容易被忽略的细节:检查ROBOTSTXT_OBEY是否误设为False,某些站点会主动在 robots 里屏蔽常见爬虫 UA,遵守协议的条件下换一个允许抓取的路径即可。

4.2 价格字符串里的“¥”和“起”,让 float 转型直接崩

现象:数据表里 prices 字段全是 0.0,或者爬虫管道抛ValueError: could not convert string to float。

原因:比价系统里最经典的字段清洗问题。不同平台的价格展示五花八门,“¥2,999”带千分位逗号,“2999起”带中文,“2999.00元”带单位。如果你在 Item 里直接拿原始字符串,存库前不处理,入库后要么程序报错,要么排序结果错乱。

解决:统一走清洗函数,顺序是先去货币符号和中文单位,再处理千分位逗号和区间。我在第 2.4 节给出的clean_price基础上,补一个逗号处理就能覆盖大多数情况:

price_str = re.sub(r'[¥¥$元起\s]', '', raw_price) price_str = price_str.replace(',', '')

这一条建议写进管道而不是写在爬虫里。爬虫只管抽字段,管道管清洗,职责分离后换平台只需要改管道里的规则,不用改解析逻辑。血泪经验:价格清洗规则在开发初期就想全,等数据入库后再补清洗,存量脏数据只能删了重抓。

4.3 同一个商品在两家平台标题不同,比价匹配失败

现象:SQL 查出来差值永远为 NULL,因为按标题匹配时一个写“Apple iPhone 15”,另一个写“苹果手机15 国行”,根本关联不上。

原因:比价系统的数据模型如果按标题关联,必然踩坑。不同平台对商品标题的规范完全不同,会加“国行”“官方标配”“送壳膜”这类后缀,纯粹字符串相等是匹配不上的。

解决:不要用完整标题做关联键,改用商品型号或者搜索关键词。实现方案是在数据模型里增加matched_key字段,由管道从标题里抽取统一标识。比如识别 iPhone 型号,可以提炼出iphone15这样的归一化词,存库时和keyword一起作为比价分组依据。如果平台搜索页本身自带商品 ID,用platform + 商品ID做关联比任何标题处理都稳。

4.4 重启爬虫变成全量重抓,数据表里出现大量重复

现象:跑第二次scrapy crawl price后,数据库里同一个商品出现好几条记录,比价结果里旧价格和新价格混在一起,最低价显示的还是几天前的数据。

原因:数据库表没有唯一约束,管道采用直接INSERT而不是更新策略。Scrapy 的去重默认只在内存里生效,爬虫重启后去重集合清空,URL 被重新抓取一遍很正常,但如果数据库不拦截,重复数据就沉淀下来了。

解决:给product_price表加唯一索引,键设为platform + url。管道里用INSERT OR REPLACE写入,让同一条商品的记录被新数据覆盖:

# pipelines.py 落库片段 self.cursor.execute( 'INSERT OR REPLACE INTO product_price ' '(platform, url, title, price, keyword, crawl_time) ' 'VALUES (?, ?, ?, ?, ?, ?)', (item['platform'], item['url'], item['title'], item['price'], item['keyword'], item['crawl_time']) )

INSERT OR REPLACE是 SQLite 的语法,效果是存在则更新、不存在则插入。加上唯一索引后,重复问题从数据库层面被堵死,比在爬虫里去重更可靠。注意crawl_time每次都要更新,这样你随时知道这条价格是什么时候抓的。

4.5 并发和延迟参数乱调,还没比出价先把自己封了

现象:为了跑快点把CONCURRENT_REQUESTS调到 32,DOWNLOAD_DELAY设成 0,结果爬到第十条记录时所有请求全部 403,后面的任务全军覆没。

原因:定向爬虫目标明确,但目标站点对同一 IP 的请求频率很敏感。比价系统本身数据量不大,小时级更新频率就能满足,完全不需要高并发。调高并发却把延迟清零,等于向平台宣告这是程序。

解决:我把参数固定在一组保守值上——CONCURRENT_REQUESTS = 8,DOWNLOAD_DELAY = 2.0,AUTOTHROTTLE_ENABLED = True。Scrapy 自带的 AutoThrottle 扩展会根据响应时间动态调整延迟,既能跑稳又不用手搓一套频率控制逻辑。记住一个判断标准:如果抓取一轮超过五分钟,通常不是代码慢,而是目标平台响应本身有限制,这时候合理的做法是扩展商品覆盖率而不是增加并发。

5. 给毕设加分:增量爬取、价格曲线与降价提醒

比价系统做到能跑能查,已经具备基本盘。但如果你想在答辩时拿出更有说服力的话术,可以在数据利用层面多三步。第一步是增量爬取,避免每次都全量重抓。实现思路是在 Item 管道里维护product_price表和price_history表,价格表存最新记录,历史表每次插入一条带时间戳的价格样本。爬虫启动命令里通过-a incremental=1之类参数控制,只抓最近更新的商品页,可以显著降低请求总量。

第二步是价格曲线。有了历史表,比价系统就从“当前快照”升级成“趋势分析”。用 pandas 读price_history表,按平台和关键词聚合,再调用 matplotlib 画折线图,能直观展示某商品在一周内的价格波动。这个功能对毕设答辩非常加分,因为比起一张最低价表格,折线图更容易让评审理解系统价值。

第三步是降价提醒。在查询脚本里加一个阈值参数,每次抓取结束后跑一遍SELECT ... WHERE price < ? GROUP BY keyword,低于预期价格的商品直接输出到终端或写入提醒文件。注意阈值要按商品设置,不要全局一个值,否则手机和鼠标共用阈值没有任何参考意义。

最后说一个我的习惯:每次改完爬虫,先用-O prices.json跑一遍并diff前后两次输出,再放开落库管道。确认字段和清洗规则没改坏,再让数据进库。这套流程帮我避开过很多次“数据库脏了只能删表重来”的尴尬。做毕设也是做工程,代码短不等于逻辑简单,定向爬虫的边界、请求节奏和数据结构才是真正要打磨的地方。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/30 5:39:53

鸿蒙Navigation组件:简化多页面跳转的开发实践

1. 为什么说鸿蒙的多页面跳转其实很简单&#xff1f;作为一名从Android转战鸿蒙开发的程序员&#xff0c;我最初看到Navigation组件时内心是抗拒的。毕竟在Android时代&#xff0c;Fragment和Activity之间的跳转逻辑已经够复杂了&#xff0c;再加上各种返回栈的处理&#xff0c…

作者头像 李华
网站建设 2026/10/30 5:38:14

产品经理AI转型指南:核心能力与实战策略

1. 为什么产品经理需要AI转型&#xff1f;三年前我负责的智能客服项目遭遇瓶颈时&#xff0c;第一次真切感受到AI转型的紧迫性。当时竞品通过意图识别将客服响应速度提升40%&#xff0c;而我们还在用传统的规则引擎疲于应对。这个教训让我明白&#xff1a;AI不再是技术团队的专…

作者头像 李华
网站建设 2026/10/30 5:36:21

AI原生企业的技术架构与商业价值解析

1. AI原生企业的本质特征解析AI原生企业&#xff08;AI-Native Enterprise&#xff09;是近年来随着人工智能技术成熟而涌现的新型组织形态。这类企业从创立之初就将人工智能作为核心基础设施和战略能力&#xff0c;而非后期附加的技术组件。其本质特征体现在三个维度&#xff…

作者头像 李华
网站建设 2026/10/30 5:34:14

自动驾驶三自由度非线性NMPC路径跟踪技术解析

1. 自动驾驶控制中的三自由度非线性NMPC路径跟踪解析在自动驾驶领域&#xff0c;路径跟踪控制一直是核心难题之一。不同于传统的线性化处理方法&#xff0c;三自由度非线性模型预测控制&#xff08;NMPC&#xff09;直接处理车辆动力学非线性特性&#xff0c;在双移线等复杂工况…

作者头像 李华
网站建设 2026/10/30 5:30:33

实用数值计算方法C源码拆解:编译对拍与算法避坑

简介&#xff1a;面向数值计算初学者与需要对照教材动手实践的C语言开发者&#xff0c;这份资料包提供甄西丰《实用数值计算方法》的全套配套源码&#xff0c;可配合矩阵运算、数值积分、微分方程求解、插值与拟合、非线性方程组及最优化等章节逐项运行验证。压缩包共702个文件…

作者头像 李华
网站建设 2026/10/30 5:30:01

Java开发者如何利用AI工具提升开发效率

1. Java程序员的AI时代生存指南作为一位深耕Java领域十余年的老码农&#xff0c;我亲眼见证了从JDK1.4到Java21的语言演进&#xff0c;也经历了Spring从XML配置到注解驱动再到响应式编程的技术变革。但最近两年AI的爆发式发展&#xff0c;确实让我们这些传统后端开发者感到前所…

作者头像 李华