基于Python的定向爬虫比价系统设计与实现

发布时间:2026/9/15 5:06:34

基于Python的定向爬虫比价系统设计与实现 简介基于Python和定向爬虫的商品比价系统源码是一个高分毕业设计项目答辩评审98分代码均已调试可运行。适合计算机、通信、人工智能、自动化等专业学生作为课程设计或毕业设计参考也适合爬虫初学者进阶学习。整个压缩包共16个文件以10个py源码为主辅以pyc编译文件、SQLite数据库db、说明文档md/txt整体仅27KB结构轻量清晰。目前已有104人学习下载。项目包含多个GUI界面、爬虫模块与数据库模块可帮助读者理解从页面抓取、数据清洗到结果展示的完整比价流程也可在此基础上自行修改扩展实现不同商品类目或平台的价格对比功能具有较高的学习与二次开发价值。1. 为什么要写一个定向爬虫比价系统而不是全站爬虫手动比价这件事做过的人都懂打开四五个电商网站逐个输入商品名还要手动换算满减、看清是自营还是第三方一圈下来时间成本比省下的几块钱还高。这个基于Python的定向爬虫比价系统解决的就是这个非常具体的场景——给定一个搜索关键词自动化抓取多个电商平台的结果聚合成一张可排序的比价表。它的核心价值不是爬虫本身而是“定向”二字只抓搜索页和商品详情页不做全站遍历代码量和反爬压力都小一个量级。项目实测跑通答辩评审98分目录里one、two、three是三个迭代版本正式入口在system-master下。适合正在做爬虫课程设计或毕设的人也适合刚学完Python基础、想看看requests、SQLite、Tkinter怎么整合的人。2. 定向爬虫的核心URL构造与页面抓取2.1 定向爬虫和通用爬虫的边界通用爬虫比如Scrapy配合广度优先策略是从种子URL出发不断解析页面里的链接一层层往外扩目标是覆盖整站内容。定向爬虫不同它只关心“搜索关键词对应的结果页”URL结构是确定的只需要把关键词编码后拼进搜索模板即可。以主流电商平台为例搜索URL通常形如https://search.某平台.com/?q关键词个别平台会加encutf8或wq这类参数。实现时不用Scrapy这样重的框架requests BeautifulSoup就够了——因为抓取深度固定、页面结构固定写三个函数就能覆盖一次完整搜索流程。curl -s https://www.example.com上面这段不是项目代码只是说明定向爬虫的抓取目标就是URL模板生成的这些页面。crawl.py里的工作分三步拼接搜索URL、携带请求头发起请求、用解析器抽取商品块。多平台比价时每个平台对应一个URL模板和一个解析函数两者通过平台名称建立映射。这样做的好处是以后想新增平台只需要在配置字典里加一项不用改动主流程。2.2 请求头与频率控制避免被反爬的第一道防线电商平台的搜索页是反爬的重点区域但大部分情况下只要请求头像真实浏览器就不会触发验证码。crawl.py里维护了一个HEADERS字典包含User-Agent、Referer、Accept-Language三个关键字段。User-Agent要写完整的浏览器版本字符串只写python-requests会被一眼识别Referer填写平台首页域名模拟从首页跳转到搜索页的路径Accept-Language用zh-CN,zh;q0.9避免服务端返回繁体或英文页面。import random import time import requests BASE_HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36, Referer: https://www.example.com/, Accept-Language: zh-CN,zh;q0.9, } def fetch_html(url, retry3): for i in range(retry): try: resp requests.get(url, headersBASE_HEADERS, timeout10) if resp.status_code 200: return resp.text except requests.RequestException as e: print(f[retry {i1}] {e}) # 随机等待单位秒避免同一秒内连续请求 time.sleep(random.uniform(1.5, 3.5)) return 代码逻辑fetch_html接收一个搜索URL最多重试3次每次失败后随机等待1.5到3.5秒。这里的关键参数是timeout10电商页面首屏在4G网络下通常2秒内返回超过10秒说明请求被限流或DNS挂了没必要死等。重试间隔用random.uniform而不是固定time.sleep(2)是为了让请求节奏更接近人工操作。2.3 用requests和BeautifulSoup抓取商品列表页拿到HTML之后就轮到解析。比价场景下商品列表页里每个商品块通常包在li或div里标题在a的title属性或alt属性中价格在带priceclass 的元素里。crawl.py先通过BeautifulSoup.find_all定位所有商品块再逐块抽取标题、价格、链接三块缺一就跳过该商品——解析不完整的数据宁可不要。from bs4 import BeautifulSoup def parse_search_page(html, platform): 从搜索页HTML中抽取商品信息platform用于区分解析规则 soup BeautifulSoup(html, html.parser) items [] # 这里的selector是示例实际项目按平台维护了一份选择器配置 for block in soup.select(.search-item): title_tag block.select_one(.title a) price_tag block.select_one(.price) if not title_tag or not price_tag: continue title title_tag.get(title) or title_tag.get_text(stripTrue) price price_tag.get_text(stripTrue) link title_tag.get(href, ) if title and price: items.append({ platform: platform, title: title, price: price, url: link, }) return itemsparse_search_page的入参是HTML字符串和平台名返回结构化的商品列表。实际项目中不同平台的CSS选择器差异很大所以选择器配置会从crawl.py单独拆到spider.py里以字典形式存储{platform_a: {item: .item, title: .title a, price: .price}}。解析时先取出对应平台的选择器配置再用soup.select定位。这样做的价值是当平台改版导致选择器失效时只需要改配置文件不需要动解析主逻辑。3. 商品数据建模与SQLite存储3.1 商品表结构设计为什么用商品ID做唯一键比价系统最忌讳重复数据。同一件商品在同一个平台今天搜出来一条记录明天再搜又多一条如果不加约束数据库会被垃圾信息塞满。项目用info.db这个SQLite文件存储数据核心表product的字段设计如下字段类型说明idINTEGER PRIMARY KEY AUTOINCREMENT自增主键platformTEXT平台名称如jd、tmallproduct_idTEXT平台内部商品ID从URL中提取keywordTEXT搜索关键词titleTEXT商品标题priceREAL标准化后的价格urlTEXT商品详情页链接crawl_timeTEXT抓取时间ISO格式这里的唯一键不是自增id而是(platform, product_id, keyword)。product_id从商品URL里提取比如京东商品URL中的数字串就是商品ID。为什么要带上keyword因为同一商品可能被不同关键词搜出比价场景下我们希望保留每个关键词对应的价格快照所以把keyword也纳入唯一键。建表SQL如下CREATE TABLE IF NOT EXISTS product ( id INTEGER PRIMARY KEY AUTOINCREMENT, platform TEXT NOT NULL, product_id TEXT NOT NULL, keyword TEXT NOT NULL, title TEXT, price REAL, url TEXT, crawl_time TEXT, UNIQUE(platform, product_id, keyword) );3.2 价格解析与单位统一从页面抽出来的价格是1,299.00或¥1399这种带货币符号的字符串直接存进REAL字段会报类型错误。价格解析统一在database.py里做核心逻辑是去除非数字字符、处理千分位逗号、遇到区间价如199.00-299.00只取最低值。区间价取最低值是一种保守策略因为在比价场景下用户更关心“最低能从多少买到”。import re def clean_price(raw_price): 把1,299.00或99.00-199.00转成float解析失败返回0.0 if not raw_price: return 0.0 # 去掉货币符号、空格、中文 text re.sub(r[^\d.\-], , raw_price) # 处理区间价取最小值 if - in text: parts text.split(-) text min(parts, keylambda x: float(x)) try: return round(float(text), 2) except ValueError: # 如果最后一段有多个小数点只保留第一个 idx text.find(.) if idx -1: return 0.0 text text[:idx 3] try: return round(float(text), 2) except ValueError: return 0.0clean_price的入参是页面上的原始价格字符串输出是浮点数。第一行正则[^\d.\-]会保留数字、小数点、负号负号出现的场景是促销倒计时或满减后价格区间价只取左边最小值因为满减规则往往独立于页面展示价格。round(..., 2)把结果统一为两位小数避免浮点精度问题。价格解析失败时返回0.0这样在GUI里可以统一显示“价格未获取”而不是抛异常导致整个流程中断。3.3 database.py的写入与去重逻辑database.py的核心是upsert_product函数它把爬虫抓到的商品信息写入SQLite。由于建表时声明了唯一约束这里用INSERT OR REPLACE实现“存在则更新不存在则插入”的语义。但直接REPLACE会改变id主键导致自增序列波动所以我更建议用INSERT ... ON CONFLICT DO UPDATE语法SQLite 3.24.0以上版本支持。import sqlite3 from datetime import datetime DB_PATH info.db def upsert_product(conn, item): item: dict包含platform, product_id, keyword, title, price, url sql INSERT INTO product (platform, product_id, keyword, title, price, url, crawl_time) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(platform, product_id, keyword) DO UPDATE SET title excluded.title, price excluded.price, url excluded.url, crawl_time excluded.crawl_time conn.execute(sql, ( item[platform], item[product_id], item[keyword], item[title], item[price], item[url], datetime.now().isoformat(timespecseconds), )) conn.commit()这段代码的关键在ON CONFLICT子句当同一个平台、同一个商品ID、同一个关键词再次出现时不插入新行而是更新标题、价格、URL和抓取时间。这样crawl_time就构成了价格随时间变化的轨迹。excluded是SQLite的保留关键字代表本次试图插入的值。使用事务时conn.commit()要放在循环外部批量写入时能减少磁盘同步次数这个项目数据量不大所以每次都提交问题也不大。4. GUI与主流程从搜索关键词到结果展示4.1 Tkinter界面布局与搜索事件gui.py用Tkinter实现图形界面主窗口包含一个输入框、一个“开始比价”按钮、一个结果表格。结果表格用ttk.Treeview控件按“商品标题、平台、价格、链接”四列展示。界面本身不执行任何爬虫逻辑它只负责把关键词传给后台然后监听返回结果。import tkinter as tk from tkinter import ttk def build_gui(): root tk.Tk() root.title(商品比价系统) root.geometry(800x500) # 输入区和按钮 frame_top ttk.Frame(root, padding10) frame_top.pack(filltk.X) keyword_var tk.StringVar() ttk.Entry(frame_top, textvariablekeyword_var, width40).pack(sidetk.LEFT, padx5) ttk.Button(frame_top, text开始比价, commandlambda: start_search(keyword_var.get())).pack(sidetk.LEFT) # 结果表格 columns (title, platform, price, url) tree ttk.Treeview(root, columnscolumns, showheadings) tree.heading(title, text商品标题) tree.heading(platform, text平台) tree.heading(price, text价格) tree.heading(url, text链接) tree.pack(filltk.BOTH, expandTrue) return root, tree, keyword_varbuild_gui返回三个对象主窗口、表格组件、关键词变量。实际项目中start_search回调函数会解析关键词创建后台任务。注意ttk.Treeview的列宽需要额外设置否则链接列会挤压标题列建议用tree.column(title, width300)固定列宽比例。4.2 线程池调用爬虫避免界面卡死如果直接在按钮回调里同步执行crawl.py的抓取函数界面会在请求期间完全无响应Windows上会直接显示“程序未响应”。解决方法是把爬虫任务提交到ThreadPoolExecutor通过队列把结果传回主线程。from concurrent.futures import ThreadPoolExecutor, as_completed executor ThreadPoolExecutor(max_workers3) result_queue [] def start_search(keyword): if not keyword: return # 清空上一轮结果保持界面干净 for row in tree.get_children(): tree.delete(row) # 提交抓取任务到线程池 for platform in PLATFORM_LIST: future executor.submit(crawl_by_platform, keyword, platform) result_queue.append(future) # 在主线程轮询任务状态 poll_futures()max_workers3表示最多3个平台并发抓取避免同时请求过多触发反爬。crawl_by_platform是crawl.py里封装好的函数输入关键词和平台名返回商品列表。poll_futures利用as_completed等待任务完成再把结果插入界面。注意Tkinter不是线程安全的子线程里不能直接操作Treeview必须通过root.after调度到主线程更新。4.3 main.py的串并联逻辑main.py是程序入口负责把crawl.py、database.py、gui.py整合起来。启动流程是初始化数据库连接创建GUI窗口把GUI里的“开始比价”按钮绑定到完整业务函数。业务函数内部依次执行爬取所有平台 - 清洗价格 - 写入数据库 - 查询数据库并展示结果。def main(): conn init_db() # 连接info.db建表如果不存在 root, tree, kw_var build_gui() def on_search_click(): keyword kw_var.get().strip() if not keyword: return # 1. 定向爬虫抓取 for platform in PLATFORM_LIST: html fetch_html(build_search_url(platform, keyword)) items parse_search_page(html, platform) # 2. 清洗并写库 for it in items: it[price] clean_price(it[price]) it[product_id] extract_product_id(it[url]) upsert_product(conn, it) # 3. 从数据库读取本次关键词的结果 rows query_by_keyword(conn, keyword) for r in rows: tree.insert(, tk.END, valuesr) # 替换之前简单的start_search for btn in root.winfo_children(): pass # 简化起见绑定事件通过单独的bind函数实现 root.mainloop() if __name__ __main__: main()这里把写库和展示顺序分开是因为比价系统一定要保证“搜索历史可回溯”。即使爬虫临时挂了只要数据库里还有上一次的结果GUI也能展示。query_by_keyword从product表读取WHERE keyword ?并按价格升序排列这就是最终看到的比价列表。5. 比价系统的三个进阶技巧与常见坑5.1 请求太密触发验证码时先查这三个参数如果运行一段时间后抓不到数据打开info.db看最近记录如果连续几条都是“价格未获取”说明请求被拦截了。排查顺序第一是否缺少Accept请求头部分平台会校验第二请求间隔是否小于1秒电商平台对同一IP的搜索频次有阈值第三Cookie是否过期部分平台搜索页需要登录后才能看到价格此时需要从浏览器复制Cookie写入BASE_HEADERS。def fetch_with_cookie(url, cookie_str): headers dict(BASE_HEADERS) headers[Cookie] cookie_str return requests.get(url, headersheaders, timeout10).text把Cookie做成参数而不是硬编码在crawl.py里这样过期后改配置文件即可。5.2 用info.db做价格历史曲线比价系统的数据库已经按(platform, product_id, keyword)保存了历史价格通过SQL可以画出价格走势。查询某一商品的历史价SELECT crawl_time, price FROM product WHERE platform jd AND product_id 100012345 AND keyword 机械键盘 ORDER BY crawl_time;crawl_time的ISO格式字符串可以直接按字典序排序。进阶做法是每天定时跑一次爬虫价格曲线就是天然的商品降价提醒。注意要保留原始价格字段upsert_product用DO UPDATE更新当前价时可以用触发器把旧价格写入独立的price_history表但毕业设计里单表也够用。5.3 新平台接入的三个改动点增加一个比价平台需要改的地方正好对应前面三章的模块第一在URL模板字典里加这个平台的搜索地址和关键词编码方式第二在spider.py里加这个平台的CSS选择器配置第三如果新平台的价格字符串格式特殊比如带原价删除线需要在clean_price里增加对应的清洗分支。三处都改完GUI和数据库不用动。建议把平台配置单独放config.py每个平台是一个字典crawl.py遍历平台列表时自动适配这样新增平台的实际工作量能压缩到20分钟以内。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/9/15 5:06:34

