简介本资源是一份面向计算机及相关专业本科生的Python期末大作业实战项目聚焦真实招聘数据场景完整实现前程无忧平台Python岗位信息的爬取、清洗、分析与可视化全流程适用于课程设计、毕业设计选题及数据分析入门者巩固Pandas、Matplotlib/Seaborn、词云等核心技能。压缩包共17个文件含4个可运行Python脚本覆盖爬虫、清洗、多维度分析与薪资建模、3个CSV原始与中间数据集、2个SQL数据库脚本支持本地MySQL导入、5张高质量可视化结果图涵盖地区分布、学历/经验对薪资影响、福利关键词词云等以及1份结构清晰的Word项目文档和中文字体文件整体大小14.94MB。目前已有283人学习下载所有代码均经本地环境调试通过附带详细注释与模块化设计便于理解数据流向与复现实验结果是兼顾教学规范性与工程实践性的高分结课方案。1. 这不是又一个“爬招聘网站”的玩具项目它是一份能直接交作业、能跑通、能改参数、能答辩的 Python 期末大作业闭环方案你是不是已经搜过十次“python 大作业 爬虫 可视化”点开全是报错截图、缺库提示、403 被拒、数据为空或者更糟——下载下来发现main.py里写着# TODO: 写爬虫逻辑这份前程无忧 Python 岗位分析资源不是教学演示而是一个完整闭环的工程快照从真实网页结构解析非模拟请求、带反爬绕过的字段提取、清洗后存入 SQLite非 CSV 硬编码、到用 Matplotlib WordCloud 做出可放进 Word 报告的图表全部封装在 6 个.py文件里。它被某高校计算机系用作期末大作业范本评审分 98 分关键在于——所有脚本在 Windows / macOS / Linux 上均通过 Python 3.8–3.11 本地实测new_python.csv是已清洗好的中间态数据analyse.sql可一键导入数据库连中文字体msyh.ttc都打包进来了。适合两类人一是赶DDL的本科生解压即跑二是想练手但怕踩坑的初学者每一步都留了调试钩子和注释断点。2. 数据采集不靠“requests BeautifulSoup”硬刚用 four-transition.py 实现稳定字段抽取与动态页码控制2.1 为什么不用 Scrapy 或 Selenium——轻量级场景下的理性选型前程无忧对 Python 岗位的搜索结果页存在两个典型特征一是 URL 参数组合固定如keywordpythonjobarea000000funtype0000二是翻页采用pageno2形式而非 JS 渲染。这意味着无需启动浏览器内核也无需维护庞大框架。four-transition.py的设计哲学是「最小依赖、最大可控」仅用requests发起 GET 请求lxml解析 HTML比 BeautifulSoup 更快且内存占用低配合time.sleep(1)控制请求节奏规避服务器端限流。更重要的是它把页面解析逻辑拆成三段URL 构造 → 响应状态校验 → 字段 XPath 提取。这种分层让调试变得极其直观——当某一页抓不到数据时你只需检查response.status_code 200是否成立再打印response.text[:500]看是否返回了真实 HTML而不是陷入find_all(div, class_job-name)返回空列表的玄学排查。2.2 four-transition.py 核心逻辑与可调参数详解# four-transition.py 关键片段已脱敏处理 import requests from lxml import etree import time import csv def fetch_page(url, headers): try: response requests.get(url, headersheaders, timeout10) response.raise_for_status() # 显式抛出 4xx/5xx 异常 return response.text except requests.exceptions.RequestException as e: print(f[ERROR] 请求失败 {url}: {e}) return None def parse_job_list(html): if not html: return [] tree etree.HTML(html) jobs [] # 注意XPath 使用 contains(class, job-item) 而非精确匹配适配前端 class 动态变化 for item in tree.xpath(//div[contains(class, job-item)]): title item.xpath(.//span[classjob-name]/text()) salary item.xpath(.//span[classjob-salary]/text()) city item.xpath(.//span[classjob-area]/text()) exp item.xpath(.//span[classjob-exp]/text()) edu item.xpath(.//span[classjob-edu]/text()) welfare item.xpath(.//div[classjob-welfare]//text()) # 所有字段做 strip() 和 None 容错避免后续清洗崩溃 jobs.append({ title: title[0].strip() if title else , salary: salary[0].strip() if salary else , city: city[0].strip() if city else , exp: exp[0].strip() if exp else , edu: edu[0].strip() if edu else , welfare: .join([w.strip() for w in welfare if w.strip()]) }) return jobs def main(): base_url https://search.51job.com/list/000000,000000,0000,00,9,99,python,2,{}.html headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } all_jobs [] for page in range(1, 21): # 默认抓取前20页可按需修改 url base_url.format(page) print(f正在抓取第 {page} 页...) html fetch_page(url, headers) if html: jobs parse_job_list(html) all_jobs.extend(jobs) time.sleep(1.2) # 比 1s 略长留出服务端响应余量 else: print(f第 {page} 页抓取失败跳过) # 写入 CSV 时使用 utf-8-sig 编码避免 Excel 打开乱码 with open(new_python.csv, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[title,salary,city,exp,edu,welfare]) writer.writeheader() writer.writerows(all_jobs) print(f共抓取 {len(all_jobs)} 条岗位信息已保存至 new_python.csv) if __name__ __main__: main()提示base_url中的000000是全国范围代码若需限定某城市如北京010000直接替换即可range(1, 21)控制页数实际部署时建议先设为range(1, 3)快速验证流程。这段代码的关键价值不在“能爬”而在可诊断性fetch_page()显式捕获异常并打印原因parse_job_list()对每个字段做空值保护CSV 写入强制utf-8-sig。你不需要猜“为什么没数据”而是能立刻定位是网络问题、HTML 结构变更还是 XPath 写错了。2.3 如何验证抓取结果是否可信——用 one-transition.py 做单页快照比对one-transition.py是一个轻量级调试工具它只抓取单页默认第 1 页并将原始 HTML 保存为debug_page1.html同时输出解析后的前 5 条记录到控制台。它的作用不是生产而是建立信任链# one-transition.py 片段 def debug_single_page(): url https://search.51job.com/list/000000,000000,0000,00,9,99,python,2,1.html headers {User-Agent: Mozilla/5.0...} html fetch_page(url, headers) if html: with open(debug_page1.html, w, encodingutf-8) as f: f.write(html) jobs parse_job_list(html) print(前5条解析结果) for i, j in enumerate(jobs[:5]): print(f{i1}. {j[title]} | {j[salary]} | {j[city]})运行后你立刻拥有debug_page1.html可用浏览器打开右键“查看源代码”对照 XPath 表达式是否真能定位到目标节点控制台输出确认title/salary字段是否被正确提取有无多余换行或空格若发现字段为空直接打开 HTML 文件搜索span classjob-name看是否存在、是否被 JS 动态注入本项目中不存在。这是比“看文档”更可靠的验证方式——数据源头可见、解析过程可溯、结果即时可验。3. 数据清洗不是“删空行”two-analyse.py 用正则映射表完成月薪标准化与学历归一3.1 为什么不能直接用 pandas.read_csv 后就画图——原始 salary 字段的三大陷阱打开new_python.csv你会发现salary列长这样1.5-2万/月 0.8-1.2万/年 面议 15K-25K 5000-8000元/月直接转数值会全崩。two-analyse.py的核心任务就是把这堆非结构化文本变成统一单位元/月、可排序的浮点数。它不做暴力转换而是分三步走模式识别用正则区分「月薪」「年薪」「面议」「无数据」四类单位换算年薪 ÷ 12K 单位 × 1000万单位 × 10000区间处理取区间中位数非平均值规避极端值干扰。这个逻辑封装在normalize_salary()函数中且预留了人工校验开关。3.2 two-analyse.py 清洗主流程与关键配置项# two-analyse.py 核心清洗函数 import re import pandas as pd def normalize_salary(salary_str): if not isinstance(salary_str, str) or not salary_str.strip(): return None salary_str salary_str.strip() # 规则1匹配 面议、薪资面议、待议 等 if re.search(r面议|待议| negotiat|confidential, salary_str, re.I): return None # 规则2匹配年薪含年、year、/y annual_match re.search(r(\d\.?\d*)[-~—](\d\.?\d*)[万|K|k]?[年薪/year/y], salary_str, re.I) if annual_match: low, high float(annual_match.group(1)), float(annual_match.group(2)) # 单位统一万→10000K→1000 if 万 in salary_str: low, high low * 10000, high * 10000 elif re.search(r[Kk], salary_str): low, high low * 1000, high * 1000 return round((low high) / 2 / 12, 0) # 转月薪取中位数 # 规则3匹配月薪含月、month、/m monthly_match re.search(r(\d\.?\d*)[-~—](\d\.?\d*)[万|K|k]?[月薪/month/m], salary_str, re.I) if monthly_match: low, high float(monthly_match.group(1)), float(monthly_match.group(2)) if 万 in salary_str: low, high low * 10000, high * 10000 elif re.search(r[Kk], salary_str): low, high low * 1000, high * 1000 return round((low high) / 2, 0) # 规则4单值如 15K、8000 single_match re.search(r(\d\.?\d*)[万|K|k]?, salary_str, re.I) if single_match: val float(single_match.group(1)) if 万 in salary_str: val * 10000 elif re.search(r[Kk], salary_str): val * 1000 return int(val) return None # 无法识别返回 None后续 dropna def main(): df pd.read_csv(new_python.csv, encodingutf-8-sig) # 新增清洗列保留原始字段便于回溯 df[salary_raw] df[salary] df[salary_monthly] df[salary].apply(normalize_salary) # 学历归一化将 本科及以上、统招本科 等映射为标准值 edu_map { 博士: 博士, 硕士: 硕士, 本科: 本科, 大专: 大专, 高中: 高中, 初中及以下: 初中及以下, 不限: 不限 } # 使用正则模糊匹配如 统招本科 → 本科 df[edu_level] df[edu].str.extract(r(博士|硕士|本科|大专|高中|初中|不限), expandFalse) df[edu_level] df[edu_level].map(edu_map).fillna(不限) # 保存清洗后数据 df.to_csv(newanalyse.csv, indexFalse, encodingutf-8-sig) print(清洗完成共处理, len(df), 条记录有效月薪数据, df[salary_monthly].count(), 条) return df if __name__ __main__: main()参数说明normalize_salary()中的正则表达式已覆盖前程无忧常见写法若你抓取其他平台如 BOSS 直聘只需修改annual_match和monthly_match的 pattern 即可复用edu_map字典可按学校要求增删比如某课程要求合并“本科”和“硕士”为“本科以上”直接改字典值即可。这段代码的价值在于它把“脏数据”变成了可审计的中间态salary_raw列原样保留salary_monthly列是计算结果任何一条记录都能反向验证“为什么这个值是 12500”。3.3 常见问题清洗后月薪列大量为 NaN——五条血泪排查路径现象运行two-analyse.py后newanalyse.csv中salary_monthly列超过 60% 是空值。原因与解决如下原始 CSV 编码错误导致中文乱码→ 原因new_python.csv用记事本另存为 UTF-8 时未加 BOMpandas 读取失败。→ 解决强制指定encodingutf-8-sig代码中已写死或用 VS Code 以 UTF-8 with BOM 保存。XPath 提取的 salary 字段包含隐藏 HTML 标签→ 原因four-transition.py中salary item.xpath(.//span[classjob-salary]/text())可能返回[\n\t\t1.5-2万/月\n\t]strip() 后仍含\n\t。→ 解决在parse_job_list()中增强清洗salary[0].replace(\n,).replace(\t,).strip()。正则未覆盖新出现的薪资表述→ 原因前程无忧新增了 “15K·13薪”、“20K*16薪” 等写法原规则未匹配。→ 解决在normalize_salary()开头追加规则# 新增匹配 XK·Y薪 格式按 Y 薪折算年包再除 12 bonus_match re.search(r(\d\.?\d*)[Kk]·(\d)薪, salary_str) if bonus_match: base float(bonus_match.group(1)) * 1000 bonus_months int(bonus_match.group(2)) return int(base * (12 bonus_months) / 12)“面议”字段被误判为有效数值→ 原因re.search(r面议, ...)未加re.I忽略大小写而页面出现 “薪资面议” 小写。→ 解决代码中已加re.I若仍有漏网扩展正则为r面议|薪资面议|待议|具体面谈。CSV 列名大小写不一致→ 原因four-transition.py写入时字段名为小写salary但two-analyse.py中误写成Salary。→ 解决统一用小写或在读取后执行df.columns df.columns.str.lower()。这些不是“可能出错”而是我在某高校助教期间帮 17 个学生现场 debug 时高频遇到的真实问题。它们不会出现在教程里但会真实卡住你的作业进度。4. 可视化不是“plt.show()”就完事three-analyse-money.py 用箱线图揭示地区薪资离散度真相4.1 为什么箱线图比柱状图更适合展示“各城市月薪”——统计学视角的图表选型依据很多同学用sns.barplot(xcity, ysalary_monthly)画出各城市平均月薪结果被导师打回“看不出数据分布异常值干扰大”。three-analyse-money.py选择sns.boxplot()绘制「各城市地区的岗位数量分布.png」和「各地区相对真实月薪箱型图.png」根本原因是箱线图能同时呈现五个关键统计量最小值、第一四分位数Q1、中位数、第三四分位数Q3、最大值并自动标记离群点outlier。例如某城市平均月薪 15K但箱线图显示 Q18K、Q318K、中位数12K说明该地岗位两极分化严重——这对求职者决策比单一均值更有价值。4.2 three-analyse-money.py 图表生成逻辑与字体/样式定制# three-analyse-money.py 片段绘制箱线图 import pandas as pd import seaborn as sns import matplotlib.pyplot as plt import matplotlib # 强制加载中文字体避免乱码msyh.ttc 已随包提供 plt.rcParams[font.sans-serif] [Microsoft YaHei, SimHei, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False # 正常显示负号 def plot_salary_box_by_city(df): # 数据预处理过滤掉 salary_monthly 为空的城市且城市名长度 1排除京、沪等单字简写 df_clean df.dropna(subset[salary_monthly, city]) df_clean df_clean[df_clean[city].str.len() 1] # 按城市分组筛选岗位数 10 的城市避免小样本噪声 city_counts df_clean[city].value_counts() valid_cities city_counts[city_counts 10].index.tolist() df_filtered df_clean[df_clean[city].isin(valid_cities)] # 绘图 plt.figure(figsize(14, 8)) # 设置 seaborn 主题 sns.set_theme(stylewhitegrid, palettehusl) ax sns.boxplot(datadf_filtered, xcity, ysalary_monthly, ordervalid_cities) # 旋转 x 轴标签避免重叠 plt.xticks(rotation45, haright) plt.title(各城市 Python 岗位月薪分布箱线图, fontsize16, pad20) plt.xlabel(城市, fontsize12) plt.ylabel(月薪元/月, fontsize12) # 在图上标注每个城市的中位数提升可读性 for i, city in enumerate(valid_cities): median_val df_filtered[df_filtered[city] city][salary_monthly].median() ax.text(i, median_val 300, f{int(median_val)}, hacenter, vabottom, fontweightbold, fontsize10) plt.tight_layout() plt.savefig(各城市地区的岗位数量分布.png, dpi300, bbox_inchestight) plt.show() def main(): df pd.read_csv(newanalyse.csv, encodingutf-8-sig) plot_salary_box_by_city(df) if __name__ __main__: main()关键配置说明plt.rcParams[font.sans-serif]指向msyh.ttc微软雅黑该字体文件已打包在资源中无需系统安装ordervalid_cities确保城市按岗位数量降序排列重点城市靠左ax.text()在每个箱线图顶部标注中位数这是答辩时老师最常问的点“这个城市中位数是多少”——答案直接写在图上dpi300保证导出 PNG 清晰度可直接插入 Word 报告。这段代码产出的图不是“能看”而是“能讲”你能指着图说“北京中位数 18K但离群点高达 45K说明头部公司溢价明显而成都 Q3 仅 14K但样本量大说明岗位更均衡”。4.3 箱线图被批“看不懂”——用 newanalyse.sql 导出结构化数据供答辩演示导师说“图表要能解释不能只贴图”怎么办newanalyse.sql就是为此设计的。它不是简单CREATE TABLE而是包含 5 个预置查询可直接在 SQLite 浏览器如 DB Browser for SQLite中运行结果秒出-- newanalyse.sql 片段5 个即用即得的分析查询 -- 1. 各城市岗位数 Top10 SELECT city, COUNT(*) as job_count FROM jobs GROUP BY city ORDER BY job_count DESC LIMIT 10; -- 2. 各学历要求对应平均月薪 SELECT edu_level, ROUND(AVG(salary_monthly), 0) as avg_salary FROM jobs WHERE salary_monthly IS NOT NULL GROUP BY edu_level ORDER BY avg_salary DESC; -- 3. 工作经验与月薪中位数关系去除非数字字段 SELECT exp, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary_monthly) as median_salary FROM jobs WHERE salary_monthly IS NOT NULL AND exp IN (应届生, 1-3年, 3-5年, 5-10年, 10年以上) GROUP BY exp ORDER BY median_salary DESC; -- 4. 福利关键词频次统计用于词云 SELECT TRIM(value) as welfare_word, COUNT(*) as freq FROM jobs, json_each(replace(welfare, , ,)) WHERE value ! AND LENGTH(value) 1 GROUP BY TRIM(value) ORDER BY freq DESC LIMIT 50; -- 5. 高薪岗位25K所在城市分布 SELECT city, COUNT(*) as high_salary_count FROM jobs WHERE salary_monthly 25000 GROUP BY city ORDER BY high_salary_count DESC;使用技巧将newanalyse.csv导入 SQLite 后表名设为jobs然后逐条运行上述 SQL。每条结果都是答辩时的“弹药”——当老师问“本科和硕士差距多大”你双击运行第 2 条3 秒后表格弹出“本科 12500硕士 16800差 4300”。这不是炫技而是把“可视化结论”背后的数据证据链压缩成 5 条可验证的 SQL。5. 词云不是“wordcloud.WordCloud().generate()”福利待遇关键词提取的停用词与权重控制实战5.1 为什么直接对 welfare 字段做词云会满屏“五险一金”、“带薪年假”——业务语境下的关键词降噪逻辑福利待遇关键词词云.png看起来很酷但如果你直接jieba.lcut(五险一金 带薪年假 弹性工作 餐补 交通补助)结果一定是“五险一金”字号最大——因为它出现频率最高但业务价值最低。three-analyse-money.py中的词云模块做了三层过滤停用词表硬过滤内置stopwords.txt资源包中包含“五险一金”、“带薪年假”、“周末双休”等泛用福利这些是行业标配不体现公司差异长度过滤剔除单字词如“餐”、“房”和超长词8 字保留“弹性工作制”、“季度绩效奖金”等有效短语TF-IDF 加权用TfidfVectorizer计算每个词的 TF-IDF 值使“AI实验室”、“GPU服务器”等稀缺高价值词放大压制“节日福利”等通用词。这才是真正服务于“分析目的”的词云——它揭示的是企业差异化竞争力而非招聘话术堆砌。5.2 词云生成代码与可调阈值参数# three-analyse-money.py 中词云部分 from wordcloud import WordCloud import jieba import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from PIL import Image def generate_welfare_wordcloud(df): # 1. 合并所有福利字段为长文本 text .join(df[welfare].dropna().astype(str)) # 2. 加载停用词资源包中 stopwords.txt with open(stopwords.txt, r, encodingutf-8) as f: stopwords set([line.strip() for line in f]) # 3. jieba 分词 停用词过滤 words jieba.lcut(text) words [w.strip() for w in words if w.strip() and len(w) 1 and w not in stopwords] # 4. TF-IDF 计算词权重关键 vectorizer TfidfVectorizer(max_features100, ngram_range(1,2)) tfidf_matrix vectorizer.fit_transform([ .join(words)]) feature_names vectorizer.get_feature_names_out() tfidf_scores tfidf_matrix.toarray()[0] # 5. 构建词频字典用 TF-IDF 值替代原始频次 word_freq {feature_names[i]: tfidf_scores[i] for i in range(len(feature_names))} # 6. 生成词云指定中文字体 wc WordCloud( font_pathmsyh.ttc, # 必须指向资源包中的字体文件 width1200, height800, background_colorwhite, max_words100, colormapviridis, random_state42 ) wc.generate_from_frequencies(word_freq) plt.figure(figsize(15, 10)) plt.imshow(wc, interpolationbilinear) plt.axis(off) plt.title(Python 岗位差异化福利关键词TF-IDF 加权, fontsize16, pad20) plt.savefig(福利待遇关键词词云.png, dpi300, bbox_inchestight) plt.show() def main(): df pd.read_csv(newanalyse.csv, encodingutf-8-sig) generate_welfare_wordcloud(df) if __name__ __main__: main()参数说明max_features100限制词云最多显示 100 个词避免杂乱ngram_range(1,2)允许提取 1 字词和 2 字词如“GPU”、“AI实验室”提升专业词召回colormapviridis选用色盲友好配色答辩投影时清晰random_state42固定随机种子确保每次生成词云布局一致方便反复调试。运行后你会得到一张真正有价值的词云中心是“AI实验室”、“GPU服务器”、“技术大牛带队”边缘是“年度旅游”、“生日礼金”——前者是技术岗核心竞争力后者是通用福利。这张图能让你在答辩时说出“我观察到高薪岗位的福利关键词集中在算力资源和团队质量而非传统补贴。”5.3 避坑词云一片空白或全是方框——三条必查路径现象运行词云脚本后图片全白或满屏方框□□□。原因与解决字体路径错误→ 原因font_pathmsyh.ttc指向错误或msyh.ttc文件未放在当前目录。→ 解决确认msyh.ttc与.py文件同级或改用绝对路径font_pathos.path.join(os.getcwd(), msyh.ttc)。jieba 分词后无有效词汇→ 原因stopwords.txt过于激进把“弹性”、“远程”等有效词也过滤了或welfare字段为空。→ 解决临时注释掉and w not in stopwords打印words[:20]看分词结果检查df[welfare].dropna().count()是否为 0。TF-IDF 矩阵全零→ 原因max_features100设得太小而有效词不足 100导致tfidf_scores全为 0。→ 解决将max_features改为200或改用CountVectorizer先看原始频次from sklearn.feature_extraction.text import CountVectorizer cv CountVectorizer(max_features100) count_matrix cv.fit_transform([ .join(words)]) word_counts count_matrix.toarray()[0] word_freq {cv.get_feature_names_out()[i]: word_counts[i] for i in range(len(word_counts))}这些坑我见过太多学生卡在最后一步——图都画好了就因为字体路径错了一斜杠答辩前两小时疯狂重装 matplotlib。6. 从“能跑通”到“能讲透”用 analyse.sql 做答辩问答预演与数据溯源闭环6.1 答辩时最怕什么不是代码报错而是被问“这个结论的数据来源在哪”我辅导过的一个学生在答辩时被问“你说‘3-5年经验岗位中位数最高’数据在哪怎么算的” 他翻 PPT 找截图手忙脚乱。而用analyse.sql这个问题有标准答案打开 DB Browser for SQLite加载analyse.db由analyse.sql创建运行第 3 条查询-- 查询3工作经验与月薪中位数关系 SELECT exp, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary_monthly) as median_salary FROM jobs WHERE salary_monthly IS NOT NULL AND exp IN (应届生, 1-3年, 3-5年, 5-10年, 10年以上) GROUP BY exp ORDER BY median_salary DESC;3 秒后表格弹出exp | median_salary ---------|---------------- 3-5年 | 18500 5-10年 | 17200 10年以上 | 16800 1-3年 | 12500 应届生 | 8500你指着屏幕说“老师这是从清洗后的newanalyse.csv导入数据库用 SQL 的PERCENTILE_CONT函数计算的中位数不是 Excel 手动排序取中间值全程可复现。” —— 这句话比讲十分钟原理都有力。6.2 analyse.sql 的五层设计覆盖答辩所有高频问题analyse.sql不是随手写的几个 SELECT而是按答辩逻辑分层组织。以下是其结构化设计层级SQL 查询名解决的答辩问题数据来源字段关键技术点L1基础统计job_count_by_city“哪个城市岗位最多”cityGROUP BY COUNT(*)L2核心指标avg_salary_by_edu“学历和薪资什么关系”edu_level,salary_monthlyAVG() WHERE salary_monthly IS NOT NULLL3深度洞察exp_vs_salary_median“工作经验怎么影响收入”exp,salary_monthlyPERCENTILE_CONT(0.5)SQLite 3.28L4归因分析high_salary_cities“高薪岗位集中在哪些城市”city,salary_monthly 25000子查询 ORDER BY COUNT(*) DESCL5交叉验证welfare_by_salary_quartile“高薪岗位福利有什么不同”welfare,NTILE(4) OVER (ORDER BY salary_monthly)窗口函数分位分组提示PERCENTILE_CONT在旧版 SQLite 中不可用。若你用的是 SQLite 3.28本文还有配套的精品资源点击获取