Price Tracker Application with Django | Track Discounts

By Reverse Python

Hi DEVs!

Youtube Channel with video tutorials – Reverse Python Youtube

In this tutorial we are going to build price tracker application which will notify us for discounts.

Creating complex projects is the key to learn fast! If you are following me for a while, you already know I like complex stuff, so we are going to use RabbitMQ + Celery + BeautifulSoup + Django for creating this app.

Alright! Let’s Start!

Celery is the best choice for doing background task processing in the Python/Django ecosystem. It has a simple and clear API, and it integrates beautifully with Django. So, we are using Celery to handle the time-consuming tasks by passing them to queue to be executed in the background and always keep the server ready to respond to new requests.

Celery requires a solution to send and receive messages; usually this comes in the form of a separate service called a message broker. We will be configuring celery to use the RabbitMQ messaging system, as it provides robust, stable performance and interacts well with celery.

We can install RabbitMQ through Ubuntu’s repositories by following command:

sudo apt-get install rabbitmq-server
sudo apt-get install rabbitmq-server
sudo apt-get install rabbitmq-server

Enter fullscreen mode Exit fullscreen mode

Then enable and start the RabbitMQ service:

sudo systemctl enable rabbitmq-server
sudo systemctl start rabbitmq-server
sudo systemctl enable rabbitmq-server
sudo systemctl start rabbitmq-server
sudo systemctl enable rabbitmq-server sudo systemctl start rabbitmq-server

Enter fullscreen mode Exit fullscreen mode

Install RabbitMQ on Mac

Well, create a new Django project named pricetracker and app named tracker

Install following dependencies:

pip3 install beautifulsoup4 httplib2 Celery
pip3 install beautifulsoup4 httplib2 Celery
pip3 install beautifulsoup4 httplib2 Celery

Enter fullscreen mode Exit fullscreen mode

Once installation completed, add the CELERY_BROKER_URL configuration to the settings.py file:

<span>CELERY_BROKER_URL</span> <span>=</span> <span>'</span><span>amqp://localhost</span><span>'</span>
<span>CELERY_BROKER_URL</span> <span>=</span> <span>'</span><span>amqp://localhost</span><span>'</span>
CELERY_BROKER_URL = 'amqp://localhost'

Enter fullscreen mode Exit fullscreen mode

Then, create celery.py inside your project.

celery.py

<span>import</span> <span>os</span>
<span>from</span> <span>celery</span> <span>import</span> <span>Celery</span>
<span>os</span><span>.</span><span>environ</span><span>.</span><span>setdefault</span><span>(</span><span>'</span><span>DJANGO_SETTINGS_MODULE</span><span>'</span><span>,</span> <span>'</span><span>pricetracker.settings</span><span>'</span><span>)</span>
<span>app</span> <span>=</span> <span>Celery</span><span>(</span><span>'</span><span>pricetracker</span><span>'</span><span>)</span>
<span>app</span><span>.</span><span>config_from_object</span><span>(</span><span>'</span><span>django.conf:settings</span><span>'</span><span>,</span> <span>namespace</span><span>=</span><span>'</span><span>CELERY</span><span>'</span><span>)</span>
<span>app</span><span>.</span><span>autodiscover_tasks</span><span>()</span>
<span>import</span> <span>os</span>
<span>from</span> <span>celery</span> <span>import</span> <span>Celery</span>

<span>os</span><span>.</span><span>environ</span><span>.</span><span>setdefault</span><span>(</span><span>'</span><span>DJANGO_SETTINGS_MODULE</span><span>'</span><span>,</span> <span>'</span><span>pricetracker.settings</span><span>'</span><span>)</span>

<span>app</span> <span>=</span> <span>Celery</span><span>(</span><span>'</span><span>pricetracker</span><span>'</span><span>)</span>
<span>app</span><span>.</span><span>config_from_object</span><span>(</span><span>'</span><span>django.conf:settings</span><span>'</span><span>,</span> <span>namespace</span><span>=</span><span>'</span><span>CELERY</span><span>'</span><span>)</span>
<span>app</span><span>.</span><span>autodiscover_tasks</span><span>()</span>
import os from celery import Celery os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pricetracker.settings') app = Celery('pricetracker') app.config_from_object('django.conf:settings', namespace='CELERY') app.autodiscover_tasks()

Enter fullscreen mode Exit fullscreen mode

We are setting the default Django settings module for the ‘celery’ program and loading task modules from all registered Django app configs.

Now inside your __init__.py import the celery:

