news 2026/10/28 7:26:39

Python定向爬虫实战:商品比价系统从抓取到匹配全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python定向爬虫实战:商品比价系统从抓取到匹配全流程

简介:这份资源是面向计算机专业学生与Python初学者的一套商品比价系统完整实现,可直接用于毕业设计、课程设计或大作业场景。项目基于Python 3.6与定向爬虫技术,围绕商品价格采集、存储与展示展开,帮助读者理解从数据抓取到图形界面呈现的完整链路。压缩包共16个文件,以10个py源码文件为核心,另含3个pyc编译文件、1个txt说明、1个db数据库文件与1个md文档,整体约27KB,体积轻便便于携带与二次修改。内容按实现方式分为三个文件夹:first采用面向对象思路,整合数据库与图形界面;second以面向过程方式实现数据库与图形界面;third同样面向过程,但改用文件保存数据并搭配图形界面,便于对比不同架构的取舍。目前已有1024人学习,读者可借此掌握爬虫编写、数据持久化与GUI开发等关键技能,并参考目录结构快速搭建自己的比价系统。

1. 从零搭一套商品比价系统:定向爬虫到底比通用爬虫强在哪

电商页面每天都在变,通用爬虫今天能跑通,明天可能就抓到一堆空字段。我见过太多毕业设计卡在这一步:代码写了几百行,跑出来的价格全是None。问题不在 Python 水平,而在「定向」两个字没吃透。定向爬虫的核心思路是——先锁定目标站点的页面结构、字段位置和翻页规律,再针对性地写解析规则,而不是指望一套通用选择器打天下。这套基于 Python 和定向爬虫的商品比价系统,要解决的就是把多个平台的商品标题、价格、销量、店铺抓下来,清洗后存库,再按商品名做匹配比价。适合正在做计算机毕业设计、想找一个能跑通又有技术含量的题目的同学,也适合想入门爬虫但被通用框架劝退的开发者。下面按「选型 → 抓取 → 清洗 → 比价 → 避坑 → 进阶」的顺序,把每个环节的参数和坑讲清楚。

2. 定向爬虫的选型与请求层搭建:为什么不用 Scrapy 全家桶

2.1 定向爬虫和通用爬虫的本质区别

通用爬虫(比如 Scrapy 默认配置)假设你面对的是未知站点,所以它把大量精力花在调度、去重、分布式上。但毕业设计场景里,目标站点通常就三五个,页面结构固定,真正花时间的是「解析规则怎么写」和「反爬怎么绕」。定向爬虫的做法是:用requests发请求,用BeautifulSoup或lxml做解析,自己控制请求频率和重试逻辑。这样做的好处是代码量小、调试直观、每个字段的来源都能追溯。

我一般会先做一件事:打开目标商品列表页,按 F12 看 Network 面板,确认数据是服务端渲染还是接口返回。如果是接口返回 JSON,直接请求接口比解析 HTML 省一半工作量。这一步决定了后面所有代码的写法,千万别跳过。

2.2 请求头、Cookie 与频率控制的最小实现

下面是一个定向请求的最小可用模板,包含请求头伪装、超时重试和随机延时:

import requests import time import random from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36", "Accept-Language": "zh-CN,zh;q=0.9", "Referer": "https://www.example.com/", # 替换为目标站点首页 } def build_session(): session = requests.Session() retry = Retry(total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503]) session.mount("https://", HTTPAdapter(max_retries=retry)) session.headers.update(HEADERS) return session def fetch(session, url, params=None): try: resp = session.get(url, params=params, timeout=10) resp.raise_for_status() return resp except requests.RequestException as e: print(f"[fetch error] {url} -> {e}") return None def polite_sleep(): time.sleep(random.uniform(1.5, 3.5)) # 随机延时,降低被封概率

逻辑说明:Retry负责在 429/5xx 时自动重试,backoff_factor=1表示重试间隔按 1s、2s、4s 递增。polite_sleep的随机区间建议设在 1.5 到 3.5 秒之间,太短容易触发风控,太长抓取效率低。参数方面,timeout=10是经验值,超过 10 秒基本说明对方在限流,重试也没意义。

提示:Cookie 不要硬编码在代码里,建议单独存一个cookies.json,登录后导出再加载,避免过期后满屏报错。

2.3 翻页策略:URL 规律 vs 接口分页

翻页有两种常见做法。第一种是 URL 里带page=1,2,3,直接循环拼接;第二种是接口返回has_next字段,靠它判断是否继续。我一般优先用第二种,因为更稳定。如果站点用 AJAX 加载,可以在 Network 面板找到真实接口,通常返回 JSON,解析起来比 HTML 快得多。翻页时记得加一个最大页数保护,比如max_page=50,防止死循环把 IP 跑废。

3. 商品字段解析与数据清洗:价格、标题、销量的提取规则

3.1 用 CSS 选择器定位价格和标题

