├── .idea ├── .gitignore ├── bosszp.iml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml └── modules.xml ├── README.md ├── bosszp ├── __init__.py ├── clean │ ├── __init__.py │ └── dataclean.py ├── items.py ├── middlewares.py ├── pipelines.py ├── settings.py ├── spiders │ ├── __init__.py │ └── boss.py └── web │ ├── __init__.py │ ├── dbutils.py │ ├── run.py │ ├── static │ ├── css │ │ └── mystyle.css │ ├── highcharts │ │ ├── dark-unica.js │ │ ├── highcharts-more.js │ │ ├── highcharts.js │ │ ├── oldie.js │ │ └── wordcloud.js │ ├── img │ │ ├── bg.png │ │ └── favicon.png │ └── js │ │ ├── cylindrical.js │ │ ├── fan.js │ │ ├── jquery-1.8.3.min.js │ │ ├── order.js │ │ ├── packgebubble.js │ │ ├── pie.js │ │ └── word.js │ └── templates │ └── index.html ├── runspider.py ├── scrapy.cfg └── 全国-热门城市岗位数据.csv /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /../../../../../../../:\pyCharmSource\python基础\bigdata0032\scrapy框架学习\bosszp\.idea/dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/bosszp.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Boss直聘岗位数据分析 2 | 该项目不再更新,请移步至新项目 [boss_selenium](https://github.com/jhcoco/bosszp-selenium) 3 | ## 1. 项目背景 4 | 5 | ​ 随着科技的飞速发展,数据呈现爆发式的增长,任何人都摆脱不了与数据打交道,社会对于“数据”方面的人才需求也在不断增大。因此了解当下企业究竟需要招聘什么样的人才?需要什么样的技能?不管是对于在校生,还是对于求职者来说,都显得很有必要。 6 | 7 | ​ 本文基于这个问题,针对 boss 直聘网站,使用 Scrapy 框架爬取了全国热门城市大数据、数据分析、数据挖掘、机器学习、人工智能等相关岗位的招聘信息。分析比较了不同岗位的薪资、学历要求;分析比较了不同区域、行业对相关人才的需求情况;分析比较了不同岗位的知识、技能要求等。 8 | 9 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112170141751.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dtZXRidGdia2k=,size_16,color_FFFFFF,t_70#pic_center) 10 | 11 | 12 |
图 1 岗位情况分析可视化
13 | 14 | ## 2. 环境准备 15 | 16 |
表 1-1 开发工具和环境
17 | 18 | | 开发工具/环境 | 版本 | 备注 | 19 | | ------------- | :----------------------------------------------------------- | -------- | 20 | | Windows | Windows10 | 系统 | 21 | | PyCharm | Professional 2020.3 | 编写代码 | 22 | | Anaconda3 | [Anaconda3-2019.03-Windows-x86_64.exe](https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-2019.03-Windows-x86_64.exe) | 运行环境 | 23 | 24 |
表 1-2 Python 第三方依赖库
25 | 26 | | 库名 | 版本 | 备注 | 27 | | ---------- | ------ | ------------- | 28 | | Scrapy | 2.4.1 | WEB爬虫框架 | 29 | | SQLAlchemy | 1.3.5 | 数据库操作 | 30 | | PyMySQL | 0.9.3 | 数据库操作 | 31 | | pandas | 0.24.2 | 数据分析 | 32 | | Flask | 1.1.1 | 轻量级web框架 | 33 | 34 | ## 3. 项目实现 35 | 36 | ​ 该项目一共分为三个子任务完成,**数据采集—数据预处理—数据分析/可视化。** 37 | 38 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112170211457.png#pic_center) 39 | 40 | 41 |
图 2 项目流程图
42 | 43 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112170223198.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dtZXRidGdia2k=,size_16,color_FFFFFF,t_70#pic_center) 44 | 45 | 46 |
图 3 项目架构图
47 | 48 | ### 3.1 数据采集 49 | 50 | ​ 爬取 Boss直聘热门城市岗位数据,并将数据以 CSV 文件格式进行保存。如下图所示: 51 | 52 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112170341320.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dtZXRidGdia2k=,size_16,color_FFFFFF,t_70#pic_center) 53 | 54 | 55 | 56 |
图 4 全国-Boss直聘热门城市岗位数据
57 | 58 | #### 3.1.1 创建 Scrapy 爬虫项目 59 | 60 | ① 环境安装: 61 | 62 | ```cmd 63 | $ pip install scrapy 64 | ``` 65 | 66 | ② 项目创建: 67 | 68 | ```cmd 69 | $ scrapy startproject bosszp 70 | $ cd bosszp 71 | $ scrapy genspider boss zhipin.com 72 | ``` 73 | 74 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112170424872.png#pic_center) 75 | 76 | 77 |
图 5 Scrapy 项目结构
78 | 79 | #### 3.1.2 配置 Scrapy 项目 80 | 81 | ​ 爬取 Boss 直聘网站数据,通过检测 Boss 直聘网站,发现有 `Cookie`,`User-Agent`,`Referer`,`Robots协议`等常见反爬策略。根据这些策略我们需要在`settings.py` 文件中做对应的一些配置和修改。具体配置如下: 82 | 83 | ① 关闭 Robots 协议 84 | 85 | ```python 86 | # Obey robots.txt rules 87 | ROBOTSTXT_OBEY = False 88 | ``` 89 | 90 | ② 修改下载延迟为 60 s 91 | 92 | ```python 93 | # Configure a delay for requests for the same website (default: 0) 94 | DOWNLOAD_DELAY = 60 95 | ``` 96 | 97 | ③ 禁用系统 cookie 98 | 99 | ```python 100 | # Disable cookies (enabled by default) 101 | COOKIES_ENABLED = False 102 | ``` 103 | 104 | ④ 开启 item-pipelines 105 | 106 | ```python 107 | # Configure item pipelines 108 | # See https://docs.scrapy.org/en/latest/topics/item-pipeline.html 109 | ITEM_PIPELINES = { 110 | 'bosszhipin.pipelines.BosszhipinPipeline': 300, 111 | } 112 | ``` 113 | 114 | #### 3.1.3 编写爬虫程序 115 | 116 | ​ 创建和配置好 `Scrapy` 项目以后,我们就可以编写 `Scrapy` 爬虫程序了。 117 | 118 | ① 确定目标(编辑 `items.py`) 119 | 120 | ```python 121 | import scrapy 122 | 123 | class BosszpItem(scrapy.Item): 124 | job_name = scrapy.Field() # 岗位名 125 | job_area = scrapy.Field() # 工作地址 126 | job_salary = scrapy.Field() # 薪资 127 | com_name = scrapy.Field() # 企业名称 128 | com_type = scrapy.Field() # 企业类型 129 | com_size = scrapy.Field() # 企业规模 130 | finance_stage = scrapy.Field() # 融资情况 131 | work_year = scrapy.Field() # 工作年限 132 | education = scrapy.Field() # 学历要求 133 | job_benefits = scrapy.Field() # 岗位福利 134 | ``` 135 | 136 | ② 编写爬虫(编辑 `spiders/boss.py`),需要替换成最新的 `cookie` 137 | 138 | ```python 139 | import scrapy 140 | import json 141 | import logging 142 | import random 143 | from bosszp.items import BosszpItem 144 | 145 | 146 | class BossSpider(scrapy.Spider): 147 | name = 'boss' 148 | allowed_domains = ['zhipin.com'] 149 | start_urls = ['https://www.zhipin.com/wapi/zpCommon/data/cityGroup.json'] # 热门城市列表url 150 | # 设置多个 cookie,建议数量为 页数/2 + 1 个cookie.至少 设置 4 个 151 | # 只需复制 __zp_stoken__ 部分即可 152 | cookies = [ '__zp_stoken__=f330bOEgsRnsAIS5Bb2FXe250elQKNzAgMBcQZ1hvWyBjUFE1DCpKLWBtBn99Nwd%2BPHtlVRgdOi1vDEAkOz9sag50aRNRfhs6TQ9kWmNYc0cFI3kYKg5fAGVPPX0WO2JCOipvRlwbP1YFBQlHOQ%3D%3D', '__zp_stoken__=f330bOEgsRnsAIUsENEIbe250elRsb2U4Bg0QZ1hvW19mPEdeeSpKLWBtN3Y9QCN%2BPHtlVRgdOilvfTYkSTMiaFN0X3NRAGMjOgENX2krc0cFI3kYKiooQGx%2BPX0WO2I3OipvRlwbP1YFBQlHOQ%3D%3D', '__zp_stoken__=f330bOEgsRnsAITsLNnJIe250elRJMH95DBAQZ1hvW1J1ewdmDCpKLWBtBHZtagV%2BPHtlVRgdOil1LjkkR1MeRAgdY3tXbxVORWVuTxQlc0cFI3kYKgwCEGxNPX0WO2JCOipvRlwbP1YFBQlHOQ%3D%3D' 153 | ] 154 | # 设置多个请求头 155 | user_agents = [ 156 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36', 157 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15', 158 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0', 159 | 'Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1', 160 | 'Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11', 161 | 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)', 162 | 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)', 163 | 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)', 164 | 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)', 165 | ] 166 | page_no = 1 # 初始化分页 167 | 168 | def random_header(self): 169 | """ 170 | 随机生成请求头 171 | :return: headers 172 | """ 173 | headers = {'Referer': 'https://www.zhipin.com/c101020100/?ka=sel-city-101020100'} 174 | headers['cookie'] = random.choice(self.cookies) 175 | headers['user-agent'] = random.choice(self.user_agents) 176 | return headers 177 | 178 | def parse(self, response): 179 | """ 180 | 解析首页热门城市列表,选择热门城市进行爬取 181 | :param response: 热门城市字典数据 182 | :return: 183 | """ 184 | # 获取服务器返回的内容 185 | city_group = json.loads(response.body.decode()) 186 | # 获取热门城市列表 187 | hot_city_list = city_group['zpData']['hotCityList'] 188 | # 初始化空列表,存储打印信息 189 | # city_lst = [] 190 | # for index,item in enumerate(hot_city_list): 191 | # city_lst.apend({index+1: item['name']}) 192 | # 列表推导式: 193 | hot_city_names = [{index + 1: item['name']} for index, item in enumerate(hot_city_list)] 194 | print("--->", hot_city_names) 195 | # 从键盘获取城市编号 196 | city_no = int(input('请从上述城市列表中,选择编号开始爬取:')) 197 | # 拼接url https://www.zhipin.com/job_detail/?query=&city=101040100&industry=&position= 198 | # 获取城市编码code 199 | city_code = hot_city_list[city_no - 1]['code'] 200 | # 拼接查询接口 201 | city_url = 'https://www.zhipin.com/job_detail/?query=&city={}&industry=&position='.format(city_code) 202 | logging.info("<<<<<<<<<<<<<正在爬取第_{}_页岗位数据>>>>>>>>>>>>>".format(self.page_no)) 203 | yield scrapy.Request(url=city_url, headers=self.random_header(), callback=self.parse_city) 204 | 205 | def parse_city(self, response): 206 | 207 | """ 208 | 解析岗位页数据 209 | :param response: 岗位页响应数据 210 | :return: 211 | """ 212 | if response.status != 200: 213 | logging.warning("<<<<<<<<<<<<<获取城市招聘信息失败,ip已被封禁。请稍后重试>>>>>>>>>>>>>") 214 | return 215 | li_elements = response.xpath('//div[@class="job-list"]/ul/li') # 定位到所有的li标签 216 | next_url = response.xpath('//div[@class="page"]/a[last()]/@href').get() # 获取下一页 217 | 218 | for li in li_elements: 219 | job_name = li.xpath('./div/div[1]//div[@class="job-title"]/span[1]/a/text()').get() 220 | job_area = li.xpath('./div/div[1]//div[@class="job-title"]/span[2]/span[1]/text()').get() 221 | job_salary = li.xpath('./div/div[1]//span[@class="red"]/text()').get() 222 | com_name = li.xpath('./div/div[1]/div[2]//div[@class="company-text"]/h3/a/text()').get() 223 | com_type = li.xpath('./div/div[1]/div[2]/div[1]/p/a/text()').get() 224 | com_size = li.xpath('./div/div[1]/div[2]/div[1]/p/text()[2]').get() 225 | finance_stage = li.xpath('./div/div[1]/div[2]/div[1]/p/text()[1]').get() 226 | work_year = li.xpath('./div/div[1]/div[1]/div[1]/div[2]/p/text()[1]').get() 227 | education = li.xpath('./div/div[1]/div[1]/div[1]/div[2]/p/text()[2]').get() 228 | job_benefits = li.xpath('./div/div[2]/div[2]/text()').get() 229 | item = BosszpItem(job_name=job_name, job_area=job_area, job_salary=job_salary, com_name=com_name, 230 | com_type=com_type, com_size=com_size, 231 | finance_stage=finance_stage, work_year=work_year, education=education, 232 | job_benefits=job_benefits) 233 | yield item 234 | if next_url == "javascript:;": 235 | logging.info('<<<<<<<<<<<<<热门城市岗位数据已爬取结束>>>>>>>>>>>>>') 236 | logging.info("<<<<<<<<<<<<<一共爬取了_{}_页岗位数据>>>>>>>>>>>>>".format(self.page_no)) 237 | return 238 | next_url = response.urljoin(next_url) # 网址拼接 239 | self.page_no += 1 240 | logging.info("<<<<<<<<<<<<<正在爬取第_{}_页岗位数据>>>>>>>>>>>>>".format(self.page_no)) 241 | yield scrapy.Request(url=next_url, headers=self.random_header(), callback=self.parse_city) 242 | ``` 243 | 244 | ③ 保存数据(编辑 `pipelines.py`) 245 | 246 | ```python 247 | from itemadapter import ItemAdapter 248 | 249 | 250 | class BosszpPipeline: 251 | def process_item(self, item, spider): 252 | """ 253 | 保存数据到本地 csv 文件 254 | :param item: 数据项 255 | :param spider: 256 | :return: 257 | """ 258 | with open(file='全国-热门城市岗位数据.csv', mode='a+', encoding='utf8') as f: 259 | f.write( 260 | '{job_name},{job_area},{job_salary},{com_name},{com_type},{com_size},{finance_stage},{work_year},' 261 | '{education},{job_benefits}'.format( 262 | **item)) 263 | return item 264 | ``` 265 | 266 | ④ 运行爬虫 267 | 268 | ```cmd 269 | $ scrapy crawl boss 270 | ``` 271 | 272 | ⑤ 编辑本地 `CSV` 文件 273 | 274 | ​ 数据爬取完成后,打开 `CSV` 文件,复制下方文本粘贴在文件第一行。 275 | 276 | ```csv 277 | job_name,job_area,job_salary,com_name,com_type,com_size,finance_stage,work_year,education,job_benefits 278 | ``` 279 | 280 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112170451410.png#pic_center) 281 | 282 | 283 |
图 6 爬虫数据预览
284 | 285 | > 注意: 286 | > 287 | > 1. 如果爬虫执行过程中出现 `xxxxx DEBUG: Redirecting (302) to xxxxxxx` 问题,替换最新的 `cookie` 即可解决。 288 | > 2. 目前版本替换新的 `cookie` 以后,不能从断点继续爬取。 289 | > 3. 如需断点继续爬取,需重写 `start_requests()` 函数。 290 | 291 | ### 3.2 数据清洗与预处理 292 | 293 | ​ 完成上面爬虫程序的编写与运行,我们就能将 Boss 直聘热门城市岗位数据爬取到本地。通过观察发现爬取到的数据出现了大量的脏数据和高耦合的数据。我们需要对这些脏数据进行清洗与预处理后才能正常使用。 294 | 295 | > 需求: 296 | > 297 | > 1. 读取 `全国-热门城市岗位数据.csv` 文件 298 | > 2. 对重复行进行清洗。 299 | > 3. 对`工作地址`字段进行预处理。要求:北京·海淀区·西北旺 --> 北京,海淀区,西北旺。分隔成3个字段 300 | > 4. 对`薪资`字段进行预处理。要求:30-60K·15薪 --> 最低:30,最高:60 301 | > 5. 对`工作经验`字段进行预处理。要求:经验不限/在校/应届 :0,1-3年:1,3-5年:2,5-10年:3,10年以上:4 302 | > 6. 对`企业规模`字段进行预处理。要求:500人以下:0,500-999:1,1000-9999:2,10000人以上:3 303 | > 7. 对`岗位福利`字段进行预处理。要求:将描述中的中文','(逗号),替换成英文','(逗号) 304 | > 8. 对缺失值所在行进行清洗。 305 | > 9. 将处理后的数据保存到 MySQL 数据库 306 | 307 | #### 3.2.1 需求实现 308 | 309 | ​ ① 在 `bosszp` 项目下新建 `clean` 包,在 模块下新建 `dataclean.py` 模块。如下图所示: 310 | 311 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112170524810.png#pic_center) 312 | 313 | 314 |
图 7 数据预处理项目结构
315 | 316 | ​ ② 在 `dataclean.py` 文件内编写清洗与预处理的代码。 317 | 318 | ```python 319 | # -*- coding:utf-8 -*- 320 | """ 321 | 作者:jhzhong 322 | 功能:对岗位数据进行清洗与预处理 323 | 需求: 324 | 1. 读取 `全国-热门城市岗位数据.csv` 文件 325 | 2. 对重复行进行清洗。 326 | 3. 对`工作地址`字段进行预处理。要求:北京·海淀区·西北旺 --> 北京,海淀区,西北旺。分隔成3个字段 327 | 4. 对`薪资`字段进行预处理。要求:30-60K·15薪 --> 最低:30,最高:60 328 | 5. 对`工作经验`字段进行预处理。要求:经验不限/在校/应届 :0,1-3年:1,3-5年:2,5-10年:3,10年以上:4 329 | 6. 对`企业规模`字段进行预处理。要求:500人以下:0,500-999:1,1000-9999:2,10000人以上:3 330 | 7. 对`岗位福利`字段进行预处理。要求:将描述中的中文','(逗号),替换成英文','(逗号) 331 | 8. 对缺失值所在行进行清洗。 332 | 9. 将处理后的数据保存到 MySQL 数据库 333 | """ 334 | # 导入模块 335 | import pandas as pd 336 | from sqlalchemy import create_engine 337 | import logging 338 | 339 | # 读取 全国-热门城市岗位招聘数据.csv 文件 340 | all_city_zp_df = pd.read_csv('../全国-热门城市岗位数据.csv', encoding='utf8') 341 | 342 | # 对重复行进行清洗。 343 | all_city_zp_df.drop_duplicates(inplace=True) 344 | 345 | # 对`工作地址`字段进行预处理。要求:北京·海淀区·西北旺 --> 北京,海淀区,西北旺。分隔成3个字段 346 | all_city_zp_area_df = all_city_zp_df['job_area'].str.split('·', expand=True) 347 | all_city_zp_area_df = all_city_zp_area_df.rename(columns={0: "city", 1: "district", 2: "street"}) 348 | 349 | # 对`薪资`字段进行预处理。要求:30-60K·15薪 --> 最低:30,最高:60 350 | all_city_zp_salary_df = all_city_zp_df['job_salary'].str.split('K', expand=True)[0].str.split('-', expand=True) 351 | all_city_zp_salary_df = all_city_zp_salary_df.rename(columns={0: 'salary_lower', 1: 'salary_high'}) 352 | 353 | 354 | # 对`工作经验`字段进行预处理。要求:经验不限/在校/应届 :0,1-3年:1,3-5年:2,5-10年:3,10年以上:4 355 | def fun_work_year(x): 356 | if x in "1-3年": 357 | return 1 358 | elif x in "3-5年": 359 | return 2 360 | elif x in "5-10年": 361 | return 3 362 | elif x in "10年以上": 363 | return 4 364 | else: 365 | return 0 366 | 367 | 368 | all_city_zp_df['work_year'] = all_city_zp_df['work_year'].apply(lambda x: fun_work_year(x)) 369 | 370 | 371 | # 对`企业规模`字段进行预处理。要求:500人以下:0,500-999:1,1000-9999:2,10000人以上:3 372 | def fun_com_size(x): 373 | if x in "500-999人": 374 | return 1 375 | elif x in "1000-9999人": 376 | return 2 377 | elif x in "10000人以上": 378 | return 3 379 | else: 380 | return 0 381 | 382 | 383 | # 对`岗位福利`字段进行预处理。要求:将描述中的中文','(逗号),替换成英文','(逗号) 384 | all_city_zp_df['job_benefits'] = all_city_zp_df['job_benefits'].str.replace(',', ',') 385 | 386 | # 合并所有数据集 387 | clean_all_city_zp_df = pd.concat([all_city_zp_df, all_city_zp_salary_df, all_city_zp_area_df], axis=1) 388 | 389 | # 删除冗余列 390 | clean_all_city_zp_df.drop('job_area', axis=1, inplace=True) # 删除原区域 391 | clean_all_city_zp_df.drop('job_salary', axis=1, inplace=True) # 删除原薪资 392 | 393 | # 对缺失值所在行进行清洗。 394 | clean_all_city_zp_df.dropna(axis=0, how='any', inplace=True) 395 | clean_all_city_zp_df.drop(axis=0, 396 | index=(clean_all_city_zp_df.loc[(clean_all_city_zp_df['job_benefits'] == 'None')].index), 397 | inplace=True) 398 | # 将处理后的数据保存到 MySQL 数据库 399 | engine = create_engine('mysql+pymysql://root:123456@localhost:3306/bosszp_db?charset=utf8') 400 | clean_all_city_zp_df.to_sql('t_boss_zp_info', con=engine, if_exists='replace') 401 | logging.info("Write to MySQL Successfully!") 402 | ``` 403 | 404 | ​ ③ 运行程序,检查数据是否清洗成功和插入到数据库 405 | 406 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112170559410.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dtZXRidGdia2k=,size_16,color_FFFFFF,t_70#pic_center) 407 | 408 | 409 |
图 8 数据库数据预览
410 | 411 | ### 3.3 数据分析与可视化 412 | 413 | ​ 成功运行上面两个流程后,我们已经得到了可用于数据分析的高质量数据。拿到这些数据以后,我们使用 `python + sql` 脚本的方式对数据进行多维度分析,并使用 `highcharts` 工具进行数据可视化。整个分析可视化通过轻量化 `WEB` 框架 `Flask` 来进行部署。 414 | 415 | #### 3.3.1 搭建 Flask 项目 416 | 417 | ① 安装 `Flask` 418 | 419 | ```cmd 420 | $ pip install flask 421 | ``` 422 | 423 | 424 | 425 | ② 搭建 Flask 项目环境 426 | 427 | ​ 在 `bosszp` 项目下,新建 `web` 包,在 `web` 包下分别创建 `templates` 文件夹、`static`文件夹和 `run.py` 文件。 428 | 429 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112170616877.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dtZXRidGdia2k=,size_16,color_FFFFFF,t_70#pic_center) 430 | 431 | 432 |
图 9 Flask 项目结构
433 | 434 | > 小贴士: 435 | > 436 | > ​ `Flask` 框架目录介绍: 437 | > 438 | > ​ `web/` -- `flask` 项目名 439 | > 440 | > ​ `static/` -- 存放静态资源。如 `js`, `css`, `img` 等 441 | > 442 | > ​ `templates/` -- 存放网页模板。如 `*.html` 443 | > 444 | > ​ `run.py` -- 编写应用程序 445 | 446 | #### 3.3.2 分析和可视化 447 | 448 | ​ Flask web项目环境搭建好以后,我们就可以开始做数据分析和可视化了。 449 | 450 | ① 新建数据库工具模块 `dbutils.py` 451 | 452 | ```python 453 | # -*- coding:utf-8 -*- 454 | """ 455 | 作者:jhzhong 456 | 功能:连接数据库,封装了操作数据库的增删改查等常见操作函数 457 | """ 458 | 459 | import pymysql 460 | import logging 461 | 462 | 463 | class DBUtils(object): 464 | # 初始化连接对象和游标对象 465 | _db_conn = None 466 | _db_cursor = None 467 | 468 | """ 469 | 数据库工具类 初始化方法 470 | 传入 host,user,password,db 进行数据库连接 471 | """ 472 | 473 | def __init__(self, host, user, password, db, port=3306, charset='utf8'): 474 | try: 475 | self._db_conn = pymysql.connect(host=host, user=user, password=password, port=port, db=db, charset=charset) 476 | self._db_cursor = self._db_conn.cursor() 477 | except Exception as e: 478 | logging.error(e) 479 | 480 | def get_one(self, sql_str, args=None): 481 | """ 482 | 查询单个结果,返回具体的数据内容 483 | :param sql_str: sql语句 484 | :param args: 参数列表 485 | :return: result 486 | """ 487 | try: 488 | if self._db_conn is not None: 489 | self._db_cursor.execute(sql_str, args=args) 490 | result = self._db_cursor.fetchone() 491 | return result 492 | else: 493 | logging.error("请检查数据库连接") 494 | except Exception as e: 495 | logging.error(e) 496 | 497 | # 查询多个结果 498 | def get_all(self, sql_str, args=None): 499 | """ 500 | 查询多个结果,返回元组对象 501 | :param sql_str: sql 语句 502 | :param args: 参数列表 503 | :return: result 504 | """ 505 | try: 506 | if self._db_conn is not None: 507 | self._db_cursor.execute(sql_str, args=args) 508 | result = self._db_cursor.fetchall() 509 | return result 510 | else: 511 | logging.error("请检查数据库连接") 512 | except Exception as e: 513 | logging.error(e) 514 | 515 | # 插入数据 516 | def insert(self, sql_str, args=None): 517 | """ 518 | 向数据库插入数据,返回影响行数(int) 519 | :param sql_str: sql 语句 520 | :param args: 参数列表 521 | :return: affect_rows 522 | """ 523 | try: 524 | if self._db_conn is not None: 525 | affect_rows = self._db_cursor.execute(sql_str, args=args) 526 | self._db_conn.commit() 527 | return affect_rows 528 | else: 529 | logging.error("请检查数据库连接") 530 | except Exception as e: 531 | self._db_conn.rollback() 532 | logging.error(e) 533 | 534 | # 修改数据 535 | def modify(self, sql_str, args=None): 536 | """ 537 | 更新数据,返回影响行数(int) 538 | :param sql_str: sql 语句 539 | :param args: 参数列表 540 | :return: affect_rows 541 | """ 542 | return self.insert(sql_str=sql_str, args=args) 543 | 544 | # 删除数据 545 | def delete(self, sql_str, args=None): 546 | """ 547 | 删除数据,返回影响行数(int) 548 | :param sql_str: sql 语句 549 | :param args: 参数列表 550 | :return: affect_rows 551 | """ 552 | return self.insert(sql_str=sql_str, args=args) 553 | 554 | def __del__(self): 555 | """ 556 | 程序运行结束后,会默认调用 __del__ 方法 557 | 销毁对象 558 | """ 559 | if self._db_conn is not None: 560 | self._db_cursor.close() 561 | self._db_conn.close() 562 | ``` 563 | 564 | ② 在 `templates` 文件夹下新建 `index.html`,并进行编辑 565 | 566 | ```html 567 | 568 | 569 | 570 | 571 | Boss岗位分析可视化 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 |
594 | 595 |
596 |

Boss直聘岗位分析可视化

597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 | 606 |
607 |
608 |

学历占比

609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 | 618 |
619 |
620 |
621 |
622 |
623 |
624 |

企业招聘Top 10

625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 |
排名公司名称岗位数量
636 |
637 |
638 |
639 |
640 |
641 | 644 |
645 | 648 | 649 | 650 | ``` 651 | 652 | 653 | 654 | ③ 在 `static` 文件夹下分别建 `js`、`css` 、`img` 和 `highcharts` 文件夹。 655 | 656 | `highcharts\` 文件夹下放入以下静态文件 657 | 658 | ```python 659 | highcharts.js 660 | highcharts-more.js 661 | dark-unica.js 662 | wordcloud.js 663 | oldie.js 664 | ``` 665 | 666 | `static` 下的静态资源下载地址: 667 | 668 | ④ 编写 `run.py` 669 | 670 | ```python 671 | # -*- coding:utf-8 -*- 672 | """ 673 | 作者:jhzhong 674 | 功能:数据分析于可视化 675 | """ 676 | 677 | from flask import Flask, render_template 678 | from bosszp.web.dbutils import DBUtils 679 | import json 680 | 681 | app = Flask(__name__) 682 | 683 | 684 | def get_db_conn(): 685 | """ 686 | 获取数据库连接 687 | :return: db_conn 数据库连接对象 688 | """ 689 | return DBUtils(host='localhost', user='root', password='123456', db='bosszp_db') 690 | 691 | 692 | def msg(status, data='未加载到数据'): 693 | """ 694 | :param status: 状态码 200成功,201未找到数据 695 | :param data: 响应数据 696 | :return: 字典 如{'status': 201, 'data': ‘未加载到数据’} 697 | """ 698 | return json.dumps({'status': status, 'data': data}) 699 | 700 | 701 | @app.route('/') 702 | def index(): 703 | """ 704 | 首页 705 | :return: index.html 跳转到首页 706 | """ 707 | return render_template('index.html') 708 | 709 | 710 | @app.route('/getwordcloud') 711 | def get_word_cloud(): 712 | """ 713 | 获取岗位福利词云数据 714 | :return: 715 | """ 716 | db_conn = get_db_conn() 717 | text = \ 718 | db_conn.get_one(sql_str="SELECT GROUP_CONCAT(job_benefits) FROM t_boss_zp_info")[0] 719 | if text is None: 720 | return msg(201) 721 | return msg(200, text) 722 | 723 | 724 | @app.route('/getjobinfo') 725 | def get_job_info(): 726 | """ 727 | 获取热门岗位招聘区域分布 728 | :return: 729 | """ 730 | db_conn = get_db_conn() 731 | results = db_conn.get_all( 732 | sql_str="SELECT city,district,COUNT(1) as num FROM t_boss_zp_info GROUP BY city,district") 733 | # {"city":"北京","info":[{"district":"朝阳区","num":27},{"海淀区":43}]} 734 | 735 | if results is None or len(results) == 0: 736 | return msg(201) 737 | data = [] 738 | city_detail = {} 739 | for r in results: 740 | info = {'name': r[1], 'value': r[2]} 741 | if r[0] not in city_detail: 742 | city_detail[r[0]] = [info] 743 | else: 744 | city_detail[r[0]].append(info) 745 | for k, v in city_detail.items(): 746 | temp = {'name': k, 'data': v} 747 | data.append(temp) 748 | return msg(200, data) 749 | 750 | 751 | @app.route('/getjobnum') 752 | def get_job_num(): 753 | """ 754 | 获取个城市岗位数量 755 | :return: 756 | """ 757 | db_conn = get_db_conn() 758 | results = db_conn.get_all(sql_str="SELECT city,COUNT(1) num FROM t_boss_zp_info GROUP BY city") 759 | if results is None or len(results) == 0: 760 | return msg(201) 761 | if results is None or len(results) == 0: 762 | return msg(201) 763 | data = [] 764 | for r in results: 765 | data.append(list(r)) 766 | return msg(200, data) 767 | 768 | 769 | @app.route('/getcomtypenum') 770 | def get_com_type_num(): 771 | """ 772 | 获取企业类型占比 773 | :return: 774 | """ 775 | db_conn = get_db_conn() 776 | results = db_conn.get_all( 777 | sql_str="SELECT com_type, ROUND(COUNT(1)/(SELECT SUM(t1.num) FROM (SELECT COUNT(1) num FROM t_boss_zp_info GROUP BY com_type) t1)*100,2) percent FROM t_boss_zp_info GROUP BY com_type") 778 | if results is None or len(results) == 0: 779 | return msg(201) 780 | data = [] 781 | for r in results: 782 | data.append({'name': r[0], 'y': float(r[1])}) 783 | return msg(200, data) 784 | 785 | 786 | # 扇形图 787 | @app.route('/geteducationnum') 788 | def geteducationnum(): 789 | """ 790 | 获取学历占比 791 | :return: 792 | """ 793 | db_conn = get_db_conn() 794 | results = db_conn.get_all( 795 | sql_str="SELECT t1.education,ROUND(t1.num/(SELECT SUM(t2.num) FROM(SELECT COUNT(1) num FROM t_boss_zp_info t GROUP BY t.education)t2)*100,2) FROM( SELECT t.education,COUNT(1) num FROM t_boss_zp_info t GROUP BY t.education) t1") 796 | if results is None or len(results) == 0: 797 | return msg(201) 798 | data = [] 799 | for r in results: 800 | data.append([r[0], float(r[1])]) 801 | return msg(200, data) 802 | 803 | 804 | # 获取排行榜 805 | @app.route('/getorder') 806 | def getorder(): 807 | """ 808 | 获取企业招聘数量排行榜 809 | :return: 810 | """ 811 | db_conn = get_db_conn() 812 | results = db_conn.get_all( 813 | sql_str="SELECT t.com_name,COUNT(1) FROM t_boss_zp_info t GROUP BY t.com_name ORDER BY COUNT(1) DESC LIMIT 10") 814 | if results is None or len(results) == 0: 815 | return msg(201) 816 | data = [] 817 | for i, r in enumerate(results): 818 | data.append({'id': i + 1, 819 | 'name': r[0], 820 | 'num': r[1]}) 821 | return msg(200, data) 822 | 823 | 824 | if __name__ == '__main__': 825 | app.run(host='127.0.0.1', port=8080, debug=True) 826 | ``` 827 | 828 | ⑤ 运行 `run.py` 829 | 830 | ```cmd 831 | $ export FLASK_APP=run.py 832 | $ python -m flask run 833 | * Running on http://127.0.0.1:8080/ 834 | ``` 835 | 836 | ```text 837 | 外部可见的服务器 838 | 运行服务器后,会发现只有你自己的电脑可以使用服务,而网络中的其他电脑却 不行。缺省设置就是这样的,因为在调试模式下该应用的用户可以执行你电脑中 的任意 Python 代码。 839 | 840 | 如果你关闭了调试器或信任你网络中的用户,那么可以让服务器被公开访问。 只要在命令行上简单的加上 --host=0.0.0.0 即可: 841 | 842 | $ flask run --host=0.0.0.0 843 | 这行代码告诉你的操作系统监听所有公开的 IP 。 844 | ``` 845 | 846 | 现在在浏览器中打开 http://127.0.0.1:8080/ ,应该可以看到可视化大屏了。 847 | 848 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112170638686.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dtZXRidGdia2k=,size_16,color_FFFFFF,t_70#pic_center) 849 | 850 | 851 | 852 | 853 | ## 4. 项目总结 854 | 855 | ​ 本文主要讲解了Boss直聘网站数据爬取分析与可视化的实现流程及代码编写。目前项目任然还有很多的不足和考虑不全面的地方,当然如果您有疑问的地方和更好的优化建议也请给我留言,我一定尽力的去回复和改进这些地方。 856 | -------------------------------------------------------------------------------- /bosszp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcoco/bosszp/522250ff097909439bd72a58d2433bfc1a5bdab3/bosszp/__init__.py -------------------------------------------------------------------------------- /bosszp/clean/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | 作者:jhzhong 4 | """ 5 | -------------------------------------------------------------------------------- /bosszp/clean/dataclean.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | 作者:jhzhong 4 | 功能:对岗位数据进行清洗与预处理 5 | 需求: 6 | 1. 读取 `全国-热门城市岗位数据.csv` 文件 7 | 2. 对重复行进行清洗。 8 | 3. 对`工作地址`字段进行预处理。要求:北京·海淀区·西北旺 --> 北京,海淀区,西北旺。分隔成3个字段 9 | 4. 对`薪资`字段进行预处理。要求:30-60K·15薪 --> 最低:30,最高:60 10 | 5. 对`工作经验`字段进行预处理。要求:经验不限/在校/应届 :0,1-3年:1,3-5年:2,5-10年:3,10年以上:4 11 | 6. 对`企业规模`字段进行预处理。要求:500人以下:0,500-999:1,1000-9999:2,10000人以上:3 12 | 7. 对`岗位福利`字段进行预处理。要求:将描述中的中文','(逗号),替换成英文','(逗号) 13 | 8. 对缺失值所在行进行清洗。 14 | 9. 将处理后的数据保存到 MySQL 数据库 15 | """ 16 | # 导入模块 17 | import pandas as pd 18 | from sqlalchemy import create_engine 19 | import logging 20 | 21 | # 读取 全国-热门城市岗位招聘数据.csv 文件 22 | all_city_zp_df = pd.read_csv('../../全国-热门城市岗位数据.csv', encoding='utf8') 23 | 24 | # 对重复行进行清洗。 25 | all_city_zp_df.drop_duplicates(inplace=True) 26 | 27 | # 对`工作地址`字段进行预处理。要求:北京·海淀区·西北旺 --> 北京,海淀区,西北旺。分隔成3个字段 28 | all_city_zp_area_df = all_city_zp_df['job_area'].str.split('·', expand=True) 29 | all_city_zp_area_df = all_city_zp_area_df.rename(columns={0: "city", 1: "district", 2: "street"}) 30 | 31 | # 对`薪资`字段进行预处理。要求:30-60K·15薪 --> 最低:30,最高:60 32 | all_city_zp_salary_df = all_city_zp_df['job_salary'].str.split('K', expand=True)[0].str.split('-', expand=True) 33 | all_city_zp_salary_df = all_city_zp_salary_df.rename(columns={0: 'salary_lower', 1: 'salary_high'}) 34 | 35 | 36 | # 对`工作经验`字段进行预处理。要求:经验不限/在校/应届 :0,1-3年:1,3-5年:2,5-10年:3,10年以上:4 37 | def fun_work_year(x): 38 | if x in "1-3年": 39 | return 1 40 | elif x in "3-5年": 41 | return 2 42 | elif x in "5-10年": 43 | return 3 44 | elif x in "10年以上": 45 | return 4 46 | else: 47 | return 0 48 | 49 | 50 | all_city_zp_df['work_year'] = all_city_zp_df['work_year'].apply(lambda x: fun_work_year(x)) 51 | 52 | 53 | # 对`企业规模`字段进行预处理。要求:500人以下:0,500-999:1,1000-9999:2,10000人以上:3 54 | def fun_com_size(x): 55 | if x in "500-999人": 56 | return 1 57 | elif x in "1000-9999人": 58 | return 2 59 | elif x in "10000人以上": 60 | return 3 61 | else: 62 | return 0 63 | 64 | 65 | # 对`岗位福利`字段进行预处理。要求:将描述中的中文','(逗号),替换成英文','(逗号) 66 | all_city_zp_df['job_benefits'] = all_city_zp_df['job_benefits'].str.replace(',', ',') 67 | 68 | # 合并所有数据集 69 | clean_all_city_zp_df = pd.concat([all_city_zp_df, all_city_zp_salary_df, all_city_zp_area_df], axis=1) 70 | 71 | # 删除冗余列 72 | clean_all_city_zp_df.drop('job_area', axis=1, inplace=True) # 删除原区域 73 | clean_all_city_zp_df.drop('job_salary', axis=1, inplace=True) # 删除原薪资 74 | 75 | # 对缺失值所在行进行清洗。 76 | clean_all_city_zp_df.dropna(axis=0, how='any', inplace=True) 77 | clean_all_city_zp_df.drop(axis=0, 78 | index=(clean_all_city_zp_df.loc[(clean_all_city_zp_df['job_benefits'] == 'None')].index), 79 | inplace=True) 80 | # 将处理后的数据保存到 MySQL 数据库 81 | engine = create_engine('mysql+pymysql://root:123456@localhost:3306/bosszp_db?charset=utf8') 82 | clean_all_city_zp_df.to_sql('t_boss_zp_info', con=engine, if_exists='replace') 83 | logging.info("Write to MySQL Successfully!") 84 | -------------------------------------------------------------------------------- /bosszp/items.py: -------------------------------------------------------------------------------- 1 | # Define here the models for your scraped items 2 | # 3 | # See documentation in: 4 | # https://docs.scrapy.org/en/latest/topics/items.html 5 | 6 | import scrapy 7 | 8 | 9 | class BosszpItem(scrapy.Item): 10 | job_name = scrapy.Field() # 岗位名 11 | job_area = scrapy.Field() # 工作地址 12 | job_salary = scrapy.Field() # 薪资 13 | com_name = scrapy.Field() # 企业名称 14 | com_type = scrapy.Field() # 企业类型 15 | com_size = scrapy.Field() # 企业规模 16 | finance_stage = scrapy.Field() # 融资情况 17 | work_year = scrapy.Field() # 工作年限 18 | education = scrapy.Field() # 学历要求 19 | job_benefits = scrapy.Field() # 岗位福利 20 | -------------------------------------------------------------------------------- /bosszp/middlewares.py: -------------------------------------------------------------------------------- 1 | # Define here the models for your spider middleware 2 | # 3 | # See documentation in: 4 | # https://docs.scrapy.org/en/latest/topics/spider-middleware.html 5 | 6 | from scrapy import signals 7 | 8 | # useful for handling different item types with a single interface 9 | from itemadapter import is_item, ItemAdapter 10 | 11 | 12 | class BosszpSpiderMiddleware: 13 | # Not all methods need to be defined. If a method is not defined, 14 | # scrapy acts as if the spider middleware does not modify the 15 | # passed objects. 16 | 17 | @classmethod 18 | def from_crawler(cls, crawler): 19 | # This method is used by Scrapy to create your spiders. 20 | s = cls() 21 | crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) 22 | return s 23 | 24 | def process_spider_input(self, response, spider): 25 | # Called for each response that goes through the spider 26 | # middleware and into the spider. 27 | 28 | # Should return None or raise an exception. 29 | return None 30 | 31 | def process_spider_output(self, response, result, spider): 32 | # Called with the results returned from the Spider, after 33 | # it has processed the response. 34 | 35 | # Must return an iterable of Request, or item objects. 36 | for i in result: 37 | yield i 38 | 39 | def process_spider_exception(self, response, exception, spider): 40 | # Called when a spider or process_spider_input() method 41 | # (from other spider middleware) raises an exception. 42 | 43 | # Should return either None or an iterable of Request or item objects. 44 | pass 45 | 46 | def process_start_requests(self, start_requests, spider): 47 | # Called with the start requests of the spider, and works 48 | # similarly to the process_spider_output() method, except 49 | # that it doesn’t have a response associated. 50 | 51 | # Must return only requests (not items). 52 | for r in start_requests: 53 | yield r 54 | 55 | def spider_opened(self, spider): 56 | spider.logger.info('Spider opened: %s' % spider.name) 57 | 58 | 59 | class BosszpDownloaderMiddleware: 60 | # Not all methods need to be defined. If a method is not defined, 61 | # scrapy acts as if the downloader middleware does not modify the 62 | # passed objects. 63 | 64 | @classmethod 65 | def from_crawler(cls, crawler): 66 | # This method is used by Scrapy to create your spiders. 67 | s = cls() 68 | crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) 69 | return s 70 | 71 | def process_request(self, request, spider): 72 | # Called for each request that goes through the downloader 73 | # middleware. 74 | 75 | # Must either: 76 | # - return None: continue processing this request 77 | # - or return a Response object 78 | # - or return a Request object 79 | # - or raise IgnoreRequest: process_exception() methods of 80 | # installed downloader middleware will be called 81 | return None 82 | 83 | def process_response(self, request, response, spider): 84 | # Called with the response returned from the downloader. 85 | 86 | # Must either; 87 | # - return a Response object 88 | # - return a Request object 89 | # - or raise IgnoreRequest 90 | return response 91 | 92 | def process_exception(self, request, exception, spider): 93 | # Called when a download handler or a process_request() 94 | # (from other downloader middleware) raises an exception. 95 | 96 | # Must either: 97 | # - return None: continue processing this exception 98 | # - return a Response object: stops process_exception() chain 99 | # - return a Request object: stops process_exception() chain 100 | pass 101 | 102 | def spider_opened(self, spider): 103 | spider.logger.info('Spider opened: %s' % spider.name) 104 | -------------------------------------------------------------------------------- /bosszp/pipelines.py: -------------------------------------------------------------------------------- 1 | # Define your item pipelines here 2 | # 3 | # Don't forget to add your pipeline to the ITEM_PIPELINES setting 4 | # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html 5 | 6 | 7 | # useful for handling different item types with a single interface 8 | from itemadapter import ItemAdapter 9 | 10 | 11 | class BosszpPipeline: 12 | def process_item(self, item, spider): 13 | """ 14 | 保存数据到本地 csv 文件 15 | :param item: 数据项 16 | :param spider: 17 | :return: 18 | """ 19 | with open(file='全国-热门城市岗位数据.csv', mode='a+', encoding='utf8') as f: 20 | f.write( 21 | '{job_name},{job_area},{job_salary},{com_name},{com_type},{com_size},{finance_stage},{work_year},' 22 | '{education},{job_benefits}\n'.format( 23 | **item)) 24 | return item 25 | -------------------------------------------------------------------------------- /bosszp/settings.py: -------------------------------------------------------------------------------- 1 | # Scrapy settings for bosszp project 2 | # 3 | # For simplicity, this file contains only settings considered important or 4 | # commonly used. You can find more settings consulting the documentation: 5 | # 6 | # https://docs.scrapy.org/en/latest/topics/settings.html 7 | # https://docs.scrapy.org/en/latest/topics/downloader-middleware.html 8 | # https://docs.scrapy.org/en/latest/topics/spider-middleware.html 9 | 10 | BOT_NAME = 'bosszp' 11 | 12 | SPIDER_MODULES = ['bosszp.spiders'] 13 | NEWSPIDER_MODULE = 'bosszp.spiders' 14 | 15 | 16 | # Crawl responsibly by identifying yourself (and your website) on the user-agent 17 | #USER_AGENT = 'bosszp (+http://www.yourdomain.com)' 18 | 19 | # Obey robots.txt rules 20 | ROBOTSTXT_OBEY = False 21 | 22 | # Configure maximum concurrent requests performed by Scrapy (default: 16) 23 | #CONCURRENT_REQUESTS = 32 24 | 25 | # Configure a delay for requests for the same website (default: 0) 26 | # See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay 27 | # See also autothrottle settings and docs 28 | DOWNLOAD_DELAY = 60 29 | # The download delay setting will honor only one of: 30 | #CONCURRENT_REQUESTS_PER_DOMAIN = 16 31 | #CONCURRENT_REQUESTS_PER_IP = 16 32 | 33 | # Disable cookies (enabled by default) 34 | COOKIES_ENABLED = False 35 | 36 | # Disable Telnet Console (enabled by default) 37 | #TELNETCONSOLE_ENABLED = False 38 | 39 | # Override the default request headers: 40 | # DEFAULT_REQUEST_HEADERS = { 41 | # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 42 | # 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 43 | # 'Referer': 'https://www.zhipin.com/c101020100/?ka=sel-city-101020100', 44 | # # 'Cookie': '此处需替换成最新的 Cookie值', 45 | # 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36', 46 | # } 47 | 48 | # Enable or disable spider middlewares 49 | # See https://docs.scrapy.org/en/latest/topics/spider-middleware.html 50 | #SPIDER_MIDDLEWARES = { 51 | # 'bosszp.middlewares.BosszpSpiderMiddleware': 543, 52 | #} 53 | 54 | # Enable or disable downloader middlewares 55 | # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html 56 | #DOWNLOADER_MIDDLEWARES = { 57 | # 'bosszp.middlewares.BosszpDownloaderMiddleware': 543, 58 | #} 59 | 60 | # Enable or disable extensions 61 | # See https://docs.scrapy.org/en/latest/topics/extensions.html 62 | #EXTENSIONS = { 63 | # 'scrapy.extensions.telnet.TelnetConsole': None, 64 | #} 65 | 66 | # Configure item pipelines 67 | # See https://docs.scrapy.org/en/latest/topics/item-pipeline.html 68 | ITEM_PIPELINES = { 69 | 'bosszp.pipelines.BosszpPipeline': 300, 70 | } 71 | 72 | # Enable and configure the AutoThrottle extension (disabled by default) 73 | # See https://docs.scrapy.org/en/latest/topics/autothrottle.html 74 | #AUTOTHROTTLE_ENABLED = True 75 | # The initial download delay 76 | #AUTOTHROTTLE_START_DELAY = 5 77 | # The maximum download delay to be set in case of high latencies 78 | #AUTOTHROTTLE_MAX_DELAY = 60 79 | # The average number of requests Scrapy should be sending in parallel to 80 | # each remote server 81 | #AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 82 | # Enable showing throttling stats for every response received: 83 | #AUTOTHROTTLE_DEBUG = False 84 | 85 | # Enable and configure HTTP caching (disabled by default) 86 | # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings 87 | #HTTPCACHE_ENABLED = True 88 | #HTTPCACHE_EXPIRATION_SECS = 0 89 | #HTTPCACHE_DIR = 'httpcache' 90 | #HTTPCACHE_IGNORE_HTTP_CODES = [] 91 | #HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage' 92 | -------------------------------------------------------------------------------- /bosszp/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 | -------------------------------------------------------------------------------- /bosszp/spiders/boss.py: -------------------------------------------------------------------------------- 1 | import scrapy 2 | import json 3 | import logging 4 | import random 5 | from bosszp.items import BosszpItem 6 | 7 | 8 | class BossSpider(scrapy.Spider): 9 | name = 'boss' 10 | allowed_domains = ['zhipin.com'] 11 | start_urls = ['https://www.zhipin.com/wapi/zpCommon/data/cityGroup.json'] 12 | # 设置多个 cookie,建议数量为 页数/2 + 1 个cookie.至少 设置 3 个 13 | cookies = [ 14 | '__zp_stoken__=f330bOEgsRnsAIR5zQCIme250elRqcEdVLyMQZ1hvW3kSUG5%2BBSpKLWBtXH9Bcix%2BPHtlVRgdTX5vDzYkP1I7JTcKM38qB2MnL2hrWRIic0cFI3kYKiUaPGUVPX0WO2JLOipvRlwbP1YFBQlHOQ%3D%3D', 15 | '__zp_stoken__=f330bOEgsRnsAIU1dUCAZe250elQRdl89P2IQZ1hvW1JSe1NuCipKLWBtTU9iTAJ%2BPHtlVRgdTX5vZx8kSDwNZHY9InBRawJLKX8ESx5ac0cFI3kYKgskH1UEPX0WO2JEOipvRlwbP1YFBQlHOQ%3D%3D', 16 | '__zp_stoken__=f330bOEgsRnsAISRBIWtKe250elRwAXZ4fBEQZ1hvW2xPZBdzYipKLWBtE0xGIkV%2BPHtlVRgdTX51chckSi0jd1g6GW8vAAYpS31hX3ogc0cFI3kYKkxKO1ZaPX0WO2IsOipvRlwbP1YFBQlHOQ%3D%3D' 17 | '__zp_stoken__=f330bOEgsRnsAIVF5f10ce250elQIJnYiaigQZ1hvW29ScE1JWSpKLWBtAUNYRlh%2BPHtlVRgdTX51fRckLFRBFE1OBRhVeQYtcG5iRxEsc0cFI3kYKlEuJVlIPX0WO2IXOipvRlwbP1YFBQlHOQ%3D%3D' 18 | '__zp_stoken__=f330bOEgsRnsAIUgPQXkQe250elQRe3peYykQZ1hvW0V%2FKVskBCpKLWBtLAg3SDl%2BPHtlVRgdTX5xJkAkP146HCwDLxEucRknIgBtXBJUc0cFI3kYKjAgShJlPX0WO2JKOipvRlwbP1YFBQlHOQ%3D%3D' 19 | ] 20 | # 设置多个请求头 21 | user_agents = [ 22 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36', 23 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15', 24 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0', 25 | 'Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1', 26 | 'Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11', 27 | 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)', 28 | 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)', 29 | 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)', 30 | 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)', 31 | ] 32 | page_no = 1 # 初始化分页 33 | 34 | def random_header(self): 35 | """ 36 | 随机生成请求头 37 | :return: headers 38 | """ 39 | headers = {'Referer': 'https://www.zhipin.com/c101020100/?ka=sel-city-101020100'} 40 | headers['cookie'] = random.choice(self.cookies) 41 | headers['user-agent'] = random.choice(self.user_agents) 42 | return headers 43 | 44 | def parse(self, response): 45 | """ 46 | 解析首页热门城市列表,选择热门城市进行爬取 47 | :param response: 热门城市字典数据 48 | :return: 49 | """ 50 | # 获取服务器返回的内容 51 | city_group = json.loads(response.body.decode()) 52 | # 获取热门城市列表 53 | hot_city_list = city_group['zpData']['hotCityList'] 54 | # 初始化空列表,存储打印信息 55 | # city_lst = [] 56 | # for index,item in enumerate(hot_city_list): 57 | # city_lst.apend({index+1: item['name']}) 58 | # 列表推导式: 59 | hot_city_names = [{index + 1: item['name']} for index, item in enumerate(hot_city_list)] 60 | print("--->", hot_city_names) 61 | # 从键盘获取城市编号 62 | city_no = int(input('请从上述城市列表中,选择编号开始爬取:')) 63 | # 拼接url https://www.zhipin.com/job_detail/?query=&city=101040100&industry=&position= 64 | # 获取城市编码code 65 | city_code = hot_city_list[city_no - 1]['code'] 66 | city_url = 'https://www.zhipin.com/job_detail/?query=&city={}&industry=&position='.format(city_code) 67 | logging.info("<<<<<<<<<<<<<正在爬取第_{}_页岗位数据>>>>>>>>>>>>>".format(self.page_no)) 68 | yield scrapy.Request(url=city_url, headers=self.random_header(), callback=self.parse_city) 69 | 70 | def parse_city(self, response): 71 | 72 | """ 73 | 解析岗位页数据 74 | :param response: 岗位页响应数据 75 | :return: 76 | """ 77 | if response.status != 200: 78 | logging.warning("<<<<<<<<<<<<<获取城市招聘信息失败,ip已被封禁。请稍后重试>>>>>>>>>>>>>") 79 | return 80 | li_elements = response.xpath('//div[@class="job-list"]/ul/li') # 定位到所有的li标签 81 | next_url = response.xpath('//div[@class="page"]/a[last()]/@href').get() # 获取下一页 82 | 83 | for li in li_elements: 84 | job_name = li.xpath('./div/div[1]//div[@class="job-title"]/span[1]/a/text()').get() 85 | job_area = li.xpath('./div/div[1]//div[@class="job-title"]/span[2]/span[1]/text()').get() 86 | job_salary = li.xpath('./div/div[1]//span[@class="red"]/text()').get() 87 | com_name = li.xpath('./div/div[1]/div[2]//div[@class="company-text"]/h3/a/text()').get() 88 | com_type = li.xpath('./div/div[1]/div[2]/div[1]/p/a/text()').get() 89 | com_size = li.xpath('./div/div[1]/div[2]/div[1]/p/text()[2]').get() 90 | finance_stage = li.xpath('./div/div[1]/div[2]/div[1]/p/text()[1]').get() 91 | work_year = li.xpath('./div/div[1]/div[1]/div[1]/div[2]/p/text()[1]').get() 92 | education = li.xpath('./div/div[1]/div[1]/div[1]/div[2]/p/text()[2]').get() 93 | job_benefits = li.xpath('./div/div[2]/div[2]/text()').get() 94 | item = BosszpItem(job_name=job_name, job_area=job_area, job_salary=job_salary, com_name=com_name, 95 | com_type=com_type, com_size=com_size, 96 | finance_stage=finance_stage, work_year=work_year, education=education, 97 | job_benefits=job_benefits) 98 | yield item 99 | if next_url == "javascript:;": 100 | logging.info('<<<<<<<<<<<<<热门城市岗位数据已爬取结束>>>>>>>>>>>>>') 101 | logging.info("<<<<<<<<<<<<<一共爬取了_{}_页岗位数据>>>>>>>>>>>>>".format(self.page_no)) 102 | return 103 | next_url = response.urljoin(next_url) # 网址拼接 104 | self.page_no += 1 105 | logging.info("<<<<<<<<<<<<<正在爬取第_{}_页岗位数据>>>>>>>>>>>>>".format(self.page_no)) 106 | yield scrapy.Request(url=next_url, headers=self.random_header(), callback=self.parse_city) 107 | -------------------------------------------------------------------------------- /bosszp/web/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | 作者:jhzhong 4 | """ 5 | -------------------------------------------------------------------------------- /bosszp/web/dbutils.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | 作者:jhzhong 4 | 功能:连接数据库,封装了操作数据库的增删改查等常见操作函数 5 | """ 6 | 7 | import pymysql 8 | import logging 9 | 10 | 11 | class DBUtils(object): 12 | # 初始化连接对象和游标对象 13 | _db_conn = None 14 | _db_cursor = None 15 | 16 | """ 17 | 数据库工具类 初始化方法 18 | 传入 host,user,password,db 进行数据库连接 19 | """ 20 | 21 | def __init__(self, host, user, password, db, port=3306, charset='utf8'): 22 | try: 23 | self._db_conn = pymysql.connect(host=host, user=user, password=password, port=port, db=db, charset=charset) 24 | self._db_cursor = self._db_conn.cursor() 25 | except Exception as e: 26 | logging.error(e) 27 | 28 | def get_one(self, sql_str, args=None): 29 | """ 30 | 查询单个结果,返回具体的数据内容 31 | :param sql_str: sql语句 32 | :param args: 参数列表 33 | :return: result 34 | """ 35 | try: 36 | if self._db_conn is not None: 37 | self._db_cursor.execute(sql_str, args=args) 38 | result = self._db_cursor.fetchone() 39 | return result 40 | else: 41 | logging.error("请检查数据库连接") 42 | except Exception as e: 43 | logging.error(e) 44 | 45 | # 查询多个结果 46 | def get_all(self, sql_str, args=None): 47 | """ 48 | 查询多个结果,返回元组对象 49 | :param sql_str: sql 语句 50 | :param args: 参数列表 51 | :return: result 52 | """ 53 | try: 54 | if self._db_conn is not None: 55 | self._db_cursor.execute(sql_str, args=args) 56 | result = self._db_cursor.fetchall() 57 | return result 58 | else: 59 | logging.error("请检查数据库连接") 60 | except Exception as e: 61 | logging.error(e) 62 | 63 | # 插入数据 64 | def insert(self, sql_str, args=None): 65 | """ 66 | 向数据库插入数据,返回影响行数(int) 67 | :param sql_str: sql 语句 68 | :param args: 参数列表 69 | :return: affect_rows 70 | """ 71 | try: 72 | if self._db_conn is not None: 73 | affect_rows = self._db_cursor.execute(sql_str, args=args) 74 | self._db_conn.commit() 75 | return affect_rows 76 | else: 77 | logging.error("请检查数据库连接") 78 | except Exception as e: 79 | self._db_conn.rollback() 80 | logging.error(e) 81 | 82 | # 修改数据 83 | def modify(self, sql_str, args=None): 84 | """ 85 | 更新数据,返回影响行数(int) 86 | :param sql_str: sql 语句 87 | :param args: 参数列表 88 | :return: affect_rows 89 | """ 90 | return self.insert(sql_str=sql_str, args=args) 91 | 92 | # 删除数据 93 | def delete(self, sql_str, args=None): 94 | """ 95 | 删除数据,返回影响行数(int) 96 | :param sql_str: sql 语句 97 | :param args: 参数列表 98 | :return: affect_rows 99 | """ 100 | return self.insert(sql_str=sql_str, args=args) 101 | 102 | def __del__(self): 103 | """ 104 | 程序运行结束后,会默认调用 __del__ 方法 105 | 销毁对象 106 | """ 107 | if self._db_conn is not None: 108 | self._db_cursor.close() 109 | self._db_conn.close() 110 | -------------------------------------------------------------------------------- /bosszp/web/run.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | 作者:jhzhong 4 | 功能:数据分析于可视化 5 | """ 6 | 7 | from flask import Flask, render_template 8 | from bosszp.web.dbutils import DBUtils 9 | import json 10 | 11 | app = Flask(__name__) 12 | 13 | 14 | def get_db_conn(): 15 | """ 16 | 获取数据库连接 17 | :return: db_conn 数据库连接对象 18 | """ 19 | return DBUtils(host='localhost', user='root', password='123456', db='bosszp_db') 20 | 21 | 22 | def msg(status, data='未加载到数据'): 23 | """ 24 | :param status: 状态码 200成功,201未找到数据 25 | :param data: 响应数据 26 | :return: 字典 如{'status': 201, 'data': ‘未加载到数据’} 27 | """ 28 | return json.dumps({'status': status, 'data': data}) 29 | 30 | 31 | @app.route('/') 32 | def index(): 33 | """ 34 | 首页 35 | :return: index.html 跳转到首页 36 | """ 37 | return render_template('index.html') 38 | 39 | 40 | @app.route('/getwordcloud') 41 | def get_word_cloud(): 42 | """ 43 | 获取岗位福利词云数据 44 | :return: 45 | """ 46 | db_conn = get_db_conn() 47 | text = \ 48 | db_conn.get_one(sql_str="SELECT GROUP_CONCAT(job_benefits) FROM t_boss_zp_info")[0] 49 | if text is None: 50 | return msg(201) 51 | return msg(200, text) 52 | 53 | 54 | @app.route('/getjobinfo') 55 | def get_job_info(): 56 | """ 57 | 获取热门岗位招聘区域分布 58 | :return: 59 | """ 60 | db_conn = get_db_conn() 61 | results = db_conn.get_all( 62 | sql_str="SELECT city,district,COUNT(1) as num FROM t_boss_zp_info GROUP BY city,district") 63 | # {"city":"北京","info":[{"district":"朝阳区","num":27},{"海淀区":43}]} 64 | 65 | if results is None or len(results) == 0: 66 | return msg(201) 67 | data = [] 68 | city_detail = {} 69 | for r in results: 70 | info = {'name': r[1], 'value': r[2]} 71 | if r[0] not in city_detail: 72 | city_detail[r[0]] = [info] 73 | else: 74 | city_detail[r[0]].append(info) 75 | for k, v in city_detail.items(): 76 | temp = {'name': k, 'data': v} 77 | data.append(temp) 78 | return msg(200, data) 79 | 80 | 81 | @app.route('/getjobnum') 82 | def get_job_num(): 83 | """ 84 | 获取个城市岗位数量 85 | :return: 86 | """ 87 | db_conn = get_db_conn() 88 | results = db_conn.get_all(sql_str="SELECT city,COUNT(1) num FROM t_boss_zp_info GROUP BY city") 89 | if results is None or len(results) == 0: 90 | return msg(201) 91 | if results is None or len(results) == 0: 92 | return msg(201) 93 | data = [] 94 | for r in results: 95 | data.append(list(r)) 96 | return msg(200, data) 97 | 98 | 99 | @app.route('/getcomtypenum') 100 | def get_com_type_num(): 101 | """ 102 | 获取企业类型占比 103 | :return: 104 | """ 105 | db_conn = get_db_conn() 106 | results = db_conn.get_all( 107 | sql_str="SELECT com_type, ROUND(COUNT(1)/(SELECT SUM(t1.num) FROM (SELECT COUNT(1) num FROM t_boss_zp_info GROUP BY com_type) t1)*100,2) percent FROM t_boss_zp_info GROUP BY com_type") 108 | if results is None or len(results) == 0: 109 | return msg(201) 110 | data = [] 111 | for r in results: 112 | data.append({'name': r[0], 'y': float(r[1])}) 113 | return msg(200, data) 114 | 115 | 116 | # 扇形图 117 | @app.route('/geteducationnum') 118 | def geteducationnum(): 119 | """ 120 | 获取学历占比 121 | :return: 122 | """ 123 | db_conn = get_db_conn() 124 | results = db_conn.get_all( 125 | sql_str="SELECT t1.education,ROUND(t1.num/(SELECT SUM(t2.num) FROM(SELECT COUNT(1) num FROM t_boss_zp_info t GROUP BY t.education)t2)*100,2) FROM( SELECT t.education,COUNT(1) num FROM t_boss_zp_info t GROUP BY t.education) t1") 126 | if results is None or len(results) == 0: 127 | return msg(201) 128 | data = [] 129 | for r in results: 130 | data.append([r[0], float(r[1])]) 131 | return msg(200, data) 132 | 133 | 134 | # 获取排行榜 135 | @app.route('/getorder') 136 | def getorder(): 137 | """ 138 | 获取企业招聘数量排行榜 139 | :return: 140 | """ 141 | db_conn = get_db_conn() 142 | results = db_conn.get_all( 143 | sql_str="SELECT t.com_name,COUNT(1) FROM t_boss_zp_info t GROUP BY t.com_name ORDER BY COUNT(1) DESC LIMIT 10") 144 | if results is None or len(results) == 0: 145 | return msg(201) 146 | data = [] 147 | for i, r in enumerate(results): 148 | data.append({'id': i + 1, 149 | 'name': r[0], 150 | 'num': r[1]}) 151 | return msg(200, data) 152 | 153 | 154 | if __name__ == '__main__': 155 | app.run(host='127.0.0.1', port=8080, debug=True) 156 | -------------------------------------------------------------------------------- /bosszp/web/static/css/mystyle.css: -------------------------------------------------------------------------------- 1 | /* 去除页面边距 */ 2 | * { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | body { 8 | /*background-color: #47475c;*/ 9 | background: url("../img/bg.png") repeat; 10 | background-size: 100% 100%; 11 | } 12 | 13 | /* 设置主页 */ 14 | .index { 15 | width: 100%; 16 | height: 100%; 17 | min-width: 1200px; 18 | min-height: 960px; 19 | } 20 | 21 | /* 设置标题栏 */ 22 | .title { 23 | width: 100%; 24 | height: 100%; 25 | min-height: 60px; 26 | color: #FFFFFF; 27 | font-size: 45px; 28 | font-family: "microsoft yahei"; 29 | /* 文本水平居中 */ 30 | text-align: center; 31 | /* 文本垂直居中 line-height 需与min-height一致*/ 32 | line-height: 60px; 33 | vertical-align: middle; 34 | } 35 | 36 | /* 设置绘图区域 */ 37 | .paint_area { 38 | width: 100%; 39 | min-width: 1200px; 40 | min-height: 900px; 41 | } 42 | 43 | 44 | /* 设置数据框 */ 45 | .data_frame_top, .data_frame_bottom { 46 | width: 100%; 47 | height: 100%; 48 | min-width: 1200px; 49 | min-height: 450px; 50 | overflow: hidden; 51 | } 52 | 53 | .data_frame_left_top, 54 | .data_frame_middle_top, 55 | .data_frame_right_top, 56 | .data_frame_left_bottom, 57 | .data_frame_middle_bottom, 58 | .data_frame_right_bottom { 59 | width: 33.3%; 60 | height: 100%; 61 | min-width: 400px; 62 | min-height: 450px; 63 | float: left; 64 | text-align: center; 65 | } 66 | 67 | /* 设置图表区域*/ 68 | #packedbubble,#pie,#fan,#cylindrical,#word_cloud,#order { 69 | width: 100%; 70 | height: 100%; 71 | min-width: 400px; 72 | min-height: 450px; 73 | margin: 0 auto; 74 | color: whitesmoke; 75 | /*背景透明*/ 76 | background-color: rgba(0,0,0,0); 77 | } 78 | 79 | table { 80 | border-collapse: collapse; 81 | border-spacing: 0; 82 | width: 100%; 83 | text-align: center; 84 | } 85 | 86 | th, td { 87 | padding: 10px; 88 | } 89 | 90 | th { 91 | font: bold 16px "微软雅黑"; 92 | color: #fff; 93 | } 94 | 95 | td { 96 | font: 14px "微软雅黑"; 97 | } 98 | 99 | tbody *:hover { 100 | cursor: pointer; 101 | font-size: larger; 102 | background-color: #7777c1; 103 | } 104 | -------------------------------------------------------------------------------- /bosszp/web/static/highcharts/dark-unica.js: -------------------------------------------------------------------------------- 1 | /* 2 | Highcharts JS v8.2.2 (2020-10-22) 3 | 4 | (c) 2009-2019 Torstein Honsi 5 | 6 | License: www.highcharts.com/license 7 | */ 8 | (function(a){"object"===typeof module&&module.exports?(a["default"]=a,module.exports=a):"function"===typeof define&&define.amd?define("highcharts/themes/dark-unica",["highcharts"],function(b){a(b);a.Highcharts=b;return a}):a("undefined"!==typeof Highcharts?Highcharts:void 0)})(function(a){function b(a,c,b,d){a.hasOwnProperty(c)||(a[c]=d.apply(null,b))}a=a?a._modules:{};b(a,"Extensions/Themes/DarkUnica.js",[a["Core/Globals.js"],a["Core/Utilities.js"]],function(a,b){b=b.setOptions;a.createElement("link", 9 | {href:"https://fonts.googleapis.com/css?family=Unica+One",rel:"stylesheet",type:"text/css"},null,document.getElementsByTagName("head")[0]);a.theme={colors:"#2b908f #90ee7e #f45b5b #7798BF #aaeeee #ff0066 #eeaaee #55BF3B #DF5353 #7798BF #aaeeee".split(" "),chart:{backgroundColor:{linearGradient:{x1:0,y1:0,x2:1,y2:1},stops:[[0,"#2a2a2b"],[1,"#3e3e40"]]},style:{fontFamily:"'Unica One', sans-serif"},plotBorderColor:"#606063"},title:{style:{color:"#E0E0E3",textTransform:"uppercase",fontSize:"20px"}},subtitle:{style:{color:"#E0E0E3", 10 | textTransform:"uppercase"}},xAxis:{gridLineColor:"#707073",labels:{style:{color:"#E0E0E3"}},lineColor:"#707073",minorGridLineColor:"#505053",tickColor:"#707073",title:{style:{color:"#A0A0A3"}}},yAxis:{gridLineColor:"#707073",labels:{style:{color:"#E0E0E3"}},lineColor:"#707073",minorGridLineColor:"#505053",tickColor:"#707073",tickWidth:1,title:{style:{color:"#A0A0A3"}}},tooltip:{backgroundColor:"rgba(0, 0, 0, 0.85)",style:{color:"#F0F0F0"}},plotOptions:{series:{dataLabels:{color:"#F0F0F3",style:{fontSize:"13px"}}, 11 | marker:{lineColor:"#333"}},boxplot:{fillColor:"#505053"},candlestick:{lineColor:"white"},errorbar:{color:"white"}},legend:{backgroundColor:"rgba(0, 0, 0, 0.5)",itemStyle:{color:"#E0E0E3"},itemHoverStyle:{color:"#FFF"},itemHiddenStyle:{color:"#606063"},title:{style:{color:"#C0C0C0"}}},credits:{style:{color:"#666"}},labels:{style:{color:"#707073"}},drilldown:{activeAxisLabelStyle:{color:"#F0F0F3"},activeDataLabelStyle:{color:"#F0F0F3"}},navigation:{buttonOptions:{symbolStroke:"#DDDDDD",theme:{fill:"#505053"}}}, 12 | rangeSelector:{buttonTheme:{fill:"#505053",stroke:"#000000",style:{color:"#CCC"},states:{hover:{fill:"#707073",stroke:"#000000",style:{color:"white"}},select:{fill:"#000003",stroke:"#000000",style:{color:"white"}}}},inputBoxBorderColor:"#505053",inputStyle:{backgroundColor:"#333",color:"silver"},labelStyle:{color:"silver"}},navigator:{handles:{backgroundColor:"#666",borderColor:"#AAA"},outlineColor:"#CCC",maskFill:"rgba(255,255,255,0.1)",series:{color:"#7798BF",lineColor:"#A6C7ED"},xAxis:{gridLineColor:"#505053"}}, 13 | scrollbar:{barBackgroundColor:"#808083",barBorderColor:"#808083",buttonArrowColor:"#CCC",buttonBackgroundColor:"#606063",buttonBorderColor:"#606063",rifleColor:"#FFF",trackBackgroundColor:"#404043",trackBorderColor:"#404043"}};b(a.theme)});b(a,"masters/themes/dark-unica.src.js",[],function(){})}); 14 | //# sourceMappingURL=dark-unica.js.map -------------------------------------------------------------------------------- /bosszp/web/static/highcharts/highcharts-more.js: -------------------------------------------------------------------------------- 1 | /* 2 | Highcharts JS v8.2.2 (2020-10-22) 3 | 4 | (c) 2009-2018 Torstein Honsi 5 | 6 | License: www.highcharts.com/license 7 | */ 8 | (function(c){"object"===typeof module&&module.exports?(c["default"]=c,module.exports=c):"function"===typeof define&&define.amd?define("highcharts/highcharts-more",["highcharts"],function(A){c(A);c.Highcharts=A;return c}):c("undefined"!==typeof Highcharts?Highcharts:void 0)})(function(c){function A(c,b,h,a){c.hasOwnProperty(b)||(c[b]=a.apply(null,h))}c=c?c._modules:{};A(c,"Extensions/Pane.js",[c["Core/Chart/Chart.js"],c["Core/Globals.js"],c["Core/Pointer.js"],c["Core/Utilities.js"],c["Mixins/CenteredSeries.js"]], 9 | function(c,b,h,a,f){function q(e,f,m){return Math.sqrt(Math.pow(e-m[0],2)+Math.pow(f-m[1],2))<=m[2]/2}var u=a.addEvent,z=a.extend,E=a.merge,B=a.pick,e=a.splat;c.prototype.collectionsWithUpdate.push("pane");a=function(){function b(e,m){this.options=this.chart=this.center=this.background=void 0;this.coll="pane";this.defaultOptions={center:["50%","50%"],size:"85%",innerSize:"0%",startAngle:0};this.defaultBackgroundOptions={shape:"circle",borderWidth:1,borderColor:"#cccccc",backgroundColor:{linearGradient:{x1:0, 10 | y1:0,x2:0,y2:1},stops:[[0,"#ffffff"],[1,"#e6e6e6"]]},from:-Number.MAX_VALUE,innerRadius:0,to:Number.MAX_VALUE,outerRadius:"105%"};this.init(e,m)}b.prototype.init=function(e,m){this.chart=m;this.background=[];m.pane.push(this);this.setOptions(e)};b.prototype.setOptions=function(e){this.options=E(this.defaultOptions,this.chart.angular?{background:{}}:void 0,e)};b.prototype.render=function(){var b=this.options,m=this.options.background,f=this.chart.renderer;this.group||(this.group=f.g("pane-group").attr({zIndex:b.zIndex|| 11 | 0}).add());this.updateCenter();if(m)for(m=e(m),b=Math.max(m.length,this.background.length||0),f=0;ff?0:f,this.center[2]/2)-this.offset)};f.postTranslate=function(e,l){var k=this.chart,d=this.center;e=this.startAngleRad+e;return{x:k.plotLeft+d[0]+Math.cos(e)*l,y:k.plotTop+d[1]+Math.sin(e)*l}};f.getPlotBandPath=function(f,l,k){var d=function(d){if("string"=== 20 | typeof d){var g=parseInt(d,10);n.test(d)&&(g=g*D/100);return g}return d},g=this.center,r=this.startAngleRad,D=g[2]/2,t=Math.min(this.offset,0),n=/%$/;var y=this.isCircular;var b=e(d(k.outerRadius),D),p=d(k.innerRadius);d=e(d(k.thickness),10);if("polygon"===this.options.gridLineInterpolation)t=this.getPlotLinePath({value:f}).concat(this.getPlotLinePath({value:l,reverse:!0}));else{f=Math.max(f,this.min);l=Math.min(l,this.max);f=this.translate(f);l=this.translate(l);y||(b=f||0,p=l||0);if("circle"!== 21 | k.shape&&y)k=r+(f||0),r+=l||0;else{k=-Math.PI/2;r=1.5*Math.PI;var a=!0}b-=t;t=this.chart.renderer.symbols.arc(this.left+g[0],this.top+g[1],b,b,{start:Math.min(k,r),end:Math.max(k,r),innerR:e(p,b-(d-t)),open:a});y&&(y=(r+k)/2,a=this.left+g[0]+g[2]/2*Math.cos(y),t.xBounds=y>-Math.PI/2&&y-Math.PI&&0>y||y>Math.PI?-10:10)}return t};f.getCrosshairPosition=function(e,l,k){var d=e.value,g=this.pane.center; 22 | if(this.isCircular){if(u(d))e.point&&(r=e.point.shapeArgs||{},r.start&&(d=this.chart.inverted?this.translate(e.point.rectPlotY,!0):e.point.x));else{var r=e.chartX||0;var D=e.chartY||0;d=this.translate(Math.atan2(D-k,r-l)-this.startAngleRad,!0)}e=this.getPosition(d);r=e.x;D=e.y}else u(d)||(r=e.chartX,D=e.chartY),u(r)&&u(D)&&(k=g[1]+this.chart.plotTop,d=this.translate(Math.min(Math.sqrt(Math.pow(r-l,2)+Math.pow(D-k,2)),g[2]/2)-g[3]/2,!0));return[d,r||0,D||0]};f.getPlotLinePath=function(e){var l=this, 23 | k=l.pane.center,d=l.chart,g=d.inverted,r=e.value,D=e.reverse,t=l.getPosition(r),n=l.pane.options.background?l.pane.options.background[0]||l.pane.options.background:{},f=n.innerRadius||"0%",b=n.outerRadius||"100%";n=k[0]+d.plotLeft;var a=k[1]+d.plotTop,p=t.x,m=t.y,h=l.height;t=k[3]/2;var v;e.isCrosshair&&(m=this.getCrosshairPosition(e,n,a),r=m[0],p=m[1],m=m[2]);if(l.isCircular)r=Math.sqrt(Math.pow(p-n,2)+Math.pow(m-a,2)),D="string"===typeof f?x(f,1):f/r,d="string"===typeof b?x(b,1):b/r,k&&t&&(r=t/ 24 | r,Dr||r>h)&&(r=0),"circle"===l.options.gridLineInterpolation)k=l.getLinePath(0,r,t);else if(k=[],d[g?"yAxis":"xAxis"].forEach(function(d){d.pane===l.pane&&(v=d)}),v)for(n=v.tickPositions,v.autoConnect&&(n=n.concat([n[0]])),D&&(n=n.slice().reverse()),r&&(r+=t),p=0;py?y+360:y,m=a,h=0,v=0,p=null===g.y?.3*-d.height:0;if(l.isRadial){var c=l.getPosition(this.pos,l.center[2]/2+x(e(g.distance,-25),l.center[2]/2,-l.center[2]/2));"auto"===g.rotation?k.attr({rotation:n}):null===r&&(r=l.chart.renderer.fontMetrics(k.styles&&k.styles.fontSize).b-d.height/2);null===t&&(l.isCircular? 30 | (d.width>l.len*l.tickInterval/(l.max-l.min)&&(D=0),t=n>D&&n<180-D?"left":n>180+D&&n<360-D?"right":"center"):t="center",k.attr({align:t}));if("auto"===t&&2===l.tickPositions.length&&l.isCircular){90a?a=180-a:270=a&&(a=540-a);180=m&&(m=360-m);if(l.pane.options.startAngle===y||l.pane.options.startAngle===y+360||l.pane.options.startAngle===y-360)b="start";t=-90<=y&&90>=y||-360<=y&&-270>=y||270<=y&&360>=y?"start"===b?"right":"left":"start"===b?"left":"right";70m&&(t="center"); 31 | 15>a||180<=a&&195>a?h=.3*d.height:15<=a&&35>=a?h="start"===b?0:.75*d.height:195<=a&&215>=a?h="start"===b?.75*d.height:0:35=a?h="start"===b?.25*-d.height:d.height:215=a&&(h="start"===b?d.height:.25*-d.height);15>m?v="start"===b?.15*-d.height:.15*d.height:165=m&&(v="start"===b?.15*d.height:.15*-d.height);k.attr({align:t});k.translate(v,h+p)}f.pos.x=c.x+g.x;f.pos.y=c.y+r}}});w(m.prototype,"getMarkPath",function(e,l,k,d,g,r,D){var t=this.axis;t.isRadial?(e=t.getPosition(this.pos, 32 | t.center[2]/2+d),l=["M",l,k,"L",e.x,e.y]):l=e.call(this,l,k,d,g,r,D);return l})};b.defaultCircularOptions={gridLineWidth:1,labels:{align:null,distance:15,x:0,y:null,style:{textOverflow:"none"}},maxPadding:0,minPadding:0,showLastLabel:!1,tickLength:0};b.defaultRadialGaugeOptions={labels:{align:"center",x:0,y:null},minorGridLineWidth:0,minorTickInterval:"auto",minorTickLength:10,minorTickPosition:"inside",minorTickWidth:1,tickLength:10,tickPosition:"inside",tickWidth:2,title:{rotation:0},zIndex:2}; 33 | b.defaultRadialOptions={gridLineInterpolation:"circle",gridLineWidth:1,labels:{align:"right",x:-3,y:-2},showLastLabel:!1,title:{x:4,text:null,rotation:90}};return b}();a.compose(c,b);return a});A(c,"Series/AreaRangeSeries.js",[c["Core/Series/Series.js"],c["Core/Globals.js"],c["Core/Series/Point.js"],c["Core/Utilities.js"]],function(c,b,h,a){var f=a.defined,q=a.extend,u=a.isArray,z=a.isNumber,E=a.pick,B=c.seriesTypes.area.prototype,e=c.seriesTypes.column.prototype,x=h.prototype,w=b.Series.prototype; 34 | c.seriesType("arearange","area",{lineWidth:1,threshold:null,tooltip:{pointFormat:'\u25cf {series.name}: {point.low} - {point.high}
'},trackByArea:!0,dataLabels:{align:void 0,verticalAlign:void 0,xLow:0,xHigh:0,yLow:0,yHigh:0}},{pointArrayMap:["low","high"],pointValKey:"low",deferTranslatePolar:!0,toYData:function(e){return[e.low,e.high]},highToXY:function(e){var f=this.chart,b=this.xAxis.postTranslate(e.rectPlotX,this.yAxis.len-e.plotHigh); 35 | e.plotHighX=b.x-f.plotLeft;e.plotHigh=b.y-f.plotTop;e.plotLowX=e.plotX},translate:function(){var e=this,f=e.yAxis,b=!!e.modifyValue;B.translate.apply(e);e.points.forEach(function(a){var l=a.high,k=a.plotY;a.isNull?a.plotY=null:(a.plotLow=k,a.plotHigh=f.translate(b?e.modifyValue(l,a):l,0,1,0,1),b&&(a.yBottom=a.plotHigh))});this.chart.polar&&this.points.forEach(function(f){e.highToXY(f);f.tooltipPos=[(f.plotHighX+f.plotLowX)/2,(f.plotHigh+f.plotLow)/2]})},getGraphPath:function(e){var f=[],b=[],a,l= 36 | B.getGraphPath;var k=this.options;var d=this.chart.polar,g=d&&!1!==k.connectEnds,r=k.connectNulls,D=k.step;e=e||this.points;for(a=e.length;a--;){var t=e[a];var n=d?{plotX:t.rectPlotX,plotY:t.yBottom,doCurve:!1}:{plotX:t.plotX,plotY:t.plotY,doCurve:!1};t.isNull||g||r||e[a+1]&&!e[a+1].isNull||b.push(n);var y={polarPlotY:t.polarPlotY,rectPlotX:t.rectPlotX,yBottom:t.yBottom,plotX:E(t.plotHighX,t.plotX),plotY:t.plotHigh,isNull:t.isNull};b.push(y);f.push(y);t.isNull||g||r||e[a-1]&&!e[a-1].isNull||b.push(n)}e= 37 | l.call(this,e);D&&(!0===D&&(D="left"),k.step={left:"right",center:"center",right:"left"}[D]);f=l.call(this,f);b=l.call(this,b);k.step=D;k=[].concat(e,f);!this.chart.polar&&b[0]&&"M"===b[0][0]&&(b[0]=["L",b[0][1],b[0][2]]);this.graphPath=k;this.areaPath=e.concat(b);k.isArea=!0;k.xMap=e.xMap;this.areaPath.xMap=e.xMap;return k},drawDataLabels:function(){var e=this.points,f=e.length,b,a=[],l=this.options.dataLabels,k,d=this.chart.inverted;if(u(l)){var g=l[0]||{enabled:!1};var r=l[1]||{enabled:!1}}else g= 38 | q({},l),g.x=l.xHigh,g.y=l.yHigh,r=q({},l),r.x=l.xLow,r.y=l.yLow;if(g.enabled||this._hasPointLabels){for(b=f;b--;)if(k=e[b]){var D=g.inside?k.plotHighk.plotLow;k.y=k.high;k._plotY=k.plotY;k.plotY=k.plotHigh;a[b]=k.dataLabel;k.dataLabel=k.dataLabelUpper;k.below=D;d?g.align||(g.align=D?"right":"left"):g.verticalAlign||(g.verticalAlign=D?"top":"bottom")}this.options.dataLabels=g;w.drawDataLabels&&w.drawDataLabels.apply(this,arguments);for(b=f;b--;)if(k=e[b])k.dataLabelUpper=k.dataLabel, 39 | k.dataLabel=a[b],delete k.dataLabels,k.y=k.low,k.plotY=k._plotY}if(r.enabled||this._hasPointLabels){for(b=f;b--;)if(k=e[b])D=r.inside?k.plotHighk.plotLow,k.below=!D,d?r.align||(r.align=D?"left":"right"):r.verticalAlign||(r.verticalAlign=D?"bottom":"top");this.options.dataLabels=r;w.drawDataLabels&&w.drawDataLabels.apply(this,arguments)}if(g.enabled)for(b=f;b--;)if(k=e[b])k.dataLabels=[k.dataLabelUpper,k.dataLabel].filter(function(d){return!!d});this.options.dataLabels=l},alignDataLabel:function(){e.alignDataLabel.apply(this, 40 | arguments)},drawPoints:function(){var e=this.points.length,b;w.drawPoints.apply(this,arguments);for(b=0;br&&(r*=-1,g-=r);q?(c=l.barX+h,l.shapeType="arc",l.shapeArgs=a.polarArc(g+ 46 | r,g,c,c+l.pointWidth)):(k.height=r,k.y=g,l.tooltipPos=m.inverted?[b.len+b.pos-m.plotLeft-g-r/2,e.len+e.pos-m.plotTop-k.x-k.width/2,r]:[e.left-m.plotLeft+k.x+k.width/2,b.pos-m.plotTop+g+r/2,r])})},directTouch:!0,trackerGroups:["group","dataLabelsGroup"],drawGraph:b,getSymbol:b,crispCol:function(){return z.crispCol.apply(this,arguments)},drawPoints:function(){return z.drawPoints.apply(this,arguments)},drawTracker:function(){return z.drawTracker.apply(this,arguments)},getColumnMetrics:function(){return z.getColumnMetrics.apply(this, 47 | arguments)},pointAttribs:function(){return z.pointAttribs.apply(this,arguments)},animate:function(){return z.animate.apply(this,arguments)},polarArc:function(){return z.polarArc.apply(this,arguments)},translate3dPoints:function(){return z.translate3dPoints.apply(this,arguments)},translate3dShapes:function(){return z.translate3dShapes.apply(this,arguments)}},{setState:z.pointClass.prototype.setState});""});A(c,"Series/ColumnPyramidSeries.js",[c["Core/Series/Series.js"],c["Series/ColumnSeries.js"], 48 | c["Core/Utilities.js"]],function(c,b,h){var a=b.prototype,f=h.clamp,q=h.pick;c.seriesType("columnpyramid","column",{},{translate:function(){var b=this,h=b.chart,c=b.options,B=b.dense=2>b.closestPointRange*b.xAxis.transA;B=b.borderWidth=q(c.borderWidth,B?0:1);var e=b.yAxis,x=c.threshold,w=b.translatedThreshold=e.getThreshold(x),m=q(c.minPointLength,5),C=b.getColumnMetrics(),v=C.width,p=b.barW=Math.max(v,1+2*B),l=b.pointXOffset=C.offset;h.inverted&&(w-=.5);c.pointPadding&&(p=Math.ceil(p));a.translate.apply(b); 49 | b.points.forEach(function(k){var d=q(k.yBottom,w),g=999+Math.abs(d),r=f(k.plotY,-g,e.len+g);g=k.plotX+l;var a=p/2,t=Math.min(r,d);d=Math.max(r,d)-t;var n;k.barX=g;k.pointWidth=v;k.tooltipPos=h.inverted?[e.len+e.pos-h.plotLeft-r,b.xAxis.len-g-a,d]:[g+a,r+e.pos-h.plotTop,d];r=x+(k.total||k.y);"percent"===c.stacking&&(r=x+(0>k.y)?-100:100);r=e.toPixels(r,!0);var y=(n=h.plotHeight-r-(h.plotHeight-w))?a*(t-r)/n:0;var G=n?a*(t+d-r)/n:0;n=g-y+a;y=g+y+a;var H=g+G+a;G=g-G+a;var u=t-m;var F=t+d;0>k.y&&(u=t, 50 | F=t+d+m);h.inverted&&(H=h.plotWidth-t,n=r-(h.plotWidth-w),y=a*(r-H)/n,G=a*(r-(H-d))/n,n=g+a+y,y=n-2*y,H=g-G+a,G=g+G+a,u=t,F=t+d-m,0>k.y&&(F=t+d+m));k.shapeType="path";k.shapeArgs={x:n,y:u,width:y-n,height:d,d:[["M",n,u],["L",y,u],["L",H,F],["L",G,F],["Z"]]}})}});""});A(c,"Series/GaugeSeries.js",[c["Core/Series/Series.js"],c["Core/Globals.js"],c["Core/Utilities.js"]],function(c,b,h){var a=h.clamp,f=h.isNumber,q=h.merge,u=h.pick,z=h.pInt,E=b.Series;h=b.TrackerMixin;c.seriesType("gauge","line",{dataLabels:{borderColor:"#cccccc", 51 | borderRadius:3,borderWidth:1,crop:!1,defer:!1,enabled:!0,verticalAlign:"top",y:15,zIndex:2},dial:{},pivot:{},tooltip:{headerFormat:""},showInLegend:!1},{angular:!0,directTouch:!0,drawGraph:b.noop,fixedBox:!0,forceDL:!0,noSharedTooltip:!0,trackerGroups:["group","dataLabelsGroup"],translate:function(){var b=this.yAxis,e=this.options,h=b.center;this.generatePoints();this.points.forEach(function(c){var m=q(e.dial,c.dial),x=z(u(m.radius,"80%"))*h[2]/200,w=z(u(m.baseLength,"70%"))*x/100,p=z(u(m.rearLength, 52 | "10%"))*x/100,l=m.baseWidth||3,k=m.topWidth||1,d=e.overshoot,g=b.startAngleRad+b.translate(c.y,null,null,null,!0);if(f(d)||!1===e.wrap)d=f(d)?d/180*Math.PI:0,g=a(g,b.startAngleRad-d,b.endAngleRad+d);g=180*g/Math.PI;c.shapeType="path";c.shapeArgs={d:m.path||[["M",-p,-l/2],["L",w,-l/2],["L",x,-k/2],["L",x,k/2],["L",w,l/2],["L",-p,l/2],["Z"]],translateX:h[0],translateY:h[1],rotation:g};c.plotX=h[0];c.plotY=h[1]})},drawPoints:function(){var a=this,e=a.chart,b=a.yAxis.center,f=a.pivot,h=a.options,c=h.pivot, 53 | z=e.renderer;a.points.forEach(function(b){var f=b.graphic,k=b.shapeArgs,d=k.d,g=q(h.dial,b.dial);f?(f.animate(k),k.d=d):b.graphic=z[b.shapeType](k).attr({rotation:k.rotation,zIndex:1}).addClass("highcharts-dial").add(a.group);if(!e.styledMode)b.graphic[f?"animate":"attr"]({stroke:g.borderColor||"none","stroke-width":g.borderWidth||0,fill:g.backgroundColor||"#000000"})});f?f.animate({translateX:b[0],translateY:b[1]}):(a.pivot=z.circle(0,0,u(c.radius,5)).attr({zIndex:2}).addClass("highcharts-pivot").translate(b[0], 54 | b[1]).add(a.group),e.styledMode||a.pivot.attr({"stroke-width":c.borderWidth||0,stroke:c.borderColor||"#cccccc",fill:c.backgroundColor||"#000000"}))},animate:function(a){var e=this;a||e.points.forEach(function(a){var b=a.graphic;b&&(b.attr({rotation:180*e.yAxis.startAngleRad/Math.PI}),b.animate({rotation:a.shapeArgs.rotation},e.options.animation))})},render:function(){this.group=this.plotGroup("group","series",this.visible?"visible":"hidden",this.options.zIndex,this.chart.seriesGroup);E.prototype.render.call(this); 55 | this.group.clip(this.chart.clipRect)},setData:function(a,e){E.prototype.setData.call(this,a,!1);this.processData();this.generatePoints();u(e,!0)&&this.chart.redraw()},hasData:function(){return!!this.points.length},drawTracker:h&&h.drawTrackerPoint},{setState:function(a){this.state=a}});""});A(c,"Series/BoxPlotSeries.js",[c["Core/Series/Series.js"],c["Series/ColumnSeries.js"],c["Core/Globals.js"],c["Core/Utilities.js"]],function(c,b,h,a){var f=b.prototype;b=h.noop;var q=a.pick;c.seriesType("boxplot", 56 | "column",{threshold:null,tooltip:{pointFormat:'\u25cf {series.name}
Maximum: {point.high}
Upper quartile: {point.q3}
Median: {point.median}
Lower quartile: {point.q1}
Minimum: {point.low}
'},whiskerLength:"50%",fillColor:"#ffffff",lineWidth:1,medianWidth:2,whiskerWidth:2},{pointArrayMap:["low","q1","median","q3","high"],toYData:function(a){return[a.low,a.q1,a.median,a.q3,a.high]},pointValKey:"high",pointAttribs:function(){return{}}, 57 | drawDataLabels:b,translate:function(){var a=this.yAxis,b=this.pointArrayMap;f.translate.apply(this);this.points.forEach(function(f){b.forEach(function(b){null!==f[b]&&(f[b+"Plot"]=a.translate(f[b],0,1,0,1))});f.plotHigh=f.highPlot})},drawPoints:function(){var a=this,b=a.options,f=a.chart,h=f.renderer,e,c,w,m,C,v,p=0,l,k,d,g,r=!1!==a.doQuartiles,D,t=a.options.whiskerLength;a.points.forEach(function(n){var y=n.graphic,G=y?"animate":"attr",x=n.shapeArgs,I={},F={},J={},K={},u=n.color||a.color;"undefined"!== 58 | typeof n.plotY&&(l=Math.round(x.width),k=Math.floor(x.x),d=k+l,g=Math.round(l/2),e=Math.floor(r?n.q1Plot:n.lowPlot),c=Math.floor(r?n.q3Plot:n.lowPlot),w=Math.floor(n.highPlot),m=Math.floor(n.lowPlot),y||(n.graphic=y=h.g("point").add(a.group),n.stem=h.path().addClass("highcharts-boxplot-stem").add(y),t&&(n.whiskers=h.path().addClass("highcharts-boxplot-whisker").add(y)),r&&(n.box=h.path(void 0).addClass("highcharts-boxplot-box").add(y)),n.medianShape=h.path(void 0).addClass("highcharts-boxplot-median").add(y)), 59 | f.styledMode||(F.stroke=n.stemColor||b.stemColor||u,F["stroke-width"]=q(n.stemWidth,b.stemWidth,b.lineWidth),F.dashstyle=n.stemDashStyle||b.stemDashStyle||b.dashStyle,n.stem.attr(F),t&&(J.stroke=n.whiskerColor||b.whiskerColor||u,J["stroke-width"]=q(n.whiskerWidth,b.whiskerWidth,b.lineWidth),J.dashstyle=n.whiskerDashStyle||b.whiskerDashStyle||b.dashStyle,n.whiskers.attr(J)),r&&(I.fill=n.fillColor||b.fillColor||u,I.stroke=b.lineColor||u,I["stroke-width"]=b.lineWidth||0,I.dashstyle=n.boxDashStyle||b.boxDashStyle|| 60 | b.dashStyle,n.box.attr(I)),K.stroke=n.medianColor||b.medianColor||u,K["stroke-width"]=q(n.medianWidth,b.medianWidth,b.lineWidth),K.dashstyle=n.medianDashStyle||b.medianDashStyle||b.dashStyle,n.medianShape.attr(K)),v=n.stem.strokeWidth()%2/2,p=k+g+v,y=[["M",p,c],["L",p,w],["M",p,e],["L",p,m]],n.stem[G]({d:y}),r&&(v=n.box.strokeWidth()%2/2,e=Math.floor(e)+v,c=Math.floor(c)+v,k+=v,d+=v,y=[["M",k,c],["L",k,e],["L",d,e],["L",d,c],["L",k,c],["Z"]],n.box[G]({d:y})),t&&(v=n.whiskers.strokeWidth()%2/2,w+= 61 | v,m+=v,D=/%$/.test(t)?g*parseFloat(t)/100:t/2,y=[["M",p-D,w],["L",p+D,w],["M",p-D,m],["L",p+D,m]],n.whiskers[G]({d:y})),C=Math.round(n.medianPlot),v=n.medianShape.strokeWidth()%2/2,C+=v,y=[["M",k,C],["L",d,C]],n.medianShape[G]({d:y}))})},setStackedPoints:b});""});A(c,"Series/ErrorBarSeries.js",[c["Core/Series/Series.js"],c["Core/Globals.js"]],function(c,b){b=b.noop;var h=c.seriesTypes;c.seriesType("errorbar","boxplot",{color:"#000000",grouping:!1,linkedTo:":previous",tooltip:{pointFormat:'\u25cf {series.name}: {point.low} - {point.high}
'}, 62 | whiskerWidth:null},{type:"errorbar",pointArrayMap:["low","high"],toYData:function(a){return[a.low,a.high]},pointValKey:"high",doQuartiles:!1,drawDataLabels:h.arearange?function(){var a=this.pointValKey;h.arearange.prototype.drawDataLabels.call(this);this.data.forEach(function(b){b.y=b[a]})}:b,getColumnMetrics:function(){return this.linkedParent&&this.linkedParent.columnMetrics||h.column.prototype.getColumnMetrics.call(this)}});""});A(c,"Series/WaterfallSeries.js",[c["Core/Axis/Axis.js"],c["Core/Series/Series.js"], 63 | c["Core/Chart/Chart.js"],c["Core/Globals.js"],c["Core/Series/Point.js"],c["Extensions/Stacking.js"],c["Core/Utilities.js"]],function(c,b,h,a,f,q,u){var z=b.seriesTypes,E=u.addEvent,B=u.arrayMax,e=u.arrayMin,x=u.correctFloat,w=u.isNumber,m=u.objectEach,C=u.pick,v=a.Series,p;(function(e){function a(){var d=this.waterfall.stacks;d&&(d.changed=!1,delete d.alreadyChanged)}function d(){var d=this.options.stackLabels;d&&d.enabled&&this.waterfall.stacks&&this.waterfall.renderStackTotals()}function g(){for(var d= 64 | this.axes,g=this.series,e=g.length;e--;)g[e].options.stacking&&(d.forEach(function(d){d.isXAxis||(d.waterfall.stacks.changed=!0)}),e=0)}function b(){this.waterfall||(this.waterfall=new f(this))}var f=function(){function d(d){this.axis=d;this.stacks={changed:!1}}d.prototype.renderStackTotals=function(){var d=this.axis,g=d.waterfall.stacks,e=d.stacking&&d.stacking.stackTotalGroup,a=new q(d,d.options.stackLabels,!1,0,void 0);this.dummyStackItem=a;m(g,function(d){m(d,function(d){a.total=d.stackTotal; 65 | d.label&&(a.label=d.label);q.prototype.render.call(a,e);d.label=a.label;delete a.label})});a.total=null};return d}();e.Composition=f;e.compose=function(e,k){E(e,"init",b);E(e,"afterBuildStacks",a);E(e,"afterRender",d);E(k,"beforeRedraw",g)}})(p||(p={}));b.seriesType("waterfall","column",{dataLabels:{inside:!0},lineWidth:1,lineColor:"#333333",dashStyle:"Dot",borderColor:"#333333",states:{hover:{lineWidthPlus:0}}},{pointValKey:"y",showLine:!0,generatePoints:function(){var e;z.column.prototype.generatePoints.apply(this); 66 | var a=0;for(e=this.points.length;aq.height&&(q.y+=q.height,q.height*=-1);m.plotY=q.y=Math.round(q.y)-this.borderWidth%2/2;q.height=Math.max(Math.round(q.height),.001);m.yBottom=q.y+q.height;q.height<=g&&!m.isNull?(q.height=g,q.y-=b,m.plotY=q.y,m.minPointLengthOffset=0>m.y?-b:b):(m.isNull&& 70 | (q.width=0),m.minPointLengthOffset=0);q=m.plotY+(m.negative?q.height:0);this.chart.inverted?m.tooltipPos[0]=a.len-q:m.tooltipPos[1]=q}},processData:function(a){var e=this.options,d=this.yData,g=e.data,b=d.length,f=e.threshold||0,t,n,l,h,c;for(c=n=t=l=h=0;cm.y&&!t||0h.indexOf(t)&&(x=!0);d[t]||(d[t]={});h=d[t];for(var w=0;w=d&&this.renderRange(a)}, 85 | this);this.legendSymbol.add(this.legendItem);this.legendItem.add(this.legendGroup);this.hideOverlappingLabels()};a.prototype.renderRange=function(a){var d=this.options,g=d.labels,e=this.chart.renderer,b=this.symbols,f=b.labels,n=a.center,k=Math.abs(a.radius),c=d.connectorDistance||0,h=g.align,l=g.style.fontSize;c=this.legend.options.rtl||"left"===h?-c:c;g=d.connectorWidth;var m=this.ranges[0].radius||0,q=n-k-d.borderWidth/2+g/2;l=l/2-(this.fontMetrics.h-l)/2;var p=e.styledMode;"center"===h&&(c=0, 86 | d.connectorDistance=0,a.labelStyle.align="center");h=q+d.labels.y;var x=m+c+d.labels.x;b.bubbleItems.push(e.circle(m,n+((q%1?1:.5)-(g%2?0:.5)),k).attr(p?{}:a.bubbleStyle).addClass((p?"highcharts-color-"+this.options.seriesIndex+" ":"")+"highcharts-bubble-legend-symbol "+(d.className||"")).add(this.legendSymbol));b.connectors.push(e.path(e.crispLine([["M",m,q],["L",m+c,q]],d.connectorWidth)).attr(p?{}:a.connectorStyle).addClass((p?"highcharts-color-"+this.options.seriesIndex+" ":"")+"highcharts-bubble-legend-connectors "+ 87 | (d.connectorClassName||"")).add(this.legendSymbol));a=e.text(this.formatLabel(a),x,h+l).attr(p?{}:a.labelStyle).addClass("highcharts-bubble-legend-labels "+(d.labels.className||"")).add(this.legendSymbol);f.push(a);a.placed=!0;a.alignAttr={x:x,y:h+l}};a.prototype.getMaxLabelSize=function(){var a,d;this.symbols.labels.forEach(function(g){d=g.getBBox(!0);a=a?d.width>a.width?d:a:d});return a||{}};a.prototype.formatLabel=function(a){var d=this.options,g=d.labels.formatter;d=d.labels.format;var e=this.chart.numberFormatter; 88 | return d?f.format(d,a):g?g.call(a):e(a.value,1)};a.prototype.hideOverlappingLabels=function(){var a=this.chart,d=this.symbols;!this.options.labels.allowOverlap&&d&&(a.hideOverlappingLabels(d.labels),d.labels.forEach(function(a,e){a.newOpacity?a.newOpacity!==a.oldOpacity&&d.connectors[e].show():d.connectors[e].hide()}))};a.prototype.getRanges=function(){var a=this.legend.bubbleLegend,d=a.options.ranges,g,e=Number.MAX_VALUE,b=-Number.MAX_VALUE;a.chart.series.forEach(function(d){d.isBubble&&!d.ignoreSeries&& 89 | (g=d.zData.filter(E),g.length&&(e=x(d.options.zMin,Math.min(e,Math.max(z(g),!1===d.options.displayNegative?d.options.zThreshold:-Number.MAX_VALUE))),b=x(d.options.zMax,Math.max(b,u(g)))))});var f=e===b?[{value:b}]:[{value:e},{value:(e+b)/2},{value:b,autoRanges:!0}];d.length&&d[0].radius&&f.reverse();f.forEach(function(a,g){d&&d[g]&&(f[g]=B(!1,d[g],a))});return f};a.prototype.predictBubbleSizes=function(){var a=this.chart,d=this.fontMetrics,g=a.legend.options,e="horizontal"===g.layout,b=e?a.legend.lastLineHeight: 90 | 0,f=a.plotSizeX,n=a.plotSizeY,c=a.series[this.options.seriesIndex];a=Math.ceil(c.minPxSize);var h=Math.ceil(c.maxPxSize);c=c.options.maxSize;var l=Math.min(n,f);if(g.floating||!/%$/.test(c))d=h;else if(c=parseFloat(c),d=(l+b-d.h/2)*c/100/(c/100+1),e&&n-d>=f||!e&&f-d>=n)d=h;return[a,Math.ceil(d)]};a.prototype.updateRanges=function(a,d){var g=this.legend.options.bubbleLegend;g.minSize=a;g.maxSize=d;g.ranges=this.getRanges()};a.prototype.correctSizes=function(){var a=this.legend,d=this.chart.series[this.options.seriesIndex]; 91 | 1f.height&&(f.height=a[b].itemHeight); 93 | f.step=g}return e};a.prototype.retranslateItems=function(a){var e,d,g,b=this.options.rtl,f=0;this.allItems.forEach(function(t,n){e=t.legendGroup.translateX;d=t._legendItemPos[1];if((g=t.movementX)||b&&t.ranges)g=b?e-t.options.maxSize/2:e+g,t.legendGroup.attr({translateX:g});n>a[f].step&&f++;t.legendGroup.attr({translateY:Math.round(d+a[f].height/2)});t._legendItemPos[1]=d+a[f].height/2})};b(v,"legendItemClick",function(){var a=this.chart,e=this.visible,d=this.chart.legend;d&&d.bubbleLegend&&(this.visible= 94 | !e,this.ignoreSeries=e,a=0<=a.getVisibleBubbleSeriesIndex(),d.bubbleLegend.visible!==a&&(d.update({bubbleLegend:{enabled:a}}),d.bubbleLegend.visible=a),this.visible=e)});C(c.prototype,"drawChartBox",function(a,b,d){var g=this.legend,f=0<=this.getVisibleBubbleSeriesIndex();if(g&&g.options.enabled&&g.bubbleLegend&&g.options.bubbleLegend.autoRanges&&f){var c=g.bubbleLegend.options;f=g.bubbleLegend.predictBubbleSizes();g.bubbleLegend.updateRanges(f[0],f[1]);c.placed||(g.group.placed=!1,g.allItems.forEach(function(d){d.legendGroup.translateY= 95 | null}));g.render();this.getMargins();this.axes.forEach(function(d){d.visible&&d.render();c.placed||(d.setScale(),d.updateNames(),e(d.ticks,function(d){d.isNew=!0;d.isNewLabel=!0}))});c.placed=!0;this.getMargins();a.call(this,b,d);g.bubbleLegend.correctSizes();g.retranslateItems(g.getLinesHeights())}else a.call(this,b,d),g&&g.options.enabled&&g.bubbleLegend&&(g.render(),g.retranslateItems(g.getLinesHeights()))});h.BubbleLegend=w;return h.BubbleLegend});A(c,"Series/Bubble/BubbleSeries.js",[c["Core/Axis/Axis.js"], 96 | c["Core/Series/Series.js"],c["Core/Color/Color.js"],c["Core/Globals.js"],c["Core/Series/Point.js"],c["Core/Utilities.js"]],function(c,b,h,a,f,q){var u=h.parse;h=a.noop;var z=q.arrayMax,E=q.arrayMin,B=q.clamp,e=q.extend,x=q.isNumber,w=q.pick,m=q.pInt,C=a.Series,v=b.seriesTypes;"";b.seriesType("bubble","scatter",{dataLabels:{formatter:function(){return this.point.z},inside:!0,verticalAlign:"middle"},animationLimit:250,marker:{lineColor:null,lineWidth:1,fillOpacity:.5,radius:null,states:{hover:{radiusPlus:0}}, 97 | symbol:"circle"},minSize:8,maxSize:"20%",softThreshold:!1,states:{hover:{halo:{size:5}}},tooltip:{pointFormat:"({point.x}, {point.y}), Size: {point.z}"},turboThreshold:0,zThreshold:0,zoneAxis:"z"},{pointArrayMap:["y","z"],parallelArrays:["x","y","z"],trackerGroups:["group","dataLabelsGroup"],specialGroup:"group",bubblePadding:!0,zoneAxis:"z",directTouch:!0,isBubble:!0,pointAttribs:function(a,e){var b=this.options.marker.fillOpacity;a=C.prototype.pointAttribs.call(this,a,e);1!==b&&(a.fill=u(a.fill).setOpacity(b).get("rgba")); 98 | return a},getRadii:function(a,e,b){var d=this.zData,g=this.yData,f=b.minPxSize,c=b.maxPxSize,t=[];var n=0;for(b=d.length;n=this.minPxSize/2?(d.marker=e(d.marker, 100 | {radius:g,width:2*g,height:2*g}),d.dlBox={x:d.plotX-g,y:d.plotY-g,width:2*g,height:2*g}):d.shapeArgs=d.plotY=d.dlBox=void 0}},alignDataLabel:v.column.prototype.alignDataLabel,buildKDTree:h,applyZones:h},{haloPath:function(a){return f.prototype.haloPath.call(this,0===a?0:(this.marker?this.marker.radius||0:0)+a)},ttBelow:!1});c.prototype.beforePadding=function(){var a=this,e=this.len,b=this.chart,d=0,g=e,f=this.isXAxis,c=f?"xData":"yData",t=this.min,n={},h=Math.min(b.plotWidth,b.plotHeight),q=Number.MAX_VALUE, 101 | u=-Number.MAX_VALUE,v=this.max-t,F=e/v,C=[];this.series.forEach(function(d){var e=d.options;!d.bubblePadding||!d.visible&&b.options.chart.ignoreHiddenSeries||(a.allowZoomOutside=!0,C.push(d),f&&(["minSize","maxSize"].forEach(function(d){var a=e[d],g=/%$/.test(a);a=m(a);n[d]=g?h*a/100:a}),d.minPxSize=n.minSize,d.maxPxSize=Math.max(n.maxSize,n.minSize),d=d.zData.filter(x),d.length&&(q=w(e.zMin,B(E(d),!1===e.displayNegative?e.zThreshold:-Number.MAX_VALUE,q)),u=w(e.zMax,Math.max(u,z(d))))))});C.forEach(function(e){var b= 102 | e[c],n=b.length;f&&e.getRadii(q,u,e);if(0b?1:0)},barycenter:function(){var b=this.options.gravitationalConstant,c=this.barycenter.xFactor,a=this.barycenter.yFactor;c=(c-(this.box.left+this.box.width)/2)*b;a=(a-(this.box.top+this.box.height)/2)*b;this.nodes.forEach(function(b){b.fixedPosition||(b.plotX-=c/b.mass/b.degree,b.plotY-=a/b.mass/b.degree)})},repulsive:function(b,c,a){c=c*this.diffTemperature/b.mass/b.degree;b.fixedPosition||(b.plotX+=a.x*c,b.plotY+=a.y*c)}, 107 | attractive:function(b,c,a){var f=b.getMass(),h=-a.x*c*this.diffTemperature;c=-a.y*c*this.diffTemperature;b.fromNode.fixedPosition||(b.fromNode.plotX-=h*f.fromNode/b.fromNode.degree,b.fromNode.plotY-=c*f.fromNode/b.fromNode.degree);b.toNode.fixedPosition||(b.toNode.plotX+=h*f.toNode/b.toNode.degree,b.toNode.plotY+=c*f.toNode/b.toNode.degree)},integrate:function(b,c){var a=-b.options.friction,f=b.options.maxSpeed,h=(c.plotX+c.dispX-c.prevX)*a;a*=c.plotY+c.dispY-c.prevY;var u=Math.abs,z=u(h)/(h||1); 108 | u=u(a)/(a||1);h=z*Math.min(f,Math.abs(h));a=u*Math.min(f,Math.abs(a));c.prevX=c.plotX+c.dispX;c.prevY=c.plotY+c.dispY;c.plotX+=h;c.plotY+=a;c.temperature=b.vectorLength({x:h,y:a})},getK:function(b){return Math.pow(b.box.width*b.box.height/b.nodes.length,.5)}},euler:{attractiveForceFunction:function(b,c){return b*b/c},repulsiveForceFunction:function(b,c){return c*c/b},barycenter:function(){var b=this.options.gravitationalConstant,c=this.barycenter.xFactor,a=this.barycenter.yFactor;this.nodes.forEach(function(f){if(!f.fixedPosition){var h= 109 | f.getDegree();h*=1+h/2;f.dispX+=(c-f.plotX)*b*h/f.degree;f.dispY+=(a-f.plotY)*b*h/f.degree}})},repulsive:function(b,c,a,f){b.dispX+=a.x/f*c/b.degree;b.dispY+=a.y/f*c/b.degree},attractive:function(b,c,a,f){var h=b.getMass(),u=a.x/f*c;c*=a.y/f;b.fromNode.fixedPosition||(b.fromNode.dispX-=u*h.fromNode/b.fromNode.degree,b.fromNode.dispY-=c*h.fromNode/b.fromNode.degree);b.toNode.fixedPosition||(b.toNode.dispX+=u*h.toNode/b.toNode.degree,b.toNode.dispY+=c*h.toNode/b.toNode.degree)},integrate:function(b, 110 | c){c.dispX+=c.dispX*b.options.friction;c.dispY+=c.dispY*b.options.friction;var a=c.temperature=b.vectorLength({x:c.dispX,y:c.dispY});0!==a&&(c.plotX+=c.dispX/a*Math.min(Math.abs(c.dispX),b.temperature),c.plotY+=c.dispY/a*Math.min(Math.abs(c.dispY),b.temperature))},getK:function(b){return Math.pow(b.box.width*b.box.height/b.nodes.length,.3)}}}});A(c,"Series/Networkgraph/QuadTree.js",[c["Core/Globals.js"],c["Core/Utilities.js"]],function(c,b){b=b.extend;var h=c.QuadTreeNode=function(a){this.box=a;this.boxSize= 111 | Math.min(a.width,a.height);this.nodes=[];this.body=this.isInternal=!1;this.isEmpty=!0};b(h.prototype,{insert:function(a,b){this.isInternal?this.nodes[this.getBoxPosition(a)].insert(a,b-1):(this.isEmpty=!1,this.body?b?(this.isInternal=!0,this.divideBox(),!0!==this.body&&(this.nodes[this.getBoxPosition(this.body)].insert(this.body,b-1),this.body=!0),this.nodes[this.getBoxPosition(a)].insert(a,b-1)):(b=new h({top:a.plotX,left:a.plotY,width:.1,height:.1}),b.body=a,b.isInternal=!1,this.nodes.push(b)): 112 | (this.isInternal=!1,this.body=a))},updateMassAndCenter:function(){var a=0,b=0,c=0;this.isInternal?(this.nodes.forEach(function(f){f.isEmpty||(a+=f.mass,b+=f.plotX*f.mass,c+=f.plotY*f.mass)}),b/=a,c/=a):this.body&&(a=this.body.mass,b=this.body.plotX,c=this.body.plotY);this.mass=a;this.plotX=b;this.plotY=c},divideBox:function(){var a=this.box.width/2,b=this.box.height/2;this.nodes[0]=new h({left:this.box.left,top:this.box.top,width:a,height:b});this.nodes[1]=new h({left:this.box.left+a,top:this.box.top, 113 | width:a,height:b});this.nodes[2]=new h({left:this.box.left+a,top:this.box.top+b,width:a,height:b});this.nodes[3]=new h({left:this.box.left,top:this.box.top+b,width:a,height:b})},getBoxPosition:function(a){var b=a.plotYMath.abs(this.systemTemperature-this.prevSystemTemperature)||0>=this.temperature},getSystemTemperature:function(){return this.nodes.reduce(function(a,b){return a+b.temperature},0)},vectorLength:function(a){return Math.sqrt(a.x*a.x+ 127 | a.y*a.y)},getDistR:function(a,b){a=this.getDistXY(a,b);return this.vectorLength(a)},getDistXY:function(a,b){var c=a.plotX-b.plotX;a=a.plotY-b.plotY;return{x:c,y:a,absX:Math.abs(c),absY:Math.abs(a)}}});b(c,"predraw",function(){this.graphLayoutsLookup&&this.graphLayoutsLookup.forEach(function(a){a.stop()})});b(c,"render",function(){function a(a){a.maxIterations--&&isFinite(a.temperature)&&!a.isStable()&&!a.enableSimulation&&(a.beforeStep&&a.beforeStep(),a.step(),c=!1,b=!0)}var b=!1;if(this.graphLayoutsLookup){f(!1, 128 | this);for(this.graphLayoutsLookup.forEach(function(a){a.start()});!c;){var c=!0;this.graphLayoutsLookup.forEach(a)}b&&this.series.forEach(function(a){a&&a.layout&&a.render()})}});b(c,"beforePrint",function(){this.graphLayoutsLookup&&(this.graphLayoutsLookup.forEach(function(a){a.updateSimulation(!1)}),this.redraw())});b(c,"afterPrint",function(){this.graphLayoutsLookup&&this.graphLayoutsLookup.forEach(function(a){a.updateSimulation()});this.redraw()})});A(c,"Series/PackedBubbleSeries.js",[c["Core/Series/Series.js"], 129 | c["Core/Chart/Chart.js"],c["Core/Color/Color.js"],c["Core/Globals.js"],c["Core/Series/Point.js"],c["Core/Utilities.js"]],function(c,b,h,a,f,q){var u=h.parse,z=q.addEvent,E=q.clamp,B=q.defined,e=q.extend;h=q.extendClass;var x=q.fireEvent,w=q.isArray,m=q.isNumber,C=q.merge,v=q.pick,p=a.Series,l=a.layouts["reingold-fruchterman"],k=a.dragNodesMixin;"";b.prototype.getSelectedParentNodes=function(){var a=[];this.series.forEach(function(d){d.parentNode&&d.parentNode.selected&&a.push(d.parentNode)});return a}; 130 | a.networkgraphIntegrations.packedbubble={repulsiveForceFunction:function(a,b,c,e){return Math.min(a,(c.marker.radius+e.marker.radius)/2)},barycenter:function(){var a=this,b=a.options.gravitationalConstant,c=a.box,e=a.nodes,f,n;e.forEach(function(d){a.options.splitSeries&&!d.isParentNode?(f=d.series.parentNode.plotX,n=d.series.parentNode.plotY):(f=c.width/2,n=c.height/2);d.fixedPosition||(d.plotX-=(d.plotX-f)*b/(d.mass*Math.sqrt(e.length)),d.plotY-=(d.plotY-n)*b/(d.mass*Math.sqrt(e.length)))})},repulsive:function(a, 131 | b,c,e){var d=b*this.diffTemperature/a.mass/a.degree;b=c.x*d;c=c.y*d;a.fixedPosition||(a.plotX+=b,a.plotY+=c);e.fixedPosition||(e.plotX-=b,e.plotY-=c)},integrate:a.networkgraphIntegrations.verlet.integrate,getK:a.noop};a.layouts.packedbubble=h(l,{beforeStep:function(){this.options.marker&&this.series.forEach(function(a){a&&a.calculateParentRadius()})},setCircularPositions:function(){var a=this,b=a.box,c=a.nodes,e=2*Math.PI/(c.length+1),f,n,h=a.options.initialPositionRadius;c.forEach(function(d,g){a.options.splitSeries&& 132 | !d.isParentNode?(f=d.series.parentNode.plotX,n=d.series.parentNode.plotY):(f=b.width/2,n=b.height/2);d.plotX=d.prevX=v(d.plotX,f+h*Math.cos(d.index||g*e));d.plotY=d.prevY=v(d.plotY,n+h*Math.sin(d.index||g*e));d.dispX=0;d.dispY=0})},repulsiveForces:function(){var a=this,b,c,e,f=a.options.bubblePadding;a.nodes.forEach(function(d){d.degree=d.mass;d.neighbours=0;a.nodes.forEach(function(g){b=0;d===g||d.fixedPosition||!a.options.seriesInteraction&&d.series!==g.series||(e=a.getDistXY(d,g),c=a.vectorLength(e)- 133 | (d.marker.radius+g.marker.radius+f),0>c&&(d.degree+=.01,d.neighbours++,b=a.repulsiveForce(-c/Math.sqrt(d.neighbours),a.k,d,g)),a.force("repulsive",d,b*g.mass,e,g,c))})})},applyLimitBox:function(a){if(this.options.splitSeries&&!a.isParentNode&&this.options.parentNodeLimit){var d=this.getDistXY(a,a.series.parentNode);var b=a.series.parentNodeRadius-a.marker.radius-this.vectorLength(d);0>b&&b>-2*a.marker.radius&&(a.plotX-=.01*d.x,a.plotY-=.01*d.y)}l.prototype.applyLimitBox.apply(this,arguments)}});c.seriesType("packedbubble", 134 | "bubble",{minSize:"10%",maxSize:"50%",sizeBy:"area",zoneAxis:"y",crisp:!1,tooltip:{pointFormat:"Value: {point.value}"},draggable:!0,useSimulation:!0,parentNode:{allowPointSelect:!1},dataLabels:{formatter:function(){return this.point.value},parentNodeFormatter:function(){return this.name},parentNodeTextPath:{enabled:!0},padding:0,style:{transition:"opacity 2000ms"}},layoutAlgorithm:{initialPositions:"circle",initialPositionRadius:20,bubblePadding:5,parentNodeLimit:!1,seriesInteraction:!0,dragBetweenSeries:!1, 135 | parentNodeOptions:{maxIterations:400,gravitationalConstant:.03,maxSpeed:50,initialPositionRadius:100,seriesInteraction:!0,marker:{fillColor:null,fillOpacity:1,lineWidth:1,lineColor:null,symbol:"circle"}},enableSimulation:!0,type:"packedbubble",integration:"packedbubble",maxIterations:1E3,splitSeries:!1,maxSpeed:5,gravitationalConstant:.01,friction:-.981}},{hasDraggableNodes:!0,forces:["barycenter","repulsive"],pointArrayMap:["value"],trackerGroups:["group","dataLabelsGroup","parentNodesGroup"],pointValKey:"value", 136 | isCartesian:!1,requireSorting:!1,directTouch:!0,axisTypes:[],noSharedTooltip:!0,searchPoint:a.noop,accumulateAllPoints:function(a){var d=a.chart,b=[],c,e;for(c=0;cMath.sqrt(d*d+c*c)-Math.abs(a[2]+b[2])},positionBubble:function(a,b,c){var d=Math.sqrt,e=Math.asin,g=Math.acos,f=Math.pow,h=Math.abs;d=d(f(a[0]-b[0],2)+f(a[1]-b[1],2));g=g((f(d, 148 | 2)+f(c[2]+b[2],2)-f(c[2]+a[2],2))/(2*(c[2]+b[2])*d));e=e(h(a[0]-b[0])/d);a=(0>a[1]-b[1]?0:Math.PI)+g+e*(0>(a[0]-b[0])*(a[1]-b[1])?1:-1);return[b[0]+(b[2]+c[2])*Math.sin(a),b[1]-(b[2]+c[2])*Math.cos(a),c[2],c[3],c[4]]},placeBubbles:function(a){var b=this.checkOverlap,d=this.positionBubble,c=[],e=1,f=0,h=0;var l=[];var k;a=a.sort(function(a,b){return b[2]-a[2]});if(a.length){c.push([[0,0,a[0][2],a[0][3],a[0][4]]]);if(1e&&(e=a),ad&&(e.series.addPoint(C(a.options,{plotX:a.plotX,plotY:a.plotY}), 153 | !1),c.removeElementFromCollection(a,c.nodes),a.remove()))});k.onMouseUp.apply(this,arguments)}},destroy:function(){this.chart.graphLayoutsLookup&&this.chart.graphLayoutsLookup.forEach(function(a){a.removeElementFromCollection(this,a.series)},this);this.parentNode&&(this.parentNodeLayout.removeElementFromCollection(this.parentNode,this.parentNodeLayout.nodes),this.parentNode.dataLabel&&(this.parentNode.dataLabel=this.parentNode.dataLabel.destroy()));a.Series.prototype.destroy.apply(this,arguments)}, 154 | alignDataLabel:a.Series.prototype.alignDataLabel},{destroy:function(){this.series.layout&&this.series.layout.removeElementFromCollection(this,this.series.layout.nodes);return f.prototype.destroy.apply(this,arguments)},firePointEvent:function(a,b,c){var d=this.series.options;if(this.isParentNode&&d.parentNode){var e=d.allowPointSelect;d.allowPointSelect=d.parentNode.allowPointSelect;f.prototype.firePointEvent.apply(this,arguments);d.allowPointSelect=e}else f.prototype.firePointEvent.apply(this,arguments)}, 155 | select:function(a,c){var d=this.series.chart;this.isParentNode?(d.getSelectedPoints=d.getSelectedParentNodes,f.prototype.select.apply(this,arguments),d.getSelectedPoints=b.prototype.getSelectedPoints):f.prototype.select.apply(this,arguments)}});z(b,"beforeRedraw",function(){this.allDataPoints&&delete this.allDataPoints});""});A(c,"Extensions/Polar.js",[c["Core/Animation/AnimationUtilities.js"],c["Core/Chart/Chart.js"],c["Core/Globals.js"],c["Extensions/Pane.js"],c["Core/Pointer.js"],c["Core/Renderer/SVG/SVGRenderer.js"], 156 | c["Core/Utilities.js"]],function(c,b,h,a,f,q,u){var z=c.animObject,E=u.addEvent,B=u.defined,e=u.find,x=u.isNumber,w=u.pick,m=u.splat,C=u.uniqueKey;c=u.wrap;var v=h.Series,p=h.seriesTypes,l=v.prototype;f=f.prototype;l.searchPointByAngle=function(a){var b=this.chart,d=this.xAxis.pane.center;return this.searchKDTree({clientX:180+-180/Math.PI*Math.atan2(a.chartX-d[0]-b.plotLeft,a.chartY-d[1]-b.plotTop)})};l.getConnectors=function(a,b,c,e){var d=e?1:0;var f=0<=b&&b<=a.length-1?b:0>b?a.length-1+b:0;b=0> 157 | f-1?a.length-(1+d):f-1;d=f+1>a.length-1?d:f+1;var g=a[b];d=a[d];var h=g.plotX;g=g.plotY;var k=d.plotX;var l=d.plotY;d=a[f].plotX;f=a[f].plotY;h=(1.5*d+h)/2.5;g=(1.5*f+g)/2.5;k=(1.5*d+k)/2.5;var r=(1.5*f+l)/2.5;l=Math.sqrt(Math.pow(h-d,2)+Math.pow(g-f,2));var m=Math.sqrt(Math.pow(k-d,2)+Math.pow(r-f,2));h=Math.atan2(g-f,h-d);r=Math.PI/2+(h+Math.atan2(r-f,k-d))/2;Math.abs(h-r)>Math.PI/2&&(r-=Math.PI);h=d+Math.cos(r)*l;g=f+Math.sin(r)*l;k=d+Math.cos(Math.PI+r)*m;r=f+Math.sin(Math.PI+r)*m;d={rightContX:k, 158 | rightContY:r,leftContX:h,leftContY:g,plotX:d,plotY:f};c&&(d.prevPointCont=this.getConnectors(a,b,!1,e));return d};l.toXY=function(a){var b=this.chart,d=this.xAxis;var c=this.yAxis;var e=a.plotX,f=a.plotY,h=a.series,k=b.inverted,l=a.y,m=k?e:c.len-f;k&&h&&!h.isRadialBar&&(a.plotY=f="number"===typeof l?c.translate(l)||0:0);a.rectPlotX=e;a.rectPlotY=f;c.center&&(m+=c.center[3]/2);c=k?c.postTranslate(f,m):d.postTranslate(e,m);a.plotX=a.polarPlotX=c.x-b.plotLeft;a.plotY=a.polarPlotY=c.y-b.plotTop;this.kdByAngle? 159 | (b=(e/Math.PI*180+d.pane.options.startAngle)%360,0>b&&(b+=360),a.clientX=b):a.clientX=a.plotX};p.spline&&(c(p.spline.prototype,"getPointSpline",function(a,b,c,e){this.chart.polar?e?(a=this.getConnectors(b,e,!0,this.connectEnds),a=["C",a.prevPointCont.rightContX,a.prevPointCont.rightContY,a.leftContX,a.leftContY,a.plotX,a.plotY]):a=["M",c.plotX,c.plotY]:a=a.call(this,b,c,e);return a}),p.areasplinerange&&(p.areasplinerange.prototype.getPointSpline=p.spline.prototype.getPointSpline));E(v,"afterTranslate", 160 | function(){var a=this.chart;if(a.polar&&this.xAxis){(this.kdByAngle=a.tooltip&&a.tooltip.shared)?this.searchPoint=this.searchPointByAngle:this.options.findNearestPointBy="xy";if(!this.preventPostTranslate)for(var b=this.points,c=b.length;c--;)this.toXY(b[c]),!a.hasParallelCoordinates&&!this.yAxis.reversed&&b[c].yb&&(b= 164 | g),0>a&&(a=g));return{x:d[0],y:d[1],r:b,innerR:a,start:c,end:e}},c(p,"animate",k),c(p,"translate",function(a){var b=this.options,c=b.stacking,d=this.chart,e=this.xAxis,f=this.yAxis,h=f.reversed,k=f.center,l=e.startAngleRad,m=e.endAngleRad-l;this.preventPostTranslate=!0;a.call(this);if(e.isRadial){a=this.points;e=a.length;var p=f.translate(f.min);var q=f.translate(f.max);b=b.threshold||0;if(d.inverted&&x(b)){var v=f.translate(b);B(v)&&(0>v?v=0:v>m&&(v=m),this.translatedThreshold=v+l)}for(;e--;){b= 165 | a[e];var w=b.barX;var z=b.x;var E=b.y;b.shapeType="arc";if(d.inverted){b.plotY=f.translate(E);if(c&&f.stacking){if(E=f.stacking.stacks[(0>E?"-":"")+this.stackKey],this.visible&&E&&E[z]&&!b.isNull){var C=E[z].points[this.getStackIndicator(void 0,z,this.index).key];var A=f.translate(C[0]);C=f.translate(C[1]);B(A)&&(A=u.clamp(A,0,m))}}else A=v,C=b.plotY;A>C&&(C=[A,A=C][0]);if(!h)if(Aq)C=q;else{if(Cq)A=C=0}else if(C>p)C=p;else if(Ap||Cf.max&&(A=C= 166 | h?m:0);A+=l;C+=l;k&&(b.barX=w+=k[3]/2);z=Math.max(w,0);E=Math.max(w+b.pointWidth,0);b.shapeArgs={x:k&&k[0],y:k&&k[1],r:E,innerR:z,start:A,end:C};b.opacity=A===C?0:void 0;b.plotY=(B(this.translatedThreshold)&&(Ak[1])}}}),p.findAlignments= 167 | function(a,b){null===b.align&&(b.align=20a?"left":200a?"right":"center");null===b.verticalAlign&&(b.verticalAlign=45>a||315a?"top":"middle");return b},v&&(v.findAlignments=p.findAlignments),c(p,"alignDataLabel",function(a,b,c,e,f,h){var d=this.chart,g=w(e.inside,!!this.options.stacking);d.polar?(a=b.rectPlotX/Math.PI*180,d.inverted?(this.forceDL=d.isInsidePlot(b.plotX,Math.round(b.plotY),!1),g&&b.shapeArgs?(f=b.shapeArgs,f=this.yAxis.postTranslate((f.start+f.end)/ 168 | 2-this.xAxis.startAngleRad,b.barX+b.pointWidth/2),f={x:f.x-d.plotLeft,y:f.y-d.plotTop}):b.tooltipPos&&(f={x:b.tooltipPos[0],y:b.tooltipPos[1]}),e.align=w(e.align,"center"),e.verticalAlign=w(e.verticalAlign,"middle")):this.findAlignments&&(e=this.findAlignments(a,e)),l.alignDataLabel.call(this,b,c,e,f,h),this.isRadialBar&&b.shapeArgs&&b.shapeArgs.start===b.shapeArgs.end&&c.hide(!0)):a.call(this,b,c,e,f,h)}));c(f,"getCoordinates",function(a,b){var c=this.chart,d={xAxis:[],yAxis:[]};c.polar?c.axes.forEach(function(a){var e= 169 | a.isXAxis,f=a.center;if("colorAxis"!==a.coll){var g=b.chartX-f[0]-c.plotLeft;f=b.chartY-f[1]-c.plotTop;d[e?"xAxis":"yAxis"].push({axis:a,value:a.translate(e?Math.PI-Math.atan2(g,f):Math.sqrt(Math.pow(g,2)+Math.pow(f,2)),!0)})}}):d=a.call(this,b);return d});q.prototype.clipCircle=function(a,b,c,e){var d=C(),f=this.createElement("clipPath").attr({id:d}).add(this.defs);a=e?this.arc(a,b,c,e,0,2*Math.PI).add(f):this.circle(a,b,c).add(f);a.id=d;a.clipPath=f;return a};E(b,"getAxes",function(){this.pane|| 170 | (this.pane=[]);m(this.options.pane).forEach(function(b){new a(b,this)},this)});E(b,"afterDrawChartBox",function(){this.pane.forEach(function(a){a.render()})});E(h.Series,"afterInit",function(){var a=this.chart;a.inverted&&a.polar&&(this.isRadialSeries=!0,this.is("column")&&(this.isRadialBar=!0))});c(b.prototype,"get",function(a,b){return e(this.pane,function(a){return a.options.id===b})||a.call(this,b)})});A(c,"masters/highcharts-more.src.js",[],function(){})}); 171 | //# sourceMappingURL=highcharts-more.js.map -------------------------------------------------------------------------------- /bosszp/web/static/highcharts/oldie.js: -------------------------------------------------------------------------------- 1 | /* 2 | Highcharts JS v8.2.2 (2020-10-22) 3 | 4 | Old IE (v6, v7, v8) module for Highcharts v6+. 5 | 6 | (c) 2010-2019 Highsoft AS 7 | Author: Torstein Honsi 8 | 9 | License: www.highcharts.com/license 10 | */ 11 | (function(a){"object"===typeof module&&module.exports?(a["default"]=a,module.exports=a):"function"===typeof define&&define.amd?define("highcharts/modules/oldie",["highcharts"],function(A){a(A);a.Highcharts=A;return a}):a("undefined"!==typeof Highcharts?Highcharts:void 0)})(function(a){function A(a,z,p,u){a.hasOwnProperty(z)||(a[z]=u.apply(null,p))}a=a?a._modules:{};A(a,"Extensions/Math3D.js",[a["Core/Globals.js"],a["Core/Utilities.js"]],function(a,z){var p=z.pick,v=a.deg2rad,h=a.perspective3D=function(g, 12 | a,h){a=0t&&l-t>Math.PI/2+.0001?(m=m.concat(g(k, 15 | a,b,d,t,t+Math.PI/2,c,e)),m=m.concat(g(k,a,b,d,t+Math.PI/2,l,c,e))):lMath.PI/2+.0001?(m=m.concat(g(k,a,b,d,t,t-Math.PI/2,c,e)),m=m.concat(g(k,a,b,d,t-Math.PI/2,l,c,e))):[["C",k+b*Math.cos(t)-b*N*n*Math.sin(t)+c,a+d*Math.sin(t)+d*N*n*Math.cos(t)+e,k+b*Math.cos(l)+b*N*n*Math.sin(l)+c,a+d*Math.sin(l)-d*N*n*Math.cos(l)+e,k+b*Math.cos(l)+c,a+d*Math.sin(l)+e]]}var v=a.animObject,D=z.parse,r=u.perspective,A=u.shapeArea,F=q.defined,y=q.extend,J=q.merge,M=q.objectEach,C=q.pick,L=Math.cos,G=Math.PI, 16 | I=Math.sin,H=p.charts,O=p.deg2rad;var N=4*(Math.sqrt(2)-1)/3/(G/2);f.prototype.toLinePath=function(k,a){var b=[];k.forEach(function(d){b.push(["L",d.x,d.y])});k.length&&(b[0][0]="M",a&&b.push(["Z"]));return b};f.prototype.toLineSegments=function(a){var k=[],b=!0;a.forEach(function(d){k.push(b?["M",d.x,d.y]:["L",d.x,d.y]);b=!b});return k};f.prototype.face3d=function(a){var k=this,b=this.createElement("path");b.vertexes=[];b.insidePlotArea=!1;b.enabled=!0;b.attr=function(b){if("object"===typeof b&& 17 | (F(b.enabled)||F(b.vertexes)||F(b.insidePlotArea))){this.enabled=C(b.enabled,this.enabled);this.vertexes=C(b.vertexes,this.vertexes);this.insidePlotArea=C(b.insidePlotArea,this.insidePlotArea);delete b.enabled;delete b.vertexes;delete b.insidePlotArea;var d=r(this.vertexes,H[k.chartIndex],this.insidePlotArea),l=k.toLinePath(d,!0);d=A(d);d=this.enabled&&0d.faces.length;)b.faces.pop().destroy();for(;b.faces.lengthd.faces.length;)b.faces.pop().destroy();for(;b.faces.lengthe?{x:h[e].x,y:h[e].y+10,z:h[e].z}:h[0].x===h[7].x&&4<=e?{x:h[e].x+10,y:h[e].y,z:h[e].z}:0===m&&2>e||5A(d)?a=[d,0]:0>A(n)?a=[n,1]:m&&(K.push(m),a=0>A(c)?[d,0]:0>A(e)?[n,1]:[d,0]);return a};var B=x([3,2,1,0],[7,6,5,4],"front");a=B[0];var q=B[1];B=x([1,6,7,0],[4,5,2,3],"top"); 26 | e=B[0];var E=B[1];B=x([1,2,5,6],[0,7,4,3],"side");x=B[0];B=B[1];1===B?g+=1E6*(n.plotWidth-d):B||(g+=1E6*d);g+=10*(!E||0<=w&&180>=w||360>w&&357.5Math.PI&&(c=2*Math.PI-c);return c}var b=a.x,d=a.y,h=a.start,l=a.end-.00001,c=a.r,e=a.innerR||0,m=a.depth||0,n=a.alpha,w=a.beta,k=Math.cos(h), 32 | q=Math.sin(h);a=Math.cos(l);var K=Math.sin(l),x=c*Math.cos(w);c*=Math.cos(n);var B=e*Math.cos(w),p=e*Math.cos(n);e=m*Math.sin(w);var E=m*Math.sin(n);m=[["M",b+x*k,d+c*q]];m=m.concat(g(b,d,x,c,h,l,0,0));m.push(["L",b+B*a,d+p*K]);m=m.concat(g(b,d,B,p,l,h,0,0));m.push(["Z"]);var r=0-r?h:l>-r?-r:h;var v=lu&&hG-w&&h/g,"<$1title>").replace(/height=([^" ]+)/g,'height="$1"').replace(/width=([^" ]+)/g,'width="$1"').replace(/hc-svg-href="([^"]+)">/g,'xlink:href="$1"/>').replace(/ id=([^" >]+)/g,' id="$1"').replace(/class=([^" >]+)/g,'class="$1"').replace(/ transform /g," ").replace(/:(path|rect)/g,"$1").replace(/style="([^"]+)"/g, 41 | function(c){return c.toLowerCase()})},a.prototype.isReadyToRender=function(){var c=this;return F||y!=y.top||"complete"===r.readyState?!0:(r.attachEvent("onreadystatechange",function(){r.detachEvent("onreadystatechange",c.firstRender);"complete"===r.readyState&&c.firstRender()}),!1)},r.createElementNS||(r.createElementNS=function(c,a){return r.createElement(a)}),p.addEventListenerPolyfill=function(c,a){function e(c){c.target=c.srcElement||y;a.call(b,c)}var b=this;b.attachEvent&&(b.hcEventsIE||(b.hcEventsIE= 42 | {}),a.hcKey||(a.hcKey=l()),b.hcEventsIE[a.hcKey]=e,b.attachEvent("on"+c,e))},p.removeEventListenerPolyfill=function(c,a){this.detachEvent&&(a=this.hcEventsIE[a.hcKey],this.detachEvent("on"+c,a))},a={docMode8:r&&8===r.documentMode,init:function(c,a){var e=["<",a,' filled="f" stroked="f"'],b=["position: ","absolute",";"],d="div"===a;("shape"===a||d)&&b.push("left:0;top:0;width:1px;height:1px;");b.push("visibility: ",d?"hidden":"visible");e.push(' style="',b.join(""),'"/>');a&&(e=d||"span"===a||"img"=== 43 | a?e.join(""):c.prepVML(e),this.element=M(e));this.renderer=c},add:function(c){var a=this.renderer,b=this.element,d=a.box,f=c&&c.inverted;d=c?c.element||c:d;c&&(this.parentGroup=c);f&&a.invertChild(b,d);d.appendChild(b);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();if(this.onAdd)this.onAdd();this.className&&this.attr("class",this.className);return this},updateTransform:h.prototype.htmlUpdateTransform,setSpanRotation:function(){var c=this.rotation,a=Math.cos(c*D), 44 | b=Math.sin(c*D);C(this.element,{filter:c?["progid:DXImageTransform.Microsoft.Matrix(M11=",a,", M12=",-b,", M21=",b,", M22=",a,", sizingMethod='auto expand')"].join(""):"none"})},getSpanCorrection:function(c,a,b,n,f){var e=n?Math.cos(n*D):1,m=n?Math.sin(n*D):0,h=d(this.elemHeight,this.element.offsetHeight);this.xCorr=0>e&&-c;this.yCorr=0>m&&-h;var g=0>e*m;this.xCorr+=m*a*(g?1-b:b);this.yCorr-=e*a*(n?g?b:1-b:1);f&&"left"!==f&&(this.xCorr-=c*b*(0>e?-1:1),n&&(this.yCorr-=h*b*(0>m?-1:1)),C(this.element, 45 | {textAlign:f}))},pathToVML:function(c){for(var a=c.length,b=[];a--;)k(c[a])?b[a]=Math.round(10*c[a])-5:"Z"===c[a]?b[a]="x":(b[a]=c[a],!c.isArc||"wa"!==c[a]&&"at"!==c[a]||(b[a+5]===b[a+7]&&(b[a+7]+=c[a+7]>c[a+5]?1:-1),b[a+6]===b[a+8]&&(b[a+8]+=c[a+8]>c[a+6]?1:-1)));return b.join(" ")||"x"},clip:function(c){var a=this;if(c){var b=c.members;I(b,a);b.push(a);a.destroyClip=function(){I(b,a)};c=c.getCSS(a)}else a.destroyClip&&a.destroyClip(),c={clip:a.docMode8?"inherit":"rect(auto)"};return a.css(c)},css:h.prototype.htmlCss, 46 | safeRemoveChild:function(c){c.parentNode&&G(c)},destroy:function(){this.destroyClip&&this.destroyClip();return h.prototype.destroy.apply(this)},on:function(c,a){this.element["on"+c]=function(){var c=y.event;c.target=c.srcElement;a(c)};return this},cutOffPath:function(c,a){c=c.split(/[ ,]/);var b=c.length;if(9===b||11===b)c[b-4]=c[b-2]=t(c[b-2])-10*a;return c.join(" ")},shadow:function(c,a,b){var e=[],m,f=this.element,h=this.renderer,g=f.style,l=f.path;l&&"string"!==typeof l.value&&(l="x");var k=l; 47 | if(c){var p=d(c.width,3);var q=(c.opacity||.15)/p;for(m=1;3>=m;m++){var r=2*p+1-2*m;b&&(k=this.cutOffPath(l.value,r+.5));var v=[''];var u=M(h.prepVML(v),null,{left:t(g.left)+d(c.offsetX,1),top:t(g.top)+d(c.offsetY,1)});b&&(u.cutOff=r+1);v=[''];M(h.prepVML(v),null,null,u);a?a.element.appendChild(u):f.parentNode.insertBefore(u, 48 | f);e.push(u)}this.shadows=e}return this},updateShadows:A,setAttr:function(c,a){this.docMode8?this.element[c]=a:this.element.setAttribute(c,a)},getAttr:function(c){return this.docMode8?this.element[c]:this.element.getAttribute(c)},classSetter:function(c){(this.added?this.element:this).className=c},dashstyleSetter:function(c,a,b){(b.getElementsByTagName("stroke")[0]||M(this.renderer.prepVML([""]),null,null,b))[a]=c||"solid";this[a]=c},dSetter:function(c,a,b){var e=this.shadows;c=c||[];this.d= 49 | c.join&&c.join(" ");b.path=c=this.pathToVML(c);if(e)for(b=e.length;b--;)e[b].path=e[b].cutOff?this.cutOffPath(c,e[b].cutOff):c;this.setAttr(a,c)},fillSetter:function(c,a,b){var e=b.nodeName;"SPAN"===e?b.style.color=c:"IMG"!==e&&(b.filled="none"!==c,this.setAttr("fillcolor",this.renderer.color(c,b,a,this)))},"fill-opacitySetter":function(c,a,b){M(this.renderer.prepVML(["<",a.split("-")[0],' opacity="',c,'"/>']),null,null,b)},opacitySetter:A,rotationSetter:function(c,a,b){b=b.style;this[a]=b[a]=c;b.left= 50 | -Math.round(Math.sin(c*D)+1)+"px";b.top=Math.round(Math.cos(c*D))+"px"},strokeSetter:function(c,a,b){this.setAttr("strokecolor",this.renderer.color(c,b,a,this))},"stroke-widthSetter":function(c,a,b){b.stroked=!!c;this[a]=c;k(c)&&(c+="px");this.setAttr("strokeweight",c)},titleSetter:function(c,a){this.setAttr(a,c)},visibilitySetter:function(c,a,b){"inherit"===c&&(c="visible");this.shadows&&this.shadows.forEach(function(b){b.style[a]=c});"DIV"===b.nodeName&&(c="hidden"===c?"-999em":0,this.docMode8|| 51 | (b.style[a]=c?"visible":"hidden"),a="top");b.style[a]=c},xSetter:function(c,a,b){this[a]=c;"x"===a?a="left":"y"===a&&(a="top");this.updateClipping?(this[a]=c,this.updateClipping()):b.style[a]=c},zIndexSetter:function(a,b,d){d.style[b]=a},fillGetter:function(){return this.getAttr("fillcolor")||""},strokeGetter:function(){return this.getAttr("strokecolor")||""},classGetter:function(){return this.getAttr("className")||""}},a["stroke-opacitySetter"]=a["fill-opacitySetter"],p.VMLElement=a=O(h,a),a.prototype.ySetter= 52 | a.prototype.widthSetter=a.prototype.heightSetter=a.prototype.xSetter,u={Element:a,isIE8:-1'];M(c.prepVML(m),null,null,b)};var z=a[0];var C=a[a.length-1];0C[0]&&a.push([1,C[1]]);a.forEach(function(a,c){e.test(a[1])?(J=v(a[1]),l=J.get("rgb"),n=J.get("a")):(l=a[1],n=1);y.push(100*a[0]+"% "+l);c?(q=n,r=l): 56 | (p=n,t=l)});if("fill"===d)if("gradient"===h)d=k.x1||k[0]||0,a=k.y1||k[1]||0,z=k.x2||k[2]||0,k=k.y2||k[3]||0,u='angle="'+(90-180*Math.atan((k-a)/(z-d))/Math.PI)+'"',A();else{g=k.r;var D=2*g,F=2*g,G=k.cx,L=k.cy,I=b.radialReference,H;g=function(){I&&(H=f.getBBox(),G+=(I[0]-H.x)/H.width-.5,L+=(I[1]-H.y)/H.height-.5,D*=I[2]/H.width,F*=I[2]/H.height);u='src="'+N().global.VMLRadialGradientURL+'" size="'+D+","+F+'" origin="0.5,0.5" position="'+G+","+L+'" color2="'+t+'" ';A()};f.added?g():f.onAdd=g;g=r}else g= 57 | l}else if(e.test(a)&&"IMG"!==b.tagName){var J=v(a);f[d+"-opacitySetter"](J.get("a"),d,b);g=J.get("rgb")}else g=b.getElementsByTagName(d),g.length&&(g[0].opacity=1,g[0].type="solid"),g=a;return g},prepVML:function(a){var c=this.isIE8;a=a.join("");c?(a=a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />'),a=-1===a.indexOf('style="')?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')):a=a.replace("<", 58 | "g.max||a.maxa.right||e.righta.bottom||e.bottom=f&&("boolean"===typeof c&&d>=b-a&&(c={x:e-(b-d),y:-e}),b-=a,"boolean"===typeof c&&d>=b-a&&(c={x:-e,y:-e+(b-d)}),b-=a,"boolean"===typeof c&&(c=d>=b-a?{x:-e+(b-d),y:e}:{x:e,y:e-(b-d-a)}),c.x*=5,c.y*=5);return c}function F(f,d,e){var a=2*Math.max(Math.abs(e.top),Math.abs(e.bottom));e=2*Math.max(Math.abs(e.left),Math.abs(e.right));return Math.min(0d?f/d:1;f=d>f?d/f:1;return{width:e*a,height:e*f,ratioX:a,ratioY:f}}function q(f,d,a,c){var e=!1;u(f)&&u(d)&&u(a)&&u(c)&&0a&&(e=a+d%f*((c-a)/(f-1||1)));return e}function y(f,d){var a,c=[];for(a=1;1E4>a;a++)c.push(f(a,d));return function(f){return 1E4>=f?c[f-1]:!1}}function t(f,a){var d= 17 | a.width/2,c=-(a.height/2),b=a.height/2;return!(-(a.width/2)f.right&&cf.bottom)}function C(a,d){var f=d.placed,c=d.field,b=d.rectangle,g=d.polygon,h=d.spiral,l=1,k={x:0,y:0},m=a.rect=z({},b);a.polygon=g;for(a.rotation=d.rotation;!1!==k&&(p(a,f)||t(m,c));)k=h(l),A(k)&&(m.left=b.left+k.x,m.right=b.right+k.x,m.top=b.top+k.y,m.bottom=b.bottom+k.y,a.polygon=G(k.x,k.y,g)),l++;return k}function c(a,d){if(A(a)&&A(d)){var f=d.bottom-d.top;var c=d.right-d.left;d=a.ratioX;var b=a.ratioY; 18 | f=c*d>f*b?c:f;a=L(a,{width:a.width+f*d*2,height:a.height+f*b*2})}return a}var b=h.noop,J=l.getBoundingBoxFromPolygon,m=l.getPolygon,B=l.isPolygonsColliding,G=l.movePolygon,z=k.extend,K=k.find,M=k.isArray,u=k.isNumber,A=k.isObject,L=k.merge,D=h.Series;l={animate:D.prototype.animate,animateDrilldown:b,animateDrillupFrom:b,setClip:b,bindAxes:function(){var a={endOnTick:!1,gridLineWidth:0,lineWidth:0,maxPadding:0,startOnTick:!1,title:null,tickPositions:[]};D.prototype.bindAxes.call(this);z(this.yAxis.options, 19 | a);z(this.xAxis.options,a)},pointAttribs:function(a,d){a=h.seriesTypes.column.prototype.pointAttribs.call(this,a,d);delete a.stroke;delete a["stroke-width"];return a},deriveFontSize:function(a,d,c){a=u(a)?a:0;d=u(d)?d:1;c=u(c)?c:1;return Math.floor(Math.max(c,a*d))},drawPoints:function(){var a=this,d=a.hasRendered,b=a.xAxis,g=a.yAxis,k=a.group,h=a.options,l=h.animation,p=h.allowExtendPlayingField,r=a.chart.renderer,q=r.text().add(k),t=[],B=a.placementStrategy[h.placementStrategy],D=h.rotation,G=a.points.map(function(a){return a.weight}), 20 | H=Math.max.apply(null,G),E=a.points.concat().sort(function(a,d){return d.weight-a.weight});a.group.attr({scaleX:1,scaleY:1});E.forEach(function(d){var c=a.deriveFontSize(1/H*d.weight,h.maxFontSize,h.minFontSize);c=z({fontSize:c+"px"},h.style);q.css(c).attr({x:0,y:0,text:d.name});c=q.getBBox(!0);d.dimensions={height:c.height,width:c.width}});var v=x(b.len,g.len,E);var I=y(a.spirals[h.spiral],{field:v});E.forEach(function(b){var f=a.deriveFontSize(1/H*b.weight,h.maxFontSize,h.minFontSize);f=z({fontSize:f+ 21 | "px"},h.style);var e=B(b,{data:E,field:v,placed:t,rotation:D}),g=z(a.pointAttribs(b,b.selected&&"select"),{align:"center","alignment-baseline":"middle",x:e.x,y:e.y,text:b.name,rotation:e.rotation}),q=m(e.x,e.y,b.dimensions.width,b.dimensions.height,e.rotation),n=J(q),w=C(b,{rectangle:n,polygon:q,field:v,placed:t,spiral:I,rotation:e.rotation});!w&&p&&(v=c(v,n),w=C(b,{rectangle:n,polygon:q,field:v,placed:t,spiral:I,rotation:e.rotation}));if(A(w)){g.x+=w.x;g.y+=w.y;n.left+=w.x;n.right+=w.x;n.top+=w.y; 22 | n.bottom+=w.y;e=v;if(!u(e.left)||e.left>n.left)e.left=n.left;if(!u(e.right)||e.rightn.top)e.top=n.top;if(!u(e.bottom)||e.bottom=a&&(b={x:d*Math.cos(d),y:d*Math.sin(d)},Math.min(Math.abs(b.x),Math.abs(b.y))\u25cf {series.name}: {point.weight}
'}},l,g)});p(a,"masters/modules/wordcloud.src.js",[],function(){})}); 27 | //# sourceMappingURL=wordcloud.js.map -------------------------------------------------------------------------------- /bosszp/web/static/img/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcoco/bosszp/522250ff097909439bd72a58d2433bfc1a5bdab3/bosszp/web/static/img/bg.png -------------------------------------------------------------------------------- /bosszp/web/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jhcoco/bosszp/522250ff097909439bd72a58d2433bfc1a5bdab3/bosszp/web/static/img/favicon.png -------------------------------------------------------------------------------- /bosszp/web/static/js/cylindrical.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $.ajax({ 3 | url: '/getjobnum', 4 | type: 'GET', 5 | datatype: 'json', 6 | success: function (data) { 7 | var obj2 = JSON.parse(data); 8 | if (obj2.status == 201){ 9 | this.error(xhr=obj2) 10 | return 11 | } 12 | var d = new Date(),str = ''; 13 | str += d.getFullYear() + '年'; //获取当前年份 14 | str += d.getMonth() + 1 + '月'; //获取当前月份(0——11) 15 | var chart = Highcharts.chart('cylindrical', { 16 | chart: { 17 | type: 'column', 18 | backgroundColor: 'rgba(0,0,0,0)' 19 | }, 20 | title: { 21 | text: '全国各大城市岗位数量' 22 | }, 23 | subtitle: { 24 | text: '数据截止 '+str+',来源: Boss直聘' 25 | }, 26 | xAxis: { 27 | type: 'category', 28 | labels: { 29 | rotation: -45 // 设置轴标签旋转角度 30 | } 31 | }, 32 | yAxis: { 33 | min: 0, 34 | title: { 35 | text: '岗位数 (个)' 36 | } 37 | }, 38 | legend: { 39 | enabled: false 40 | }, 41 | tooltip: { 42 | pointFormat: '岗位总量: {point.y} 个' 43 | }, 44 | series: [{ 45 | name: '总岗位数', 46 | data: obj2.data, 47 | dataLabels: { 48 | enabled: true, 49 | rotation: -90, 50 | color: '#FFFFFF', 51 | align: 'right', 52 | format: '{point.y}', // :.1f 为保留 1 位小数 53 | y: 10 54 | } 55 | }] 56 | }); 57 | }, 58 | error: function (xhr, type, errorThrown) { 59 | alert(xhr.data) 60 | } 61 | }); 62 | }); -------------------------------------------------------------------------------- /bosszp/web/static/js/fan.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $.ajax({ 3 | url: '/geteducationnum', 4 | type: 'GET', 5 | datatype: 'json', 6 | success: function (data) { 7 | var obj3 = JSON.parse(data) 8 | if (obj3.status == 201){ 9 | this.error(xhr=obj3) 10 | } 11 | var chart = Highcharts.chart('fan', { 12 | chart: { 13 | backgroundColor: 'rgba(0,0,0,0)' 14 | }, 15 | title: { 16 | text: '学历
占比', 17 | align: 'center', 18 | verticalAlign: 'middle', 19 | y: 50 20 | }, 21 | tooltip: { 22 | headerFormat: '{series.name}
', 23 | pointFormat: '{point.name}: {point.percentage:.1f}%' 24 | }, 25 | plotOptions: { 26 | pie: { 27 | dataLabels: { 28 | enabled: true, 29 | distance: -50, 30 | style: { 31 | fontWeight: 'bold', 32 | color: 'white', 33 | textShadow: '0px 1px 2px black' 34 | } 35 | }, 36 | startAngle: -90, // 圆环的开始角度 37 | endAngle: 90, // 圆环的结束角度 38 | center: ['50%', '75%'] 39 | } 40 | }, 41 | series: [{ 42 | type: 'pie', 43 | name: '学历占比', 44 | innerSize: '50%', 45 | data: obj3.data 46 | }] 47 | }); 48 | }, 49 | error: function (xhr, type, errorThrown) { 50 | alert(xhr.data) 51 | } 52 | }) 53 | }) -------------------------------------------------------------------------------- /bosszp/web/static/js/order.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $.ajax({ 3 | url: '/getorder', 4 | type: 'GET', 5 | datatype: 'json', 6 | success: function (data) { 7 | var obj = JSON.parse(data); 8 | if (obj.status == 201){ 9 | this.error(xhr=obj); 10 | return 11 | } 12 | $(function () { 13 | // var obj1 = JSON.parse(data); 14 | $("#J_TbData").empty(); 15 | for (var i = 0; i < obj.data.length; i++) { 16 | //动态创建一个tr行标签,并且转换成jQuery对象 17 | var $trTemp = $(""); 18 | //往行里面追加 td单元格 19 | $trTemp.append("" + obj.data[i].id + ""); 20 | $trTemp.append("" + obj.data[i].name + ""); 21 | $trTemp.append("" + obj.data[i].num + ""); 22 | // $("#J_TbData").append($trTemp); 23 | $trTemp.appendTo("#J_TbData"); 24 | } 25 | }); 26 | }, error: function (xhr, type, errorThrown) { 27 | alert(xhr.data) 28 | } 29 | }) 30 | }) -------------------------------------------------------------------------------- /bosszp/web/static/js/packgebubble.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $.ajax({ 3 | url: '/getjobinfo', 4 | type: 'GET', 5 | datatype: 'json', 6 | success: function (data) { 7 | var obj = JSON.parse(data); 8 | if (obj.status == 201){ 9 | this.error(xhr=obj) 10 | return 11 | } 12 | Highcharts.chart('packedbubble', { 13 | chart: { 14 | type: 'packedbubble', 15 | height: '100%', 16 | backgroundColor: 'rgba(0,0,0,0)' 17 | }, 18 | title: { 19 | text: '2020年热门岗位招聘区域分布' 20 | }, 21 | tooltip: { 22 | useHTML: true, 23 | pointFormat: '{point.name}: {point.y}' 24 | }, 25 | plotOptions: { 26 | packedbubble: { 27 | minSize: '30%', 28 | maxSize: '120%', 29 | zMin: 0, 30 | zMax: 1000, 31 | layoutAlgorithm: { 32 | splitSeries: false, 33 | gravitationalConstant: 0.02 34 | }, 35 | dataLabels: { 36 | enabled: true, 37 | format: '{point.name}', 38 | filter: { 39 | property: 'y', 40 | operator: '>=', 41 | value: 5 42 | }, 43 | style: { 44 | color: 'black', 45 | textOutline: 'none', 46 | fontWeight: 'normal' 47 | } 48 | } 49 | } 50 | }, 51 | series:obj.data 52 | }); 53 | }, 54 | error: function (xhr, type, errorThrown) { 55 | alert(xhr.data) 56 | } 57 | }); 58 | 59 | 60 | }); -------------------------------------------------------------------------------- /bosszp/web/static/js/pie.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $.ajax({ 3 | url: '/getcomtypenum', 4 | dataType: 'json',//服务器返回json格式数据 5 | type: 'GET',//HTTP请求类型 6 | timeout: 10000,//超时时间设置为10秒; 7 | success: function (data) { 8 | // var obj2 = JSON.parse(data) 9 | if (data.status == 201){ 10 | this.error(xhr=data) 11 | return 12 | } 13 | Highcharts.chart('pie', { 14 | chart: { 15 | plotBackgroundColor: null, 16 | plotBorderWidth: null, 17 | plotShadow: false, 18 | type: 'pie', 19 | backgroundColor: 'rgba(0,0,0,0)' 20 | }, 21 | title: { 22 | text: '企业类型占比' 23 | }, 24 | tooltip: { 25 | pointFormat: '{series.name}: {point.percentage:.1f}%' 26 | }, 27 | plotOptions: { 28 | pie: { 29 | allowPointSelect: true, 30 | cursor: 'pointer', 31 | dataLabels: { 32 | enabled: true, 33 | format: '{point.name}: {point.percentage:.1f} %', 34 | style: { 35 | color: (Highcharts.theme && Highcharts.theme.contrastTextColor) || 'black' 36 | } 37 | } 38 | } 39 | }, 40 | series: [{ 41 | name: 'Brands', 42 | colorByPoint: true, 43 | data: data.data, 44 | }], 45 | }); 46 | 47 | }, 48 | error: function (xhr, type, errorThrown) { 49 | alert(xhr.data) 50 | } 51 | }); 52 | }) -------------------------------------------------------------------------------- /bosszp/web/static/js/word.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | $.ajax({ 3 | type: "GET", 4 | url: "/getwordcloud", 5 | // data: "name=John&location=Boston", 6 | success: function (msg) { 7 | var obj1 = JSON.parse(msg) 8 | if (obj1.status == 201) { 9 | this.erro(xhr = obj1) 10 | return 11 | } 12 | var data = obj1.data.split(/[,\. ]+/g) 13 | .reduce(function (arr, word) { 14 | var obj = arr.find(function (obj) { 15 | return obj.name === word; 16 | }); 17 | if (obj) { 18 | obj.weight += 1; 19 | } else { 20 | obj = { 21 | name: word, 22 | weight: 1 23 | }; 24 | arr.push(obj); 25 | } 26 | return arr; 27 | }, []); 28 | Highcharts.chart('word_cloud', { 29 | chart: { 30 | backgroundColor: 'rgba(0,0,0,0)' 31 | }, 32 | series: [{ 33 | type: 'wordcloud', 34 | data: data 35 | }], 36 | title: { 37 | text: '企业福利词云图' 38 | } 39 | }); 40 | }, 41 | erro: function (xhr, type, errorThrown) { 42 | alert(xhr.data) 43 | } 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /bosszp/web/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Boss岗位分析可视化 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 |

Boss直聘岗位分析可视化

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 |

企业招聘Top 10

59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
排名公司名称岗位数量
70 |
71 |
72 |
73 |
74 |
75 | 78 |
79 | 82 | 83 | -------------------------------------------------------------------------------- /runspider.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | 作者:jhzhong 4 | """ 5 | """ 6 | 运行爬虫 7 | """ 8 | from scrapy import cmdline 9 | 10 | cmdline.execute('scrapy crawl boss'.split()) -------------------------------------------------------------------------------- /scrapy.cfg: -------------------------------------------------------------------------------- 1 | # Automatically created by: scrapy startproject 2 | # 3 | # For more information about the [deploy] section see: 4 | # https://scrapyd.readthedocs.io/en/latest/deploy.html 5 | 6 | [settings] 7 | default = bosszp.settings 8 | 9 | [deploy] 10 | #url = http://localhost:6800/ 11 | project = bosszp 12 | -------------------------------------------------------------------------------- /全国-热门城市岗位数据.csv: -------------------------------------------------------------------------------- 1 | job_name,job_area,job_salary,com_name,com_type,com_size,finance_stage,work_year,education,job_benefits 2 | 自然语言处理研究员,上海·长宁区·北新泾,25-50K,携程旅行网,互联网,10000人以上,已上市,经验不限,硕士,员工旅游,带薪年假,餐补,五险一金,加班补助,绩效奖金,年终奖,定期体检,节日福利,股票期权,补充医疗保险 3 | BI工程师,北京·大兴区,20-40K,京东集团,电子商务,10000人以上,已上市,5-10年,本科,交通补助,定期体检,年终奖,带薪年假,补充医疗保险,免费班车,五险一金,零食下午茶,员工旅游,股票期权,餐补,包吃,节日福利 4 | 腾讯北京区域招聘—后台开发(C++/JAVA/GO),北京·海淀区·中关村,20-40K·16薪,腾讯,互联网,10000人以上,已上市,3-5年,本科,带薪年假,员工旅游,年终奖,定期体检,补充医疗保险,节日福利,五险一金,免费班车 5 | Flutter研发工程师-企业平台,北京·海淀区·上地,14-28K·16薪,网易,移动互联网,10000人以上,已上市,1-3年,本科,五险一金,补充医疗保险,带薪年假,全勤奖,定期体检,年终奖,节日福利,免费班车,包吃 6 | 前端工程师-【用户增长】,北京,20-40K·16薪,快手,社交网络,10000人以上,D轮及以上,3-5年,本科,带薪年假,补充医疗保险,住房补贴,零食下午茶,节日福利,年终奖,包吃,定期体检,加班补助,五险一金,股票期 7 | 权高级Java开发工程师,北京·朝阳区·酒仙桥,25-40K·15薪,车好多集团,互联网,10000人以上,D轮及以上,3-5年,本科,补充医疗保险,定期体检,五险一金,股票期权,带薪年假 8 | 公有云运维工程师,上海,20-40K,SAP Labs,互联网,10000人以上,已上市,3-5年,本科,住房补贴,加班补助,五险一金,餐补,带薪年假,节日福利,员工旅游,补充医疗保险,定期体检,股票期权,年终奖 9 | 服务端开发工程师 — 音视频,上海·闵行区·虹梅路,25-50K,今日头条,移动互联网,1000-9999人,不需要融资,经验不限,本科,补充医疗保险,住房补贴,餐补,节日福利,五险一金,带薪年假,交通补助,试用期同薪,零食下午茶,定期体检,年终奖,加班补助 10 | Java技术专家,上海·浦东新区·陆家嘴,28-45K,盒马生鲜,生活服务,10000人以上,不需要融资,3-5年,硕士,节日福利,五险一金,年终奖,带薪年假,全勤奖,餐补,加班补助 11 | 阿里影业-B端团队-Java开发高级工程师,北京·朝阳区·望京,25-40K,阿里巴巴集团,互联网,10000人以上,已上市,3-5年,本科,员工旅游,餐补,带薪年假,交通补助,加班补助,年终奖,补充医疗保险,五险一金,免费班车,节日福利,股票期权,定期体检 12 | 教育行业售前,北京·朝阳区·望京,12-24K·13薪,东华云通,计算机软件,10000人以上,已上市,3-5年,本科,None 13 | Java开发工程师,北京·海淀区·上地,15-23K,京北方,互联网金融,10000人以上,已上市,3-5年,本科,定期体检,年终奖,餐补,节日福利,带薪年假,加班补助,员工旅游,五险一金 14 | 深度学习算法工程师,北京·海淀区·西北旺,35-65K·16薪,百度,互联网,10000人以上,已上市,3-5年,本科,年终奖,老板Nice,零食下午茶,加班补助,定期体检,包吃,五险一金,餐补,带薪年假,交通补助,补充医疗保险,住房补贴,节日福利,通讯补贴,免费班车,团队氛围好,股票期权,员工旅游 15 | C++(北京),北京·大兴区·亦庄,16-25K,文思海辉,计算机软件,10000人以上,已上市,1-3年,本科,员工旅游,带薪年假,五险一金,节日福利,定期体检,加班补助,年终奖,优惠券,交通补助 16 | 大数据开发工程师,上海·青浦区·华新,20-40K,中通快递,物流/仓储,10000人以上,已上市,5-10年,本科,定期体检,加班补助,五险一金,员工旅游,节日福利,餐补,年终奖,补充医疗保险,免费班车,零食下午茶 17 | 测试开发工程师,北京·朝阳区·团结湖,20-30K·16薪,蚂蚁金服,互联网,10000人以上,不需要融资,3-5年,本科,员工旅游,定期体检,餐补,补充医疗保险,股票期权,包吃,带薪年假,五险一金,节日福利,年终奖,免费班车 18 | 应用架构师,北京·大兴区·亦庄,40-60K·14薪,京东世纪贸易有限公司,互联网,10000人以上,已上市,10年以上,本科,补充医疗保险,定期体检,年终奖,五险一金,餐补,全勤奖机器视觉嵌入式 19 | 软件开发工程师,北京·昌平区·回龙观,25-50K,三一重工,企业服务,10000人以上,已上市,经验不限,硕士,五险一金,餐补,节日福利,免费班车,定期体检,股票期权,专利奖金,带薪年假,年终奖,包吃,员工旅游,通讯补贴,提供食宿 20 | 移动APP高级测试开发,北京·朝阳区·石佛营,30-40K,VIPKID,移动互联网,10000人以上,D轮及以上,3-5年,本科,带薪年假,定期体检,住房补贴,六险一金,五险一金,节日福利,通讯补贴,餐补,补充医疗保险,交通补助,免费班车 21 | Java开发工程师,上海·浦东新区·张江,20-40K·13薪,叮咚买菜,互联网,10000人以上,C轮,经验不限,本科,扁平化管理,交通补助,餐补,零食下午茶,节日福利,五险一金 22 | 后端开发工程师-飞书,北京·海淀区·五道口,30-60K·15薪,北京字节跳动,移动互联网,10000人以上,D轮及以上,经验不限,本科,交通补助,住房补贴,节日福利,带薪年假,定期体检,五险一金,包吃,股票期权,零食下午茶,六险一金,餐补,年终奖,员工旅游,补充医疗保险,通讯补贴 23 | C++/Java 后端开发,上海·长宁区·北新泾,25-50K·15薪,美团点评,O2O,10000人以上,不需要融资,1-3年,本科,定期体检,住房补贴,五险一金,包吃,节假日休,五险一金,通讯补贴,员工旅游,补充医疗保险,全勤奖,餐补,股票期权,交通补助,季度奖金,加班补助,双休,年终奖,全薪病假,带薪年假,节日福利,团建聚餐 24 | 资深项目管理 (MJ006014),北京·海淀区·上地,15-25K·14薪,作业帮,互联网,10000人以上,D轮及以上,3-5年,本科,五险一金,定期体检,包吃,交通补助,带薪年假,补充医疗保险,节日福利,零食下午茶,年终奖 25 | 风控策略岗(数据模型),北京·海淀区·清河,50-70K·14薪,小米,互联网,10000人以上,已上市,3-5年,学历不限,年终奖,节日福利,全勤奖,12%公积金,带薪年假,餐补,定期体检,五险一金,加班补助,补充医疗保险 26 | 前端开发工程师,北京·海淀区·上地,20-40K·20薪,华为,计算机软件,10000人以上,不需要融资,经验不限,本科,年终奖,五险一金,节日福利,零食下午茶,股票期权,宵夜,加班补助,通讯补贴,补充医疗保险,住房补贴,交通补助,免费班车,定期体检,带薪年假,员工旅游 27 | BI数据分析,上海·普陀区·长征,28-35K·13薪,饿了么,O2O,10000人以上,不需要融资,3-5年,本科,餐补,节日福利,股票期权,五险一金,年终奖,免费班车,公司团建,带薪年假,通讯补贴,定期体检,零食下午茶,员工旅游,补充医疗保险,包吃,交通补助 28 | 高级测试工程师,上海·长宁区·北新泾,20-30K·16薪,携程集团,互联网,10000人以上,已上市,3-5年,本科,年终奖,五险一金 29 | .Net开发工程师,上海·浦东新区·外高桥,15-25K·14薪,药明康德,医疗健康,10000人以上,已上市,3-5年,本科,五险一金,餐补,股票期权,带薪年假,节日福利,加班补助,定期体检,员工旅游,年终奖,交通补助,免费班车 30 | 3D机器视觉算法开发工程师,上海·松江区·小昆山,28-35K·20薪,美的,其他行业,10000人以上,已上市,3-5年,本科,年终奖,股票期权,免费班车,住房补贴,零食下午茶,补充医疗保险,加班补助,五险一金,交通补助,餐补,员工旅游,通讯补贴,带薪年假,节日福利,包吃,全勤奖,重大消息,定期体检 31 | 百度地图高级研发工程师,北京·海淀区·上地,25-35K·15薪,百度在线,互联网,10000人以上,已上市,3-5年,本科,补充医疗保险,股票期权,带薪年假,通讯补贴,五险一金,年终奖,定期体检 32 | 百度地图高级研发工程师,北京·海淀区·上地,25-35K·15薪,百度在线,互联网,10000人以上,已上市,3-5年,本科,补充医疗保险,股票期权,带薪年假,通讯补贴,五险一金,年终奖,定期体检 33 | 顺丰同城科技基础架构高级工程师,北京·海淀区·学院路,20-40K·15薪,顺丰快递,生活服务,10000人以上,已上市,1-3年,本科,五险一金,加班补助,员工旅游,餐补,通讯补贴,节日福利,补充医疗保险,住房补贴 34 | support engineer—LInux&network,上海·闵行区·吴泾,20-35K,微软中国,计算机软件,10000人以上,不需要融资,经验不限,本科,零食下午茶,带薪年假,免费班车,股票期权,餐补,年终奖,五险一金,节日福利,定期体检,补充医疗保险,员工旅游 35 | 高级技术专家 (财账方向),北京·朝阳区·望京,30-45K,ALIYUN,互联网,10000人以上,不需要融资,5-10年,本科,None 36 | 系统维护工程师(.Net&SQL),上海·浦东新区·外高桥,13-21K·14薪,药明康德,医疗健康,10000人以上,已上市,3-5年,本科,五险一金,免费班车,交通补助,加班补助,餐补,员工旅游,年终奖,带薪年假,节日福利,股票期权,定期体检 37 | Java,上海·虹口区·海伦路,18-35K·14薪,掌门1对1,互联网,10000人以上,D轮及以上,3-5年,本科,零食下午茶,孝心奖,节日福利,五险一金,定期体检 38 | web前端,上海·浦东新区·张江,20-30K·15薪,叮咚买菜,互联网,10000人以上,C轮,5-10年,本科,扁平化管理,节日福利,五险一金,零食下午茶,交通补助,餐补 39 | 资深业务DBA(MySQL),北京·海淀区·苏州街,40-60K·16薪,腾讯,互联网,10000人以上,已上市,5-10年,本科,免费班车,补充医疗保险,定期体检,年终奖,节日福利,员工旅游,五险一金,带薪年假 40 | SRE工程师/专家 — 音视频,上海,25-50K,今日头条,移动互联网,1000-9999人,不需要融资,经验不限,本科,住房补贴,五险一金,零食下午茶,交通补助,补充医疗保险,年终奖,带薪年假,餐补,节日福利,定期体检,加班补助,试用期同薪 41 | java高级开发工程师,北京·大兴区·亦庄,20-40K·14薪,京东集团,电子商务,10000人以上,已上市,5-10年,本科,节日福利,带薪年假,零食下午茶,年终奖,补充医疗保险,五险一金,员工旅游,包吃,定期体检,免费班车,股票期权,交通补助,餐补 42 | 高级/资深测试工程师,北京·海淀区·上地,30-60K·16薪,快手,社交网络,10000人以上,D轮及以上,3-5年,本科,五险一金,定期体检,股票期权,加班补助,带薪年假,节日福利,包吃,零食下午茶,住房补贴,年终奖,补充医疗保险 43 | 语音算法工程师,上海·长宁区·北新泾,25-50K,携程旅行网,互联网,10000人以上,已上市,1年以内,硕士,带薪年假,年终奖,员工旅游,节日福利,定期体检,补充医疗保险,五险一金,绩效奖金,加班补助,股票期权,餐补 44 | 测试开发工程师,北京·朝阳区·团结湖,20-30K·16薪,蚂蚁金服,互联网,10000人以上,不需要融资,3-5年,本科,员工旅游,定期体检,餐补,补充医疗保险,股票期权,包吃,带薪年假,五险一金,节日福利,年终奖,免费班车 45 | 广告平台研发专家,北京·朝阳区·望京,30-60K,阿里巴巴集团,互联网,10000人以上,已上市,经验不限,本科,员工旅游,免费班车,股票期权,节日福利,带薪年假,年终奖,补充医疗保险,交通补助,五险一金,定期体检,餐补,加班补助 46 | 应用架构师,北京·大兴区·亦庄,40-60K·14薪,京东世纪贸易有限公司,互联网,10000人以上,已上市,10年以上,本科,补充医疗保险,定期体检,年终奖,五险一金,餐补,全勤奖 47 | 存储虚拟化高级研发工程师,上海·浦东新区·张江,20-40K·15薪,百度,互联网,10000人以上,已上市,经验不限,本科,包吃,免费班车,年终奖,定期体检,补充医疗保险,加班补助,股票期权,员工旅游,老板Nice,团队氛围好,零食下午茶,餐补,住房补贴,五险一金,通讯补贴,交通补助,带薪年假,节日福利 48 | 软件工程师,北京·海淀区·知春路,25-50K·15薪,北京字节跳动,移动互联网,10000人以上,D轮及以上,经验不限,本科,带薪年假,补充医疗保险,零食下午茶,餐补,股票期权,交通补助,住房补贴,包吃,年终奖,定期体检,员工旅游,六险一金,节日福利,五险一金,通讯补贴 49 | Java,北京·海淀区·西北旺,12-24K,华为,计算机软件,10000人以上,不需要融资,经验不限,本科,年终奖,补充医疗保险,节日福利,五险一金,宵夜,股票期权,加班补助,零食下午茶,带薪年假,住房补贴,定期体检,交通补助,免费班车,员工旅游,通讯补贴 50 | 高级/资深iOS开发工程师,北京·海淀区·西北旺,30-60K·16薪,网易,移动互联网,10000人以上,已上市,3-5年,本科,免费班车,年终奖,补充医疗保险,带薪年假,五险一金,包吃,全勤奖,定期体检,节日福利 51 | 大数据研发工程师(资深),北京·海淀区·清河,70-80K·14薪,小米,互联网,10000人以上,已上市,5-10年,本科,定期体检,带薪年假,五险一金,12%公积金,全勤奖,补充医疗保险,节日福利,年终奖,加班补助,餐补 52 | Java专家-上海,上海·浦东新区·联洋,20-40K·16薪,饿了么,O2O,10000人以上,不需要融资,5-10年,本科,股票期权,补充医疗保险,交通补助,年终奖,公司团建,包吃,定期体检,节日福利,免费班车,员工旅游,通讯补贴,零食下午茶,餐补,五险一金,带薪年假 53 | iOS客户端开发工程师,上海·闵行区·虹梅路,20-40K,腾讯,互联网,10000人以上,已上市,1-3年,本科,补充医疗保险,定期体检,员工旅游,带薪年假,年终奖,免费班车,五险一金,节日福利 54 | 软件开发工程师,北京·朝阳区·奥林匹克公园,30-60K,京东集团,电子商务,10000人以上,已上市,3-5年,本科,员工旅游,免费班车,股票期权,节日福利,五险一金,带薪年假,补充医疗保险,年终奖,餐补,零食下午茶,包吃,定期体检,交通补助 55 | 服务端开发工程师 — 音视频,上海·闵行区·虹梅路,25-50K,今日头条,移动互联网,1000-9999人,不需要融资,经验不限,本科,补充医疗保险,住房补贴,餐补,节日福利,五险一金,带薪年假,交通补助,试用期同薪,零食下午茶,定期体检,年终奖,加班补助 56 | Java开发工程师,上海·长宁区·北新泾,15-30K,携程旅行网,互联网,10000人以上,已上市,1-3年,硕士,定期体检,股票期权,五险一金,节日福利,餐补,年终奖,绩效奖金,补充医疗保险,带薪年假,员工旅游,加班补助 57 | Java,北京·朝阳区·团结湖,17-25K·13薪,蚂蚁金服,互联网,10000人以上,不需要融资,1-3年,本科,年终奖,五险一金,免费班车,餐补,定期体检,包吃,节日福利,股票期权,补充医疗保险,员工旅游,带薪年假 58 | 高级前端工程师,上海·浦东新区·陆家嘴,25-50K,阿里巴巴集团,互联网,10000人以上,已上市,3-5年,本科,股票期权,节日福利,五险一金,餐补,员工旅游,年终奖,加班补助,免费班车,补充医疗保险,定期体检,交通补助,带薪年假 59 | Java开发工程师,上海·浦东新区·张江,20-40K·13薪,叮咚买菜,互联网,10000人以上,C轮,经验不限,本科,扁平化管理,交通补助,餐补,零食下午茶,节日福利,五险一金 60 | Linux内核研发工程师,上海·浦东新区·张江,20-40K·15薪,百度,互联网,10000人以上,已上市,经验不限,本科,餐补,老板Nice,免费班车,加班补助,零食下午茶,团队氛围好,员工旅游,定期体检,节日福利,交通补助,补充医疗保险,包吃,住房补贴,股票期权,年终奖,通讯补贴,带薪年假,五险一金 61 | 视觉算法工程师(检测分割方向),北京·朝阳区·奥林匹克公园,30-45K,京东世纪贸易有限公司,互联网,10000人以上,已上市,3-5年,硕士,餐补,五险一金,全勤奖,定期体检,年终奖,补充医疗保险 62 | 安卓开发工程师-飞书,北京·海淀区·五道口,30-60K·15薪,北京字节跳动,移动互联网,10000人以上,D轮及以上,经验不限,本科,补充医疗保险,包吃,年终奖,通讯补贴,五险一金,餐补,带薪年假,零食下午茶,定期体检,员工旅游,住房补贴,六险一金,交通补助,节日福利,股票期权 63 | 测试工程师,北京·海淀区·上地,30-50K·16薪,快手,社交网络,10000人以上,D轮及以上,3-5年,本科,包吃,年终奖,补充医疗保险,节日福利,住房补贴,零食下午茶,定期体检,加班补助,五险一金,股票期权,带薪年假 64 | 业务分析专家,上海·普陀区·长征,25-40K·14薪,饿了么,O2O,10000人以上,不需要融资,5-10年,本科,五险一金,免费班车,通讯补贴,节日福利,餐补,带薪年假,定期体检,年终奖,员工旅游,包吃,补充医疗保险,零食下午茶,股票期权,公司团建,交通补助 65 | 安全开发工程师,北京·海淀区·西二旗,20-40K·14薪,小米,互联网,10000人以上,已上市,3-5年,本科,五险一金,定期体检,加班补助,节日福利,补充医疗保险,12%公积金,带薪年假,餐补,全勤奖,年终奖 66 | iOS高级开发工程师,北京·大兴区·亦庄,30-50K,京东集团,电子商务,10000人以上,已上市,3-5年,本科,带薪年假,年终奖,节日福利,五险一金,股票期权,零食下午茶,包吃,餐补,补充医疗保险,免费班车,定期体检,交通补助,员工旅游 67 | 证券产品后台测试开发专家(北京海淀),北京·海淀区·中关村,20-40K·16薪,腾讯,互联网,10000人以上,已上市,3-5年,本科,五险一金,年终奖,补充医疗保险,免费班车,定期体检,节日福利,员工旅游,带薪年假 68 | PaaS平台开发,上海,25-50K,今日头条,移动互联网,1000-9999人,不需要融资,经验不限,本科,带薪年假,节日福利,五险一金,零食下午茶,餐补,补充医疗保险,试用期同薪,住房补贴,加班补助,年终奖,定期体检,交通补助 69 | 测试开发专家/开发专家,北京·朝阳区·团结湖,15-30K·13薪,蚂蚁金服,互联网,10000人以上,不需要融资,3-5年,本科,股票期权,补充医疗保险,节日福利,年终奖,员工旅游,定期体检,五险一金,餐补,免费班车,包吃,带薪年假 70 | 基础平台研发工程师,北京·海淀区·西北旺,15-30K·15薪,百度,互联网,10000人以上,已上市,经验不限,本科,团队氛围好,五险一金,股票期权,包吃,餐补,零食下午茶,通讯补贴,交通补助,带薪年假,节日福利,定期体检,年终奖,补充医疗保险,老板Nice,住房补贴,加班补助,员工旅游,免费班车 71 | iOS技术专家,北京·朝阳区·望京,30-50K,阿里巴巴集团,互联网,10000人以上,已上市,3-5年,本科,节日福利,餐补,加班补助,补充医疗保险,交通补助,带薪年假,五险一金,员工旅游,股票期权,年终奖,免费班车,定期体检 72 | 高级前端研发工程师【急招】,北京·海淀区·中关村,30-60K·15薪,北京字节跳动,移动互联网,10000人以上,D轮及以上,3-5年,本科,补充医疗保险,带薪年假,五险一金,年终奖,定期体检,员工旅游,节日福利,住房补贴,零食下午茶,交通补助,通讯补贴,包吃,股票期权,六险一金,餐补 73 | 视频特效算法工程师,北京·朝阳区·小营,18-30K,京东世纪贸易有限公司,互联网,10000人以上,已上市,3-5年,本科,定期体检,补充医疗保险,五险一金,年终奖,全勤奖,餐补 74 | 后端开发JAVA(供应链),上海·浦东新区·张江,25-50K·15薪,叮咚买菜,互联网,10000人以上,C轮,3-5年,本科,零食下午茶,扁平化管理,交通补助,餐补,节日福利,五险一金 75 | Android开发工程师,北京·海淀区·上地,25-50K·16薪,快手,社交网络,10000人以上,D轮及以上,3-5年,本科,年终奖,五险一金,包吃,零食下午茶,住房补贴,节日福利,加班补助,补充医疗保险,定期体检,股票期权,带薪年假 76 | 混合云专线运维工程师,北京·朝阳区·鸟巢,20-40K·14薪,京东集团,电子商务,10000人以上,已上市,3-5年,本科,餐补,员工旅游,定期体检,零食下午茶,补充医疗保险,年终奖,交通补助,节日福利,免费班车,带薪年假,五险一金,股票期权,包吃 77 | 图像视频特效SDK开发工程师,上海·闵行区·虹梅路,20-40K·14薪,腾讯,互联网,10000人以上,已上市,1-3年,本科,年终奖,五险一金,节日福利,带薪年假,定期体检,员工旅游,补充医疗保险,免费班车 78 | 资深服务端开发工程师,北京·海淀区·知春路,40-70K·15薪,今日头条,移动互联网,1000-9999人,不需要融资,3-5年,本科,节日福利,交通补助,试用期同薪,住房补贴,补充医疗保险,餐补,零食下午茶,五险一金,带薪年假,年终奖,加班补助,定期体检 79 | 蚂蚁金服-java研发高级工程师/专家,上海·浦东新区·陆家嘴,25-50K,蚂蚁金服,互联网,10000人以上,不需要融资,3-5年,本科,餐补,补充医疗保险,定期体检,五险一金,员工旅游,节日福利,包吃,免费班车,年终奖,带薪年假,股票期权 80 | 车联网事业部_安卓研发工程师,北京·海淀区·西北旺,20-40K,百度,互联网,10000人以上,已上市,3-5年,本科,年终奖,补充医疗保险,交通补助,员工旅游,定期体检,住房补贴,节日福利,通讯补贴,免费班车,带薪年假,包吃,团队氛围好,股票期权,零食下午茶,老板Nice,餐补,加班补助,五险一金 81 | 阿里巴巴淘系技术 技术专家,北京·朝阳区·望京,30-60K·16薪,阿里巴巴集团,互联网,10000人以上,已上市,3-5年,本科,补充医疗保险,餐补,年终奖,免费班车,股票期权,定期体检,交通补助,五险一金,带薪年假,节日福利,加班补助,员工旅游 82 | 前端开发工程师-【用户增长】,北京·海淀区·上地,20-40K·16薪,快手,社交网络,10000人以上,D轮及以上,经验不限,本科,定期体检,住房补贴,加班补助,节日福利,带薪年假,年终奖,零食下午茶,五险一金,包吃,补充医疗保险,股票期权 83 | 安卓开发工程师,上海·浦东新区·张江,20-40K·14薪,叮咚买菜,互联网,10000人以上,C轮,5-10年,本科,五险一金,扁平化管理,节日福利,餐补,交通补助,零食下午茶 84 | Android高级开发工程师,北京·大兴区·亦庄,25-45K,京东集团,电子商务,10000人以上,已上市,5-10年,本科,股票期权,零食下午茶,免费班车,补充医疗保险,交通补助,五险一金,带薪年假,定期体检,节日福利,年终奖,员工旅游,包吃,餐补 85 | 云网络研发高级工程师,北京·海淀区·西北旺,18-35K,腾讯,互联网,10000人以上,已上市,经验不限,本科,定期体检,免费班车,五险一金,员工旅游,年终奖,带薪年假,补充医疗保险,节日福利 86 | TTS模型运营专家,北京·海淀区·五道口,15-25K,今日头条,移动互联网,1000-9999人,不需要融资,1-3年,本科,试用期同薪,补充医疗保险,零食下午茶,五险一金,餐补,交通补助,带薪年假,年终奖,节日福利,加班补助,定期体检,住房补贴 87 | DPDK 研发工程师,北京·海淀区·上地,20-40K·15薪,百度,互联网,10000人以上,已上市,经验不限,本科,住房补贴,包吃,交通补助,补充医疗保险,加班补助,员工旅游,餐补,带薪年假,老板Nice,免费班车,五险一金,股票期权,年终奖,通讯补贴,团队氛围好,定期体检,零食下午茶,节日福利 88 | java高级开发工程师/技术专家,上海·浦东新区·陆家嘴,30-60K·16薪,蚂蚁金服,互联网,10000人以上,不需要融资,3-5年,本科,股票期权,包吃,年终奖,免费班车,补充医疗保险,带薪年假,节日福利,员工旅游,定期体检,餐补,五险一金 89 | 高级Java开发工程师(电商),北京·海淀区·上地,20-40K·16薪,快手,社交网络,10000人以上,D轮及以上,3-5年,本科,加班补助,包吃,带薪年假,年终奖,住房补贴,五险一金,定期体检,补充医疗保险,零食下午茶,股票期权,节日福利 90 | 阿里巴巴Java研发-北京,北京·朝阳区·望京,25-50K,阿里巴巴集团,互联网,10000人以上,已上市,3-5年,本科,节日福利,加班补助,股票期权,餐补,员工旅游,免费班车,定期体检,带薪年假,补充医疗保险,交通补助,年终奖,五险一金 91 | HTML5高级级开发工程师,北京·大兴区·亦庄,25-50K,京东集团,电子商务,10000人以上,已上市,5-10年,本科,交通补助,补充医疗保险,带薪年假,股票期权,免费班车,定期体检,年终奖,零食下午茶,餐补,包吃,五险一金,员工旅游,节日福利 92 | AI反爬虫-高级工程师(云WAF),北京·海淀区·中关村,27-50K·16薪,腾讯,互联网,10000人以上,已上市,3-5年,本科,五险一金,免费班车,员工旅游,补充医疗保险,带薪年假,年终奖,定期体检,节日福利 93 | 测试开发工程师,北京·海淀区·中关村,30-60K·15薪,今日头条,移动互联网,1000-9999人,不需要融资,3-5年,本科,带薪年假,五险一金,餐补,年终奖,试用期同薪,住房补贴,定期体检,交通补助,节日福利,加班补助,零食下午茶,补充医疗保险 94 | 后端开发工程师,北京·海淀区·西北旺,20-40K·16薪,百度,互联网,10000人以上,已上市,3-5年,本科,老板Nice,包吃,年终奖,住房补贴,节日福利,零食下午茶,带薪年假,补充医疗保险,餐补,股票期权,定期体检,员工旅游,加班补助,五险一金,交通补助,通讯补贴,免费班车,团队氛围好 95 | 资深数据研发工程师/技术专家,上海·浦东新区·陆家嘴,25-45K,蚂蚁金服,互联网,10000人以上,不需要融资,5-10年,本科,节日福利,带薪年假,股票期权,包吃,免费班车,补充医疗保险,五险一金,年终奖,餐补,定期体检,员工旅游 96 | 高级运维专家-电商SRE,北京·海淀区·西二旗,40-60K·16薪,快手,社交网络,10000人以上,D轮及以上,5-10年,本科,股票期权,补充医疗保险,加班补助,五险一金,年终奖,节日福利,定期体检,带薪年假,零食下午茶,包吃,住房补贴 97 | C/C++服务端研发工程师,上海·宝山区·通河,20-40K·14薪,京东集团,电子商务,10000人以上,已上市,经验不限,本科,零食下午茶,股票期权,带薪年假,五险一金,定期体检,年终奖,免费班车,交通补助,节日福利,包吃,餐补,员工旅游,补充医疗保险 98 | 大数据开发工程师,北京·海淀区·中关村,25-50K·16薪,今日头条,移动互联网,1000-9999人,不需要融资,5-10年,本科,加班补助,节日福利,定期体检,年终奖,五险一金,零食下午茶,带薪年假,住房补贴,试用期同薪,补充医疗保险,餐补,交通补助 99 | 智慧零售研发中心高级后台开发工程师,上海·徐汇区·漕河泾,25-50K,腾讯,互联网,10000人以上,已上市,3-5年,本科,补充医疗保险,五险一金,年终奖,员工旅游,节日福利,免费班车,定期体检,带薪年假 100 | 高级测试开发工程师,北京·海淀区·西北旺,15-30K,百度,互联网,10000人以上,已上市,1-3年,硕士,住房补贴,带薪年假,包吃,节日福利,年终奖,定期体检,通讯补贴,免费班车,零食下午茶,交通补助,五险一金,团队氛围好,餐补,加班补助,股票期权,补充医疗保险,员工旅游,老板Nice 101 | Java开发工程师-用户增长,北京·海淀区·上地,20-40K·16薪,快手,社交网络,10000人以上,D轮及以上,经验不限,本科,五险一金,包吃,带薪年假,补充医疗保险,加班补助,住房补贴,节日福利,定期体检,年终奖,股票期权,零食下午茶 102 | 软件开发工程师,北京·大兴区·亦庄,25-40K·14薪,京东集团,电子商务,10000人以上,已上市,5-10年,本科,五险一金,定期体检,年终奖,节日福利,员工旅游,零食下午茶,补充医疗保险,餐补,包吃,免费班车,带薪年假,交通补助,股票期权 103 | 后端开发工程师-飞书,北京·海淀区·学院路,30-60K·15薪,今日头条,移动互联网,1000-9999人,不需要融资,经验不限,本科,餐补,零食下午茶,节日福利,五险一金,年终奖,带薪年假,补充医疗保险,交通补助,住房补贴,加班补助,试用期同薪,定期体检 104 | 腾讯上海后台高级开发工程师,上海,25-50K,腾讯,互联网,10000人以上,已上市,5-10年,本科,员工旅游,五险一金,节日福利,年终奖,定期体检,补充医疗保险,带薪年假,免费班车 105 | iOS开发工程师,北京·海淀区·上地,25-50K·16薪,快手,社交网络,10000人以上,D轮及以上,3-5年,本科,带薪年假,定期体检,年终奖,股票期权,节日福利,加班补助,零食下午茶,补充医疗保险,五险一金,包吃,住房补贴 106 | 大数据开发工程师,北京·朝阳区·鸟巢,30-60K,京东集团,电子商务,10000人以上,已上市,3-5年,硕士,股票期权,零食下午茶,年终奖,定期体检,免费班车,包吃,餐补,五险一金,交通补助,节日福利,带薪年假,员工旅游,补充医疗保险 107 | 数据分析师(上海),上海·闵行区·虹梅路,15-30K·18薪,今日头条,移动互联网,1000-9999人,不需要融资,1-3年,本科,加班补助,年终奖,交通补助,节日福利,零食下午茶,带薪年假,五险一金,住房补贴,补充医疗保险,定期体检,试用期同薪,餐补 108 | C++,北京·海淀区·西北旺,20-40K·16薪,腾讯,互联网,10000人以上,已上市,经验不限,本科,五险一金,年终奖,带薪年假,补充医疗保险,节日福利,定期体检,员工旅游,免费班车 109 | Java,北京·大兴区·亦庄,20-40K·14薪,京东集团,电子商务,10000人以上,已上市,5-10年,本科,包吃,节日福利,补充医疗保险,股票期权,零食下午茶,定期体检,年终奖,带薪年假,交通补助,餐补,免费班车,五险一金,员工旅游 110 | 【急招】测试工程师-支付,北京·海淀区·双榆树,18-35K·15薪,今日头条,移动互联网,1000-9999人,不需要融资,经验不限,本科,带薪年假,节日福利,补充医疗保险,五险一金,定期体检,加班补助,住房补贴,餐补,交通补助,试用期同薪,零食下午茶,年终奖 111 | 高级Android开发工程师,北京·海淀区·双榆树,30-60K,腾讯,互联网,10000人以上,已上市,5-10年,本科,免费班车,年终奖,员工旅游,五险一金,带薪年假,定期体检,补充医疗保险,节日福利 112 | k8s高级研发工程师/技术专家,北京·朝阳区·鸟巢,30-60K·14薪,京东集团,电子商务,10000人以上,已上市,3-5年,本科,节日福利,员工旅游,带薪年假,补充医疗保险,餐补,零食下午茶,交通补助,五险一金,包吃,定期体检,免费班车,年终奖,股票期权 113 | Android 开发工程师-飞书,北京·海淀区·学院路,30-60K·15薪,今日头条,移动互联网,1000-9999人,不需要融资,经验不限,本科,交通补助,补充医疗保险,五险一金,定期体检,试用期同薪,餐补,节日福利,住房补贴,年终奖,加班补助,零食下午茶,带薪年假 114 | 高级后台工程师,北京·海淀区·双榆树,30-60K,腾讯,互联网,10000人以上,已上市,5-10年,本科,年终奖,带薪年假,员工旅游,五险一金,补充医疗保险,定期体检,免费班车,节日福利 115 | 测试工程师,北京·朝阳区·奥林匹克公园,21-40K,京东集团,电子商务,10000人以上,已上市,3-5年,本科,定期体检,年终奖,交通补助,带薪年假,零食下午茶,包吃,股票期权,节日福利,五险一金,补充医疗保险,免费班车,员工旅游,餐补 116 | iOS开发工程师-飞书,北京·海淀区·学院路,30-60K·15薪,今日头条,移动互联网,1000-9999人,不需要融资,经验不限,本科,定期体检,住房补贴,交通补助,补充医疗保险,年终奖,加班补助,五险一金,节日福利,带薪年假,餐补,零食下午茶,试用期同薪 117 | 字节电商-高级前端工程师,北京·海淀区·中关村,25-50K·15薪,今日头条,移动互联网,1000-9999人,不需要融资,3-5年,本科,零食下午茶,餐补,交通补助,节日福利,定期体检,带薪年假,住房补贴,试用期同薪,五险一金,年终奖,加班补助,补充医疗保险 118 | 软件测试工程师,上海·宝山区·通河,15-25K·14薪,京东集团,电子商务,10000人以上,已上市,5-10年,本科,零食下午茶,免费班车,年终奖,股票期权,餐补,补充医疗保险,员工旅游,交通补助,五险一金,定期体检,带薪年假,包吃,节日福利 119 | 番茄小说后端开发工程师,北京·海淀区·皂君庙,30-60K·15薪,今日头条,移动互联网,1000-9999人,不需要融资,3-5年,本科,零食下午茶,定期体检,试用期同薪,带薪年假,五险一金,交通补助,年终奖,加班补助,餐补,住房补贴,节日福利,补充医疗保险 120 | Java高级开发工程师,北京·大兴区·亦庄,20-40K·14薪,京东集团,电子商务,10000人以上,已上市,5-10年,本科,员工旅游,股票期权,节日福利,定期体检,交通补助,包吃,零食下午茶,免费班车,补充医疗保险,五险一金,带薪年假,餐补,年终奖 121 | .net高级工程师,北京·海淀区·学院路,30-50K·15薪,今日头条,移动互联网,1000-9999人,不需要融资,3-5年,本科,带薪年假,零食下午茶,餐补,试用期同薪,年终奖,补充医疗保险,五险一金,定期体检,加班补助,节日福利,住房补贴,交通补助 122 | 测试开发工程师,北京,20-40K·14薪,京东集团,电子商务,10000人以上,已上市,3-5年,本科,免费班车,补充医疗保险,年终奖,股票期权,节日福利,包吃,交通补助,五险一金,定期体检,零食下午茶,员工旅游,带薪年假,餐补 123 | 前端开发工程师-飞书,北京·海淀区·学院路,30-60K·15薪,今日头条,移动互联网,1000-9999人,不需要融资,经验不限,本科,定期体检,住房补贴,加班补助,零食下午茶,餐补,节日福利,补充医疗保险,五险一金,交通补助,带薪年假,试用期同薪,年终奖 124 | 大数据开发工程师,北京·朝阳区·鸟巢,25-50K,京东集团,电子商务,10000人以上,已上市,3-5年,本科,定期体检,免费班车,年终奖,员工旅游,交通补助,节日福利,零食下午茶,餐补,补充医疗保险,五险一金,股票期权,带薪年假,包吃 125 | 高级安全研究员,上海·闵行区·虹梅路,25-50K·16薪,今日头条,移动互联网,1000-9999人,不需要融资,经验不限,本科,带薪年假,餐补,节日福利,补充医疗保险,加班补助,试用期同薪,定期体检,交通补助,零食下午茶,年终奖,住房补贴,五险一金 126 | 测试开发工程师,北京·大兴区·亦庄,20-40K,京东集团,电子商务,10000人以上,已上市,5-10年,本科,补充医疗保险,零食下午茶,年终奖,包吃,餐补,五险一金,股票期权,定期体检,带薪年假,员工旅游,免费班车,交通补助,节日福利 127 | 【急招】NLP算法工程师 — 国际化,北京·海淀区·双榆树,30-60K·15薪,今日头条,移动互联网,1000-9999人,不需要融资,3-5年,本科,试用期同薪,带薪年假,餐补,五险一金,零食下午茶,住房补贴,加班补助,交通补助,年终奖,定期体检,补充医疗保险,节日福利 128 | 算法工程师(机器学习/深度学习),北京·朝阳区·鸟巢,35-65K·14薪,京东集团,电子商务,10000人以上,已上市,3-5年,硕士,交通补助,股票期权,带薪年假,五险一金,年终奖,员工旅游,补充医疗保险,餐补,零食下午茶,免费班车,包吃,节日福利,定期体检 129 | 前端工程师-线上教育,北京·海淀区·中关村,25-50K·15薪,今日头条,移动互联网,1000-9999人,不需要融资,1-3年,本科,五险一金,定期体检,餐补,交通补助,零食下午茶,试用期同薪,补充医疗保险,住房补贴,节日福利,年终奖,加班补助,带薪年假 130 | 后台研发工程师 - 抖音,北京·海淀区·中关村,25-45K,今日头条,移动互联网,1000-9999人,不需要融资,3-5年,本科,补充医疗保险,年终奖,零食下午茶,定期体检,加班补助,带薪年假,住房补贴,五险一金,试用期同薪,餐补,节日福利,交通补助 131 | 高级前端开发工程师,北京·海淀区·中关村,30-60K·15薪,今日头条,移动互联网,1000-9999人,不需要融资,经验不限,本科,五险一金,年终奖,节日福利,住房补贴,带薪年假,餐补,交通补助,定期体检,试用期同薪,加班补助,零食下午茶,补充医疗保险 --------------------------------------------------------------------------------