<span>from</span> <span>.celery</span> <span>import</span> <span>app</span> <span>as</span> <span>celery_app</span>
<span>__all__</span> <span>=</span> <span>[</span><span>'</span><span>celery_app</span><span>'</span><span>]</span>
<span>from</span> <span>.celery</span> <span>import</span> <span>app</span> <span>as</span> <span>celery_app</span>

<span>__all__</span> <span>=</span> <span>[</span><span>'</span><span>celery_app</span><span>'</span><span>]</span>
from .celery import app as celery_app __all__ = ['celery_app']

Enter fullscreen mode Exit fullscreen mode

This will make sure our Celery app loaded every time Django starts.

Now, Let’s create our model

models.py

<span>from</span> <span>django.db</span> <span>import</span> <span>models</span>
<span>class</span> <span>Item</span><span>(</span><span>models</span><span>.</span><span>Model</span><span>):</span>
<span>title</span> <span>=</span> <span>models</span><span>.</span><span>CharField</span><span>(</span><span>max_length</span><span>=</span><span>200</span><span>)</span>
<span>url</span> <span>=</span> <span>models</span><span>.</span><span>CharField</span><span>(</span><span>max_length</span><span>=</span><span>600</span><span>)</span>
<span>requested_price</span> <span>=</span> <span>models</span><span>.</span><span>IntegerField</span><span>(</span><span>default</span><span>=</span><span>0</span><span>)</span>
<span>last_price</span> <span>=</span> <span>models</span><span>.</span><span>IntegerField</span><span>(</span><span>null</span><span>=</span><span>True</span><span>,</span> <span>blank</span><span>=</span><span>True</span><span>)</span>
<span>discount_price</span> <span>=</span> <span>models</span><span>.</span><span>CharField</span><span>(</span><span>max_length</span><span>=</span><span>100</span><span>,</span> <span>null</span><span>=</span><span>True</span><span>,</span> <span>blank</span><span>=</span><span>True</span><span>)</span>
<span>date</span> <span>=</span> <span>models</span><span>.</span><span>DateTimeField</span><span>(</span><span>auto_now_add</span><span>=</span><span>True</span><span>)</span>
<span>def</span> <span>__str__</span><span>(</span><span>self</span><span>):</span>
<span>return</span> <span>self</span><span>.</span><span>title</span>
<span>from</span> <span>django.db</span> <span>import</span> <span>models</span>

<span>class</span> <span>Item</span><span>(</span><span>models</span><span>.</span><span>Model</span><span>):</span>
    <span>title</span> <span>=</span> <span>models</span><span>.</span><span>CharField</span><span>(</span><span>max_length</span><span>=</span><span>200</span><span>)</span>
    <span>url</span> <span>=</span> <span>models</span><span>.</span><span>CharField</span><span>(</span><span>max_length</span><span>=</span><span>600</span><span>)</span>
    <span>requested_price</span> <span>=</span> <span>models</span><span>.</span><span>IntegerField</span><span>(</span><span>default</span><span>=</span><span>0</span><span>)</span>
    <span>last_price</span> <span>=</span> <span>models</span><span>.</span><span>IntegerField</span><span>(</span><span>null</span><span>=</span><span>True</span><span>,</span> <span>blank</span><span>=</span><span>True</span><span>)</span>
    <span>discount_price</span> <span>=</span> <span>models</span><span>.</span><span>CharField</span><span>(</span><span>max_length</span><span>=</span><span>100</span><span>,</span> <span>null</span><span>=</span><span>True</span><span>,</span> <span>blank</span><span>=</span><span>True</span><span>)</span>
    <span>date</span> <span>=</span> <span>models</span><span>.</span><span>DateTimeField</span><span>(</span><span>auto_now_add</span><span>=</span><span>True</span><span>)</span>

    <span>def</span> <span>__str__</span><span>(</span><span>self</span><span>):</span>
        <span>return</span> <span>self</span><span>.</span><span>title</span>
from django.db import models class Item(models.Model): title = models.CharField(max_length=200) url = models.CharField(max_length=600) requested_price = models.IntegerField(default=0) last_price = models.IntegerField(null=True, blank=True) discount_price = models.CharField(max_length=100, null=True, blank=True) date = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title

Enter fullscreen mode Exit fullscreen mode

We are going to crawl eBay. User will enter URL of specific item and requested price.

So, let’s create a form for that:

forms.py

<span>from</span> <span>django</span> <span>import</span> <span>forms</span>
<span>class</span> <span>AddNewItemForm</span><span>(</span><span>forms</span><span>.</span><span>Form</span><span>):</span>
<span>url</span> <span>=</span> <span>forms</span><span>.</span><span>CharField</span><span>(</span><span>max_length</span><span>=</span><span>600</span><span>)</span>
<span>requested_price</span> <span>=</span> <span>forms</span><span>.</span><span>IntegerField</span><span>()</span>
<span>from</span> <span>django</span> <span>import</span> <span>forms</span>