定向爬虫的解析规则要针对每个站点单独写。以常见的商品列表页为例,价格通常在<span class="price">或>from bs4 import BeautifulSoup import re def parse_list_page(html): soup = BeautifulSoup(html, "lxml") items = [] for card in soup.select("div.product-card"): title_el = card.select_one("a.title") price_el = card.select_one("span.price") sales_el = card.select_one("span.sales") if not title_el or not price_el: continue # 跳过结构不完整的卡片 title = title_el.get("title") or title_el.get_text(strip=True) price_text = price_el.get_text(strip=True) price = extract_price(price_text) sales = parse_sales(sales_el.get_text(strip=True)) if sales_el else 0 items.append({"title": title, "price": price, "sales": sales}) return items def extract_price(text): match = re.search(r"(\d+\.?\d*)", text.replace(",", "")) return float(match.group(1)) if match else None def parse_sales(text): text = text.strip() if "万" in text: return int(float(re.search(r"(\d+\.?\d*)", text).group(1)) * 10000) match = re.search(r"(\d+)", text) return int(match.group(1)) if match else 0

逻辑说明:extract_price用正则提取第一个数字,兼容「¥99.00」「99元」「99.00」等格式。parse_sales处理「1.2万」这类中文单位,统一转成整数。参数上,select里的类名必须和实际页面一致,建议先用浏览器控制台document.querySelector验证一遍再写进代码。

3.2 数据清洗:去重、异常价格过滤和标题归一化

抓下来的原始数据不能直接入库。常见问题有三个:同一商品重复出现、价格出现 0 或 999999 这种异常值、标题里带促销后缀导致比价匹配失败。清洗步骤建议这样写:

def clean_items(items): seen = set() cleaned = [] for item in items: if item["price"] is None or item["price"] <= 0 or item["price"] > 100000: continue title_key = re.sub(r"[【】\[\]促销秒杀包邮]", "", item["title"]).strip() if title_key in seen: continue seen.add(title_key) item["title"] = title_key cleaned.append(item) return cleaned

逻辑说明:价格过滤区间设 0 到 100000,覆盖绝大多数商品。标题归一化去掉「【】」「促销」「秒杀」「包邮」等干扰词,让不同平台的同一商品能匹配上。这一步做完,数据质量会明显提升。

3.3 存储方案:SQLite 够用,别急着上 MySQL

毕业设计阶段,SQLite 完全够用,零配置、单文件、方便打包。建表语句如下:

CREATE TABLE products ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, price REAL NOT NULL, sales INTEGER DEFAULT 0, platform TEXT, url TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_title ON products(title);

逻辑说明:idx_title索引是为了后面比价查询时按标题匹配更快。platform字段记录来源平台,方便后续按平台筛选。如果数据量超过 10 万条再考虑 MySQL,否则 SQLite 的读写性能足够。

4. 比价匹配与查询接口:怎么让同一商品在不同平台对上号

4.1 基于标题相似度的匹配策略

比价的核心是「同一商品在不同平台怎么识别」。最简单的方法是标题精确匹配,但实际标题差异很大,所以要用相似度。常见做法是用difflib.SequenceMatcher或jieba分词后算 Jaccard 相似度。下面是一个轻量实现:

import jieba from difflib import SequenceMatcher def similarity(a, b): return SequenceMatcher(None, a, b).ratio() def match_products(target_title, candidates, threshold=0.6): results = [] for item in candidates: score = similarity(target_title, item["title"]) if score >= threshold: results.append((score, item)) results.sort(key=lambda x: x[0], reverse=True) return results

逻辑说明:threshold=0.6是经验值,低于 0.6 基本不是同一商品,高于 0.85 可能是完全相同的标题。如果标题很长,建议先用jieba分词再算 Jaccard,效果更稳。参数可以按实际数据调,先跑 100 条看准确率再定。

4.2 比价查询接口:用 Flask 暴露一个最小 API

为了让系统能演示,可以用 Flask 写一个查询接口:

from flask import Flask, request, jsonify import sqlite3 app = Flask(__name__) @app.route("/compare") def compare(): keyword = request.args.get("q", "").strip() if not keyword: return jsonify({"error": "empty query"}), 400 conn = sqlite3.connect("products.db") cur = conn.cursor() cur.execute("SELECT title, price, platform, url FROM products WHERE title LIKE ?", (f"%{keyword}%",)) rows = cur.fetchall() conn.close() return jsonify([{"title": r[0], "price": r[1], "platform": r[2], "url": r[3]} for r in rows]) if __name__ == "__main__": app.run(debug=True, port=5000)

逻辑说明:LIKE查询适合小数据量,数据多了要换成全文索引或向量匹配。debug=True只用于开发,部署时关掉。接口返回 JSON,前端可以直接渲染成比价表格。

4.3 定时更新与增量抓取

