简介:基于Python与Django框架开发的京东商品比价系统,利用requests库编写爬虫抓取商品数据,附带完整源码与数据库文件,面向毕业设计、课程设计及项目开发场景,也适合系统学习电商类Web项目的开发者参考。系统实现注册登录、商品收藏、十五天内价格折线图展示、按品类推荐降幅最大商品及跳转购买等功能,覆盖电商比价类项目的核心业务模块。资源压缩包共2001个文件,大小约16.2MB,除Python源码外,还包含Django模板、HTML页面、前端JS与CSS样式、SQL数据库脚本以及JSON/XML配置和多语言mo/po翻译文件,类型相对完整,可直接导入运行并在此基础上扩展。源码经过严格测试,稳定性有保障,涵盖爬虫采集、数据存储、价格走势可视化与推荐逻辑等关键模块,可作为课程设计、毕设演示或企业级比价系统的参考蓝本并快速延展使用。目前已有75人学习下载,适合具备一定Python与Django基础、希望快速搭建完整比价项目的开发者使用。
1. 京东商品比价系统:为什么说它是 Django 爬虫项目最稳的练手模板
“京东商品比价系统”这个名字看着像标准课程设计模板,但它其实就是一条完整的 Python + Django + requests 爬虫生产链路:requests 从京东搜索页抓商品标题和价格,Django 把数据落到 MySQL,再用模板把结果按价格排序展示出来。对要交毕业设计、课程设计的人来说,它把网络爬虫、Django ORM、视图与模板、后台管理四块内容一次性串了起来,比单独写爬虫脚本或者只做 CRUD 更有答辩素材。
我拆这类资源包的习惯是先看它能不能跑通,再看里面哪些代码值得改成自己的写法。这篇就按这个顺序写:先讲清楚系统结构和数据表设计,再带着你从环境配置跑到爬虫入库,最后把反爬、编码、数据库这几个最容易翻车的点单独拎出来。拿到资源包后,你完全可以照着这篇重新走一遍,而不是停留在“能打开项目”的阶段。
2. 系统架构与数据表设计:先搞懂比价数据的来龙去脉
2.1 MVT 分工与 requests 爬虫任务的边界
Django 的 MVT 结构一旦拆开看,这套比价系统的职责边界就非常清楚:模板负责展示商品卡片和价格表格,视图负责接收搜索关键词、查询数据库并返回页面,模型负责定义商品表和价格记录表。剩下最敏感的爬虫逻辑往哪里放,才是很多课程设计做得最乱的地方。
我见过不少同学把 requests 的抓取代码直接写在 views.py 里,视图每被访问一次就去抓一次京东。这种做法有两个直接后果:一是页面响应时间会被拖到十几秒,用户搜索一个关键词要等很久;二是搜索请求稍微多点,就会同时发出大量外部请求,京东的反爬拦截概率成倍上升。所以我在拆这套源码时,会特别看它的爬虫模块是不是独立存在的。常见做法是单独建一个 crawler.py 或者 utils 目录,视图只负责读数据库,爬虫只负责写数据库,两者通过表来解耦。
requests 和 Scrapy 的选型也是同理。如果这是生产级大规模采集,Scrapy 的引擎、调度器、中间件值得用;但这种比价系统的抓取量不大,requests 写出来的代码是线性流程,请求、响应、解析、入库每一步都摆在明面上,调试时不容易出玄学问题。答辩时老师问“爬虫怎么实现”,你只需要指着代码说清楚每行在做什么就行。这也是为什么这类毕业设计资源普遍带的是 requests 而不是 Scrapy。
2.2 商品表和价格记录表:字段、类型与索引这么定
比价系统的核心是两张表:一张存商品当前信息,一张存价格历史记录。如果只存一张商品表,那比价就退化成“静态商品列表”,无法体现历史价格变化,答辩时也少了一个可以展开讲的技术点。所以拿到源码包后,先打开 models.py,看看是不是有类似下面这种拆分。
商品表至少要包含商品 ID、标题、链接、图片、店铺名、当前价格。价格记录表则用外键关联商品,记录每次抓到的价格和时间点。为什么要拆两张表?因为“当前价格”和“历史价格”的查询模式完全不同:当前价格要高频读取,历史价格要按时间范围查询并可能画趋势图。如果都塞进一张表,每跑一次爬虫就插几万行新记录,商品表会无限膨胀,列表页查询也会越来越慢。
字段类型上有个常见的坑:价格不要用 FloatField,要用 DecimalField。浮点数在 MySQL 里的存储精度会随计算飘移,比价系统的核心逻辑就是比较价格,一旦出现 49.100000001 这种脏数据,排序结果就是错的。DecimalField 的 max_digits 给 10,decimal_places 给 2,足够覆盖大多数商品价格。下面是我习惯的模型写法,也是这套资源里最应该先看的文件:
# price_app/models.py from django.db import models class Product(models.Model): sku_id = models.CharField(max_length=32, unique=True) # 京东商品ID,唯一约束 title = models.CharField(max_length=255, db_index=True) # 商品标题 url = models.URLField() # 商品链接 image = models.URLField(blank=True) # 图片地址 shop = models.CharField(max_length=128, blank=True) # 店铺名 price = models.DecimalField(max_digits=10, decimal_places=2, default=0) # 当前价格 updated_at = models.DateTimeField(auto_now=True) # 最后更新时间 class Meta: ordering = ['price'] # 列表页默认按价格升序 class PriceRecord(models.Model): product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='price_records') price = models.DecimalField(max_digits=10, decimal_places=2) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-created_at']这段代码里有两个必须注意的细节。一个是 sku_id 设了 unique,这既保证了商品不会重复入库,也是后续 update_or_create 的查询条件。另一个是外键的 related_name 设成了 price_records,这让你能从商品对象直接取历史价格,写法是 product.price_records.all()。如果漏掉 related_name,Django 默认会给一个 price_record_set 的名字,读起来别扭,命令行调试时也容易敲错。
索引方面,商品表的 title 建议加 db_index,因为比价系统最常见的入口就是关键词搜索,标题模糊查询必须走索引。PriceRecord 表的 created_at 也建议建索引,因为后面按时间范围查价格趋势时,没有索引的扫描会非常慢。外键字段 product_id 在 Django 里默认会自动建索引,不用手动加,这一点和原生 MySQL 的习惯略有差异。
2.3 从搜索词到比价页面:一条完整的数据流
现在把整条链路穿起来:用户在页面输入“无线鼠标”并提交,URL 变成 /search/?q=无线鼠标。视图函数拿到关键词后,先去数据库检查有没有近期的商品缓存,有就直接读库,没有就调用爬虫模块抓取京东搜索页。爬虫解析出商品标题、链接、价格、店铺名后,用 update_or_create 写入商品表,同时往价格记录表补一条快照。最后视图把商品列表按价格升序渲染到模板,用户看到的就是一个低到高排列的比价表。
这个流程还说明了一个容易被忽视的点:为什么非要数据库?如果把抓取结果直接存在内存变量里,进程重启数据就没了,而且多个用户同时搜索时会相互污染。有了数据库,爬虫和页面展示就完全解耦了,你甚至可以白天用 Django 后台看结果,晚上定时跑爬虫,第二天早上再打开页面就是新的比价数据。比价系统里“价”是核心,历史记录是灵魂,这两样都得靠数据库持久化。
3. Django 侧复现:从创建 app 到数据库迁移跑通项目
3.1 环境准备:虚拟环境、依赖与项目解压位置
拿到源码包后,第一件事不是急着开 IDE,而是先把项目目录结构看清楚。一个标准的 Django 课程设计包解压后,根目录应该能看到 manage.py、项目配置目录、业务 app 目录,以及一份数据库导出文件。manage.py 是 Django 项目所有命令的入口,后面所有操作几乎都从它开始。
我一般会先创建虚拟环境,避免依赖跟系统全局 Python 冲突。Python 版本建议用 3.8 到 3.11,太新的版本有时候会遇到个别依赖包还没兼容的情况。下面这组命令在 Windows 和 Linux 下都适用,只是激活命令略有不同:
# 创建虚拟环境 python -m venv venv # 激活虚拟环境 # Windows PowerShell: venv\Scripts\activate # macOS / Linux: source venv/bin/activate # 安装依赖 pip install -r requirements.txtrequirements.txt 是源码包必须带的文件,里面通常会声明 Django、requests、lxml、mysqlclient 等依赖。如果包里没带,你可以根据实际用到的包手动安装:requests 用来抓页面,lxml 用来做 xpath 解析,mysqlclient 或 pymysql 用来连 MySQL。安装完成后先执行 python manage.py check,这一步能快速发现配置层面的错误,不用等到运行服务器时再看到一堆 Traceback。
3.2 settings 配置:数据库、时区与主机名
Django 项目能不能跑起来,一半取决于 settings.py。这套比价系统的 settings 里有三个地方必须检查:ALLOWED_HOSTS、DATABASES、TIME_ZONE。本地调试时 ALLOWED_HOSTS 可以直接放空或写 ['*'],但如果部署到服务器,一定不要用星号,否则会被扫到后更容易受到恶意请求。
数据库配置是另一个关键点。如果源码包带的是 MySQL 导出文件,你需要修改 DATABASES 里的连接信息。常见配置如下:
# settings.py 中的关键配置 ALLOWED_HOSTS = ['*'] # 本地调试可以放开,部署后按域名收紧 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'jd_price', # 数据库名 'USER': 'root', # 数据库账号 'PASSWORD': '你的密码', # 数据库密码 'HOST': '127.0.0.1', # 本机连接 'PORT': '3306', 'OPTIONS': {'charset': 'utf8mb4'}, } } TIME_ZONE = 'Asia/Shanghai' USE_TZ = False # 本地比价系统中关闭 UTC 时间,避免 datetime 混乱这里 USE_TZ 的设置直接影响爬虫入库时间。很多课程设计会在描述时间时出现“差 8 小时”的问题,就是因为 Django 默认开启 UTC,而爬虫写入的时间被当成 UTC 存储。比价系统要记录价格快照的历史时间点,我建议直接设成 USE_TZ = False,让数据库存当地时间,后续展示不用再做换算。
如果包里直接使用的 SQLite,就不需要配 MySQL。但要注意,课程设计场景通常要求用 MySQL,因为答辩时老师会问数据库设计。SQLite 只是本地验证阶段最方便的选择。
3.3 用 migrate 和 createsuperuser 完成初始化
这里有两个初始化路线,只能选一种:要么导入源码包自带的 SQL 文件,要么执行 Django 迁移。最忌的是先导入 SQL,又执行 migrate,最后报“表已存在”的错误。我一般先看有没有 .sql 文件,如果有,就先把数据库建好再导入。
MySQL 下建议先创建数据库并指定字符集,然后再导入导出的 SQL 文件,否则中文会乱码:
# 登录 MySQL 创建数据库 mysql -u root -p -e "CREATE DATABASE jd_price DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" # 导入指定 SQL 文件 mysql -u root -p jd_price --default-character-set=utf8mb4 < jd_price.sql导入成功后,你只需要执行创建超级用户这一步,不需要再 migrate。如果源码包没有提供 SQL 文件,而是迁移文件已经存在,那就走标准流程:
python manage.py makemigrations python manage.py migrate python manage.py createsuperusercreatesuperuser 需要依次输入用户名、邮箱、密码。邮箱可以随便填,但密码不能太简单,否则 Django 会直接拒绝。完成后运行 python manage.py runserver,访问 http://127.0.0.1:8000/admin 登录后台,就能看到商品表和价格记录表了。到这一步,项目基础已经通了,接下来才是整个系统最有分量的部分:爬虫怎么抓、怎么解析、怎么入库。
4. requests 爬虫实战:请求头、解析与入库的完整写法
4.1 请求参数怎么设:headers、timeout、重试与编码
爬虫能不能稳定抓数据,一半看请求头。京东对请求头十分敏感,只用 requests.get 裸请求大概率拿到验证码页。常见的做法是把浏览器开发者工具里的请求头完整拷贝下来。至少要有 User-Agent、Accept、Accept-Language,如果是搜索页,Referer 也要带上。下面这个配置可以当作起点:
# crawler/request_helper.py import requests import time 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': 'text/html,application/xhtml+xml,application/xml;q=0.9,' 'image/avif,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Referer': 'https://www.jd.com/', } def get_session(): session = requests.Session() # 自动加一个重试机制,避免偶发网络抖动直接挂掉 retry = Retry(total=3, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504]) adapter = HTTPAdapter(max_retries=retry) session.mount('https://', adapter) return session这里面有两个参数值得细说。timeout 是每次请求必须设置的,建议 10 秒。不设 timeout 的话,一旦京东那边连接不返回,requests 会一直等下去,爬虫任务会卡死。重试用 urllib3 的 Retry,只重试 3 次,退避时间 0.5 秒起步,避免在服务端已经返回 500 的情况下不断轰炸。如果你用的是简单的 requests.get,也可以在 except 里自己写 sleep 再循环,效果类似。
最后一个编码细节:requests 有时解析 Content-Type 里的 charset 不准,尤其是京东页面内容较大时。稳妥做法是拿到响应后手动指定 resp.encoding = 'utf-8',再取 resp.text。如果忽略这一步,你可能会在后面的 xpath 解析里看到一堆乱码标题。
4.2 商品信息解析:用 xpath 取标题、价格和链接
页面抓回来后,下一步是解析。这套系统常见的是用 lxml 的 xpath 做解析。xpath 的优势是浏览器开发者工具里可以直接 copy,而且 text() 函数的用法非常直观。但我要先给个心理预期:京东搜索页的 DOM 结构经常改版,源码包里写好的选择器不一定永远有效,所以拿到包后第一件事是用一个简单脚本验证当前页面结构。
# crawler/parser.py from lxml import etree def parse_search_page(html): selector = etree.HTML(html) items = [] # 商品列表项的选择器,京东现在大体还是 li 元素带 gl-item 类 # 如果返回空,先在浏览器里按 F12 查看当前页面结构,再回来改这里 for node in selector.xpath('//li[contains(@class,"gl-item")]'): title = node.xpath('.//div[contains(@class,"p-name")]/a/@title') url = node.xpath('.//div[contains(@class,"p-name")]/a/@href') if not title: continue title = title[0].strip() href = url[0].strip() if href.startswith('//'): href = 'https:' + href items.append({ 'title': title, 'url': href, }) return items这段代码里 xpath 的contains(@class,"gl-item")是经典写法,因为 class 可能是gl-item或gl-item后面跟其他标识。/a/@title是拿商品标题,@href是拿链接。这些结果都是字符串数组,xpath 不会自动帮你取第一个,所以要用[0]再 strip 一下。
价格这一项我单独说。新版京东搜索页的价格往往不是静态 HTML,直接在列表项里用 xpath 找.//div[contains(@class,"p-price")]可能拿不到值,因为价格是懒加载后由 JavaScript 渲染出来的。常见做法有两种:一种是从页面内嵌的全局参数里用正则提取 JSON,另一种是拿到商品链接后去商品详情页再抓价格。很多课程设计为了节省成本,直接在列表页用“快照数据”,但那样会导致价格字段长期为空。下面这种正则提取 JSON 的方法是更稳的:
# crawler/parser.py 补充 import re import json def extract_page_json(html): # 页面里往往有一段 window.pageConfig = {...} 这样的全局变量 pattern = r'window\.pageConfig\s*=\s*({.*?});' match = re.search(pattern, html, re.S) if not match: return None try: return json.loads(match.group(1)) except json.JSONDecodeError: return None这段代码的实用性在于不依赖具体 DOM 层级,京东只要还在页面里输出 window.pageConfig,这一类数据就能解析到。拿到 JSON 后,再按字段名去找商品 ID、价格、名称,比硬磕 xpath 稳定得多。调试时可以先 print 一下 JSON 的 keys,看有没有你要的数据,再决定字段路径。
4.3 入库与去重:Django ORM 的 update_or_create 用法
解析出商品列表后,不要直接批量 create 到数据库,因为同一件商品会在多次抓取中反复出现。正确的写入姿势是 update_or_create:按 sku_id 去查,存在就更新标题、价格、店铺,不存在就新建。同时每次抓取都往 PriceRecord 表插一条价格快照,这样就保留了历史价格曲线。
from price_app.models import Product, PriceRecord def save_product(item): product, created = Product.objects.update_or_create( sku_id=item['sku_id'], defaults={ 'title': item['title'], 'url': item['url'], 'image': item.get('image', ''), 'shop': item.get('shop', ''), 'price': item['price'], } ) # 不管商品是新是旧,价格快照都要留一条 PriceRecord.objects.create(product=product, price=item['price']) return product, createdupdate_or_create 的语义是“按关键字查询,找不到就创建,找得到就更新 defaults 里的字段”。这里关键字用的是 sku_id,因为它在 models 里设了 unique,这是保证去重的关键。如果你用普通字段做查询条件,比如 title,那标题稍微改半个字就会重复入库,比价结果会越来越乱。
给 price 用 DecimalField 之后,写库时直接传字符串价格也能自动转,例如调 PriceRecord.objects.create(price='69.90')。但如果解析时拿到的是一个像“¥69.90”的字符串,必须先过滤掉非数字字符,否则 Decimal 类型会直接抛异常。具体做法可以这样:re.sub(r'[^\d.]', '', price_text)。这个坑在爬虫入 clip 的时候几乎必踩。
5. 避坑指南:京东反爬、编码和数据库同步的五个翻车现场
5.1 请求头不全被京东直接拦截
现象:requests.get 返回 200,但页面内容不是商品列表,而是一段“访问过于频繁”的提示,或者干脆返回 403 Forbidden。
原因:请求头只有默认的 Python-urllib,京东的防爬规则会立刻识别出这不是浏览器访问。缺 User-Agent、Accept-Language、Referer 任何一个,都可能被判为异常请求。
解决:把浏览器开发者工具里看到的完整请求头复制下来,至少补全 User-Agent、Accept、Accept-Language、Referer。再用 requests.Session 发起请求,Session 会自动保存 Cookie,能维持一定的连续性。如果仍然被拦截,检查是不是当前 IP 请求次数太密集,在两次请求之间加 time.sleep(3) 到 time.sleep(5) 的随机延迟。
5.2 价格解析出乱码或拿不到值
现象:标题和链接都解析正常,但价格列表是空的,或者取到的价格是一串“¥9?9.00”这样的乱码。
原因:一个是页面编码没指定,requests 默认从响应头推断编码,推断错误就会乱。另一个是价格由动态脚本渲染,静态 HTML 里根本没有对应文本,xpath 自然取不到。
解决:拿到响应后手动设置 resp.encoding = 'utf-8'。如果静态页里没有价格,改用 4.2 节里的正则提取 window.pageConfig JSON。提取价�格文本后,一定要用 re.sub 过滤掉“¥”和空格,再交给 DecimalField。乱码问题还可能是页面本身是 gzip 压缩响应,requests 会自动解压,但如果直接拿到的是二进制内容,先检查响应头里的 Content-Encoding。
5.3 Cookie 过期和请求太频繁导致 IP 被封
现象:爬虫跑了十几分钟后,突然所有请求都返回 302 跳转,或者跳到一个安全验证页面,之后无论怎么改 headers 都拿不到数据。
原因:京东会记录同一 IP 的访问频率,连续高频请求会触发临时风控封禁。另外搜索页会下发关键 Cookie,Cookie 一旦过期,商品接口就拒绝返回数据。
解决:控制抓取频率,每次请求之间随机 sleep 3 到 8 秒,避免固定间隔。Session 里的 Cookie 要保存下来,如果一段时间后失效,就重新发起一次首页请求刷新 Cookie。还有一个更容易做到的办法是把抓取量控制在前 1 到 2 页,课程设计场景通常不需要全量数据,够展示功能就行。如果已经被临时封禁,停止抓取半小时左右一般会自动恢复。
5.4 数据库迁移报错和导入导出乱码
现象:先导入了源码包的 SQL 文件,又执行 python manage.py migrate,结果报 “table already exists” 或者 “relation already exists”。另一个场景是从 MySQL 导出 SQL 后,导入到自己的机器,中文全变成问号。
原因:导入 SQL 和 migrate 是两条初始化路线,混用就会表结构冲突。中文乱码则是字符集不匹配,数据库、表、连接字符串三处字符集必须一致。
解决:选一条路线走到底。用了 SQL 文件就把迁移文件当成参考,不要再执行 migrate;用 migrate 就别导入 SQL。字符集方面统一使用 utf8mb4,创建数据库时指定 DEFAULT CHARACTER SET utf8mb4,导入时用 --default-character-set=utf8mb4 参数。如果已经乱码,把数据库表删掉重新导入,用 data 修复基本不划算。
5.5 项目在自己电脑正常,部署到服务器后样式全丢
现象:本地 python manage.py runserver 一切正常,复制到云服务器后,Django 后台和前台页面都没有 CSS,控制台一堆 404,指向 /static/ 路径。
原因:DEBUG=True 时 Django 会帮你托管静态文件,一旦 DEBUG=False,Django 就不会再处理静态文件请求,必须由 nginx 或 whitenoise 来托管。
解决:如果只是想临时演示,把 settings 里的 DEBUG 保持为 True;如果想正式部署,安装 whitenoise,并在 MIDDLEWARE 里加上 whitenoise.middleware.WhiteNoiseMiddleware,同时执行 python manage.py collectstatic 把所有静态文件收集到 STATIC_ROOT。比价系统本身没有很重的静态资源,用 whitenoise 是最省事的方案。这一条对毕业设计答辩演示尤其重要,否则你在老师电脑上打开没有样式的页面,第一印象就打折了。
6. 进阶技巧:把比价结果从“能看”变成“能用”
6.1 用 Django 管理命令做定时抓取
页面能跑通、爬虫能入库,这套系统只完成了一半。真正的比价系统需要定时更新价格,否则跑一次就停,数据很快就会过期。我建议把爬虫入口写成一个 Django 管理命令,这样既可以用命令行手动触发,也能挂到系统定时任务里。在业务 app 下新建 management/commands/fetch_prices.py,内容大致如下:
from django.core.management.base import BaseCommand import time from crawler.parser import parse_search_page from crawler.request_helper import get_session from price_app.models import Product, PriceRecord class Command(BaseCommand): help = '抓取指定关键词的京东商品价格' def add_arguments(self, parser): parser.add_argument('keyword', type=str) def handle(self, *args, **options): session = get_session() html = session.get( 'https://search.jd.com/Search', params={'keyword': options['keyword']}, timeout=10, ).text items = parse_search_page(html) # 这里只是演示入口,实际要循环处理每个 item time.sleep(3) self.stdout.write(f'解析到 {len(items)} 条商品')这样你在项目根目录执行 python manage.py fetch_prices 无线鼠标,就能把这一个关键词的最新价格抓进库。再配合 cron 或 Windows 任务计划每天跑一次,比价数据就会持续更新。Be careful:任务计划里一定要进入虚拟环境再执行,路径写绝对路径,否则定时任务会找不到 Django 环境。
6.2 用缓存把列表页提速
爬虫入库频率低,但用户访问列表页的频率可能很高。每次访问都去数据库做模糊查询,数据量大了以后会明显变慢。我一般会给搜索页加 Django 自带的缓存,使用 cache_page 装饰器按查询参数缓存结果,比如:
from django.views.decorators.cache import cache_page @cache_page(60 * 30) # 缓存 30 分钟 def search_view(request): keyword = request.GET.get('q', '') # 查询数据库并渲染模板这段代码的重点是过期时间设成 30 分钟,正好和定时抓取的频率匹配,用户看到的数据不会太旧,也不会频繁触发查询。默认缓存后端是本地内存,演示够用;如果放服务器,建议换成 Redis 或数据库缓存。
最后说一个我的习惯:拆这类课程设计包,我从来不会第一时间埋头跑 runserver,而是先打开 models.py 和爬虫解析函数,确认表结构与自己预期一致,再用一个最小关键词验证页面结构。有一次我跳过这一步直接跑包,结果因为京东改版,解析器拿不到价格,我以为是源码问题,折腾了一晚上才发现是选择器过期。从那以后,每次拿到课程设计源码,我都强制先做“表结构 + 选择器”两个体检,再谈后续功能。这套 Django + requests 的比价系统也不例外,希望你拿到手后少走一点我走过的弯路,希望帮到你。
本文还有配套的精品资源,点击获取