<span>class</span> <span>AddNewItemForm</span><span>(</span><span>forms</span><span>.</span><span>Form</span><span>):</span>
    <span>url</span> <span>=</span> <span>forms</span><span>.</span><span>CharField</span><span>(</span><span>max_length</span><span>=</span><span>600</span><span>)</span>
    <span>requested_price</span> <span>=</span> <span>forms</span><span>.</span><span>IntegerField</span><span>()</span>
from django import forms class AddNewItemForm(forms.Form): url = forms.CharField(max_length=600) requested_price = forms.IntegerField()

Enter fullscreen mode Exit fullscreen mode

We will use beautifulsoup to crawl price and title of item in given URL. After data crawled we have to convert price to float and create new object in database.

views.py

<span>from</span> <span>urllib.request</span> <span>import</span> <span>urlopen</span><span>,</span> <span>Request</span>
<span>from</span> <span>bs4</span> <span>import</span> <span>BeautifulSoup</span>
<span>def</span> <span>crawl_data</span><span>(</span><span>url</span><span>):</span>
<span># User Agent is to prevent 403 Forbidden Error </span> <span>req</span> <span>=</span> <span>Request</span><span>(</span><span>url</span><span>,</span> <span>headers</span><span>=</span><span>{</span><span>'</span><span>User-Agent</span><span>'</span><span>:</span> <span>'</span><span>Mozilla/5.0</span><span>'</span><span>})</span>
<span>html</span> <span>=</span> <span>urlopen</span><span>(</span><span>req</span><span>).</span><span>read</span><span>()</span>
<span>bs</span> <span>=</span> <span>BeautifulSoup</span><span>(</span><span>html</span><span>,</span> <span>'</span><span>html.parser</span><span>'</span><span>)</span>
<span>title</span> <span>=</span> <span>bs</span><span>.</span><span>find</span><span>(</span><span>'</span><span>h1</span><span>'</span><span>,</span> <span>id</span><span>=</span><span>"</span><span>itemTitle</span><span>"</span><span>).</span><span>get_text</span><span>().</span><span>replace</span><span>(</span><span>"</span><span>Details about</span><span>"</span><span>,</span> <span>""</span><span>)</span>
<span>price</span> <span>=</span> <span>bs</span><span>.</span><span>find</span><span>(</span><span>'</span><span>span</span><span>'</span><span>,</span> <span>id</span><span>=</span><span>"</span><span>prcIsum</span><span>"</span><span>).</span><span>get_text</span><span>()</span>
<span>clean_price</span> <span>=</span> <span>float</span><span>(</span><span>price</span><span>.</span><span>strip</span><span>().</span><span>replace</span><span>(</span><span>"</span><span>US</span><span>"</span><span>,</span> <span>""</span><span>).</span><span>replace</span><span>(</span><span>"</span><span>$</span><span>"</span><span>,</span> <span>""</span><span>))</span>
<span>return</span> <span>{</span><span>'</span><span>title</span><span>'</span><span>:</span> <span>title</span><span>,</span> <span>'</span><span>last_price</span><span>'</span><span>:</span><span>clean_price</span> <span>}</span>
<span>from</span> <span>urllib.request</span> <span>import</span> <span>urlopen</span><span>,</span> <span>Request</span>
<span>from</span> <span>bs4</span> <span>import</span> <span>BeautifulSoup</span>

