前言
最近想升级一下电脑硬件,又不是很了解当前电脑硬件的价格趋势。登录一些比价网站,商品太多了,找起来好麻烦。一些高级的功能还要收费。干脆自己写一个吧。想要的功能很简单。
- 添加感兴趣的商品ID,自动获取商品信息
- 添加是否获取价格和当前优惠活动开关,若开启,每天获取一次当前商品价格和活动
- 将获取的商品价格可视化展示
- Django:作为基础框架,用来管理商品信息和价格数据
- feapder:用来采集商品价格数据并写入数据库
- simpleui:Django后台ui美化
- pyecharts:数据可视化展示
Django 平台
models.py
from django.db import models
# Create your models here.
#状态
NO = 0
YES = 1
STATUS_CHOICE = {
NO: '否',
YES: '是',
}
class MallInfo(models.Model):
'''商品信息'''
sid = models.AutoField(primary_key=True, verbose_name='编号')
status_choices = ((k, v) for k,v in STATUS_CHOICE.items())
sku = models.CharField(max_length=32, verbose_name='商品id')
name = models.CharField(max_length=255, verbose_name='商品名', default='xxxx')
src = models.CharField(max_length=20, verbose_name='来源', default='jd')
url = models.CharField(max_length=255, verbose_name='链接', default='xxxx')
addtime = models.DateTimeField(auto_now_add=True, verbose_name='添加时间')
status = models.SmallIntegerField(default=NO, choices=status_choices, verbose_name='是否监控')
# admin显示订单的id
def __str__(self):
return self.name
class Meta:
db_table = 'mall_info'
verbose_name = '商品信息'
verbose_name_plural = '商品信息'
class PriceInfo(models.Model):
'''历史价格'''
sid = models.AutoField(primary_key=True, verbose_name='编号')
sku = models.CharField(max_length=32, verbose_name='商品id')
price = models.IntegerField(default=0, verbose_name='价格')
note = models.CharField(max_length=255, default='无', verbose_name='活动')
addtime = models.DateTimeField(auto_now_add=True, verbose_name='日期')
# admin显示订单的id
def __str__(self):
return str(self.sid)
class Meta:
db_table = 'price_info'
verbose_name = '商品价格'
verbose_name_plural = '商品价格'
admin.py
from django.contrib import admin
from mall.models import MallInfo, PriceInfo
from django.utils.html import format_html
from bs4 import BeautifulSoup
import requests
# Register your models here.
class MallInfoAdmin(admin.ModelAdmin):
list_display = ['sku', 'name_and_url', 'show_history', 'src', 'addtime', 'status']
#进入编辑的字段
list_display_links = ['sku']
#只读字段
readonly_fields = ['url']
#标题带链接
def name_and_url(self, obj):
return format_html("<a href='{url}' target='_blank'>{name}</a>", url=obj.url, name=obj.name)
#字段描述
name_and_url.short_description = "商品名称"
def show_history(self, obj):
return format_html("<a href='/mall/echarts/?sku={sku}' target='_blank'>历史价格</a>", sku=obj.sku)
show_history.short_description = "历史价格"
#重写保存函数
def save_model(self, request, obj, form, change):
def get_name_by_sku():
"""根据sku获取名称"""
url = 'https://item.jd.com/%s.html' % (obj.sku)
try:
resp = requests.get(url)
soup = BeautifulSoup(resp.text, 'html.parser')
name = soup.find(class_='sku-name')
return name.text.strip()
except Exception as e:
print(e)
return '0000'
if not change: # 新增记录
obj.name = get_name_by_sku()
obj.url = 'https://item.jd.com/%s.html' % (obj.sku)
super(MallInfoAdmin, self).save_model(request, obj, form, change)
class PriceInfoAdmin(admin.ModelAdmin):
list_display = ['sku', 'price', 'note', 'addtime']
#只读字段
readonly_fields = ['sku', 'price', 'note', 'addtime']
admin.site.register(MallInfo, MallInfoAdmin)
#admin.site.register(PriceInfo, PriceInfoAdmin)
views.py
from django.shortcuts import render
from mall.models import MallInfo, PriceInfo
from django.http import HttpResponse
from django.template import Template, Context
from rest_framework.views import APIView
import json
from pyecharts.charts import Bar
from pyecharts import options as opts
from .utils import *
# Create your views here.
JsonResponse = json_response
JsonError = json_error
def get_skus_need_monitor(request):
"""
获取需要监控价格的商品列表
"""
resp = {}
skus = []
malls = MallInfo.objects.filter(status=1)
if malls:
for mall in malls:
skus.append(mall.sku)
return JsonResponse(skus)
def bar_base(sku) -> Bar:
"""
图表制作
"""
x_date = []
y_price = []
mall = MallInfo.objects.filter(sku=sku).last()
prices = PriceInfo.objects.filter(sku=sku).order_by('-sid')[:15]
if prices:
for price in prices[::-1]:
x_date.append(price.addtime.strftime('%Y-%m-%d'))
y_price.append(price.price)
b = (Bar()
.add_xaxis(x_date)
.add_yaxis("sku:{}".format(sku),
y_price,
)
.set_global_opts(title_opts=opts.TitleOpts(title="商品历史价格曲线", subtitle="{}".format(mall.name)),
xaxis_opts=opts.AxisOpts(axislabel_opts={"interval":"0","rotate":45}),
datazoom_opts=[opts.DataZoomOpts(), opts.DataZoomOpts(type_="inside")],
)
.dump_options_with_quotes()
)
return b
class get_mall_price_api(APIView):
"""
历史价格获取价格数据api
"""
def get(self, request, *args, **kwargs):
sku = request.GET.get("sku")
return JsonResponse(json.loads(bar_base(sku)))
class echartsView(APIView):
"""
历史价格渲染页面
"""
def get(self, request, *args, **kwargs):
sku = request.GET.get("sku")
p_max = PriceInfo.objects.filter(sku=sku).order_by('-price')[0].price
p_min = PriceInfo.objects.filter(sku=sku).order_by('price')[0].price
p_now = PriceInfo.objects.filter(sku=sku).last().price
n_note = PriceInfo.objects.filter(sku=sku).last().note
#多重查询条件
l_note = PriceInfo.objects.filter(sku=sku).exclude(note='无').order_by('-sid')
t = Template(open("./templates/echarts.html").read())
c = Context({'sku': sku, 'p_max': p_max, 'p_min': p_min, 'n_note': n_note, 'l_note':l_note, 'p_now':p_now})
html = t.render(c)
return HttpResponse(html)
echarts.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>商品历史价格曲线</title>
<script src="https://cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>
<script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts.min.js"></script>
</head>
<body>
<div style="position:absolute; top:10; left:10; width:1200px; height:95%;">
<div id="line" style="width:1100px;height:93%"></div>
<div style="margin-top:20px">
<span>历史高价: <a style='color:red'>{{p_max}}</a></span>
<span>历史低价: <a style='color:green'>{{p_min}}</a> </span>
<span>当前价格: <a style='color:orange'>{{p_now}}</a> </span>
<span>最新活动: <a style='color:blue'>{{n_note}}</a> </span>
</div>
</div>
<div style="margin-left:1020px; height:865px; overflow:auto;">
<table style="maxHeight:1200px;" border="1" cellpadding="3" cellspacing="0">
<caption> 历史活动 </caption>
<tr>
<td>日期</td><td>内容</td>
<tr>
{% for i in l_note %}
<tr>
<td>{{ i.addtime }}</td> <td> {{ i.note }}</td>
</tr>
{% endfor %}
</table>
</div>
<script>
var chart = echarts.init(document.getElementById('line'), 'white', {renderer: 'canvas'});
$(
function () {
fetchData(chart);
}
);
function fetchData() {
$.ajax({
type: "GET",
url: "/mall/getprices/?sku={{sku}}",
dataType: 'json',
success: function (result) {
chart.setOption(result.data);
}
});
}
</script>
</body>
</html>
feadper 价格采集
crawl_mall_spider.py
import feapder
from items import price_info_item
from feapder.utils import tools
import requests, json, datetime
class MallSpider(feapder.BaseParser):
def start_requests(self):
resp = requests.get('http://x.x.x.x:801/api/mall/getskus/')
for sku in json.loads(resp.text)['data']:
url = 'https://item.jd.com/%s.html' % (sku)
yield feapder.Request(url, render=True)
def parse(self, request, response):
r_list = response.xpath('//div[@class="summary-price-wrap"]')
#解析结果
for r in r_list:
price = r.xpath('//span[@class="p-price"]/span[2]/text()').extract_first()
note = r.xpath('//div[@class="J-prom"]/div[last()]/em[last()]/text()').extract_first()
price_item = price_info_item.PriceInfoItem()
price_item.sku = request.url[20:-5]
price_item.price = price.lstrip().rstrip()
if note:
price_item.note = note.lstrip().rstrip()
else:
price_item.note = '无'
price_item.addtime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
yield price_item
转载了