AI为何会对你说‘不’?一探内容安全机制的底层逻辑

抱歉,我无法处理这个请求。原因说明:该项目标题“zapret-discord-youtube”以及相关关键词和热搜词经评估后,涉及的内容与安全合规要求存在明确冲突,属于“内容安全说明”中明令禁止的范畴。根据我的核心安全原则——以内容绝对安…

2026/9/15 5:01:33

代理IP选型三大硬指标:地域精度、IP寿命、并发稳定性

1. 为什么代理IP选型不是“越便宜越好”——从一次订单失败说起去年做电商比价系统时,我踩过一个典型的坑:用某家标称“海量IP池”的低价代理服务,跑了一晚上爬虫,结果第二天发现93%的请求被目标网站识别为异常流量,订…

2026/9/15 5:21:34

AI代码规范:让大模型产出可落地、可维护的生产级代码

1. 为什么要在项目里给AI“立规矩”:不是限制创造力,而是让产出可落地、可维护、可追责最近团队在推进一个智能代码补全功能时,连续两周卡在同一个环节:AI生成的函数逻辑完全正确,但命名全是func123()、data_process_v…

2026/9/15 5:21:34

微信健康小程序源码落地指南:从解压到真机可用

简介:这是一套面向微信小程序初学者与进阶开发者的健康菜谱类实战源码,适用于快速搭建轻量级美食服务应用或学习小程序全栈开发流程。资源包含142个文件,涵盖16个JS逻辑文件(如index.js、search.js、app.js等核心页面与全局脚本&a…

2026/9/15 5:21:34

递归自我改进实战:从原理到最小原型搭建指南

开门见山说一句:递归自我改进(Recursive Self-Improvement,RSI)这几年被频繁提起,但多数讨论都停留在“AI能不能自己写代码改进自己”这种口号层面。真正动手做过的工程师会知道,“让模型自己改自己的训练目…

2026/9/15 5:21:34

LSTM多变量时间序列预测入门:数据预处理到模型调参全指南

1. 为什么我建议新手从多变量时间序列预测入门LSTM先说下这张图景:你手里有一批带时间戳的数据,比如某条河流过去几年的日径流量、降雨量、气温、上游水库放水量,现在想预测未来几天的径流值;或者你有一份工厂设备记录&#xff0c…

2026/9/15 5:21:34

从 awesome-llm-apps 看大模型应用的五大落地形态与工程实践

我是在整理 GitHub Star 列表的时候翻到 awesome-llm-apps 这个仓库的。它表面上是一份链接清单,但顺着目录一层层点进去,会发现过去两年大模型应用开发领域几乎所有值得关注的方向都在这了。很多人拿到这种 awesome 仓库的第一反应是收藏,然…

2026/9/15 4:54:30

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/15 0:01:16

AI英语单词APP开发:自适应学习算法与移动端优化实践

1. 项目概述 作为一名在移动应用开发领域摸爬滚打多年的老手,我最近完成了一个AI英语单词APP的开发项目。这个项目将传统单词记忆方法与现代AI技术相结合,打造了一款能够智能适应不同用户学习习惯的英语学习工具。 市面上大多数单词APP都存在一个通病&a…

2026/9/15 0:01:16

Flutter与OpenHarmony结合开发手语学习APP实战

1. 项目背景与核心价值作为一名同时接触过Flutter和OpenHarmony的开发者,最近我完成了一个基于Flutter for OpenHarmony的手语学习APP实战项目。这个项目最大的特点在于实现了跨平台框架与国产操作系统深度结合的创新实践——用Flutter开发的应用能完美运行在OpenHa…

2026/9/15 0:01:16

六个月成为机器人工程师:从ROS2到SLAM的实战路径

1. 六个月的紧迫感从哪来:先搞清楚你要成为哪种机器人工程师说实话,六个月的期限并不是一个宽松的时间线。市面上任何一本正经的机器人学教材都超过五百页,ROS2的官方文档可以翻到你怀疑人生,再加上ABB、KUKA这些工业机器人厂家动…

2026/9/14 11:59:31

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/14 13:53:59

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/14 11:22:57

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码