<span>def</span> <span>crawl_data</span><span>(</span><span>url</span><span>):</span>
    <span># User Agent is to prevent 403 Forbidden Error </span>    <span>req</span> <span>=</span> <span>Request</span><span>(</span><span>url</span><span>,</span> <span>headers</span><span>=</span><span>{</span><span>'</span><span>User-Agent</span><span>'</span><span>:</span> <span>'</span><span>Mozilla/5.0</span><span>'</span><span>})</span>
    <span>html</span> <span>=</span> <span>urlopen</span><span>(</span><span>req</span><span>).</span><span>read</span><span>()</span>
    <span>bs</span> <span>=</span> <span>BeautifulSoup</span><span>(</span><span>html</span><span>,</span> <span>'</span><span>html.parser</span><span>'</span><span>)</span>

    <span>title</span> <span>=</span> <span>bs</span><span>.</span><span>find</span><span>(</span><span>'</span><span>h1</span><span>'</span><span>,</span> <span>id</span><span>=</span><span>"</span><span>itemTitle</span><span>"</span><span>).</span><span>get_text</span><span>().</span><span>replace</span><span>(</span><span>"</span><span>Details about</span><span>"</span><span>,</span> <span>""</span><span>)</span>
    <span>price</span> <span>=</span> <span>bs</span><span>.</span><span>find</span><span>(</span><span>'</span><span>span</span><span>'</span><span>,</span> <span>id</span><span>=</span><span>"</span><span>prcIsum</span><span>"</span><span>).</span><span>get_text</span><span>()</span>
    <span>clean_price</span> <span>=</span> <span>float</span><span>(</span><span>price</span><span>.</span><span>strip</span><span>().</span><span>replace</span><span>(</span><span>"</span><span>US</span><span>"</span><span>,</span> <span>""</span><span>).</span><span>replace</span><span>(</span><span>"</span><span>$</span><span>"</span><span>,</span> <span>""</span><span>))</span>
    <span>return</span> <span>{</span><span>'</span><span>title</span><span>'</span><span>:</span> <span>title</span><span>,</span> <span>'</span><span>last_price</span><span>'</span><span>:</span><span>clean_price</span> <span>}</span>
from urllib.request import urlopen, Request from bs4 import BeautifulSoup def crawl_data(url): # User Agent is to prevent 403 Forbidden Error req = Request(url, headers={'User-Agent': 'Mozilla/5.0'}) html = urlopen(req).read() bs = BeautifulSoup(html, 'html.parser') title = bs.find('h1', id="itemTitle").get_text().replace("Details about", "") price = bs.find('span', id="prcIsum").get_text() clean_price = float(price.strip().replace("US", "").replace("$", "")) return {'title': title, 'last_price':clean_price }

Enter fullscreen mode Exit fullscreen mode

strip() removes spaces at the beginning and at the end of the string and replace() method replaces a specified phrase with another specified phrase. So, we used these methods to get clean price and title of item.

Once data crawled successfully, it is time to create new object in database. We will use this function in form submission to crawl title and price of new item.

views.py

<span>from</span> <span>django.shortcuts</span> <span>import</span> <span>render</span><span>,</span> <span>get_object_or_404</span><span>,</span><span>HttpResponseRedirect</span>
<span>from</span> <span>.models</span> <span>import</span> <span>Item</span>
<span>from</span> <span>.forms</span> <span>import</span> <span>AddNewItemForm</span>
<span>def</span> <span>tracker_view</span><span>(</span><span>request</span><span>):</span>
<span>items</span> <span>=</span> <span>Item</span><span>.</span><span>objects</span><span>.</span><span>order_by</span><span>(</span><span>'</span><span>-id</span><span>'</span><span>)</span>
<span>form</span> <span>=</span> <span>AddNewItemForm</span><span>(</span><span>request</span><span>.</span><span>POST</span><span>)</span>
<span>if</span> <span>request</span><span>.</span><span>method</span> <span>==</span> <span>'</span><span>POST</span><span>'</span><span>:</span>
<span>if</span> <span>form</span><span>.</span><span>is_valid</span><span>():</span>
<span>url</span> <span>=</span> <span>form</span><span>.</span><span>cleaned_data</span><span>.</span><span>get</span><span>(</span><span>'</span><span>url</span><span>'</span><span>)</span>
<span>requested_price</span> <span>=</span> <span>form</span><span>.</span><span>cleaned_data</span><span>.</span><span>get</span><span>(</span><span>'</span><span>requested_price</span><span>'</span><span>)</span>
<span># crawling the data </span> <span>crawled_data</span> <span>=</span> <span>crawl_data</span><span>(</span><span>url</span><span>)</span>
<span>Item</span><span>.</span><span>objects</span><span>.</span><span>create</span><span>(</span>
<span>url</span> <span>=</span> <span>url</span><span>,</span>
<span>title</span> <span>=</span> <span>crawled_data</span><span>[</span><span>'</span><span>title</span><span>'</span><span>],</span>
<span>requested_price</span><span>=</span><span>requested_price</span><span>,</span>
<span>last_price</span><span>=</span><span>crawled_data</span><span>[</span><span>'</span><span>last_price</span><span>'</span><span>],</span>
<span>discount_price</span><span>=</span><span>'</span><span>No Discount Yet</span><span>'</span><span>,</span>
<span>)</span>
<span>return</span> <span>HttpResponseRedirect</span><span>(</span><span>''</span><span>)</span>
<span>else</span><span>:</span>
<span>form</span> <span>=</span> <span>AddNewItemForm</span><span>()</span>
<span>context</span> <span>=</span> <span>{</span>
<span>'</span><span>items</span><span>'</span><span>:</span><span>items</span><span>,</span>
<span>'</span><span>form</span><span>'</span><span>:</span><span>form</span><span>,</span>
<span>}</span>
<span>return</span> <span>render</span><span>(</span><span>request</span><span>,</span> <span>'</span><span>tracker.html</span><span>'</span><span>,</span> <span>context</span><span>)</span>
<span>from</span> <span>django.shortcuts</span> <span>import</span> <span>render</span><span>,</span> <span>get_object_or_404</span><span>,</span><span>HttpResponseRedirect</span> 
<span>from</span> <span>.models</span> <span>import</span> <span>Item</span>
<span>from</span> <span>.forms</span> <span>import</span> <span>AddNewItemForm</span>