比价系统不能只跑一次。常见做法是用schedule库或系统 cron 定时执行抓取脚本,每次只抓有变化的商品。增量抓取的关键是记录上次抓取时间,只请求更新过的页面。如果站点没有更新时间字段,就全量抓但用INSERT OR REPLACE去重。

5. 定向爬虫避坑清单:封 IP、字段错位、编码乱码怎么排查

5.1 请求被 403 或返回验证码页

现象:resp.status_code返回 403,或者返回的 HTML 里出现「验证」「滑动」等字样。原因:请求头不完整、频率过高、IP 被标记。解决:补全User-Agent、Referer、Accept等头;把延时调到 3 秒以上;如果持续被封,换用代理池或降低抓取频率。注意不要用免费代理,稳定性极差。

5.2 价格字段抓成空或抓到错误数字

现象:price全是None,或者抓到了「原价」「划线价」而不是实际售价。原因:选择器命中了多个元素,或者页面结构和你看到的不一样。解决:在浏览器控制台用document.querySelectorAll确认选择器唯一性;优先取>import math def rank_products(items, price_weight=0.7, sales_weight=0.3): if not items: return [] max_price = max(i["price"] for i in items) for item in items: price_score = 1 - (item["price"] / max_price) sales_score = math.log(item["sales"] + 1) item["score"] = price_weight * price_score + sales_weight * sales_score return sorted(items, key=lambda x: x["score"], reverse=True)

逻辑说明:price_score把价格归一化到 0 到 1,价格越低分越高。sales_score用对数平滑,避免销量差距过大导致权重失衡。参数price_weight和sales_weight可以根据答辩演示的重点调整,比如想突出低价就调高price_weight。

最后说个血泪经验:定向爬虫的代码不要一次写太多站点,先把一个站点跑通、跑稳,再复制到第二个。我见过太多人同时开五个站点,结果每个都半途而废。先把一个站点的抓取、清洗、入库、比价全流程跑通,再扩展,这才是最省时间的路径。希望帮到你。

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

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

深入理解计算机栈:原理、实现与安全防护

1. 栈的本质与计算机中的实现栈&#xff08;Stack&#xff09;是计算机科学中最基础的数据结构之一&#xff0c;它的核心特性可以概括为"后进先出"&#xff08;LIFO&#xff09;。想象一下餐厅里叠放的餐盘——你总是从最上面取用干净的盘子&#xff0c;用完后也总是…

作者头像 李华
网站建设 2026/10/28 7:26:33

LongLoRA微调策略:高效处理长文本序列的NLP技术

1. LongLoRA微调策略概述在自然语言处理领域&#xff0c;处理长文本序列一直是个技术难点。传统Transformer架构由于自注意力机制的计算复杂度与序列长度呈平方关系&#xff0c;导致其在处理长文本时面临内存和计算资源的双重压力。LongLoRA&#xff08;Long Low-Rank Adaptati…

作者头像 李华
网站建设 2026/10/28 7:25:52

手机AI智能体实战:ReAct工作流、容错控制与人机协作边界

我自己的手机里装了五六个AI助手类应用&#xff0c;其中一多半都上线了“智能体”玩法。但实话实说&#xff0c;每天真正在用的就一个。原因不是它最聪明&#xff0c;而是它最“有边界感”——该干的流程活它全包&#xff0c;关键节点一定会停下来等我确认。这个习惯养成之前&a…

作者头像 李华
网站建设 2026/10/28 7:24:34

leaflet-windy 集成高德地图与极夜黑主题实战:风场粒子动画避坑指南

简介&#xff1a;这份资源面向需要在地图上呈现气象风场效果的前端开发者与GIS爱好者&#xff0c;围绕leaflet-windy插件展开&#xff0c;解决风向风速数据难以直观可视化的问题。包内共214个文件&#xff0c;以88个png图标与截图、57个js脚本、9个css样式、8个vue组件为主&…

作者头像 李华
网站建设 2026/10/28 7:24:22

Stable Diffusion 3.0企业级应用与品牌视觉模型实践

1. Stable Diffusion 3.0与企业品牌视觉模型的融合机遇当Midjourney还在为个人创作者提供艺术灵感时&#xff0c;Stable Diffusion 3.0已经悄然打开了企业级市场的大门。作为首个支持企业私有化部署的开源扩散模型&#xff0c;其最大的突破在于允许企业用自有品牌资产训练专属视…

作者头像 李华
网站建设 2026/10/28 7:23:22

Flutter与OpenHarmony复合动画开发实战

1. 项目背景与核心挑战在移动应用开发领域&#xff0c;动画效果和交互反馈的质量直接影响用户体验。Flutter作为跨平台UI框架&#xff0c;其动画系统设计精妙但学习曲线陡峭&#xff0c;而OpenHarmony作为新兴操作系统平台&#xff0c;二者的结合使用存在不少技术盲区。这个实战…

作者头像 李华