├── .gitattributes
├── .idea
├── ProductAnalysis.iml
├── inspectionProfiles
│ └── profiles_settings.xml
├── markdown-navigator.xml
├── markdown-navigator
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
└── vcs.xml
├── ProductAnalysis
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
├── README.md
├── app
├── ProductData
│ ├── __init__.py
│ ├── config.py
│ ├── items.py
│ ├── log_helper.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── redis_helper.py
│ ├── run.py
│ ├── settings.py
│ ├── spiders
│ │ ├── __init__.py
│ │ └── zol.py
│ └── sql_hepler.py
├── __init__.py
├── admin.py
├── apps.py
├── migrations
│ ├── 0001_initial.py
│ └── __init__.py
├── models.py
├── scrapy.cfg
├── search_indexes.py
├── templatetags
│ ├── __init__.py
│ └── product_tags.py
├── tests.py
└── views.py
├── doc
├── django1.8中文文档.pdf
├── django之查询.md
├── python必备知识点.md
├── scrapy知识汇总.md
└── 数据分析之pandas.md
├── hepler
├── __init__.py
├── config.py
├── file_hepler.py
├── log_helper.py
├── make_bokeh_hepler.py
├── make_gif_helper.py
├── make_plot_helper.py
└── redis_helper.py
├── manage.py
├── requirements.txt
├── screen
├── Scrapy流程.png
├── index.png
└── search.png
├── static
├── css
│ ├── bokeh-0.12.6.min.css
│ ├── bootstrap.min.css
│ ├── font-awesome.min.css
│ ├── light-bootstrap-dashboard.css
│ ├── loading.css
│ └── main.css
├── file
│ └── product_info.json
├── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ └── fontawesome-webfont.woff
├── gif
│ ├── loading.gif
│ └── loading1.gif
├── img
│ ├── bg-1.jpg
│ ├── bg-2.jpg
│ ├── load.gif
│ ├── logo.png
│ └── sidebar.jpg
├── js
│ ├── bokeh-0.12.6.min.js
│ ├── bootstrap.min.js
│ ├── jquery-1.11.1.min.js
│ ├── jquery.backstretch.min.js
│ ├── jquery.simple-text-rotator.min.js
│ ├── loading.js
│ ├── main.js
│ ├── search.js
│ └── wow.min.js
└── upload
│ ├── no_good_comments.png
│ ├── no_hot.png
│ └── no_overview.png
└── templates
├── analysis.html
├── base.html
├── index.html
└── search
├── indexes
└── app
│ └── productinfo_text.txt
└── search.html
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js linguist-language=Python
2 | *.css linguist-language=Python
3 | *.html linguist-language=Python
4 |
--------------------------------------------------------------------------------
/.idea/ProductAnalysis.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ProductAnalysis/__init__.py:
--------------------------------------------------------------------------------
1 | import pymysql
2 | pymysql.install_as_MySQLdb()
--------------------------------------------------------------------------------
/ProductAnalysis/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for ProductAnalysis project.
3 |
4 | Generated by 'django-admin startproject' using Django 1.10.6.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.10/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/1.10/ref/settings/
11 | """
12 |
13 | import os
14 |
15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17 |
18 |
19 | # Quick-start development settings - unsuitable for production
20 | # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
21 |
22 | # SECURITY WARNING: keep the secret key used in production secret!
23 | SECRET_KEY = 'gq(b4zs=z+ckpyfol7*qbzm60$2i)j3jg66mf5f0%1w=d4$vsr'
24 |
25 | # SECURITY WARNING: don't run with debug turned on in production!
26 | DEBUG = True
27 |
28 | ALLOWED_HOSTS = ["127.0.0.1","192.168.0.123"]
29 |
30 |
31 | # Application definition
32 |
33 | INSTALLED_APPS = [
34 | 'django.contrib.admin',
35 | 'django.contrib.auth',
36 | 'django.contrib.contenttypes',
37 | 'django.contrib.sessions',
38 | 'django.contrib.messages',
39 | 'django.contrib.staticfiles',
40 | 'app',
41 | 'haystack',
42 | ]
43 |
44 | MIDDLEWARE = [
45 | 'django.middleware.security.SecurityMiddleware',
46 | 'django.contrib.sessions.middleware.SessionMiddleware',
47 | 'django.middleware.common.CommonMiddleware',
48 | 'django.middleware.csrf.CsrfViewMiddleware',
49 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
50 | 'django.contrib.messages.middleware.MessageMiddleware',
51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
52 | ]
53 |
54 | ROOT_URLCONF = 'ProductAnalysis.urls'
55 |
56 | TEMPLATES = [
57 | {
58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
59 | 'DIRS': [os.path.join(BASE_DIR, 'templates')]
60 | ,
61 | 'APP_DIRS': True,
62 | 'OPTIONS': {
63 | 'context_processors': [
64 | 'django.template.context_processors.debug',
65 | 'django.template.context_processors.request',
66 | 'django.contrib.auth.context_processors.auth',
67 | 'django.contrib.messages.context_processors.messages',
68 | ],
69 | },
70 | },
71 | ]
72 |
73 | WSGI_APPLICATION = 'ProductAnalysis.wsgi.application'
74 |
75 |
76 | # Database
77 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases
78 |
79 | DATABASES = {
80 | 'default': {
81 | 'ENGINE': 'django.db.backends.mysql',
82 | 'NAME': 'productanalysis',
83 | 'USER': 'root',
84 | 'PASSWORD': '',
85 | 'HOST': '',
86 | 'PORT': '',
87 | }
88 | }
89 |
90 |
91 | # Password validation
92 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
93 |
94 | AUTH_PASSWORD_VALIDATORS = [
95 | {
96 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
97 | },
98 | {
99 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
100 | },
101 | {
102 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
103 | },
104 | {
105 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
106 | },
107 | ]
108 |
109 |
110 | # Internationalization
111 | # https://docs.djangoproject.com/en/1.10/topics/i18n/
112 |
113 | LANGUAGE_CODE = 'zh-Hans'
114 |
115 | TIME_ZONE = 'Asia/Shanghai'
116 |
117 | USE_I18N = True
118 |
119 | USE_L10N = True
120 |
121 | USE_TZ = True
122 |
123 |
124 | # Static files (CSS, JavaScript, Images)
125 | # https://docs.djangoproject.com/en/1.10/howto/static-files/
126 |
127 | # 添加haystack配置
128 | HAYSTACK_CONNECTIONS = {
129 | 'default': {
130 | 'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine',
131 | 'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
132 | }
133 | }
134 | HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
135 |
136 |
137 | STATIC_URL = '/static/'
138 | STATICFILES_DIRS=(
139 | os.path.join(BASE_DIR,'static'),
140 | )
141 |
--------------------------------------------------------------------------------
/ProductAnalysis/urls.py:
--------------------------------------------------------------------------------
1 | """ProductAnalysis URL Configuration
2 |
3 | The `urlpatterns` list routes URLs to views. For more information please see:
4 | https://docs.djangoproject.com/en/1.10/topics/http/urls/
5 | Examples:
6 | Function views
7 | 1. Add an import: from my_app import views
8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
9 | Class-based views
10 | 1. Add an import: from other_app.views import Home
11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
12 | Including another URLconf
13 | 1. Import the include() function: from django.conf.urls import url, include
14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
15 | """
16 | from django.conf.urls import url,include
17 | from django.contrib import admin
18 | from app import views
19 |
20 | urlpatterns = [
21 | url(r'^admin/', admin.site.urls),
22 | url(r'^$',views.index),
23 | url(r'^search/', include('haystack.urls')),
24 | url(r'^make_plot/',views.make_plot),
25 | url(r'^get_analysis_res/',views.make_bokeh),
26 | ]
27 |
--------------------------------------------------------------------------------
/ProductAnalysis/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for ProductAnalysis project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 |
12 | from django.core.wsgi import get_wsgi_application
13 |
14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ProductAnalysis.settings")
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ProductAnalysis
2 |
3 | 项目说明:
4 |
5 | 主要是用来练习数据抓取和数据分析,并通过前端页面来显示分析结果,目前只抓取zol中的手机,笔记本,数码相机
6 |
7 | 相关知识点放在doc目录,更详细的还是要去官方文档查看
8 |
9 | 请用IE或Chrome浏览器打开,本人不是做前端的,好像别的浏览器打开有问题
10 |
11 | 使用步骤:
12 |
13 | 1.先mysql数据创建数据库名为:productanalysis,数据库编码一定要设置成utf-8
14 |
15 | 2.同步数据库:python manage.py makemigrations 和 python manage.py migrate
16 |
17 | 3.进入app/ProductData,执行:run.py
18 |
19 | 等待执行结束就可以了
20 |
21 | 用到技术点:
22 |
23 | 1.通过django-haystack来实现全文搜索
24 |
25 | 2.直接通过scrapy-redis来实现去重
26 |
27 | 3.前端数据分析展示通过bokeh来实现
28 |
29 | 4.数据分析用的是pandas
30 |
31 | 5.通过redis进行缓存处理
32 |
33 | ## 首页
34 | 
35 | ## 分析结果页
36 | 
37 |
--------------------------------------------------------------------------------
/app/ProductData/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/app/ProductData/__init__.py
--------------------------------------------------------------------------------
/app/ProductData/config.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 | productanalysis_db_config = {
4 | 'host': 'localhost',
5 | 'port': 3306,
6 | 'user': 'root',
7 | 'password': '',
8 | 'charset': 'utf8',
9 | }
10 |
11 | productanalysis_db = 'productanalysis'
12 |
13 | product_info = 'product_info'
14 |
15 |
16 | # 请求的header
17 | headers = {
18 | "Host": "detail.zol.com.cn",
19 | "Connection": "keep-alive",
20 | "Pragma": "no-cache",
21 | "Cache-Control": "no-cache",
22 | "Upgrade-Insecure-Requests": 1,
23 | "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
24 | "Accept": "text/html, application/xhtml + xml, application/xml;q = 0.9, image/webp, * / *;q = 0.8",
25 | "Accept-Encoding": "gzip, deflate, sdch",
26 | "Accept-Language": "zh-CN,zh;q=0.8",
27 | }
28 |
29 | # redis
30 | redis_pass = ''
31 | redis_host = '127.0.0.1'
32 | redis_port = '6379'
33 |
34 | REDIS_PRODUCT_INFO_EXPIRES_SECONDES = 86400 # 设置存储过期时间,单位为秒
--------------------------------------------------------------------------------
/app/ProductData/items.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Define here the models for your scraped items
4 | #
5 | # See documentation in:
6 | # http://doc.scrapy.org/en/latest/topics/items.html
7 |
8 | import scrapy
9 |
10 |
11 | class ProductdataItem(scrapy.Item):
12 | # define the fields for your item here like:
13 | # name = scrapy.Field()
14 | """
15 | p_url
16 | p_title
17 | p_c_score
18 | p_img
19 | p_prices
20 | p_c_all_nums
21 | p_comments
22 | p_c_times
23 | p_id
24 | p_price_trend
25 | """
26 |
27 | p_url = scrapy.Field()
28 | p_title = scrapy.Field()
29 | p_c_score = scrapy.Field()
30 | p_img = scrapy.Field()
31 | p_id = scrapy.Field()
32 | p_prices = scrapy.Field()
33 | p_c_all_nums = scrapy.Field()
34 | p_comments = scrapy.Field()
35 | p_c_times = scrapy.Field()
36 | p_price_trend = scrapy.Field()
37 |
38 |
--------------------------------------------------------------------------------
/app/ProductData/log_helper.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 | import logging
4 | import datetime
5 |
6 | def log(msg,level = logging.DEBUG):
7 | """
8 | 自定义输出日志
9 | :param msg: 日志信息
10 | :param level: 日志等级
11 | :return:
12 | """
13 | logging.log(level,msg)
14 |
15 | print("log时间[%s]-log等级[%s]-log信息[%s]" % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),level,msg))
16 |
--------------------------------------------------------------------------------
/app/ProductData/middlewares.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Define here the models for your spider middleware
4 | #
5 | # See documentation in:
6 | # http://doc.scrapy.org/en/latest/topics/spider-middleware.html
7 |
8 | from scrapy import signals
9 | from .settings import PROXIES
10 | import random
11 |
12 | class RandomProxy(object):
13 | """
14 | 设置代理IP
15 | """
16 | def process_request(self,request,spider):
17 | proxy = random.choice(PROXIES)
18 | request.meta['proxy'] = "http://" + proxy['ip_port']
19 |
20 |
--------------------------------------------------------------------------------
/app/ProductData/pipelines.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Define your item pipelines here
4 | #
5 | # Don't forget to add your pipeline to the ITEM_PIPELINES setting
6 | # See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html
7 | from . import redis_helper,config
8 | from .sql_hepler import SqlHelper
9 | p_sql = SqlHelper()
10 |
11 | class ProductdataPipeline(object):
12 |
13 | def process_item(self, item, spider):
14 | """
15 | 将数据保存到mysql数据库
16 | :param item:
17 | :param spider:
18 | :return:
19 | """
20 | item = dict(item)
21 | product_info = {
22 | 'p_url': item['p_url'],
23 | 'p_title': item['p_title'],
24 | 'p_img': item['p_img'],
25 | 'p_id': item['p_id'],
26 | 'p_prices': item['p_prices'],
27 | 'p_c_score': item['p_c_score'],
28 | 'p_comments': item['p_comments'],
29 | 'p_c_all_nums': item['p_c_all_nums'],
30 | 'p_c_time': item['p_c_times'],
31 | 'p_price_trend': item['p_price_trend'],
32 | }
33 | p_sql.insert_json(product_info, config.product_info)
34 |
35 | return item
--------------------------------------------------------------------------------
/app/ProductData/redis_helper.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 | import redis
4 | from . import config
5 | import json
6 |
7 | p_redis = redis.StrictRedis(host=config.redis_host, port=config.redis_port)
8 |
9 | def save_to_redis(item):
10 | """
11 | 保存数据的redis
12 | :param data: 需要保存的数据
13 | :return:
14 | """
15 | p_redis.setex(item['p_id'],config.REDIS_PRODUCT_INFO_EXPIRES_SECONDES,json.dumps(item))
--------------------------------------------------------------------------------
/app/ProductData/run.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 | from scrapy import cmdline
4 |
5 |
6 | def cmd():
7 | cmdline.execute("scrapy crawl zol".split())
8 |
9 | if __name__ == '__main__':
10 | cmd()
--------------------------------------------------------------------------------
/app/ProductData/settings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Scrapy settings for ProductData project
4 | #
5 | # For simplicity, this file contains only settings considered important or
6 | # commonly used. You can find more settings consulting the documentation:
7 | #
8 | # http://doc.scrapy.org/en/latest/topics/settings.html
9 | # http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
10 | # http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
11 |
12 | BOT_NAME = 'ProductData'
13 |
14 | SPIDER_MODULES = ['ProductData.spiders']
15 | NEWSPIDER_MODULE = 'ProductData.spiders'
16 |
17 | # 使用了scrapy-redis里的去重组件,不使用scrapy默认的去重
18 | DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
19 | # 使用了scrapy-redis里的调度器组件,不实用scrapy默认的调度器
20 | SCHEDULER = "scrapy_redis.scheduler.Scheduler"
21 | # 使用队列形式
22 | SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderQueue"
23 | # 允许暂停,redis请求记录不丢失
24 | SCHEDULER_PERSIST = True
25 |
26 | # Crawl responsibly by identifying yourself (and your website) on the user-agent
27 | #USER_AGENT = 'ProductData (+http://www.yourdomain.com)'
28 |
29 | # Obey robots.txt rules
30 | ROBOTSTXT_OBEY = False
31 |
32 | # Configure maximum concurrent requests performed by Scrapy (default: 16)
33 | #CONCURRENT_REQUESTS = 32
34 |
35 | # Configure a delay for requests for the same website (default: 0)
36 | # See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay
37 | # See also autothrottle settings and docs
38 | # DOWNLOAD_DELAY = 2
39 | # The download delay setting will honor only one of:
40 | #CONCURRENT_REQUESTS_PER_DOMAIN = 16
41 | #CONCURRENT_REQUESTS_PER_IP = 16
42 |
43 | # Disable cookies (enabled by default)
44 | COOKIES_ENABLED = False
45 |
46 | # Disable Telnet Console (enabled by default)
47 | #TELNETCONSOLE_ENABLED = False
48 |
49 | # Override the default request headers:
50 | #DEFAULT_REQUEST_HEADERS = {
51 | # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
52 | # 'Accept-Language': 'en',
53 | #}
54 |
55 | # Enable or disable spider middlewares
56 | # See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
57 | #SPIDER_MIDDLEWARES = {
58 | # 'ProductData.middlewares.ProductdataSpiderMiddleware': 543,
59 | #}
60 |
61 | # Enable or disable downloader middlewares
62 | # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
63 | # DOWNLOADER_MIDDLEWARES = {
64 | # 'ProductData.middlewares.RandomProxy': 100,
65 | # }
66 |
67 | # Enable or disable extensions
68 | # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
69 | #EXTENSIONS = {
70 | # 'scrapy.extensions.telnet.TelnetConsole': None,
71 | #}
72 |
73 | # Configure item pipelines
74 | # See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
75 | ITEM_PIPELINES = {
76 | 'ProductData.pipelines.ProductdataPipeline': 300,
77 | }
78 |
79 | # Enable and configure the AutoThrottle extension (disabled by default)
80 | # See http://doc.scrapy.org/en/latest/topics/autothrottle.html
81 | #AUTOTHROTTLE_ENABLED = True
82 | # The initial download delay
83 | #AUTOTHROTTLE_START_DELAY = 5
84 | # The maximum download delay to be set in case of high latencies
85 | #AUTOTHROTTLE_MAX_DELAY = 60
86 | # The average number of requests Scrapy should be sending in parallel to
87 | # each remote server
88 | #AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
89 | # Enable showing throttling stats for every response received:
90 | #AUTOTHROTTLE_DEBUG = False
91 |
92 | # Enable and configure HTTP caching (disabled by default)
93 | # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
94 | #HTTPCACHE_ENABLED = True
95 | #HTTPCACHE_EXPIRATION_SECS = 0
96 | #HTTPCACHE_DIR = 'httpcache'
97 | #HTTPCACHE_IGNORE_HTTP_CODES = []
98 | #HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
99 |
100 | REDIS_HOST = '127.0.0.1'
101 | REDIS_PORT = 6379
102 |
103 | # 代理IP
104 | PROXIES = [
105 | {'ip_port': '119.29.126.115:80'},
106 | {'ip_port': '36.97.145.29:9797'},
107 | {'ip_port': '222.52.142.242:8080'},
108 | {'ip_port': '120.132.71.212:80'},
109 | {'ip_port': '111.155.116.235:8123'},
110 | {'ip_port': '222.33.192.238:8118'},
111 | {'ip_port': '60.174.237.43:9999'},
112 | {'ip_port': '183.56.177.130:808'},
113 | {'ip_port': '111.155.124.73 8123'},
114 | ]
115 |
116 |
--------------------------------------------------------------------------------
/app/ProductData/spiders/__init__.py:
--------------------------------------------------------------------------------
1 | # This package will contain the spiders of your Scrapy project
2 | #
3 | # Please refer to the documentation for information on how to create and manage
4 | # your spiders.
5 |
--------------------------------------------------------------------------------
/app/ProductData/spiders/zol.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import logging
3 | import re
4 |
5 | import scrapy
6 | from scrapy import Request
7 | # 使用redis去重
8 | from scrapy.dupefilters import RFPDupeFilter
9 |
10 | from .. import config,log_helper
11 | from ..items import ProductdataItem
12 |
13 | class ZolSpider(scrapy.Spider):
14 | name = 'zol'
15 | allowed_domains = ['detail.zol.com.cn']
16 | base_url = "http://detail.zol.com.cn"
17 | urls = ["/notebook_index/subcate16_0_list_1_0_1_2_0_1.html","/cell_phone_index/subcate57_0_list_1_0_1_2_0_1.html","/digital_camera_index/subcate15_0_list_1_0_1_2_0_1.html"]
18 |
19 | def start_requests(self):
20 |
21 | for url in self.urls:
22 | yield Request(
23 | url=self.base_url + url,
24 | headers=config.headers,
25 | method='GET',
26 | callback=self.get_product_list,
27 | )
28 |
29 | def get_product_list(self,response):
30 | """
31 | 获取每个产品类别下的所有产品
32 | :param response:
33 | :return:
34 | """
35 | p_urls = response.xpath("//div[@class='pic-mode-box']/ul/li/a/@href").extract()
36 | p_titles = response.xpath("//div[@class='pic-mode-box']/ul/li/h3/a/text()").extract()
37 | p_scores = response.xpath("//div[@class='comment-row']/span[@class='score']/text()").extract()
38 |
39 | # 执行分页
40 | p_pages = response.xpath("//div[@class='pagebar']/a/@href").extract()
41 |
42 | try:
43 | p_nums = len(p_urls)
44 | for p_url in p_urls:
45 | # p_url = p_urls[i]
46 | res = re.findall(r'(\d+)', p_url)
47 | if len(res) > 1:
48 | p_id = res[-2]
49 | else:
50 | p_id = res[-1]
51 |
52 | product_info = {
53 | 'p_url':self.base_url + p_url,
54 | 'p_id':p_id
55 | }
56 |
57 | yield Request(
58 | url=self.base_url + p_url,
59 | headers=config.headers,
60 | method='GET',
61 | meta=product_info,
62 | callback=self.get_product_info,
63 | )
64 |
65 | for p in p_pages:
66 | # 分页回调
67 | yield Request(
68 | url=self.base_url + p,
69 | headers=config.headers,
70 | method='GET',
71 | callback=self.get_product_list,
72 | )
73 |
74 | except Exception as e:
75 |
76 | log_helper.log(e, logging.WARNING)
77 |
78 | def get_product_info(self,response):
79 | """
80 | 获取每个产品信息
81 | :param response:
82 | :return:
83 | """
84 | p_title = "".join(response.xpath("//div[@class='page-title clearfix']/h1/text()").extract())
85 | p_c_score = "".join(response.xpath("//div[@class='product-comment']/div/div/div/strong/text()").extract())
86 | comment_url = "".join(response.xpath("//ul[@class='nav']/li/a[@class='ol-comment']/@href").extract())
87 | p_img = "".join(response.xpath("//div[@class='bigpic']/a/img/@src").extract())
88 | p_prices = response.xpath("//div[@class='product-merchant-price clearfix']/ul/li/strong/a/text() | //div[@class='product-merchant-price clearfix']/ul/li/span/a/text()").extract()
89 |
90 | # 参考价
91 | p_price_retain = ",".join(response.xpath("//div[@class='product-price-info']/div/span/b[@class='price-type price-retain']/text()").extract())
92 | # 格式 ['[["7.08","7.09","7.10","7.11","7.14"],[2999,2999,2999,2999,2999],2999,2999]']
93 |
94 | p_price_trend = response.xpath("//div[@class='product-price-info']/div/span/b[@class='price-type price-retain']/@chart-data").extract()
95 |
96 | if p_price_trend:
97 | p_price_trend = p_price_trend[0]
98 |
99 | response.meta['p_img'] = p_img
100 | response.meta['p_title'] = p_title
101 | response.meta['p_c_score'] = p_c_score
102 | response.meta['p_prices'] = p_prices if p_prices else p_price_retain
103 | response.meta['p_price_trend'] = p_price_trend
104 |
105 | yield Request(
106 | url=self.base_url + comment_url,
107 | headers=config.headers,
108 | method='GET',
109 | meta=response.meta,
110 | callback=self.get_product_comment,
111 | )
112 |
113 | def get_product_comment(self,response):
114 | """
115 | 获取产品评论信息
116 | :param response:
117 | :return:
118 | """
119 | p_c_all_nums = response.xpath("//div[@class='good-words']/ul/li/a/text() | //div[@class='good-words']/ul/li/a/span/text()").extract()
120 | # 所有评论
121 | p_comments = response.xpath("//div[@class='comment-tabs']/div/label/text() | //div[@class='comment-tabs']/div/label/em/text()").extract()
122 |
123 | # 获取评论日期 这里只获取第一页 获取最新评论日期 来判断这个手机当前的活跃度
124 | p_c_times = response.xpath("//div[@class='comments-list-content']/div/span[@class='date']/text()").extract()
125 |
126 | """
127 | p_url
128 | p_title
129 | p_c_score
130 | p_img
131 | p_prices
132 | p_c_all_nums
133 | p_comments
134 | p_c_times
135 | p_id
136 | p_price_trend
137 | """
138 | product_info = response.meta
139 |
140 | item = ProductdataItem()
141 | item['p_url'] = product_info['p_url']
142 | item['p_title'] = product_info['p_title']
143 | item['p_c_score'] = product_info['p_c_score']
144 | item['p_prices'] = "".join(product_info['p_prices']) if product_info['p_prices'] else "暂无报价"
145 | item['p_img'] = product_info['p_img']
146 | item['p_id'] = product_info['p_id']
147 | item['p_c_all_nums'] = "".join(p_c_all_nums)
148 | item['p_comments'] = "".join(p_comments).replace(" ","").replace("\r\n","")
149 | item['p_c_times'] = ",".join(p_c_times)
150 | item['p_price_trend'] = product_info['p_price_trend']
151 |
152 | yield item
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
--------------------------------------------------------------------------------
/app/ProductData/sql_hepler.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 | import logging
4 | from . import config
5 | from . import log_helper
6 | import pymysql
7 |
8 | class SqlHelper(object):
9 | def __init__(self):
10 | self.conn = pymysql.connect(**config.productanalysis_db_config)
11 | self.cursor = self.conn.cursor()
12 | self.conn.select_db(config.productanalysis_db)
13 |
14 | def insert_json(self,data={},table_name = None):
15 | """
16 | 以json数据插入
17 | :param data:
18 | :param table_name:
19 | :return:
20 | """
21 | try:
22 | keys = []
23 | vals = []
24 | for k,v in data.items():
25 | keys.append(k)
26 | vals.append(v)
27 |
28 | val_str = ','.join(['%s']*len(vals))
29 | key_str = ','.join(keys)
30 |
31 | sql = 'insert ignore into {table} ({keys}) values({values})'.\
32 | format(keys=key_str,values=val_str,table=table_name)
33 |
34 | self.cursor.execute(sql,tuple(vals))
35 | self.conn.commit()
36 |
37 | return self.cursor.lastrowid
38 |
39 | except Exception as e:
40 | log_helper.log(e,logging.WARNING)
41 | return -1
42 |
43 | def query_one(self,command,cursor_type = 'tuple'):
44 | try:
45 | cursor = None
46 | if cursor_type == 'dict':
47 | cursor = self.conn.cursor(pymysql.cursors.DictCursor)
48 | else:
49 | cursor = self.cursor
50 |
51 | cursor.execute(command)
52 | data = cursor.fetchone()
53 | self.conn.commit()
54 |
55 | return data
56 | except Exception as e:
57 | log_helper.log(e,logging.WARNING)
58 | return None
59 |
60 | def close(self):
61 | """
62 | 关闭操作
63 | :return:
64 | """
65 | self.cursor.close()
66 | self.conn.close()
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/app/__init__.py
--------------------------------------------------------------------------------
/app/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from . import models
3 | # Register your models here.
4 |
5 | class ProductInfoAdmin(admin.ModelAdmin):
6 | list_display = ('p_title','p_c_score','p_id')
7 | search_fields = ('p_id','p_title')
8 |
9 | admin.site.register(models.ProductInfo,ProductInfoAdmin)
10 |
11 |
--------------------------------------------------------------------------------
/app/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class AppConfig(AppConfig):
5 | name = 'app'
6 |
--------------------------------------------------------------------------------
/app/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.10.6 on 2017-07-17 05:27
3 | from __future__ import unicode_literals
4 |
5 | from django.db import migrations, models
6 |
7 |
8 | class Migration(migrations.Migration):
9 |
10 | initial = True
11 |
12 | dependencies = [
13 | ]
14 |
15 | operations = [
16 | migrations.CreateModel(
17 | name='ProductInfo',
18 | fields=[
19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20 | ('p_url', models.CharField(max_length=256, verbose_name='产品url')),
21 | ('p_title', models.CharField(max_length=64, verbose_name='产品名称')),
22 | ('p_img', models.CharField(max_length=256, verbose_name='产品图片')),
23 | ('p_id', models.IntegerField(verbose_name='产品ID')),
24 | ('p_prices', models.CharField(blank=True, max_length=128, null=True, verbose_name='商品价格')),
25 | ('p_c_score', models.FloatField(blank=True, null=True, verbose_name='产品总评分')),
26 | ('p_comments', models.CharField(blank=True, max_length=128, null=True, verbose_name='产品评价')),
27 | ('p_c_all_nums', models.CharField(blank=True, max_length=256, null=True, verbose_name='概况点评内容')),
28 | ('p_c_time', models.CharField(blank=True, max_length=256, null=True, verbose_name='产品评论日期')),
29 | ('p_price_trend', models.CharField(blank=True, max_length=128, null=True, verbose_name='价格趋势')),
30 | ],
31 | options={
32 | 'verbose_name_plural': '产品信息',
33 | 'db_table': 'product_info',
34 | },
35 | ),
36 | ]
37 |
--------------------------------------------------------------------------------
/app/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/app/migrations/__init__.py
--------------------------------------------------------------------------------
/app/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 | # Create your models here.
4 | class ProductInfo(models.Model):
5 | """
6 | 产品信息
7 | """
8 | p_url = models.CharField(max_length=256,name='p_url',verbose_name='产品url')
9 | p_title = models.CharField(max_length=64,name='p_title',verbose_name='产品名称')
10 | p_img = models.CharField(max_length=256,name='p_img',verbose_name='产品图片')
11 | p_id = models.IntegerField(name='p_id',verbose_name='产品ID')
12 |
13 | p_prices= models.CharField(max_length=128,name='p_prices',verbose_name='商品价格', null=True,blank=True)
14 |
15 | p_c_score = models.FloatField(name='p_c_score', verbose_name='产品总评分', null=True,blank=True)
16 | p_comments = models.CharField(max_length=128,name='p_comments', verbose_name='产品评价', null=True,blank=True)
17 |
18 | p_c_all_nums = models.CharField(max_length=256,name='p_c_all_nums',verbose_name='概况点评内容',null=True,blank=True)
19 |
20 | p_c_time = models.CharField(max_length=256, name='p_c_time', verbose_name='产品评论日期', null=True, blank=True)
21 |
22 | p_price_trend = models.CharField(max_length=128,name='p_price_trend',verbose_name='价格趋势',null=True,blank=True)
23 |
24 | class Meta:
25 | db_table = 'product_info'
26 | verbose_name_plural = '产品信息'
27 |
28 | def __str__(self):
29 |
30 | return self.p_title
31 |
--------------------------------------------------------------------------------
/app/scrapy.cfg:
--------------------------------------------------------------------------------
1 | # Automatically created by: scrapy startproject
2 | #
3 | # For more information about the [deploy] section see:
4 | # https://scrapyd.readthedocs.org/en/latest/deploy.html
5 |
6 | [settings]
7 | default = ProductData.settings
8 |
9 | [deploy]
10 | #url = http://localhost:6800/
11 | project = ProductData
12 |
--------------------------------------------------------------------------------
/app/search_indexes.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 | from haystack import indexes
4 | from app.models import ProductInfo
5 |
6 | class ProductInfoSearch(indexes.SearchIndex,indexes.Indexable):
7 | """
8 | 全文搜索
9 | """
10 | text = indexes.CharField(document=True,use_template=True)
11 |
12 | def get_model(self):
13 |
14 | return ProductInfo
15 |
16 | def index_queryset(self, using=None):
17 |
18 | return self.get_model().objects.all()
--------------------------------------------------------------------------------
/app/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 |
--------------------------------------------------------------------------------
/app/templatetags/product_tags.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 | from django.utils.safestring import mark_safe
4 | from django import template
5 | register = template.Library()
6 |
7 | @register.simple_tag
8 | def get_search_result_list(product_list):
9 | """
10 | 获取搜索产品列表
11 | :param product_list:
12 | :return:
13 | """
14 | res = ""
15 | if product_list:
16 | for index in range(len(product_list)):
17 | if index == 0:
18 | res = '
%s' % (product_list[index].object.p_id,product_list[index].object.p_title)
19 | else:
20 | res += ' %s' % (product_list[index].object.p_id,product_list[index].object.p_title)
21 | else:
22 | res = '未找到相应的结果>'
23 |
24 | return mark_safe(res)
25 |
26 |
--------------------------------------------------------------------------------
/app/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
5 | def test():
6 |
7 | pass
--------------------------------------------------------------------------------
/app/views.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 | from django.http import JsonResponse
3 | import json
4 | from hepler import make_bokeh_hepler,make_plot_helper
5 | from app.models import ProductInfo
6 | from multiprocessing import Pool
7 | from hepler import redis_helper, log_helper
8 | import logging
9 |
10 |
11 | def index(request):
12 | return render(request, 'index.html')
13 |
14 |
15 | # def make_plot(request):
16 | # """
17 | # 生成分析图片
18 | # :param request:
19 | # :return:
20 | # """
21 | # # 通过进程池来存储图片
22 | # pool = Pool(3)
23 | #
24 | # p_id = int(request.POST.get('p_id'))
25 | #
26 | # images = []
27 | # result = {
28 | # 'status': 1,
29 | # }
30 | # """
31 | # {
32 | # p_id:{
33 | # "p_url":
34 | # "p_title":
35 | # "p_img":
36 | # "p_price":
37 | # "p_c_score":
38 | # "p_analysis_imgs":[]
39 | # }
40 | # }
41 | #
42 | # """
43 | # # 先从redis里获取数据,如果数据不存在再从mysql数据获取
44 | # try:
45 | # res = redis_helper.p_redis.get(p_id)
46 | # except Exception as e:
47 | # res = None
48 | # if res:
49 | # p_info = json.loads(res.decode('utf8'),encoding='utf8')
50 | # else:
51 | # try:
52 | # product_info = ProductInfo.objects.get(p_id=p_id)
53 | # p_url = product_info.p_url
54 | # p_title = product_info.p_title
55 | # p_img = product_info.p_img
56 | # p_prices = product_info.p_prices
57 | # p_c_score = product_info.p_c_score
58 | # if not p_c_score:
59 | # p_c_score = "暂无评分"
60 | # p_info = {
61 | # 'p_id': p_id,
62 | # 'p_url': p_url,
63 | # 'p_title': p_title,
64 | # 'p_img': p_img,
65 | # 'p_price': p_prices,
66 | # 'p_c_score': p_c_score
67 | # }
68 | # images.append(pool.apply_async(make_plot_helper.make_comment_plot, args=(product_info.p_comments, p_id)))
69 | # images.append(pool.apply_async(make_plot_helper.make_overview_plot, args=(product_info.p_c_all_nums, p_id)))
70 | # images.append(pool.apply_async(make_plot_helper.make_hot_plot, args=(product_info.p_c_time, p_id)))
71 | # pool.close()
72 | # pool.join()
73 | #
74 | # p_info['p_analysis_imgs'] = ["http://127.0.0.1:8080/" + img.get() for img in images]
75 | #
76 | # # 将数据保存到redis
77 | # redis_helper.save_to_redis(p_info)
78 | #
79 | # except Exception as e:
80 | # result['error_msg'] = "暂无此产品"
81 | # result['status'] = 0
82 | # p_info = {}
83 | # log_helper.log(e, logging.WARNING)
84 | #
85 | # result['data'] = p_info
86 | #
87 | # return JsonResponse(result)
88 |
89 | def make_bokeh(request):
90 | p_id = int(request.GET.get('p_id'))
91 | result = {
92 | 'status': 1,
93 | }
94 | # 先从redis里获取数据,如果数据不存在再从mysql数据获取
95 | try:
96 | res = redis_helper.p_redis.get(p_id)
97 | except Exception as e:
98 | res = None
99 | if res:
100 | p_info = json.loads(res.decode('utf8'), encoding='utf8')
101 | else:
102 | try:
103 | product_info = ProductInfo.objects.get(p_id=p_id)
104 | p_url = product_info.p_url
105 | p_title = product_info.p_title
106 | p_img = product_info.p_img
107 | p_prices = product_info.p_prices
108 | p_c_score = product_info.p_c_score
109 | if not p_c_score:
110 | p_c_score = "暂无评分"
111 | p_info = {
112 | 'p_id': p_id,
113 | 'p_url': p_url,
114 | 'p_title': p_title,
115 | 'p_img': p_img,
116 | 'p_price': p_prices,
117 | 'p_c_score': p_c_score
118 | }
119 | cb_script, cb_div = make_bokeh_hepler.make_commment_bokeh(product_info.p_comments)
120 | ob_script, ob_div = make_bokeh_hepler.make_overview_bokeh(product_info.p_c_all_nums)
121 | hb_script, hb_div = make_bokeh_hepler.make_hot_bokeh(product_info.p_c_time)
122 |
123 | p_info['p_analysis_imgs'] = [[cb_script, cb_div],[ob_script,ob_div],[hb_script,hb_div]]
124 | # 将数据保存到redis
125 | redis_helper.save_to_redis(p_info)
126 |
127 | except Exception as e:
128 | result['error_msg'] = "暂无此产品"
129 | result['status'] = 0
130 | p_info = {}
131 | log_helper.log(e, logging.WARNING)
132 |
133 | result['data'] = p_info
134 |
135 | return JsonResponse(result)
136 |
--------------------------------------------------------------------------------
/doc/django1.8中文文档.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/doc/django1.8中文文档.pdf
--------------------------------------------------------------------------------
/doc/django之查询.md:
--------------------------------------------------------------------------------
1 | ## 开篇说明
2 |
3 | 个人感觉django是一个很大的框架,通过几篇文章就想把django搞清,这难度有点大,这里,我只挑我认为最重要的也就是经常用到的----模型层中的查询,中文文档我也上传了,需要的可以下载
4 |
5 | ## QuerySet
6 | 一个对象的集合,可以包含多个过滤器,过滤器等价于mysql中的where和limit,而queryset等价于select,一般情况下,都看到一个模型类.objects这个返回的就是一个queryset对
7 | 象,而这个objects就是一个Manager,Manager实际就是数据库查询操作的一个接口
8 |
9 | 特点:QuerySet是惰性的,QuerySet创建不涉及到任何数据库操作,除非这个QuerySet被调用
10 |
11 | > models类:
12 |
13 | class UserInfo(models.Model):
14 | """
15 | 用户信息
16 | """
17 | username = models.CharField(max_length=32,verbose_name="用户名",unique=True) # unique 设置字段唯一
18 | gender_type = (
19 | (0,"保密"),
20 | (1,"男"),
21 | (2,"女")
22 | )
23 | gender = models.IntegerField(choices=gender_type,verbose_name="性别",default=0,help_text="请选择您的性别")
24 |
25 | class Meta:
26 | db_table = "user_info" # 设置数据库表名
27 | verbose_name_plural = "用户表"
28 |
29 | def __str__(self):
30 |
31 | return self.username
32 |
33 | class CardInfo(models.Model):
34 | """
35 | 帖子信息
36 | """
37 | title = models.CharField(max_length=32,verbose_name="帖子标题")
38 | content = models.TextField()
39 | ui_id = models.ForeignKey("UserInfo",verbose_name="用户")
40 | ci_id = models.ForeignKey("CircleInfo",verbose_name="圈子")
41 | create_time = models.DateTimeField(verbose_name="帖子创建日期",auto_now_add=True)
42 |
43 | def __str__(self):
44 |
45 | return "%s/%s" % (self.title,self.create_time)
46 |
47 | class Meta:
48 | db_table = "card_info"
49 | verbose_name_plural = "帖子信息表"
50 | ordering = ["create_time"] # 根据创建日期来排序
51 |
52 |
53 | class CircleInfo(models.Model):
54 | """
55 | 圈子信息
56 | """
57 | circle_name = models.CharField(max_length=32,verbose_name="圈子标题")
58 | ui_id = models.ManyToManyField("UserInfo",verbose_name="用户")
59 |
60 | class Meta:
61 | db_table = "circle_info"
62 | verbose_name_plural = "圈子信息表"
63 |
64 | def __str__(self):
65 |
66 | return "id=%s,circle_name=%s" % (self.id,self.circle_name)
67 |
68 |
69 | > 粗看objects
70 |
71 | >>> from app.models import *
72 | >>> obj = CircleInfo.objects
73 | >>> obj
74 | # 这里可以看出objects实际就是一个Manager
75 |
76 | 通过dir(obj)就可以查看里面的属性了,比如:
77 | ['aggregate', 'all', 'annotate', 'auto_created', 'bulk_create', 'check', 'complex_filter', 'contribute_to_class', 'count', 'create',
78 | 'creation_counter', 'dates', 'datetimes','db', 'db_manager', 'deconstruct', 'defer', 'distinct', 'earliest', 'exclude', 'exists', 'extra',
79 | 'filter', 'first', 'from_queryset', 'get', 'get_or_create', 'get_queryset', 'in_bulk', 'iterator', 'last', 'latest', 'model', 'name',
80 | 'none', 'only', 'order_by', 'prefetch_related', 'raw', 'reverse', 'select_for_update', 'select_related', 'update', 'update_or_create', 'use_in_migrations',
81 | 'using', 'values', 'values_list']
82 |
83 | > 常用属性
84 |
85 | > all():获取一个表中所有的对象,返回的是一个QuerySet对象
86 | >>> obj.all()
87 | , , ]>
88 | > filter():过滤器,通过条件来删选自己想要的
89 | >>> obj.filter(circle_name="爱美剪发") # 过滤出circle_name为爱美剪发的圈子
90 | ]>
91 |
92 | > order_by():默认是升序,如果想要降序的话,加上负号
93 | >>> obj.order_by("id")
94 | , , ]>
95 | >>> obj.order_by("-id")
96 | , , ]>
97 |
98 | > 关于双下划线
99 |
100 | __gt:大于
101 | __gte:大于等于
102 | __lt:小于
103 | __lte:小于等于
104 | __in:在...内
105 | __isnull:是否null
106 | __contains:包含,大小写敏感
107 | __icontains:包含,大小写不敏感
108 | __range:范围
109 | __startswith:以什么开头
110 | __endswith:以什么结尾
111 |
112 | __字段名:用来反向查询
113 |
114 | >>> obj.all()
115 | , , , , ]>
116 | >>> obj.filter(id__lt=3)
117 | , ]>
118 | >>> obj.filter(id__in=[2,3,5])
119 | , , ]>
120 |
121 | 反向查询的使用
122 | 比如现在想知道当前圈子有哪些帖子:
123 | >>> CircleInfo.objects.filter(id=2).values("cardinfo__title")
124 |
125 | 查看帖子数:
126 | >>> CircleInfo.objects.filter(id=2).values("cardinfo__title").count()
127 | 2
128 |
129 | > Q对象:主要是用来构造搜索条件的,对于filter来说,条件都是AND,如果想实现OR关系,这个时候,就可以通过Q来实现,多个Q对象可以通过|和&来连接,如果想取反,就在Q前面加~
130 |
131 | 比如现在想查询圈子名为"爱美剪发"或者"造型视觉"下的帖子:
132 | >>> obj.filter(Q(circle_name="爱美剪发") | Q(circle_name="造型视觉")).values("cardinfo__title")
133 |
134 | 如果多个Q之间用","隔开就变成了AND关系,比如现在查询名字为Lily的并且并且在造型视觉发的帖子
135 | >>> CardInfo.objects.filter(Q(ui_id__username="Lily"))
136 | , ]>
137 | >>> CardInfo.objects.filter(Q(ui_id__username="Lily"),Q(ci_id__circle_name="造型视觉"))
138 | ]>
139 |
140 | > 反向关联:*_set,_set前面关联的models类名小写,而这个_set是属于被关联models类的属性,比如,现在每个CardInfo都要关联到一个CircleInfo,那么现在通过反向关联,说着有点绕,简单的说就是,ForeignKey或者ManyToManyField字段
141 | 在那个models下,那么_set前面就是这个models的小写,通过这个这段指向的那个models对象来调用
142 |
143 | 比如:
144 | >>> circle_info = CircleInfo.objects.get(id=2)
145 | >>> circle_info.cardinfo_set.all()
146 | , , ]>
147 | >>> user_info = UserInfo.objects.get(id=1)
148 | >>> user_info.circleinfo_set.all()
149 | , ]>
150 |
151 |
152 | > 其他常用API:
153 |
154 | values():迭代返回的是一个字典
155 |
156 | 如:
157 | >>> obj.values()
158 |
159 | >>> for o in obj.values():
160 | ... type(o)
161 | ...
162 |
163 |
164 |
165 |
166 |
167 |
168 | select_related():有的时候在查询数据的时候,会用到关联表数据,而这些数据在后面的也会用到,这个时候就可以通过这个函数进行关联查询,这种查询虽然会影响到性能的损耗,但是在接下来的应用都将不会操作数据库
169 |
170 | 如:
171 | >>> cardinfo = CardInfo.objects.select_related("ci_id").get(id=2)
172 | >>> cardinfo.ci_id
173 |
174 |
175 | aggregate():聚合查询,返回的是一个字典
176 |
177 | 如:
178 | >>> CircleInfo.objects.filter(id=2).aggregate(Count("cardinfo"))
179 | {'cardinfo__count': 3}
180 |
181 |
182 | > 序列化之JSON,一般情况,希望查出的数据就能直接进行数据传输,而不需要再次封装,这个时候可以直接序列化
183 |
184 | 如:序列化帖子的所有信息
185 |
186 | >>> from django.core import serializers
187 | >>> import json
188 | >>> card_info = json.loads(serializers.serialize('json',CardInfo.objects.all()),encoding='utf8')
189 | >>> card_info
190 | [{'model': 'app.cardinfo', 'pk': 1, 'fields': {'title': '什么发型好看', 'content': '想剪个头发,但是不知道哪里剪得好看', 'ui_id': 1, 'ci_id': 3, 'create_time': '2017-07-21T01:15:32.852Z'}}, {'model': 'app.cardinfo', 'pk': 2, 'fiel
191 | ds': {'title': '美发学院活动', 'content': '今天美发学院做活动,凡是到场的将送本公司精美礼品一份', 'ui_id': 4, 'ci_id': 1, 'create_time': '2017-07-21T01:17:31.075Z'}}, {'model': 'app.cardinfo', 'pk': 3, 'fields': {'title': '杀马特'
192 | , 'content': '现在做个杀马特怎么样', 'ui_id': 3, 'ci_id': 2, 'create_time': '2017-07-21T01:18:12.834Z'}}, {'model': 'app.cardinfo', 'pk': 4, 'fields': {'title': '锅盖头', 'content': '每次看到那些锅盖头的帅锅,都感觉好别扭', 'ui_id
193 | ': 5, 'ci_id': 1, 'create_time': '2017-07-21T01:19:43.964Z'}}, {'model': 'app.cardinfo', 'pk': 5, 'fields': {'title': '脸型是圆', 'content': '圆脸适合什么样的发型呢?', 'ui_id': 2, 'ci_id': 2, 'create_time': '2017-07-21T05:29:43.7
194 | 99Z'}}, {'model': 'app.cardinfo', 'pk': 6, 'fields': {'title': '超卷发好看吗', 'content': '每次看到那些很卷的发型,第一感觉就是方便面,这种发型真的好看吗?为什么会有这么多人喜欢呢?', 'ui_id': 5, 'ci_id': 2, 'create_time': '2017-07-2
195 | 1T06:19:17.451Z'}}]
196 |
197 | 如果只想序列化一部分字段,可以通过fields来指定
198 |
199 | >>> card_info = json.loads(serializers.serialize('json',CardInfo.objects.all(),fields=('title','content','create_time')),encoding='utf8')
200 | >>> card_info
201 | [{'model': 'app.cardinfo', 'pk': 1, 'fields': {'title': '什么发型好看', 'content': '想剪个头发,但是不知道哪里剪得好看', 'create_time': '2017-07-21T01:15:32.852Z'}}, {'model': 'app.cardinfo', 'pk': 2, 'fields': {'title': '美发学院
202 | 活动', 'content': '今天美发学院做活动,凡是到场的将送本公司精美礼品一份', 'create_time': '2017-07-21T01:17:31.075Z'}}, {'model': 'app.cardinfo', 'pk': 3, 'fields': {'title': '杀马特', 'content': '现在做个杀马特怎么样', 'create_tim
203 | e': '2017-07-21T01:18:12.834Z'}}, {'model': 'app.cardinfo', 'pk': 4, 'fields': {'title': '锅盖头', 'content': '每次看到那些锅盖头的帅锅,都感觉好别扭', 'create_time': '2017-07-21T01:19:43.964Z'}}, {'model': 'app.cardinfo', 'pk': 5
204 | , 'fields': {'title': '脸型是圆', 'content': '圆脸适合什么样的发型呢?', 'create_time': '2017-07-21T05:29:43.799Z'}}, {'model': 'app.cardinfo', 'pk': 6, 'fields': {'title': '超卷发好看吗', 'content': '每次看到那些很卷的发型,第一感
205 | 觉就是方便面,这种发型真的好看吗?为什么会有这么多人喜欢呢?', 'create_time': '2017-07-21T06:19:17.451Z'}}]
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
--------------------------------------------------------------------------------
/doc/python必备知识点.md:
--------------------------------------------------------------------------------
1 | ## 常用的数据类型
2 |
3 | ### 字符串 列表 字典 集合
4 | > str:常用用到的函数有:
5 |
6 | > join():字符串拼接,一般情况是用来拼接list,tuple,比如将数据保存到mysql数据库的时候,mysql数据库是不支持list和tuple的类型的
7 | 用法:
8 | >>> l = ['Jack','Jan','Jason']
9 | >>> ','.join(l)
10 | 'Jack,Jan,Jason'
11 | > replace():替换,比如抓取一条新闻的时间部分,拿到的数据是:
12 | res = '\n 2017-07-20 09:57:48\u3000来源: ',通过replace对数据进行处理
13 | >>> res.replace(" ","").replace("\n","").replace("\u3000","")
14 | '2017-07-2009:57:48来源:'
15 | > strip():去空格
16 | res ='\n (原标题:中关村部分企业家到雄安新区调研)\n '
17 | >>> res.strip()
18 | '(原标题:中关村部分企业家到雄安新区调研)'
19 | > split():切割,比如从数据库查到用户名为username = 'Jack,Jan,Jason',需要以列表的形式返回给前台
20 | >>> username
21 | 'Jack,Jan,Jason'
22 | >>> username.split(',')
23 | ['Jack', 'Jan', 'Jason']
24 |
25 | > list:常用函数有:
26 |
27 | 插入元素:分别有三个函数append,extend,insert,现在有一个列表l = ['A','B','C']
28 | > append():尾部追加元素
29 | >>> l = ['A','B','C']
30 | >>> l.append('D')
31 | >>> l
32 | ['A', 'B', 'C', 'D']
33 |
34 | > extend():以迭代的方式,添加元素
35 | >>> l = ['A','B','C']
36 | >>> l.extend('F')
37 | >>> l
38 | ['A', 'B', 'C', 'F']
39 |
40 | > insert():指定位置添加元素
41 | >>> l = ['A','B','C']
42 | >>> l.insert(1,'D')
43 | >>> l
44 | ['A', 'D', 'B', 'C']
45 |
46 | 这三种插入的方式,我感觉最大的区别就是效率问题,就list来说,它在数据结构中属于顺序表,顺序表的插入元素特点是:尾部添加,时间复杂度为O(1),
47 | 而以其他形式添加,时间复杂度为O(n),所以append的效率相对来说要高,一般情况用到的也是append
48 |
49 | 删除元素:也是有三个函数,clear,pop,remove,同样l = ['A','B','C']
50 | > clear():删除所有元素,一般很少用到
51 | > pop():默认删除最后一个元素,也可以通过索引来指定,返回的是被删除的元素
52 | >>> l = ['A','B','C']
53 | >>> l.pop()
54 | 'C'
55 | > remove():删除被选中的元素
56 | >>> l = ['A','B','C']
57 | >>> l.remove('B')
58 | >>> l
59 | ['A', 'C']
60 |
61 | 以顺序表的特点来看,尾部删除元素时间复杂度为O(1),其他位置删除为O(n),所以通过pop()默认删除的话,效率要高
62 |
63 | > sort():排序,比如抓取到几条帖子的评论数分为comment = [400,23,89,100,55],默认排序是从小到大,如果加上reverse=True,则相反也可以
64 | 通过key来自己指定规则,
65 | >>> comment = [400,23,89,100,55]
66 | >>> comment.sort()
67 | >>> comment
68 | [23, 55, 89, 100, 400]
69 |
70 | 关于内置函数sorted(),与sort的区别就是,原函数并没有改变
71 | >>> comment = [400,23,89,100,55]
72 | >>> c = sorted(comment)
73 | >>> c
74 | [23, 55, 89, 100, 400]
75 | >>> comment
76 | [400, 23, 89, 100, 55]
77 |
78 | > dict:感觉字典比较随意,创建,获取等都比较直观
79 |
80 | 比如:
81 | >>> p_info = {"p_id":1116725,"p_title":"联想小新"}
82 | >>> type(p_info)
83 |
84 | >>> p_info["p_id"] # 直接获取
85 | 1116725
86 | >>> p_info.get("p_id") # 通过get来获取
87 | 1116725
88 | 一般情况用来做数据封装之后,进行数据传输
89 |
90 | > set:最大的特点就是去重,一般情况,也是用来去重,比如,在获取的评论日期中,有可能一天有很多评论,但是我们的需求是只要知道这一天有没有评论就行了,并不需要
91 | 知道评论数,dt = ['2015-06-12','2015-06-23','2015-03-22','2015-06-23']
92 |
93 | >>> dt = ['2015-06-12','2015-06-23','2015-03-22','2015-06-23']
94 | >>> set(dt)
95 | {'2015-06-12', '2015-03-22', '2015-06-23'}
96 | 还有一些情况,用来两个数据之间的运算
97 | a = "手机便宜,性价比高",b='性价比高,像素也好'
98 | >>> a = "手机便宜,性价比高"
99 | >>> b='性价比高,像素也好'
100 | >>> set(a) & set(b) # 交集
101 | {'价', '高', '性', '比', ','}
102 | >>> set(a) | set(b) # 并集
103 | {'机', '也', '好', ',', '手', '宜', '像', '价', '便', '性', '高', '比', '素'}
104 | >>> set(a) - set(b) # 差集
105 | {'手', '机', '宜', '便'}
106 | >>> set(a) ^ set(b) # 交叉补集
107 | {'也', '机', '好', '手', '宜', '像', '便', '素'}
108 |
109 | ## 内置函数
110 |
111 | > map():主要用来修改列表里的元素,和列表解析差不多,在效率上也差不多,dt = ['2015-06-12','2015-06-23','2015-03-22','2015-06-23'],想把里面的字符串时期
112 |
113 | 格式化成日期格式
114 | >>> list(map(lambda x:datetime.datetime.strptime(x,'%Y-%m-%d'),dt))
115 | [datetime.datetime(2015, 6, 12, 0, 0), datetime.datetime(2015, 6, 23, 0, 0), datetime.datetime(2015, 3, 22, 0, 0), datetime.datetime(2015, 6, 23, 0, 0)]
116 | >>> [datetime.datetime.strptime(d,'%Y-%m-%d') for d in dt]
117 | [datetime.datetime(2015, 6, 12, 0, 0), datetime.datetime(2015, 6, 23, 0, 0), datetime.datetime(2015, 3, 22, 0, 0), datetime.datetime(2015, 6, 23, 0, 0)]
118 | 就个人而言,我感觉,列表解析更加直观,简洁
119 |
120 | > zip():合并列表,将对应的元素合并成一个元组,合并之后,通过*来解压
121 |
122 | >>> mall = ["京东商城","天猫商城","zol商城"]
123 | >>> price = ["1599","1499","1549"]
124 | >>> z = zip(mall,price)
125 | >>> list(z)
126 | [('京东商城', '1599'), ('天猫商城', '1499'), ('zol商城', '1549')]
127 |
128 | ## 装饰器
129 |
130 | > 一般情况都是用来做登录验证
131 |
132 | >>> def auth(func):
133 | ... def wrapper(request,*args,**kwargs):
134 | ... if request.session.has_key('username'):
135 | ... result = func(request,*args,**kwargs)
136 | ... else:
137 | ... result = redirect('/login')
138 | ... return result
139 | ... return wrapper
140 |
141 | >>> @auth
142 | ... def cart(request):
143 | ... return render(reeuqet,'cart.html')
144 |
145 |
146 | 注:以上只是我常用到的,有的在项目中也会用到,需要知道更全面的,就去查看官方文档了
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
--------------------------------------------------------------------------------
/doc/scrapy知识汇总.md:
--------------------------------------------------------------------------------
1 | # 官方文档:https://doc.scrapy.org/en/master/topics/architecture.html
2 |
3 | ## 数据抓取流程
4 | 
5 |
6 | ### 各个组件的功能:
7 |
8 | > 1.Engine:引擎,图中它是位于中间的位置,所以它的主要功能就是用来提供Spider,ItemPipelines,Scheduler,Downloader之间通信的渠道
9 |
10 | > 2.Scheduler:调度器,负责接收Engine发送过来的Request,按照一定的形式进行排列,并将Request返回给引擎
11 |
12 | > 3.Downloader:下载器,负责下载Engine里所有的Request,然后将获取到的Response交给Engine
13 |
14 | > 4.Spider:负责发送Request和接收Response,对Response里的数据进行提取,分析,如果有需要跟进的URL,再次将这个Request交给Engine
15 |
16 | > 5.Item Pipeline:管道,负责处理Spider中数据,并对这些数据进行处理,比如将数据存储到数据库
17 |
18 | > 6.Middlewares:中间件,包括Downloader Middlewares 和 Spider Middewares,有的时候需要对一些请求进行处理,比如添加请求头,设置动态代理,那么这个任务就可以交给Downloader Middlewares来完成,同时,当Downloader下载
19 | 完成将Response返回给Engine的时候,这个时候Downloader Middlewares也可以对Response进行一定的处理;对于Spider Middlewares,如果需要在Spider和Engine之间的的Request和Response进行扩展的话,可以通过Spider Middewares来实现
20 |
21 | ### 流程
22 | > 对于流程的话,就是按照上图那样进行的,我感觉需要解释的就是第八步,第八步那里为什么会有两个指向箭头呢?当一个Spider完成了第六步之后,也就是接收到了
23 | Response,可以说,这个时候完成了一次请求,这个时候有两种情况就是,这个Response处理完的结果一种是需要Item Pipeline处理数据,一种还有就是里面有需要跟进的URL,比如分页,所以就把需要进行数据处理的交给Item Pipeline,需要跟进的URL交给Scheduler,由Scheduler将这个URL加入队列进行处理
24 |
25 | ### 项目的创建
26 | > scrapy startproject autohomeProduct
27 | 进入spider所在的目录:
28 |
29 | 如果创建的是Spider类:scrapy genspider autohome "autohome.com.cn",这样就创建了一个spider,如:
30 | import scrapy
31 | class AutohomeSpider(scrapy.Spider):
32 | name = 'autohome'
33 | allowed_domains = ['autohome.com.cn']
34 | start_urls = ['http://autohome.com.cn/']
35 |
36 | def parse(self, response):
37 | pass
38 |
39 | 如果创建的是CrawlSpiders类:scrapy genspider -t crawl autohome2 autohome.com.cn,如:
40 |
41 | from scrapy.linkextractors import LinkExtractor
42 | from scrapy.spiders import CrawlSpider, Rule
43 |
44 |
45 | class Autohome2Spider(CrawlSpider):
46 | name = 'autohome2'
47 | allowed_domains = ['autohome.com.cn']
48 | start_urls = ['http://autohome.com.cn/']
49 |
50 | rules = (
51 | Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
52 | )
53 |
54 | def parse_item(self, response):
55 | i = {}
56 | #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract()
57 | #i['name'] = response.xpath('//div[@id="name"]').extract()
58 | #i['description'] = response.xpath('//div[@id="description"]').extract()
59 | return i
60 |
61 | 这样项目就创建完成了
62 |
63 | ## 关于Item
64 | 先从Item讲起,就是因为,我们要抓取数据,必须清楚自己需要的是什么数据,比如现在我们需要的数据如下:
65 |
66 | import scrapy
67 |
68 | class AutohomeproductItem(scrapy.Item):
69 | # 车名
70 | title = scrapy.Field()
71 | # 车价
72 | price = scrapy.Field()
73 | # 车图片
74 | pic = scrapy.Field()
75 | # 车评分
76 | grade = scrapy.Field()
77 | # 油耗
78 | fuel_consumption = scrapy.Field()
79 |
80 | 创建Item的方式,可以通过创建对象的方式,也可以通过字典的形式
81 |
82 | 创建对象的方式:
83 | >>> from autohomeProduct.items import AutohomeproductItem
84 | >>> ahpitem = AutohomeproductItem()
85 | >>> ahpitem['title'] = '宝马'
86 | >>> ahpitem['price'] = '56万'
87 | >>> ahpitem['pic'] = '1.png'
88 | >>> ahpitem['grade'] = '8.6分'
89 | >>> ahpitem['fuel_consumption'] = '12.6L'
90 | >>> ahpitem
91 | {'fuel_consumption': '12.6L',
92 | 'grade': '8.6分',
93 | 'pic': '1.png',
94 | 'price': '56万',
95 | 'title': '宝马'}
96 |
97 | 字典的方式:
98 | >>> autoinfo = {'fuel_consumption': '12.6L',
99 | ... 'grade': '8.6分',
100 | ... 'pic': '1.png',
101 | ... 'price': '56万',
102 | ... 'title': '宝马'}
103 | >>> item = AutohomeproductItem(autoinfo)
104 | >>> item
105 | {'fuel_consumption': '12.6L',
106 | 'grade': '8.6分',
107 | 'pic': '1.png',
108 | 'price': '56万',
109 | 'title': '宝马'}
110 |
111 | ## 关于Spider和CrawlSpider
112 | 主要就是用来定义需要爬取的网站,还有就是需要爬取的规则,并根据获取的网页内容提取需要的数据
113 |
114 | 执行规则:先调用Spider的__init__方法,初始化name和start_urls,在不重写start_requests的情况下,又start_requests读取start_urls里面的内容,而start_requests实际调用的是make_requests_from_url将url交给Request去处
115 | 理,以parse为回调函数返回,在parse里面就有我们需要的网页信息Response,然后通过选择器来提取需要的内容
116 |
117 | 相关函数:
118 | start_requests():返回的是一个迭代器对象,所以一般情况将其实现生成器,如果我们我的请求需要提交参数,添加请求头或者进行数据的传递的时候,可以重写这个方法
119 |
120 | make_requests_from_url():接收一个url参数,并返回一个Request对象,未被重写的情况下,默认parse作为回调函数
121 |
122 | parse():处理返回的Response信息
123 |
124 | closed():当spider关闭的时候,该函数被调用
125 |
126 | > CrawlSpider:除了具有Spider的功能外,还提供了一些规则来继续跟进
127 |
128 | rules:包含一个或多个Rule对象的元组,这个Rule对象定义的就是需要跟进的规则,还有就是对返回信息的处理,在Rule对象,有个参数需要值得要注意一下,就是,当我们在爬取数据的时候,有的网站可能需要跟进的链接进行变动,这个时候,
129 | 可以通过process_links参数对提取的链接进行处理,一般情况下都是一个回调函数
130 |
131 | > 其他的Spider:
132 | XMLFeedSpider:主要是用来分析XML源的
133 | CSVFeedSpider:顾名思义也就是CSV源,和XMLFeedSpider有点相似,只不过它是按行遍历,XMLFeedSpider是按节点遍历
134 | SitemapSpider:通过Sitemaps来发现需要爬取的URL,比如(官网的示例):
135 | from scrapy.spiders import SitemapSpider
136 |
137 | class MySpider(SitemapSpider):
138 | sitemap_urls = ['http://www.example.com/sitemap.xml']
139 | sitemap_rules = [
140 | ('/product/', 'parse_product'),
141 | ('/category/', 'parse_category'),
142 | ]
143 |
144 | def parse_product(self, response):
145 | pass # ... scrape product ...
146 |
147 | def parse_category(self, response):
148 | pass # ... scrape category ...
149 |
150 | ## 关于选择器
151 | 除了常用的xpath之外,还有就css,同时还支持re和set匹配规则,用到的就去官网查看吧-----Selector
152 |
153 | ## 关于Item Pipeline
154 | 当我们在Spider的回调函数yield一个item的时候,数据就会传递到Item Pipeline,在这里对每个Item进行处理
155 |
156 | > 必须要实现的方法:
157 | process_item():如果需要pipeline继续处理item,必须将item返回,当抛出异常,这个item将会被丢弃
158 | 参数:item-->被抓取的item
159 | spider-->抓取该item的spider
160 |
161 | > 可以实现的方法:
162 | open_spider():当spider被开启的时候,就会调用,比如需要将数据保存到数据的时候,可以在这里进行数据库的初始化工作以及数据库的连接操作
163 |
164 | close_spider():当spider关闭的时候,会调用,比如,在进行数据保存完成之后,可以在这里执行数据库关闭操作
165 |
166 | ## 关于Downloader Middleware
167 | 介于request和response之间,主要用来全局修改request和response
168 |
169 | > 常用函数:
170 | process_request():每个request通过下载中间件时,该方法都会被调用
171 |
172 | 关于返回值:
173 | 1.如果返回的是None,将会继续处理该request
174 | 2.如果返回的是Response对象,scrapy将不会调用其他的process_request或者process_exception方法,然后将返回该response
175 | 3.如果返回Request对象,scrapy则停止调用process_request方法,并重新调度返回的request
176 | 4.如果抛出异常,测process_exception会被调用
177 |
178 | 关于参数:
179 | request:需要处理的request
180 | spider:request对应的spider
181 |
182 | process_response():
183 | 关于返回值:
184 | 1.如果返回的是一个Response,该response会被下一个中间件继续执行
185 | 2.如果返回一个Request,中间件链会停止,返回的request会被重新调度下载
186 | 3.如果抛出异常,则会调用request的errback
187 |
188 | 关于内置的中间件可以查看文档,比如CookiesMiddleware,HttpProxyMiddleware等
189 |
190 | ## 关于Spider Middleware
191 | 可以在这里添加代码来处理发送给Spider的response及spider产生的item和request
192 |
193 | > 常用函数:
194 | process_spider_input():当response通过spider中间件时,这个方法会被调用,处理该response
195 |
196 | 关于返回值:
197 | 1.如果返回None,scrapy将继续处理该response
198 | 2.如果抛出异常,则会调用request的errback,errback指向的是process_spider_output()来处理,如果这个方法也抛出异常,则会调用process_spider_exception()
199 |
200 | process_spider_output():处理response返回result时,该方法会被调用
201 |
202 | process_start_requests():以spider启动的request为参数被调用
203 |
204 | ## 关于Requests 和 Responses
205 |
206 | > Request对象:
207 | 有关参数:
208 | url:请求的url
209 | callback:请求返回的Response,由哪个函数处理
210 | method:指定请求方式
211 | headers:添加请求头信息
212 | meta:不同请求之间数据传递
213 | encoding:默认utf8
214 | cookies:请求的cookies
215 | dont_filter:表明该请求不由调度器过滤,默认就是False
216 | errback:指定处理错误函数
217 |
218 | > Response对象:
219 | 有关参数:
220 | url:响应的url
221 | status:响应状态码
222 | headers:响应头
223 | request:生成此响应的请求头
224 |
225 | ## 关于settings
226 | 关于settings的配置,可以查看官方文档
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
--------------------------------------------------------------------------------
/doc/数据分析之pandas.md:
--------------------------------------------------------------------------------
1 | ## NumPy基础
2 |
3 | > 创建ndarray的常用函数:
4 |
5 | array():将输入数据(列表,元组,数组或其他序列类型)直接转换为ndarray
6 | >>> import numpy as np
7 | # 创建一维数组
8 | >>> np.array([2,4,5,9])
9 | array([2, 4, 5, 9])
10 |
11 | # 创建二维数组
12 | >>> np.array([[3,5],[8,9]])
13 | array([[3, 5],
14 | [8, 9]])
15 |
16 | arange():类似range函数
17 | >>> np.arange(6)
18 | array([0, 1, 2, 3, 4, 5])
19 |
20 | reshape():改变数组的维度
21 | >>> n1 = np.arange(16)
22 | >>> n1
23 | array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
24 | >>> n1.shape # 查看数组的维度
25 | (16,)
26 | >>> n2 = n1.reshape(4,4) # 四行四列
27 | >>> n2
28 | array([[ 0, 1, 2, 3],
29 | [ 4, 5, 6, 7],
30 | [ 8, 9, 10, 11],
31 | [12, 13, 14, 15]])
32 | >>> n2.shape
33 | (4, 4)
34 |
35 | > 索引:主要用来获取值
36 |
37 | 一维数组的获取:
38 | >>> n3 = np.arange(16,step=2)
39 | >>> n3
40 | array([ 0, 2, 4, 6, 8, 10, 12, 14])
41 | >>> n3[3] # 获取特定索引下的值
42 | 6
43 | >>> n3[4:] # 切片的形式,索引为4到最后
44 | array([ 8, 10, 12, 14])
45 | >>> n3[:4]
46 | array([0, 2, 4, 6])
47 | >>> n3[2:6] # 索引2到6之间,不包含6
48 | array([ 4, 6, 8, 10])
49 |
50 | 二维数组的获取:
51 | >>> n2
52 | array([[ 0, 1, 2, 3],
53 | [ 4, 5, 6, 7],
54 | [ 8, 9, 10, 11],
55 | [12, 13, 14, 15]])
56 | >>> n2[1:2] # 在没有逗号分隔的情况,默认按行
57 | array([[4, 5, 6, 7]])
58 | >>> n2[:,2:] # 逗号分隔后面是按列取
59 | array([[ 2, 3],
60 | [ 6, 7],
61 | [10, 11],
62 | [14, 15]])
63 |
64 | 通过数组的形式获取值:
65 | >>> n2
66 | array([[ 0, 1, 2, 3],
67 | [ 4, 5, 6, 7],
68 | [ 8, 9, 10, 11],
69 | [12, 13, 14, 15]])
70 |
71 | >>> n2[[1,3],[2,3]]
72 | array([ 6, 15])
73 |
74 | bool索引:
75 | >>> n4 = n2 >= 10
76 | >>> n4
77 | array([[False, False, False, False],
78 | [False, False, False, False],
79 | [False, False, True, True],
80 | [ True, True, True, True]], dtype=bool)
81 |
82 | >>> n2[n4] # 通过布尔索引来获取值
83 | array([10, 11, 12, 13, 14, 15])
84 |
85 | ## pandas
86 |
87 | ### 两大数据结构
88 |
89 | > Series:可以看成是一维数组加上索引组成的对象
90 |
91 | >>> import pandas as pd
92 | >>> from pandas import Series,DataFrame
93 | >>> s = Series(np.arange(5),index=['a','b','c','d','e']) # 通过index来指定索引
94 | >>> s
95 | a 0
96 | b 1
97 | c 2
98 | d 3
99 | e 4
100 | dtype: int32
101 | >>> s.index
102 | Index(['a', 'b', 'c', 'd', 'e'], dtype='object')
103 | >>> s.values
104 | array([0, 1, 2, 3, 4])
105 |
106 | 字典的形式创建Series,字典的key默认变成索引
107 | >>> p_info = {"p_id":120364,"p_title":"雷神","p_price":12800}
108 | >>> s_p = Series(p_info)
109 | >>> s_p
110 | p_id 120364
111 | p_price 12800
112 | p_title 雷神
113 | dtype: object
114 |
115 |
116 |
117 | > DataFrame:是一个表格型的数据结构对象,所以它既有列索引,也有行索引,也可以看成是由Series组成的字典
118 |
119 | 创建方式一般是直接传入一个字典
120 | >>> p_infos = {"p_title":["雷神","机械师","战神"],
121 | ... "p_price":[12288,13000,8999],
122 | ... "p_score":[7.8,6.8,8.9]}
123 | >>> d_p = DataFrame(p_infos)
124 | >>> d_p
125 | p_price p_score p_title
126 | 0 12288 7.8 雷神
127 | 1 13000 6.8 机械师
128 | 2 8999 8.9 战神
129 |
130 | 排序
131 | 按照索引排序
132 | >>> d_p.sort_index(ascending=False)
133 | p_price p_score p_title
134 | 2 8999 8.9 战神
135 | 1 13000 6.8 机械师
136 | 0 12288 7.8 雷神
137 | 按照某一列排序
138 | >>> d_p.sort_values(by="p_score")
139 | p_price p_score p_title
140 | 1 13000 6.8 机械师
141 | 0 12288 7.8 雷神
142 | 2 8999 8.9 战神
143 |
144 |
145 | 数据的获取
146 | >>> d_p.iloc[1]
147 | p_price 13000
148 | p_score 6.8
149 | p_title 机械师
150 | Name: 1, dtype: object
151 |
152 | >>> d_p.loc[:,"p_title"]
153 | 0 雷神
154 | 1 机械师
155 | 2 战神
156 | Name: p_title, dtype: object
157 |
158 | > 关于数据丢失的处理
159 |
160 | >>> p_infos2 = {"p_title":["雷神","机械师","战神","飞行堡垒"],
161 | ... "p_score":[7.8,6.8,8.9,6.7],
162 | ... "p_price":[12288,13000,8999,6799]}
163 | >>> d_p2 = DataFrame(p_infos2)
164 | >>> d_p2
165 | p_price p_score p_title
166 | 0 12288 7.8 雷神
167 | 1 13000 6.8 机械师
168 | 2 8999 8.9 战神
169 | 3 6799 6.7 飞行堡垒
170 |
171 | d_p2.loc[3,"p_score"] = np.nan
172 | >>> d_p2
173 | p_price p_score p_title
174 | 0 12288 7.8 雷神
175 | 1 13000 6.8 机械师
176 | 2 8999 8.9 战神
177 | 3 6799 NaN 飞行堡垒
178 |
179 | 直接将缺失数据丢掉
180 | >>> d_p2.dropna()
181 | p_price p_score p_title
182 | 0 12288 7.8 雷神
183 | 1 13000 6.8 机械师
184 | 2 8999 8.9 战神
185 |
186 | 填充的方式
187 | >>> d_p2.fillna(10)
188 | p_price p_score p_title
189 | 0 12288 7.8 雷神
190 | 1 13000 6.8 机械师
191 | 2 8999 8.9 战神
192 | 3 6799 10.0 飞行堡垒
193 |
194 |
195 | > 统计
196 |
197 | 求平均值:
198 | >>> d_p2.mean()
199 | p_price 10271.500000
200 | p_score 7.833333
201 |
202 | 求和:
203 | >>> d_p2.sum()
204 | p_price 41086
205 | p_score 23.5
206 | p_title 雷神机械师战神飞行堡垒
207 |
208 | > 函数的应用
209 |
210 | >>> d = d_p2.loc[:,"p_price"]
211 | >>> d
212 | 0 12288
213 | 1 13000
214 | 2 8999
215 | 3 6799
216 | Name: p_price, dtype: int64
217 |
218 | >>> d.apply(lambda x : float(x))
219 | 0 12288.0
220 | 1 13000.0
221 | 2 8999.0
222 | 3 6799.0
223 | Name: p_price, dtype: float64
224 |
225 |
226 | 更多应用查看官方文档:http://pandas.pydata.org/
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
--------------------------------------------------------------------------------
/hepler/__init__.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
--------------------------------------------------------------------------------
/hepler/config.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 |
4 | # redis
5 | redis_pass = ''
6 | redis_host = '127.0.0.1'
7 | redis_port = '6379'
8 |
9 |
10 | REDIS_PRODUCT_INFO_EXPIRES_SECONDES = 43200 # 设置存储过期时间,单位为秒
--------------------------------------------------------------------------------
/hepler/file_hepler.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 |
4 | def get_image_path(file_name):
5 | """
6 | 获取图片地址
7 | :param file_name:
8 | :return:
9 | """
10 | return "static/upload/%s" % file_name
--------------------------------------------------------------------------------
/hepler/log_helper.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 | import logging
4 | import datetime
5 |
6 | def log(msg,level = logging.DEBUG):
7 | """
8 | 自定义输出日志
9 | :param msg: 日志信息
10 | :param level: 日志等级
11 | :return:
12 | """
13 | logging.log(level,msg)
14 |
15 | print("log时间[%s]-log等级[%s]-log信息[%s]" % (datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),level,msg))
16 |
--------------------------------------------------------------------------------
/hepler/make_bokeh_hepler.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 | from bokeh.plotting import figure
4 | from bokeh.embed import components
5 | from bokeh.resources import CDN
6 | from bokeh.charts import Bar,Line
7 | from hepler import file_hepler
8 | import re
9 | import pandas as pd
10 | import datetime
11 |
12 | TOOLS = "save"
13 |
14 | def make_commment_bokeh(data):
15 | if data:
16 | temps = "".join(data).replace(" ", "").replace("\r\n", "")
17 | values = re.findall(r'(\d+)', temps)
18 | c_values = [int(value) for value in values]
19 | c_keys = re.findall('[\u4e00-\u9fa5]+', temps) #
20 | s = pd.Series(c_values, index=c_keys, name='好评率')
21 | s = s[3:6]
22 | s_sum = s.sum()
23 | s = s.apply(lambda x: x / s_sum * 100)
24 | factors = list(s.index)
25 | x = s.values
26 | dot = figure(title="好评率(单位:%)", tools=TOOLS, toolbar_location=None,
27 | y_range=factors, x_range=[0, 100],width=400, height=400)
28 | dot.segment(0, factors, x, factors, line_width=2, line_color="green", )
29 | dot.circle(x, factors, size=15, fill_color="orange", line_color="green", line_width=3, )
30 | script, div = components(dot, CDN)
31 |
32 | return [script,div]
33 | else:
34 | return [0,file_hepler.get_image_path("no_good_comments.png")]
35 |
36 | def make_overview_bokeh(data):
37 | if data:
38 | temps = "".join(data)
39 | values = re.findall(r'(\d+)', temps)
40 | c_values = [int(value) for value in values]
41 | c_keys = re.findall('[\u4e00-\u9fa5]+', temps)
42 | s = pd.Series(c_values, index=c_keys)
43 | data = s
44 | # 创建一个新的含有标题和轴标签的窗口在线窗口
45 | p = Bar(data,title="总览图", ylabel='关键字数量', width=400, height=400,legend=None,tools="")
46 | script, div = components(p, CDN)
47 |
48 | return [script, div]
49 | else:
50 | return [0,file_hepler.get_image_path("no_overview.png")]
51 |
52 | def make_hot_bokeh(data):
53 | from bokeh.models import BoxAnnotation
54 | if data:
55 | data = data.split(",")
56 | dt1 = datetime.datetime.now()
57 | temp = list(set(data))
58 | sub_dates = [(dt1 - datetime.datetime.strptime(dt, '%Y-%m-%d')).days for dt in temp]
59 |
60 | s = pd.Series(sub_dates, [datetime.datetime.strptime(dt, '%Y-%m-%d') for dt in temp])
61 | s = s.sort_index(ascending=False)
62 |
63 | p = figure(x_axis_type="datetime", tools="",title="热度", x_axis_label='日期', y_axis_label='天数',width=400, height=400)
64 |
65 | p.line(s.index,s.values, legend="距离当前天数", line_width=2)
66 |
67 | script, div = components(p, CDN)
68 |
69 | return [script, div]
70 | else:
71 | return [0,file_hepler.get_image_path("no_hot.png")]
72 |
--------------------------------------------------------------------------------
/hepler/make_gif_helper.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 | # api:http://matplotlib.org/api/animation_api.html
4 | # imagemagick环境配置:http://www.hankcs.com/ml/using-matplotlib-and-imagemagick-to-realize-the-algorithm-visualization-and-gif-export.html
5 | import numpy as np
6 |
7 | import matplotlib.pyplot as plt
8 | import matplotlib.patches as patches
9 | import matplotlib.path as path
10 | import matplotlib.animation as animation
11 | fig, ax = plt.subplots()
12 |
13 | # histogram our data with numpy
14 | data = np.random.randn(1000)
15 | n, bins = np.histogram(data, 100)
16 |
17 | # get the corners of the rectangles for the histogram
18 | left = np.array(bins[:-1])
19 | right = np.array(bins[1:])
20 | bottom = np.zeros(len(left))
21 | top = bottom + n
22 | nrects = len(left)
23 |
24 | # here comes the tricky part -- we have to set up the vertex and path
25 | # codes arrays using moveto, lineto and closepoly
26 |
27 | # for each rect: 1 for the MOVETO, 3 for the LINETO, 1 for the
28 | # CLOSEPOLY; the vert for the closepoly is ignored but we still need
29 | # it to keep the codes aligned with the vertices
30 | nverts = nrects*(1 + 3 + 1)
31 | verts = np.zeros((nverts, 2))
32 | codes = np.ones(nverts, int) * path.Path.LINETO
33 | codes[0::5] = path.Path.MOVETO
34 | codes[4::5] = path.Path.CLOSEPOLY
35 | verts[0::5, 0] = left
36 | verts[0::5, 1] = bottom
37 | verts[1::5, 0] = left
38 | verts[1::5, 1] = top
39 | verts[2::5, 0] = right
40 | verts[2::5, 1] = top
41 | verts[3::5, 0] = right
42 | verts[3::5, 1] = bottom
43 |
44 | barpath = path.Path(verts, codes)
45 | patch = patches.PathPatch(
46 | barpath, facecolor='#787878', edgecolor='#787878', alpha=1)
47 | ax.add_patch(patch)
48 |
49 | ax.set_xlim(left[0], right[-1])
50 | ax.set_ylim(bottom.min(), top.max())
51 |
52 |
53 | def animate(i):
54 | # simulate new data coming in
55 | data = np.random.randn(1000)
56 | n, bins = np.histogram(data, 100)
57 | top = bottom + n
58 | verts[1::5, 1] = top
59 | verts[2::5, 1] = top
60 | return [patch, ]
61 |
62 | ani = animation.FuncAnimation(fig, animate, 100, repeat=False, blit=True)
63 | ani.save('loading1.gif',fps=2, writer='imagemagick')
--------------------------------------------------------------------------------
/hepler/make_plot_helper.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 | import re
4 | import matplotlib.pyplot as plt
5 | from matplotlib.pylab import mpl
6 | import datetime
7 | import pandas as pd
8 | from hepler import redis_helper,file_hepler
9 | from bokeh.plotting import figure,show,output_file
10 |
11 | mpl.rcParams['font.sans-serif'] = ['SimHei'] # 显示中文
12 | mpl.rcParams['font.serif'] = ['SimHei'] # 显示中文
13 | mpl.rcParams['axes.unicode_minus'] = False # 负号'-'正常显示
14 | import json
15 |
16 |
17 | def make_comment_plot(data,p_id):
18 | """
19 | 生成好评率的饼图
20 | :param datas:
21 | :return:
22 | """
23 | if data:
24 | temps = "".join(data).replace(" ", "").replace("\r\n", "")
25 | values = re.findall(r'(\d+)', temps)
26 | c_values = [int(value) for value in values]
27 | c_keys = re.findall('[\u4e00-\u9fa5]+', temps)
28 | print(c_keys)
29 | s = pd.Series(c_values, index=c_keys,name='好评率')
30 | s = s[3:6]
31 | s_sum = s.sum()
32 | s = s.apply(lambda x: x / s_sum)
33 | s.plot.pie(autopct='%0.2f%%', fontsize=8, colors=['g', 'y', 'r'])
34 | plt.savefig("static/upload/%s_c.png" % p_id,dpi=90)
35 | plt.close()
36 |
37 | return file_hepler.get_image_path("%s_c.png" % p_id)
38 | else:
39 | return file_hepler.get_image_path("no_good_comments.png")
40 |
41 | def make_overview_plot(data,p_id):
42 | """
43 | 评价概览
44 | :param datas:
45 | :return:
46 | """
47 | if data:
48 | temps = "".join(data)
49 | values = re.findall(r'(\d+)', temps)
50 | c_values = [int(value) for value in values]
51 | c_keys = re.findall('[\u4e00-\u9fa5]+', temps)
52 | s = pd.Series(c_values, index=c_keys)
53 | s.plot.bar(figsize=(6, 8), fontsize=8)
54 |
55 | plt.savefig("static/upload/%s_o.png" % p_id,dpi=90)
56 | plt.close()
57 | return file_hepler.get_image_path("%s_o.png" % p_id)
58 | else:
59 | return file_hepler.get_image_path("no_overview.png")
60 |
61 | def make_hot_plot(data,p_id):
62 | """
63 | 根据评价最近评价来判断这个产品是否是热门
64 | :param datas:
65 | :return:
66 | """
67 | if data:
68 | data = data.split(",")
69 | dt1 = datetime.datetime.now()
70 | temp = list(set(data))
71 | sub_dates = [(dt1 - datetime.datetime.strptime(dt, '%Y-%m-%d')).days for dt in temp]
72 | actives = []
73 | for d in sub_dates:
74 | if d <= 360:
75 | actives.append(100)
76 | elif 360 < d <= 500:
77 | actives.append(60)
78 | else:
79 | actives.append(0)
80 |
81 | date_dict = {
82 | 'sub_date': sub_dates,
83 | 'active': actives
84 | }
85 | df = pd.DataFrame(date_dict, index=temp)
86 | df.plot(subplots=True)
87 | plt.savefig("static/upload/%s_h.png" % p_id,dpi=90)
88 | return file_hepler.get_image_path("%s_h.png" % p_id)
89 | else:
90 | return file_hepler.get_image_path("no_hot.png")
91 |
92 |
93 |
--------------------------------------------------------------------------------
/hepler/redis_helper.py:
--------------------------------------------------------------------------------
1 | # !/usr/bin/env python
2 | # -*- coding:utf-8 -*-
3 | import redis
4 | from . import config
5 | import json
6 |
7 | p_redis = redis.StrictRedis(host=config.redis_host, port=config.redis_port)
8 |
9 | def save_to_redis(item):
10 | """
11 | 保存数据的redis
12 | :param data: 需要保存的数据
13 | :return:
14 | """
15 | p_redis.setex(item['p_id'],config.REDIS_PRODUCT_INFO_EXPIRES_SECONDES,json.dumps(item))
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ProductAnalysis.settings")
7 | try:
8 | from django.core.management import execute_from_command_line
9 | except ImportError:
10 | # The above import may fail for some other reason. Ensure that the
11 | # issue is really that Django is missing to avoid masking other
12 | # exceptions on Python 2.
13 | try:
14 | import django
15 | except ImportError:
16 | raise ImportError(
17 | "Couldn't import Django. Are you sure it's installed and "
18 | "available on your PYTHONPATH environment variable? Did you "
19 | "forget to activate a virtual environment?"
20 | )
21 | raise
22 | execute_from_command_line(sys.argv)
23 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | asn1crypto==0.22.0
2 | attrs==17.2.0
3 | Automat==0.6.0
4 | bkcharts==0.2
5 | bokeh==0.12.6
6 | certifi==2017.4.17
7 | cffi==1.10.0
8 | chardet==3.0.4
9 | constantly==15.1.0
10 | cryptography==1.9
11 | cssselect==1.0.1
12 | cycler==0.10.0
13 | Django==1.10.6
14 | django-haystack==2.6.1
15 | gunicorn==19.7.1
16 | hyperlink==17.2.1
17 | idna==2.5
18 | incremental==17.5.0
19 | jieba==0.38
20 | Jinja2==2.9.6
21 | lxml==3.8.0
22 | MarkupSafe==1.0
23 | matplotlib==2.0.2
24 | numpy==1.13.1
25 | pandas==0.20.3
26 | parsel==1.2.0
27 | pkg-resources==0.0.0
28 | psycopg2==2.7.1
29 | pyasn1==0.2.3
30 | pyasn1-modules==0.0.9
31 | pycparser==2.18
32 | PyDispatcher==2.0.5
33 | PyMySQL==0.7.11
34 | pyOpenSSL==17.1.0
35 | pyparsing==2.2.0
36 | python-dateutil==2.6.1
37 | pytz==2017.2
38 | PyYAML==3.12
39 | queuelib==1.4.2
40 | redis==2.10.5
41 | requests==2.18.1
42 | Scrapy==1.4.0
43 | scrapy-redis==0.6.8
44 | service-identity==17.0.0
45 | six==1.10.0
46 | tornado==4.5.1
47 | Twisted==17.5.0
48 | urllib3==1.21.1
49 | uWSGI==2.0.15
50 | w3lib==1.17.0
51 | Whoosh==2.7.4
52 | zope.interface==4.4.2
53 |
--------------------------------------------------------------------------------
/screen/Scrapy流程.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/screen/Scrapy流程.png
--------------------------------------------------------------------------------
/screen/index.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/screen/index.png
--------------------------------------------------------------------------------
/screen/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/screen/search.png
--------------------------------------------------------------------------------
/static/css/font-awesome.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.2.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}
--------------------------------------------------------------------------------
/static/css/loading.css:
--------------------------------------------------------------------------------
1 | .ui-loading{position:absolute;left:0;top:0;z-index: 9999;}
2 | .ui-loading .ui-loading-mask{ position:absolute;top:0;left:0;background-color: #000;opacity: .2;z-index: 1}
3 | .ui-loading i{height: 90px;width:90px; display: block;background: url('/static/gif/loading1.gif') no-repeat center center;background-size:100% 100%;position: absolute;z-index: 2}
4 |
--------------------------------------------------------------------------------
/static/css/main.css:
--------------------------------------------------------------------------------
1 |
2 | /* ------------------------------------------------------------------------------
3 | Typography
4 | -------------------------------------------------------------------------------*/
5 |
6 |
7 | @import url(http://fonts.googleapis.com/css?family=Source%20Sans%20Pro:300,400,500,600,700);
8 | /* ------------------------------------------------------------------------------
9 | Global Styles
10 | -------------------------------------------------------------------------------*/
11 |
12 |
13 | body {
14 | font-family: "Source Sans Pro";
15 | font-weight: 300;
16 | font-size:13px;
17 | letter-spacing:1px;
18 | line-height:1.8em;
19 | }
20 | p {
21 | color: #888;
22 | }
23 | h1, h2, h3, h5, h6 {
24 | font-weight: 400;
25 | }
26 | h4 {
27 | font-weight: 300;
28 | font-size: 15px;
29 |
30 | }
31 | a {
32 | color: #ff5335;
33 | }
34 | a:hover {
35 | color: #d92709;
36 | text-decoration: none;
37 | }
38 | a:focus {
39 | outline: none;
40 | }
41 | img {
42 | max-width: 100%;
43 | height: auto;
44 | }
45 | ul, li {
46 | list-style: none;
47 | padding: 0;
48 | margin: 0;
49 | }
50 | /* ------------------------------------------------------------------------------
51 | main
52 | -------------------------------------------------------------------------------*/
53 |
54 | .main {
55 | background-size: cover;
56 | padding: 3em 0 4.5em;
57 | position: relative !important;
58 | color: #fff;
59 | }
60 | .overlay {
61 | position: absolute;
62 | width: 100%;
63 | height: 100%;
64 | background: rgba(27, 32, 38, 0.5);
65 | top: 0;
66 | }
67 | .logo {
68 | text-align: center;
69 | margin-bottom: 2.25em;
70 | }
71 | .main h1 {
72 | font-size: 36px;
73 | font-weight: 700;
74 | letter-spacing:7px;
75 | text-transform:uppercase;
76 | }
77 | .welcome-message {
78 | padding-top: 1.5em;
79 | }
80 | .rotate {
81 | text-shadow: none !important;
82 | }
83 | .copyrights{
84 | text-indent:-9999px;
85 | height:0;
86 | line-height:0;
87 | font-size:0;
88 | overflow:hidden;
89 | }
90 |
91 | /* ------------------------------------------------------------------------------
92 | Search
93 | -------------------------------------------------------------------------------*/
94 |
95 | .form-control::-webkit-input-placeholder {
96 | color: #727272;
97 | font-weight: 300;
98 | }
99 | .form-control:-moz-placeholder {
100 | color: #727272;
101 | font-weight: 300;
102 | }
103 | .form-control::-moz-placeholder {
104 | color: #727272;
105 | font-weight: 300;
106 | }
107 | .form-control:-ms-input-placeholder {
108 | color: #727272;
109 | font-weight: 300;
110 | }
111 | .sub-form {
112 | padding-top: 1.5em;
113 | }
114 | .center-block {
115 | float: none;
116 | }
117 | .form-control {
118 | background-color: #fff;
119 | border: 1px solid #fff;
120 | box-shadow: none;
121 | height: 50px;
122 | font-weight: 300;
123 | }
124 | .form-control:focus {
125 | border-color: #fff;
126 | outline: 0;
127 | box-shadow: none;
128 | }
129 | .input-group-btn>.btn:hover, .input-group-btn>.btn:focus, .input-group-btn>.btn:active {
130 | z-index: 1;
131 | }
132 | .btn-default {
133 | color: #fff;
134 | background-color: #ff5335;
135 | border-color: #ff5335;
136 | -webkit-transition: all .5s ease-out;
137 | transition: all .5s ease-out;
138 | padding: 12px 1.5em;
139 | height: 50px;
140 | width: 100px;
141 | font-size: 18px;
142 | }
143 | .btn-default:hover, .btn-default:focus, .btn-default:active {
144 | color: #fff;
145 | background-color: #d92709;
146 | border-color: #d92709;
147 | }
148 | .btn:active:focus, .btn-default:focus, .btn:focus, .btn-default:active {
149 | outline: none;
150 | }
151 | #mc-notification {
152 | margin: .75em 0 0;
153 | color: #FFF;
154 | }
155 |
156 | .center-block {
157 | float: none;
158 | }
159 |
160 | /* ------------------------------------------------------------------------------
161 | sidebar
162 | -------------------------------------------------------------------------------*/
163 | .sidebar .nav > li.active-pro{
164 | position: absolute;
165 | width: 100%;
166 | bottom: 10px;
167 | }
168 | .sidebar .nav > li.active-pro a{
169 | background: rgba(255, 255, 255, 0.14);
170 | opacity: 1;
171 | color: #FFFFFF;
172 | }
173 |
174 | .p-detail{
175 | float: left;
176 | }
177 |
178 | /* ------------------------------------------------------------------------------
179 | Small devices (tablets, 768px and up)
180 | -------------------------------------------------------------------------------*/
181 |
182 | @media (min-width: 768px) {
183 | .main {
184 | padding: 3em 0 7.5em;
185 | }
186 | .logo {
187 | text-align: justify;
188 | margin-bottom: 0;
189 | }
190 | .welcome-message {
191 | padding-top: 6em;
192 | }
193 | .social {
194 | float: right;
195 | }
196 | ul.countdown {
197 | padding-top: 6em;
198 | }
199 | ul.countdown li span {
200 | font-size: 27px;
201 | }
202 | ul.countdown li.seperator {
203 | font-size: 27px;
204 | padding: 0 20px;
205 | }
206 | }
207 |
208 | /* ------------------------------------------------------------------------------
209 | Medium devices (desktops, 992px and up)
210 | -------------------------------------------------------------------------------*/
211 |
212 | @media (min-width: 992px) {
213 | .main {
214 | padding: 3em 0 16em;
215 | position: fixed !important;
216 | top: 0;
217 | z-index: 1;
218 | width: 100%;
219 | }
220 | .features, .twitter-feed, .contact, .site-footer {
221 | position: relative;
222 | z-index: 2;
223 | }
224 | .welcome-message {
225 | padding-top: 9em;
226 | }
227 | .section-spacing {
228 | padding: 6em 0;
229 | }
230 | .features.section-spacing {
231 | padding: 6em 0 3em;
232 | margin-top: 46.8em;
233 | }
234 | .product-features {
235 | margin-bottom: 3.75em;
236 | }
237 | .features.section-spacing, .contact {
238 | background: #fff;
239 | }
240 | .contact-form {
241 | margin-top: 0;
242 | }
243 | .animated {
244 | -webkit-animation-duration: 1s;
245 | animation-duration: 1s;
246 | -webkit-animation-fill-mode: both;
247 | animation-fill-mode: both;
248 | }
249 | @-webkit-keyframes fadeInUp {
250 | 0% {
251 | opacity:0;
252 | -webkit-transform:translate3d(0, 100%, 0);
253 | transform:translate3d(0, 100%, 0);
254 | }
255 | 100% {
256 | opacity:1;
257 | -webkit-transform:none;
258 | transform:none;
259 | }
260 | }
261 | @keyframes fadeInUp {
262 | 0% {
263 | opacity:0;
264 | -webkit-transform:translate3d(0, 100%, 0);
265 | -ms-transform:translate3d(0, 100%, 0);
266 | transform:translate3d(0, 100%, 0);
267 | }
268 | 100% {
269 | opacity:1;
270 | -webkit-transform:none;
271 | -ms-transform:none;
272 | transform:none;
273 | }
274 | }
275 | .fadeInUp {
276 | -webkit-animation-name: fadeInUp;
277 | animation-name: fadeInUp;
278 | }
279 | }
280 |
281 | /* ------------------------------------------------------------------------------
282 | small devices
283 | -------------------------------------------------------------------------------*/
284 |
285 |
286 |
287 | @media (max-width: 320px) {
288 | ul.countdown li span {
289 | font-size: 21px;
290 | }
291 | ul.countdown li.seperator {
292 | font-size: 25.629px;
293 | vertical-align: top;
294 | padding: 0 2px;
295 | }
296 | ul.countdown li p {
297 | font-size: 12.642px;
298 | }
299 | }
300 |
301 | .bk-plot-layout,.bk-plot-wrapper,.bk-canvas-wrapper,.bk-canvas{
302 | width: 100% !important;
303 | }
304 | .bk-root .bk-toolbar-right{
305 | display: none !important;
306 | }
307 |
308 |
--------------------------------------------------------------------------------
/static/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/static/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/static/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/static/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/static/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/static/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/static/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/static/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/static/gif/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/static/gif/loading.gif
--------------------------------------------------------------------------------
/static/gif/loading1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/static/gif/loading1.gif
--------------------------------------------------------------------------------
/static/img/bg-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/static/img/bg-1.jpg
--------------------------------------------------------------------------------
/static/img/bg-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/static/img/bg-2.jpg
--------------------------------------------------------------------------------
/static/img/load.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/static/img/load.gif
--------------------------------------------------------------------------------
/static/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/static/img/logo.png
--------------------------------------------------------------------------------
/static/img/sidebar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/static/img/sidebar.jpg
--------------------------------------------------------------------------------
/static/js/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.5 (http://getbootstrap.com)
3 | * Copyright 2011-2015 Twitter, Inc.
4 | * Licensed under the MIT license
5 | */
6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.5",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.5",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),a(c.target).is('input[type="radio"]')||a(c.target).is('input[type="checkbox"]')||c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.5",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.5",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.5",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.5",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.5",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.5",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
--------------------------------------------------------------------------------
/static/js/jquery.backstretch.min.js:
--------------------------------------------------------------------------------
1 | /*! Backstretch - v2.0.4 - 2013-06-19
2 | * http://srobbin.com/jquery-plugins/backstretch/
3 | * Copyright (c) 2013 Scott Robbin; Licensed MIT */
4 | (function(a,d,p){a.fn.backstretch=function(c,b){(c===p||0===c.length)&&a.error("No images were supplied for Backstretch");0===a(d).scrollTop()&&d.scrollTo(0,0);return this.each(function(){var d=a(this),g=d.data("backstretch");if(g){if("string"==typeof c&&"function"==typeof g[c]){g[c](b);return}b=a.extend(g.options,b);g.destroy(!0)}g=new q(this,c,b);d.data("backstretch",g)})};a.backstretch=function(c,b){return a("body").backstretch(c,b).data("backstretch")};a.expr[":"].backstretch=function(c){return a(c).data("backstretch")!==p};a.fn.backstretch.defaults={centeredX:!0,centeredY:!0,duration:5E3,fade:0};var r={left:0,top:0,overflow:"hidden",margin:0,padding:0,height:"100%",width:"100%",zIndex:-999999},s={position:"absolute",display:"none",margin:0,padding:0,border:"none",width:"auto",height:"auto",maxHeight:"none",maxWidth:"none",zIndex:-999999},q=function(c,b,e){this.options=a.extend({},a.fn.backstretch.defaults,e||{});this.images=a.isArray(b)?b:[b];a.each(this.images,function(){a("
")[0].src=this});this.isBody=c===document.body;this.$container=a(c);this.$root=this.isBody?l?a(d):a(document):this.$container;c=this.$container.children(".backstretch").first();this.$wrap=c.length?c:a('').css(r).appendTo(this.$container);this.isBody||(c=this.$container.css("position"),b=this.$container.css("zIndex"),this.$container.css({position:"static"===c?"relative":c,zIndex:"auto"===b?0:b,background:"none"}),this.$wrap.css({zIndex:-999998}));this.$wrap.css({position:this.isBody&&l?"fixed":"absolute"});this.index=0;this.show(this.index);a(d).on("resize.backstretch",a.proxy(this.resize,this)).on("orientationchange.backstretch",a.proxy(function(){this.isBody&&0===d.pageYOffset&&(d.scrollTo(0,1),this.resize())},this))};q.prototype={resize:function(){try{var a={left:0,top:0},b=this.isBody?this.$root.width():this.$root.innerWidth(),e=b,g=this.isBody?d.innerHeight?d.innerHeight:this.$root.height():this.$root.innerHeight(),j=e/this.$img.data("ratio"),f;j>=g?(f=(j-g)/2,this.options.centeredY&&(a.top="-"+f+"px")):(j=g,e=j*this.$img.data("ratio"),f=(e-b)/2,this.options.centeredX&&(a.left="-"+f+"px"));this.$wrap.css({width:b,height:g}).find("img:not(.deleteable)").css({width:e,height:j}).css(a)}catch(h){}return this},show:function(c){if(!(Math.abs(c)>this.images.length-1)){var b=this,e=b.$wrap.find("img").addClass("deleteable"),d={relatedTarget:b.$container[0]};b.$container.trigger(a.Event("backstretch.before",d),[b,c]);this.index=c;clearInterval(b.interval);b.$img=a("
").css(s).bind("load",function(f){var h=this.width||a(f.target).width();f=this.height||a(f.target).height();a(this).data("ratio",h/f);a(this).fadeIn(b.options.speed||b.options.fade,function(){e.remove();b.paused||b.cycle();a(["after","show"]).each(function(){b.$container.trigger(a.Event("backstretch."+this,d),[b,c])})});b.resize()}).appendTo(b.$wrap);b.$img.attr("src",b.images[c]);return b}},next:function(){return this.show(this.indexe||d.operamini&&"[object OperaMini]"==={}.toString.call(d.operamini)||n&&7458>t||-1e||h&&6>h||"palmGetResource"in d&&e&&534>e||-1=k)})(jQuery,window);
--------------------------------------------------------------------------------
/static/js/jquery.simple-text-rotator.min.js:
--------------------------------------------------------------------------------
1 | !function(e){var t={animation:"dissolve",separator:",",speed:2e3};e.fx.step.textShadowBlur=function(t){e(t.elem).prop("textShadowBlur",t.now).css({textShadow:"0 0 "+Math.floor(t.now)+"px black"})};e.fn.textrotator=function(n){var r=e.extend({},t,n);return this.each(function(){var t=e(this);var n=[];e.each(t.text().split(r.separator),function(e,t){n.push(t)});t.text(n[0]);var i=function(){switch(r.animation){case"dissolve":t.animate({textShadowBlur:20,opacity:0},500,function(){s=e.inArray(t.text(),n);if(s+1==n.length)s=-1;t.text(n[s+1]).animate({textShadowBlur:0,opacity:1},500)});break;case"flip":if(t.find(".back").length>0){t.html(t.find(".back").html())}var i=t.text();var s=e.inArray(i,n);if(s+1==n.length)s=-1;t.html("");e(""+i+"").appendTo(t);e(""+n[s+1]+"").appendTo(t);t.wrapInner("").find(".rotating").hide().addClass("flip").show().css({"-webkit-transform":" rotateY(-180deg)","-moz-transform":" rotateY(-180deg)","-o-transform":" rotateY(-180deg)",transform:" rotateY(-180deg)"});break;case"flipUp":if(t.find(".back").length>0){t.html(t.find(".back").html())}var i=t.text();var s=e.inArray(i,n);if(s+1==n.length)s=-1;t.html("");e(""+i+"").appendTo(t);e(""+n[s+1]+"").appendTo(t);t.wrapInner("").find(".rotating").hide().addClass("flip up").show().css({"-webkit-transform":" rotateX(-180deg)","-moz-transform":" rotateX(-180deg)","-o-transform":" rotateX(-180deg)",transform:" rotateX(-180deg)"});break;case"flipCube":if(t.find(".back").length>0){t.html(t.find(".back").html())}var i=t.text();var s=e.inArray(i,n);if(s+1==n.length)s=-1;t.html("");e(""+i+"").appendTo(t);e(""+n[s+1]+"").appendTo(t);t.wrapInner("").find(".rotating").hide().addClass("flip cube").show().css({"-webkit-transform":" rotateY(180deg)","-moz-transform":" rotateY(180deg)","-o-transform":" rotateY(180deg)",transform:" rotateY(180deg)"});break;case"flipCubeUp":if(t.find(".back").length>0){t.html(t.find(".back").html())}var i=t.text();var s=e.inArray(i,n);if(s+1==n.length)s=-1;t.html("");e(""+i+"").appendTo(t);e(""+n[s+1]+"").appendTo(t);t.wrapInner("").find(".rotating").hide().addClass("flip cube up").show().css({"-webkit-transform":" rotateX(180deg)","-moz-transform":" rotateX(180deg)","-o-transform":" rotateX(180deg)",transform:" rotateX(180deg)"});break;case"spin":if(t.find(".rotating").length>0){t.html(t.find(".rotating").html())}s=e.inArray(t.text(),n);if(s+1==n.length)s=-1;t.wrapInner("").find(".rotating").hide().text(n[s+1]).show().css({"-webkit-transform":" rotate(0) scale(1)","-moz-transform":"rotate(0) scale(1)","-o-transform":"rotate(0) scale(1)",transform:"rotate(0) scale(1)"});break;case"fade":t.fadeOut(r.speed,function(){s=e.inArray(t.text(),n);if(s+1==n.length)s=-1;t.text(n[s+1]).fadeIn(r.speed)});break}};setInterval(i,r.speed)})}}(window.jQuery)
--------------------------------------------------------------------------------
/static/js/loading.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Created with Sublime Text 3.
3 | * license: http://www.lovewebgames.com/jsmodule/index.html
4 | * github: https://github.com/tianxiangbing/loading
5 | * User: 田想兵
6 | * Date: 2015-08-05
7 | * Time: 11:27:55
8 | * Contact: 55342775@qq.com
9 | * desc:请尽量使用github上的代码,会修复一些问题,关注https://github.com/tianxiangbing/loading
10 | */
11 | ;
12 | (function (root, factory) {
13 | //amd
14 | if (typeof define === 'function' && define.amd) {
15 | define(['$'], factory);
16 | } else if (typeof exports === 'object') { //umd
17 | module.exports = factory();
18 | } else {
19 | root.Loading = factory(window.Zepto || window.jQuery || $);
20 | }
21 | })(window, function ($) {
22 | var Loading = function () { };
23 | Loading.prototype = {
24 | loadingTpl: '',
25 | stop: function () {
26 | var content = $(this.target);
27 | this.loading.remove();
28 | this.loading = null;
29 | },
30 | start: function () {
31 | var _this = this;
32 | var loading = this.loading;
33 | if (!loading) {
34 | loading = $(_this.loadingTpl);
35 | $('body').append(loading);
36 | }
37 | this.loading = loading;
38 | //console.log(cw,ch)
39 | this.setPosition();
40 | },
41 | setPosition: function () {
42 | var _this = this;
43 | var loading = this.loading;
44 | var target = _this.target;
45 | var content = $(target);
46 | var ch = $(content).outerHeight();
47 | var cw = $(content).outerWidth();
48 | if ($(target)[0].tagName == "HTML") {
49 | ch = Math.max($(target).height(), $(window).height());
50 | cw = Math.max($(target).width(), $(window).width());
51 | }
52 | if(loading != null){
53 | loading.height(ch).width(cw);
54 | loading.find('div').height(ch).width(cw);
55 | if (ch < 100) {
56 | loading.find('i').height(ch).width(ch);
57 | }
58 | var offset = $(content).offset();
59 | loading.css({
60 | top: offset.top,
61 | left: offset.left
62 | });
63 | var icon = loading.find('i');
64 | var h = ch,
65 | w = cw,
66 | top = 0,
67 | left = 0;
68 | if ($(target)[0].tagName == "HTML") {
69 | h = $(window).height();
70 | w = $(window).width();
71 | top = (h - icon.height()) / 2 + $(window).scrollTop();
72 | left = (w - icon.width()) / 2 + $(window).scrollLeft();
73 | } else {
74 | top = (h - icon.height()) / 2;
75 | left = (w - icon.width()) / 2;
76 | }
77 | icon.css({
78 | top: top,
79 | left: left
80 | })
81 | };
82 |
83 |
84 |
85 | },
86 | init: function (settings) {
87 | settings = settings || {};
88 | this.loadingTpl = settings.loadingTpl || this.loadingTpl;
89 | this.target = settings.target || 'html';
90 | this.bindEvent();
91 | },
92 | bindEvent: function () {
93 | var _this = this;
94 | $(this.target).on('stop', function () {
95 | _this.stop();
96 | });
97 | $(window).on('resize', function () {
98 | _this.setPosition();
99 | });
100 | }
101 | }
102 | return Loading;
103 | });
--------------------------------------------------------------------------------
/static/js/main.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | "use strict";
3 |
4 |
5 | /* ==========================================================================
6 | Background Slideshow images
7 | ========================================================================== */
8 |
9 | $(".main").backstretch([
10 | "/static/img/bg-1.jpg",
11 | "/static/img/bg-2.jpg"
12 |
13 | ], {
14 | fade: 750,
15 | duration: 4000
16 | });
17 |
18 |
19 | /* ==========================================================================
20 | On Scroll animation
21 | ========================================================================== */
22 |
23 | if ($(window).width() > 992) {
24 | new WOW().init();
25 | }
26 | ;
27 |
28 |
29 | /* ==========================================================================
30 | Fade On Scroll
31 | ========================================================================== */
32 |
33 |
34 | if ($(window).width() > 992) {
35 |
36 | $(window).on('scroll', function () {
37 | $('.main').css('opacity', function () {
38 | return 1 - ($(window).scrollTop() / $(this).outerHeight());
39 | });
40 | });
41 | }
42 | ;
43 |
44 |
45 | $(".rotate").textrotator({
46 | animation: "dissolve",
47 | separator: ",",
48 | speed: 2500
49 | });
50 |
51 |
52 | });
53 |
--------------------------------------------------------------------------------
/static/js/search.js:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/static/js/wow.min.js:
--------------------------------------------------------------------------------
1 | /*! WOW - v1.0.1 - 2014-08-15
2 | * Copyright (c) 2014 Matthieu Aussaguel; Licensed MIT */(function(){var a,b,c,d=function(a,b){return function(){return a.apply(b,arguments)}},e=[].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1};b=function(){function a(){}return a.prototype.extend=function(a,b){var c,d;for(c in b)d=b[c],null==a[c]&&(a[c]=d);return a},a.prototype.isMobile=function(a){return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(a)},a}(),c=this.WeakMap||this.MozWeakMap||(c=function(){function a(){this.keys=[],this.values=[]}return a.prototype.get=function(a){var b,c,d,e,f;for(f=this.keys,b=d=0,e=f.length;e>d;b=++d)if(c=f[b],c===a)return this.values[b]},a.prototype.set=function(a,b){var c,d,e,f,g;for(g=this.keys,c=e=0,f=g.length;f>e;c=++e)if(d=g[c],d===a)return void(this.values[c]=b);return this.keys.push(a),this.values.push(b)},a}()),a=this.MutationObserver||this.WebkitMutationObserver||this.MozMutationObserver||(a=function(){function a(){console.warn("MutationObserver is not supported by your browser."),console.warn("WOW.js cannot detect dom mutations, please call .sync() after loading new content.")}return a.notSupported=!0,a.prototype.observe=function(){},a}()),this.WOW=function(){function f(a){null==a&&(a={}),this.scrollCallback=d(this.scrollCallback,this),this.scrollHandler=d(this.scrollHandler,this),this.start=d(this.start,this),this.scrolled=!0,this.config=this.util().extend(a,this.defaults),this.animationNameCache=new c}return f.prototype.defaults={boxClass:"wow",animateClass:"animated",offset:0,mobile:!0,live:!0},f.prototype.init=function(){var a;return this.element=window.document.documentElement,"interactive"===(a=document.readyState)||"complete"===a?this.start():document.addEventListener("DOMContentLoaded",this.start),this.finished=[]},f.prototype.start=function(){var b,c,d,e;if(this.stopped=!1,this.boxes=function(){var a,c,d,e;for(d=this.element.querySelectorAll("."+this.config.boxClass),e=[],a=0,c=d.length;c>a;a++)b=d[a],e.push(b);return e}.call(this),this.all=function(){var a,c,d,e;for(d=this.boxes,e=[],a=0,c=d.length;c>a;a++)b=d[a],e.push(b);return e}.call(this),this.boxes.length)if(this.disabled())this.resetStyle();else{for(e=this.boxes,c=0,d=e.length;d>c;c++)b=e[c],this.applyStyle(b,!0);window.addEventListener("scroll",this.scrollHandler,!1),window.addEventListener("resize",this.scrollHandler,!1),this.interval=setInterval(this.scrollCallback,50)}return this.config.live?new a(function(a){return function(b){var c,d,e,f,g;for(g=[],e=0,f=b.length;f>e;e++)d=b[e],g.push(function(){var a,b,e,f;for(e=d.addedNodes||[],f=[],a=0,b=e.length;b>a;a++)c=e[a],f.push(this.doSync(c));return f}.call(a));return g}}(this)).observe(document.body,{childList:!0,subtree:!0}):void 0},f.prototype.stop=function(){return this.stopped=!0,window.removeEventListener("scroll",this.scrollHandler,!1),window.removeEventListener("resize",this.scrollHandler,!1),null!=this.interval?clearInterval(this.interval):void 0},f.prototype.sync=function(){return a.notSupported?this.doSync(this.element):void 0},f.prototype.doSync=function(a){var b,c,d,f,g;if(!this.stopped){if(null==a&&(a=this.element),1!==a.nodeType)return;for(a=a.parentNode||a,f=a.querySelectorAll("."+this.config.boxClass),g=[],c=0,d=f.length;d>c;c++)b=f[c],e.call(this.all,b)<0?(this.applyStyle(b,!0),this.boxes.push(b),this.all.push(b),g.push(this.scrolled=!0)):g.push(void 0);return g}},f.prototype.show=function(a){return this.applyStyle(a),a.className=""+a.className+" "+this.config.animateClass},f.prototype.applyStyle=function(a,b){var c,d,e;return d=a.getAttribute("data-wow-duration"),c=a.getAttribute("data-wow-delay"),e=a.getAttribute("data-wow-iteration"),this.animate(function(f){return function(){return f.customStyle(a,b,d,c,e)}}(this))},f.prototype.animate=function(){return"requestAnimationFrame"in window?function(a){return window.requestAnimationFrame(a)}:function(a){return a()}}(),f.prototype.resetStyle=function(){var a,b,c,d,e;for(d=this.boxes,e=[],b=0,c=d.length;c>b;b++)a=d[b],e.push(a.setAttribute("style","visibility: visible;"));return e},f.prototype.customStyle=function(a,b,c,d,e){return b&&this.cacheAnimationName(a),a.style.visibility=b?"hidden":"visible",c&&this.vendorSet(a.style,{animationDuration:c}),d&&this.vendorSet(a.style,{animationDelay:d}),e&&this.vendorSet(a.style,{animationIterationCount:e}),this.vendorSet(a.style,{animationName:b?"none":this.cachedAnimationName(a)}),a},f.prototype.vendors=["moz","webkit"],f.prototype.vendorSet=function(a,b){var c,d,e,f;f=[];for(c in b)d=b[c],a[""+c]=d,f.push(function(){var b,f,g,h;for(g=this.vendors,h=[],b=0,f=g.length;f>b;b++)e=g[b],h.push(a[""+e+c.charAt(0).toUpperCase()+c.substr(1)]=d);return h}.call(this));return f},f.prototype.vendorCSS=function(a,b){var c,d,e,f,g,h;for(d=window.getComputedStyle(a),c=d.getPropertyCSSValue(b),h=this.vendors,f=0,g=h.length;g>f;f++)e=h[f],c=c||d.getPropertyCSSValue("-"+e+"-"+b);return c},f.prototype.animationName=function(a){var b;try{b=this.vendorCSS(a,"animation-name").cssText}catch(c){b=window.getComputedStyle(a).getPropertyValue("animation-name")}return"none"===b?"":b},f.prototype.cacheAnimationName=function(a){return this.animationNameCache.set(a,this.animationName(a))},f.prototype.cachedAnimationName=function(a){return this.animationNameCache.get(a)},f.prototype.scrollHandler=function(){return this.scrolled=!0},f.prototype.scrollCallback=function(){var a;return!this.scrolled||(this.scrolled=!1,this.boxes=function(){var b,c,d,e;for(d=this.boxes,e=[],b=0,c=d.length;c>b;b++)a=d[b],a&&(this.isVisible(a)?this.show(a):e.push(a));return e}.call(this),this.boxes.length||this.config.live)?void 0:this.stop()},f.prototype.offsetTop=function(a){for(var b;void 0===a.offsetTop;)a=a.parentNode;for(b=a.offsetTop;a=a.offsetParent;)b+=a.offsetTop;return b},f.prototype.isVisible=function(a){var b,c,d,e,f;return c=a.getAttribute("data-wow-offset")||this.config.offset,f=window.pageYOffset,e=f+Math.min(this.element.clientHeight,innerHeight)-c,d=this.offsetTop(a),b=d+a.clientHeight,e>=d&&b>=f},f.prototype.util=function(){return null!=this._util?this._util:this._util=new b},f.prototype.disabled=function(){return!this.config.mobile&&this.util().isMobile(navigator.userAgent)},f}()}).call(this);
--------------------------------------------------------------------------------
/static/upload/no_good_comments.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/static/upload/no_good_comments.png
--------------------------------------------------------------------------------
/static/upload/no_hot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/static/upload/no_hot.png
--------------------------------------------------------------------------------
/static/upload/no_overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jasonhy/ProductAnalysis/b1015890dd6b64cd7476deb155cf26501859fe7a/static/upload/no_overview.png
--------------------------------------------------------------------------------
/templates/analysis.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
36 |
37 |
38 |
41 |
42 |
![]()
43 |
44 |
45 |
46 |
47 |
48 |
49 |
52 |
53 |
![]()
54 |
55 |
56 |
57 |
58 |
61 |
62 |
![]()
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/templates/base.html:
--------------------------------------------------------------------------------
1 | {% load static from staticfiles%}
2 |
3 |
4 |
5 |
6 |
7 |
8 | {% block title %}
9 | 菜鸟产品分析平台
10 | {% endblock %}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {% block extra %}
23 |
24 | {% endblock %}
25 |
26 |
27 |
28 |
29 | {% block body %}
30 |
31 | {% endblock %}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | {% block js %}
42 |
43 | {% endblock %}
44 |
45 |
--------------------------------------------------------------------------------
/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load static from staticfiles %}
3 |
4 | {% block body %}
5 |
6 |
7 |
8 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
26 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | {% endblock %}
--------------------------------------------------------------------------------
/templates/search/indexes/app/productinfo_text.txt:
--------------------------------------------------------------------------------
1 | {{ object.p_title }}
2 | {{ object.p_id }}
--------------------------------------------------------------------------------
/templates/search/search.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 | {% load static from staticfiles %}
3 | {% load product_tags %}
4 |
5 | {% block title %}
6 | 搜索结果
7 | {% endblock %}
8 |
9 |
10 | {% block body %}
11 |
12 |
13 | {#
#}
14 | {#
#}
15 | {#
#}
16 | {#
#}
17 | {#

#}
18 | {#
#}
19 | {# #}
22 | {#
#}
23 | {#
#}
24 | {#
#}
25 |
59 |
60 | {% include 'analysis.html' %}
61 |
62 |
63 |
64 | {% endblock %}
65 |
66 |
67 | {% block js %}
68 |
159 | {% endblock %}
--------------------------------------------------------------------------------