<span>def</span> <span>tracker_view</span><span>(</span><span>request</span><span>):</span>
    <span>items</span> <span>=</span> <span>Item</span><span>.</span><span>objects</span><span>.</span><span>order_by</span><span>(</span><span>'</span><span>-id</span><span>'</span><span>)</span>
    <span>form</span> <span>=</span> <span>AddNewItemForm</span><span>(</span><span>request</span><span>.</span><span>POST</span><span>)</span>
    <span>if</span> <span>request</span><span>.</span><span>method</span> <span>==</span> <span>'</span><span>POST</span><span>'</span><span>:</span>
        <span>if</span> <span>form</span><span>.</span><span>is_valid</span><span>():</span>
            <span>url</span> <span>=</span> <span>form</span><span>.</span><span>cleaned_data</span><span>.</span><span>get</span><span>(</span><span>'</span><span>url</span><span>'</span><span>)</span>
            <span>requested_price</span> <span>=</span> <span>form</span><span>.</span><span>cleaned_data</span><span>.</span><span>get</span><span>(</span><span>'</span><span>requested_price</span><span>'</span><span>)</span>
            <span># crawling the data </span>            <span>crawled_data</span> <span>=</span> <span>crawl_data</span><span>(</span><span>url</span><span>)</span>
            <span>Item</span><span>.</span><span>objects</span><span>.</span><span>create</span><span>(</span>
            <span>url</span> <span>=</span> <span>url</span><span>,</span>
            <span>title</span> <span>=</span> <span>crawled_data</span><span>[</span><span>'</span><span>title</span><span>'</span><span>],</span>
            <span>requested_price</span><span>=</span><span>requested_price</span><span>,</span>
            <span>last_price</span><span>=</span><span>crawled_data</span><span>[</span><span>'</span><span>last_price</span><span>'</span><span>],</span>
            <span>discount_price</span><span>=</span><span>'</span><span>No Discount Yet</span><span>'</span><span>,</span>
            <span>)</span>
            <span>return</span> <span>HttpResponseRedirect</span><span>(</span><span>''</span><span>)</span>
        <span>else</span><span>:</span>
            <span>form</span> <span>=</span> <span>AddNewItemForm</span><span>()</span>
    <span>context</span> <span>=</span> <span>{</span>
        <span>'</span><span>items</span><span>'</span><span>:</span><span>items</span><span>,</span>
        <span>'</span><span>form</span><span>'</span><span>:</span><span>form</span><span>,</span>
    <span>}</span>
    <span>return</span> <span>render</span><span>(</span><span>request</span><span>,</span> <span>'</span><span>tracker.html</span><span>'</span><span>,</span> <span>context</span><span>)</span>
from django.shortcuts import render, get_object_or_404,HttpResponseRedirect from .models import Item from .forms import AddNewItemForm def tracker_view(request): items = Item.objects.order_by('-id') form = AddNewItemForm(request.POST) if request.method == 'POST': if form.is_valid(): url = form.cleaned_data.get('url') requested_price = form.cleaned_data.get('requested_price') # crawling the data crawled_data = crawl_data(url) Item.objects.create( url = url, title = crawled_data['title'], requested_price=requested_price, last_price=crawled_data['last_price'], discount_price='No Discount Yet', ) return HttpResponseRedirect('') else: form = AddNewItemForm() context = { 'items':items, 'form':form, } return render(request, 'tracker.html', context)

Enter fullscreen mode Exit fullscreen mode

Great! Now, we need to crawl the data for all objects continuously to be aware of discounts. If we do this without celery the server connection will timeout which means that a server is taking too long to reply to a data request and our application will crash.

Create tasks.py in your app and let’s handle it with celery tasks.

tasks.py

<span>import</span> <span>time</span>
<span>from</span> <span>celery</span> <span>import</span> <span>shared_task</span>
<span>from</span> <span>.models</span> <span>import</span> <span>Vehicle</span>
<span>from</span> <span>tracker.views</span> <span>import</span> <span>crawl_data</span>
<span>@shared_task</span>
<span># do something heavy </span><span>def</span> <span>track_for_discount</span><span>():</span>
<span>items</span> <span>=</span> <span>Item</span><span>.</span><span>objects</span><span>.</span><span>all</span><span>()</span>
<span>for</span> <span>item</span> <span>in</span> <span>items</span><span>:</span>
<span># crawl item url </span> <span>data</span> <span>=</span> <span>crawl_data</span><span>(</span><span>item</span><span>.</span><span>url</span><span>)</span>
<span># check for discount </span> <span>if</span> <span>data</span><span>[</span><span>'</span><span>last_price</span><span>'</span><span>]</span> <span><</span> <span>item</span><span>.</span><span>requested_price</span><span>:</span>
<span>print</span><span>(</span><span>f</span><span>'</span><span>Discount for </span><span>{</span><span>data</span><span>[</span><span>"</span><span>title</span><span>"</span><span>]</span><span>}</span><span>'</span><span>)</span>
<span># update discount field to notify user </span> <span>item_discount</span> <span>=</span> <span>Item</span><span>.</span><span>objects</span><span>.</span><span>get</span><span>(</span><span>id</span><span>=</span><span>item</span><span>.</span><span>id</span><span>)</span>
<span>item_discount</span><span>.</span><span>discount_price</span> <span>=</span> <span>f</span><span>'</span><span>DISCOUNT! The price is </span><span>{</span><span>data</span><span>[</span><span>"</span><span>last_price</span><span>"</span><span>]</span><span>}</span><span>'</span>
<span>item_discount</span><span>.</span><span>save</span><span>()</span>
<span>while</span> <span>True</span><span>:</span>
<span>track_for_discount</span><span>()</span>
<span>time</span><span>.</span><span>sleep</span><span>(</span><span>15</span><span>)</span>
<span>import</span> <span>time</span>
<span>from</span> <span>celery</span> <span>import</span> <span>shared_task</span>
<span>from</span> <span>.models</span> <span>import</span> <span>Vehicle</span>
<span>from</span> <span>tracker.views</span> <span>import</span> <span>crawl_data</span>

<span>@shared_task</span>
<span># do something heavy </span><span>def</span> <span>track_for_discount</span><span>():</span>
    <span>items</span> <span>=</span> <span>Item</span><span>.</span><span>objects</span><span>.</span><span>all</span><span>()</span>
    <span>for</span> <span>item</span> <span>in</span> <span>items</span><span>:</span>
        <span># crawl item url </span>        <span>data</span> <span>=</span> <span>crawl_data</span><span>(</span><span>item</span><span>.</span><span>url</span><span>)</span>
        <span># check for discount </span>        <span>if</span> <span>data</span><span>[</span><span>'</span><span>last_price</span><span>'</span><span>]</span> <span><</span> <span>item</span><span>.</span><span>requested_price</span><span>:</span>
            <span>print</span><span>(</span><span>f</span><span>'</span><span>Discount for </span><span>{</span><span>data</span><span>[</span><span>"</span><span>title</span><span>"</span><span>]</span><span>}</span><span>'</span><span>)</span>
            <span># update discount field to notify user </span>            <span>item_discount</span> <span>=</span> <span>Item</span><span>.</span><span>objects</span><span>.</span><span>get</span><span>(</span><span>id</span><span>=</span><span>item</span><span>.</span><span>id</span><span>)</span>
            <span>item_discount</span><span>.</span><span>discount_price</span> <span>=</span> <span>f</span><span>'</span><span>DISCOUNT! The price is </span><span>{</span><span>data</span><span>[</span><span>"</span><span>last_price</span><span>"</span><span>]</span><span>}</span><span>'</span>
            <span>item_discount</span><span>.</span><span>save</span><span>()</span>      
<span>while</span> <span>True</span><span>:</span>
    <span>track_for_discount</span><span>()</span>
    <span>time</span><span>.</span><span>sleep</span><span>(</span><span>15</span><span>)</span>  
import time from celery import shared_task from .models import Vehicle from tracker.views import crawl_data @shared_task # do something heavy def track_for_discount(): items = Item.objects.all() for item in items: # crawl item url data = crawl_data(item.url) # check for discount if data['last_price'] < item.requested_price: print(f'Discount for {data["title"]}') # update discount field to notify user item_discount = Item.objects.get(id=item.id) item_discount.discount_price = f'DISCOUNT! The price is {data["last_price"]}' item_discount.save() while True: track_for_discount() time.sleep(15)

Enter fullscreen mode Exit fullscreen mode

@shared_task will create the independent instance of the task for each app, making task reusable. This makes the @shared_task decorator useful for libraries and reusable apps, since they will not have access to the app of the user.

We are simply crawling data every 15 seconds and comparing last price with requested price. If last price is smaller than requested price then we are updating the discount price field.

What if price will increase again?

<span>@shared_task</span>
<span>def</span> <span>track_for_not_discount</span><span>():</span>
<span>items</span> <span>=</span> <span>Item</span><span>.</span><span>objects</span><span>.</span><span>all</span><span>()</span>
<span>for</span> <span>item</span> <span>in</span> <span>items</span><span>:</span>
<span>data</span> <span>=</span> <span>crawl_data</span><span>(</span><span>item</span><span>.</span><span>url</span><span>)</span>
<span>if</span> <span>data</span><span>[</span><span>"</span><span>last_price</span><span>"</span><span>]</span> <span>></span> <span>item</span><span>.</span><span>requested_price</span><span>:</span>
<span>print</span><span>(</span><span>f</span><span>'</span><span>Discount finished for </span><span>{</span><span>data</span><span>[</span><span>"</span><span>title</span><span>"</span><span>]</span><span>}</span><span>'</span><span>)</span>
<span>item_discount_finished</span> <span>=</span> <span>Item</span><span>.</span><span>objects</span><span>.</span><span>get</span><span>(</span><span>id</span><span>=</span><span>item</span><span>.</span><span>id</span><span>)</span>
<span>item_discount_finished</span><span>.</span><span>discount_price</span> <span>=</span> <span>'</span><span>No Discount Yet</span><span>'</span>
<span>item_discount_finished</span><span>.</span><span>save</span><span>()</span>
<span>@shared_task</span>
<span>def</span> <span>track_for_not_discount</span><span>():</span>
    <span>items</span> <span>=</span> <span>Item</span><span>.</span><span>objects</span><span>.</span><span>all</span><span>()</span>
    <span>for</span> <span>item</span> <span>in</span> <span>items</span><span>:</span>
        <span>data</span> <span>=</span> <span>crawl_data</span><span>(</span><span>item</span><span>.</span><span>url</span><span>)</span>
        <span>if</span> <span>data</span><span>[</span><span>"</span><span>last_price</span><span>"</span><span>]</span> <span>></span> <span>item</span><span>.</span><span>requested_price</span><span>:</span>
            <span>print</span><span>(</span><span>f</span><span>'</span><span>Discount finished for </span><span>{</span><span>data</span><span>[</span><span>"</span><span>title</span><span>"</span><span>]</span><span>}</span><span>'</span><span>)</span>
            <span>item_discount_finished</span> <span>=</span> <span>Item</span><span>.</span><span>objects</span><span>.</span><span>get</span><span>(</span><span>id</span><span>=</span><span>item</span><span>.</span><span>id</span><span>)</span>
            <span>item_discount_finished</span><span>.</span><span>discount_price</span> <span>=</span> <span>'</span><span>No Discount Yet</span><span>'</span>
            <span>item_discount_finished</span><span>.</span><span>save</span><span>()</span>
@shared_task def track_for_not_discount(): items = Item.objects.all() for item in items: data = crawl_data(item.url) if data["last_price"] > item.requested_price: print(f'Discount finished for {data["title"]}') item_discount_finished = Item.objects.get(id=item.id) item_discount_finished.discount_price = 'No Discount Yet' item_discount_finished.save()

Enter fullscreen mode Exit fullscreen mode

Great! Now, it will possible to track discounts properly. You can add one more function which will detect closer prices and notify user about it. For instance, if item price is 100$ and requested price is 97$. But let’s keep it simple for now.

Finally, we can create our template.

tracker.html

{% extends 'base.html' %}
{% block content %}
<span><form</span> <span>method=</span><span>"POST"</span><span>></span>
{% csrf_token %}
{{form.as_p}}
<span><button</span> <span>class=</span><span>"btn btn-primary"</span> <span>type=</span><span>"submit"</span><span>></span>Send<span></button></span>
<span></form></span>
<span><table</span> <span>class=</span><span>"table"</span><span>></span>
<span><thead></span>
<span><tr></span>
<span><th</span> <span>scope=</span><span>"col"</span><span>></span>Title<span></th></span>
<span><th</span> <span>scope=</span><span>"col"</span><span>></span>Requested Price<span></th></span>
<span><th</span> <span>scope=</span><span>"col"</span><span>></span>Last Price<span></th></span>
<span><th</span> <span>scope=</span><span>"col"</span><span>></span>Discount Price<span></th></span>
<span><th</span> <span>scope=</span><span>"col"</span><span>></span>Date Created<span></th></span>
<span></tr></span>
<span></thead></span>
<span><tbody></span>
{% for item in items %}
<span><tr></span>
<span><td></span>{{item.title}}<span></td></span>
<span><td></span>{{item.requested_price}}<span></td></span>
<span><td></span>{{item.last_price}}<span></td></span>
<span><td></span>{{item.discount_price}}<span></td></span>
<span><td></span>{{item.date}}<span></td></span>
<span></tr></span>
{% endfor %}
<span></tbody></span>
<span></table></span>
{% endblock %}
{% extends 'base.html' %}

{% block content %}
<span><form</span> <span>method=</span><span>"POST"</span><span>></span>
    {% csrf_token %}
    {{form.as_p}}
    <span><button</span> <span>class=</span><span>"btn btn-primary"</span> <span>type=</span><span>"submit"</span><span>></span>Send<span></button></span>
<span></form></span>
<span><table</span> <span>class=</span><span>"table"</span><span>></span>
    <span><thead></span>
      <span><tr></span>
        <span><th</span> <span>scope=</span><span>"col"</span><span>></span>Title<span></th></span>
        <span><th</span> <span>scope=</span><span>"col"</span><span>></span>Requested Price<span></th></span>
        <span><th</span> <span>scope=</span><span>"col"</span><span>></span>Last Price<span></th></span>
        <span><th</span> <span>scope=</span><span>"col"</span><span>></span>Discount Price<span></th></span>
        <span><th</span> <span>scope=</span><span>"col"</span><span>></span>Date Created<span></th></span>
      <span></tr></span>
    <span></thead></span>
    <span><tbody></span>
    {% for item in items %}
      <span><tr></span>
        <span><td></span>{{item.title}}<span></td></span>
        <span><td></span>{{item.requested_price}}<span></td></span>
        <span><td></span>{{item.last_price}}<span></td></span>
        <span><td></span>{{item.discount_price}}<span></td></span>
        <span><td></span>{{item.date}}<span></td></span>
      <span></tr></span>
    {% endfor %}  
    <span></tbody></span>
  <span></table></span>
  {% endblock %}  
{% extends 'base.html' %} {% block content %} <form method="POST"> {% csrf_token %} {{form.as_p}} <button class="btn btn-primary" type="submit">Send</button> </form> <table class="table"> <thead> <tr> <th scope="col">Title</th> <th scope="col">Requested Price</th> <th scope="col">Last Price</th> <th scope="col">Discount Price</th> <th scope="col">Date Created</th> </tr> </thead> <tbody> {% for item in items %} <tr> <td>{{item.title}}</td> <td>{{item.requested_price}}</td> <td>{{item.last_price}}</td> <td>{{item.discount_price}}</td> <td>{{item.date}}</td> </tr> {% endfor %} </tbody> </table> {% endblock %}

Enter fullscreen mode Exit fullscreen mode

Well, you can improve the project by adding email functionality so Django will send email about discounts. Take a look How to Send Email in a Django App

Feel free to contribute the project 🙂

You can clone this project from my GitHub repository below

thepylot / Price-Tracker-Application

Discount Tracker app with Python/Django

Price-Tracker-Application

Discount Tracker app with Python/Django

Getting Started

This tutorial works on Python 3+ and Django 2+.

Install dependencies:

python3 -m pip3 install -r requirements.txt
python3 -m pip3 install -r requirements.txt
python3 -m pip3 install -r requirements.txt

start Celery worker:

celery -A pricetracker worker -l info
celery -A pricetracker worker -l info
celery -A pricetracker worker -l info

and run following commands:

python3 manage.py makemigrations tracker
python3 manage.py migrate
python3 manage.py runserver
python3 manage.py makemigrations tracker
python3 manage.py migrate
python3 manage.py runserver
python3 manage.py makemigrations tracker python3 manage.py migrate python3 manage.py runserver

View on GitHub

Mission Accomplished!

That’s it! Make sure you are following me on social media and see you in next post soon DEVs!

Reverse Python
Instagram
Twitter

原文链接:Price Tracker Application with Django | Track Discounts

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
The God only arranges a happy ending. If it is not happy, it means that it is not the final result.
上天只会安排的快乐的结局。如果不快乐,说明还不是最后结局
评论 抢沙发

请登录后发表评论

    暂无评论内容