【测AI】第06篇:数据清洗实战 —— Pandas 处理爬取的 JD 数据

写在前面

前两篇我们把 AI 测试岗位的 JD 数据爬下来了。但爬到的数据只是"原材料"------格式不统一、有脏数据、有缺失值、没法直接做统计分析。

比如薪资字段,爬下来是 25-40K 这种字符串。你想知道"AI 测试的平均薪资是多少",得先把字符串解析成数字。你想知道"哪些技能最热门",得先把 ["Python", "机器学习"] 这种列表展开成一行一条记录。你想知道"北京和上海的薪资差异",得先按城市分组再算。

这些工作叫数据清洗和数据处理,用 Python 原生代码写会很痛苦。Pandas 是 Python 数据分析的王牌库,专门干这件事。熟练使用 Pandas 是 AI 测试工程师的核心能力之一------因为你经常需要分析测试数据、对比模型效果、生成质量报告。

本篇目标:

  1. 掌握 Pandas 核心概念(DataFrame、Series)
  2. 学会用 Pandas 读取和清洗爬取的岗位数据
  3. 完成完整的数据分析流水线
  4. 产出一份"AI 测试岗位技能需求分析报告"

本篇文件结构

本篇代码会用到以下文件,建议继续放在 ai-test-crawler/ 目录下:

文件名 用途 创建方式
pandas_demo.py Pandas 基础知识练习 二、逐步追加
data_analysis.py 岗位数据分析主程序 四、逐步追加
job_data.json 数据来源(第04篇生成的) 不需要手动创建
job_data.csv 数据来源(第04篇生成的) 不需要手动创建
analysis_report.txt 分析报告输出(自动生成) 不需要手动创建

前置条件 :本篇需要第 04 篇生成的 job_data.json 和 job_data.csv 文件。如果还没有,回到第 04 篇运行 python create_mock_site.py 和 python job_scraper.py 先生成数据。


一、为什么需要 Pandas?

1.1 用原生 Python 做数据分析有多痛苦

假设你要回答这个问题:"AI 测试岗位中,最常见的技能是什么?"

用原生 Python 写,你需要手动遍历列表、展开嵌套、统计频次、排序取 Top N......第 04 篇我们已经写过类似的代码了,几十行才搞定一个简单的统计。

再换一个问题:"北京的平均薪资和上海比,高多少?"

这下麻烦了------你要筛选城市、解析薪资字符串、按城市分组、计算平均值、对比差异。用原生 Python 写,至少上百行代码,而且容易出 bug。

而用 Pandas,这些问题都是三到五行代码的事。

1.2 Pandas 的两大核心数据结构

Pandas 提供了两种数据结构来存放和操作数据:

Series ------ 一列数据,类似 Excel 中的一列。

python 复制代码
import pandas as pd

# Series 就是一列数据 + 一行索引
salaries = pd.Series([25000, 35000, 30000, 20000, 45000])
print(salaries)
# 0    25000
# 1    35000
# 2    30000
# 3    20000
# 4    45000
# dtype: int64

# 一行代码算平均值
print(salaries.mean())    # 31000.0

DataFrame ------ 一张表格,类似 Excel 中的一个工作表。有行有列,每列是一个 Series。

python 复制代码
# DataFrame 就是一张表格
df = pd.DataFrame({
    "title": ["AI测试工程师", "大模型测试专家", "CV测试工程师"],
    "salary": [25000, 45000, 22000],
    "location": ["北京", "北京", "成都"]
})
print(df)
#           title  salary location
# 0   AI测试工程师   25000       北京
# 1  大模型测试专家   45000       北京
# 2   CV测试工程师   22000       成都

# 一行代码筛选北京的岗位
print(df[df["location"] == "北京"])

# 一行代码算平均薪资
print(df["salary"].mean())    # 30666.67

理解了这两个概念,你就理解了 Pandas 的 80%。后面所有的操作都是围绕 DataFrame 来做的:读数据进来是 DataFrame,清洗也是对 DataFrame 操作,分析也是从 DataFrame 里提取信息。

1.3 安装 Pandas

【运行】 在终端执行:

bash 复制代码
pip install pandas

返回上一级目录,用这个命令:

bash 复制代码
cd ..

可使用镜像 在终端执行:

bash 复制代码
pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple

二、Pandas 基础操作速通

从这里到第三节结束,是 Pandas 知识点的讲解。建议新建一个 pandas_demo.py 文件,每段代码都可以独立运行,跟着敲一遍就能理解。第四节开始才是完整的数据分析项目。

2.1 创建和查看 DataFrame

【新建文件】 在项目目录下新建 pandas_demo.py,写入以下代码:

python 复制代码
"""
Pandas 基础知识练习
每段代码可以独立运行
"""

import pandas as pd

# ========== 创建 DataFrame ==========

# 用字典创建(最常用)
data = {
    "职位名称": ["AI测试工程师", "高级AI测试开发", "大模型测试工程师", "CV测试工程师", "NLP测试工程师"],
    "薪资下限": [25000, 35000, 30000, 22000, 25000],
    "薪资上限": [40000, 60000, 50000, 38000, 45000],
    "城市": ["北京", "上海", "杭州", "成都", "广州"],
    "经验": ["3-5年", "5-10年", "3-5年", "1-3年", "3-5年"]
}

df = pd.DataFrame(data)

# 查看前几行(默认前5行)
print("--- 前 5 行数据 ---")
print(df.head())

# 查看后几行
print("\n--- 后 3 行数据 ---")
print(df.tail(3))

# 查看基本信息:行数、列数、每列数据类型、缺失值数量
print("\n--- 基本信息 ---")
print(df.info())

# 查看统计摘要(数值列的均值、标准差、最小值、最大值等)
print("\n--- 统计摘要 ---")
print(df.describe())

【运行】 在终端执行 python pandas_demo.py,看看输出。df.info() 会告诉你每列的数据类型和有没有缺失值,df.describe() 会给你数值列的统计概况------这两个是你拿到新数据后最先要看的。

2.2 选取列和行

【追加到 pandas_demo.py】 在上一段代码末尾继续添加:

python 复制代码
# ========== 选取列和行 ==========

print("\n--- 选取列 ---")

# 选一列(返回 Series)
print(df["城市"])
print(type(df["城市"]))   # <class 'pandas.core.series.Series'>

# 选多列(返回 DataFrame)
print(df[["职位名称", "城市"]])

print("\n--- 选取行 ---")

# 按索引选取行(iloc:按位置)
print(df.iloc[0])      # 第 1 行
print(df.iloc[1:3])    # 第 2 到第 3 行

# 按标签选取行(loc:按索引标签)
print(df.loc[0])       # 索引为 0 的行
print(df.loc[0:2])     # 索引 0 到 2 的行(注意:loc 是包含右端点的)

print("\n--- 条件筛选 ---")

# 筛选北京的岗位
beijing = df[df["城市"] == "北京"]
print(beijing)

# 筛选薪资上限超过 50000 的
high_salary = df[df["薪资上限"] > 50000]
print(high_salary)

# 组合条件(注意:每个条件要用括号包起来,用 & 和 | 连接)
result = df[(df["城市"] == "北京") & (df["薪资上限"] > 35000)]
print(result)

条件筛选是 Pandas 最高频的操作。核心语法是 df[条件表达式],条件表达式返回一个布尔 Series,Pandas 根据它来选择行。组合条件时要注意:每个条件必须用括号 () 包起来,条件之间用 &(且)或 |(或)连接,不能用 Python 的 and / or。

2.3 添加列和修改数据

【追加到 pandas_demo.py】 在上一段代码末尾继续添加:

python 复制代码
# ========== 添加列和修改数据 ==========

print("\n--- 添加列 ---")

# 直接赋值就创建了新列
df["平均薪资"] = (df["薪资下限"] + df["薪资上限"]) / 2
print(df[["职位名称", "薪资下限", "薪资上限", "平均薪资"]])

# 用 apply 对每行做计算(非常常用)
df["薪资跨度"] = df.apply(lambda row: row["薪资上限"] - row["薪资下限"], axis=1)
print(df[["职位名称", "薪资下限", "薪资上限", "薪资跨度"]])

print("\n--- 修改数据 ---")

# 修改单个值
df.loc[0, "城市"] = "北京(总部)"
print(df.loc[0])

# 批量替换
df["城市"] = df["城市"].str.replace("(总部)", "")
print(df["城市"])

df.apply() 是 Pandas 的瑞士军刀------它能对每一行或每一列执行自定义的计算逻辑。axis=1 表示按行操作(每个 lambda 参数 row 是一行数据),axis=0 表示按列操作。

2.4 缺失值处理

真实数据中经常有缺失值(空值、None、NaN)。Pandas 有一套完整的缺失值处理方法。

【追加到 pandas_demo.py】 在上一段代码末尾继续添加:

python 复制代码
# ========== 缺失值处理 ==========

print("\n--- 缺失值处理 ---")

# 制造一些缺失值用于演示
import numpy as np

df_dirty = pd.DataFrame({
    "name": ["张三", "李四", None, "王五"],
    "salary": [25000, np.nan, 35000, np.nan],
    "city": ["北京", "上海", "杭州", None]
})

print("原始数据:")
print(df_dirty)

# 1. 检测缺失值
print("\n每列缺失值数量:")
print(df_dirty.isnull().sum())

# 2. 删除包含缺失值的行
print("\n删除有缺失值的行:")
print(df_dirty.dropna())

# 3. 填充缺失值
print("\n用默认值填充:")
print(df_dirty.fillna({"name": "未知", "salary": 0, "city": "未知"}))

# 4. 用均值/中位数填充数值列
print("\n用均值填充薪资:")
salary_mean = df_dirty["salary"].mean()
print(df_dirty.fillna({"salary": salary_mean}))

处理策略怎么选:

  • 删除 (dropna):数据量大、缺失值少的时候用,简单粗暴
  • 填充默认值 (fillna):有合理的默认值时用(比如城市填"未知")
  • 填充统计值:数值列可以填均值、中位数;分类列可以填众数
  • 保留不处理:有些分析可以忽略缺失值(比如算薪资平均值时,NaN 自动跳过)

2.5 分组统计 ------ 最强大的功能

【追加到 pandas_demo.py】 在上一段代码末尾继续添加:

python 复制代码
# ========== 分组统计 ==========

print("\n--- 分组统计 ---")

# 按城市分组,计算平均薪资
print("各城市平均薪资上限:")
print(df.groupby("城市")["薪资上限"].mean())

# 按城市分组,计算多种统计量
print("\n各城市薪资统计:")
print(df.groupby("城市")[["薪资下限", "薪资上限"]].agg(["mean", "min", "max", "count"]))

# 按经验分组,计算平均薪资
print("\n各经验等级平均薪资:")
print(df.groupby("经验")[["薪资下限", "薪资上限"]].mean())

# 多级分组(按城市 + 经验)
print("\n城市 × 经验 分组统计:")
print(df.groupby(["城市", "经验"])["薪资上限"].mean())

groupby() 是 Pandas 分析的核心。它的逻辑和 SQL 的 GROUP BY 一模一样:按某个列分组,然后对每组做聚合计算。你可以对一个列算单一统计量(如 .mean()),也可以用 .agg() 同时算多个统计量。

2.6 排序和计数

【追加到 pandas_demo.py】 在上一段代码末尾继续添加:

python 复制代码
# ========== 排序和计数 ==========

print("\n--- 排序 ---")

# 按薪资上限降序排列
print(df.sort_values("薪资上限", ascending=False))

# 按多列排序(先按城市升序,再按薪资上限降序)
print(df.sort_values(["城市", "薪资上限"], ascending=[True, False]))

print("\n--- 计数 ---")

# 统计每个城市的岗位数量
print(df["城市"].value_counts())

# 统计每个经验等级的岗位数量
print(df["经验"].value_counts())

value_counts() 是最方便的计数方法------一行代码就能拿到"每个值出现了多少次",做频次分析时非常常用。sort_values() 用来排序,ascending 参数控制升序还是降序。


三、Pandas 读取数据

3.1 读取 CSV

【追加到 pandas_demo.py】 在上一段代码末尾继续添加:

python 复制代码
# ========== 读取 CSV 文件 ==========

print("\n--- 读取 CSV ---")

# 读取第04篇生成的岗位数据 CSV
try:
    job_df = pd.read_csv("job_data.csv", encoding="utf-8-sig")
    print(f"读取成功,共 {len(job_df)} 行,{len(job_df.columns)} 列")
    print(f"列名:{list(job_df.columns)}")
    print(job_df.head(3))
except FileNotFoundError:
    print("job_data.csv 不存在,请先运行第04篇的爬虫代码")

pd.read_csv() 读取 CSV 文件,返回 DataFrame。参数说明:

  • encoding="utf-8-sig":因为我们保存 CSV 时用了这个编码(为了 Excel 兼容),读取时也要用同样的编码
  • 还有其他常用参数:sep=","(分隔符,默认逗号)、header=0(表头在第几行)、usecols=["col1", "col2"](只读指定列)

3.2 读取 JSON

【追加到 pandas_demo.py】 在上一段代码末尾继续添加:

python 复制代码
# ========== 读取 JSON 文件 ==========

print("\n--- 读取 JSON ---")

# 读取第04篇生成的岗位数据 JSON
try:
    job_json_df = pd.read_json("job_data.json")
    print(f"读取成功,共 {len(job_json_df)} 行")
    print(job_json_df.head(3))
except FileNotFoundError:
    print("job_data.json 不存在,请先运行第04篇的爬虫代码")

# JSON 格式不同,参数也不同
# 如果是 JSON Lines(每行一个 JSON 对象)
# df = pd.read_json("data.jsonl", lines=True)

【运行】 在终端执行 python pandas_demo.py,确认所有 Pandas 示例都正常输出。


四、岗位数据分析实战

前面的 Pandas 基础操作你已经练过了。现在用它们来做一个完整的数据分析项目:分析 AI 测试岗位的技能需求,产出一份分析报告。

4.1 项目初始化

【新建文件】 在项目目录下新建 data_analysis.py,写入以下代码:

python 复制代码
"""
AI 测试岗位数据分析
读取爬取的 JD 数据,进行清洗、转换、分析,输出分析报告

数据来源:job_data.json(第04篇爬虫生成)
输出文件:analysis_report.txt
"""

import pandas as pd
import json
from collections import Counter


# 常量配置
INPUT_JSON = "job_data.json"
OUTPUT_REPORT = "analysis_report.txt"

print("AI 测试岗位数据分析启动")
print("=" * 60)

4.2 读取数据

【追加到 data_analysis.py】 在上一段代码末尾继续添加:

python 复制代码
def load_data():
    """
    读取岗位数据,返回 DataFrame
    """
    try:
        with open(INPUT_JSON, "r", encoding="utf-8") as f:
            data = json.load(f)
        df = pd.DataFrame(data)
        print(f"数据加载成功:{len(df)} 条岗位记录")
        return df
    except FileNotFoundError:
        print(f"数据文件不存在:{INPUT_JSON}")
        print("请先运行第04篇的爬虫代码生成数据")
        return None

4.3 数据清洗

爬到的原始数据通常不能直接分析,这一步做三件事:检查数据质量、处理缺失值、统一数据格式。

【追加到 data_analysis.py】 在上一段代码末尾继续添加:

python 复制代码
def clean_data(df):
    """
    数据清洗:检查质量、处理缺失值、统一格式
    """
    print(f"\n--- 数据清洗 ---")

    # 1. 查看数据基本信息
    print(f"原始数据量:{len(df)} 条")
    print(f"列名:{list(df.columns)}")

    # 2. 检查缺失值
    print(f"\n缺失值统计:")
    null_counts = df.isnull().sum()
    for col, count in null_counts.items():
        if count > 0:
            print(f"  {col}: {count} 个缺失值")

    # 3. 处理缺失值:字符串列填"未知",数值列填 None 保持缺失
    str_cols = ["title", "company", "location", "experience", "education", "description"]
    for col in str_cols:
        if col in df.columns:
            df[col] = df[col].fillna("未知")

    # 4. 确保 tags 列是列表类型
    if "tags" in df.columns:
        df["tags"] = df["tags"].apply(lambda x: x if isinstance(x, list) else [])

    # 5. 删除完全重复的行
    before_count = len(df)
    df = df.drop_duplicates(subset=["title", "company", "location", "salary_raw"])
    after_count = len(df)
    if before_count != after_count:
        print(f"  去重:删除了 {before_count - after_count} 条重复数据")

    # 6. 标准化城市名(去掉多余空格)
    if "location" in df.columns:
        df["location"] = df["location"].str.strip()

    print(f"清洗后数据量:{len(df)} 条")
    return df

清洗做了五件事:检查缺失值、填充默认值、确保列表类型、去重、标准化文本。这些是数据清洗的通用套路,不只适用于岗位数据------后面你分析测试数据、模型评估数据时,这些方法同样适用。

4.4 数据转换

数据转换是把"原始格式"变成"分析友好格式"。对我们的岗位数据来说,核心转换有三个:薪资解析成数字、技能标签展开、分层分类。

【追加到 data_analysis.py】 在上一段代码末尾继续添加:

python 复制代码
def transform_data(df):
    """
    数据转换:薪资解析、技能标签展开、增加分析维度
    """
    print(f"\n--- 数据转换 ---")

    # 1. 计算平均薪资(取上下限的中间值)
    # 只对月薪岗位(salary_type == "K")计算
    def calc_avg_salary(row):
        # 只计算月薪岗位(salary_type == "K"),排除日薪等其他类型
        if row.get("salary_type") == "K" and row.get("salary_low") and row.get("salary_high"):
            return (row["salary_low"] + row["salary_high"]) / 2
        return None

    df["avg_salary"] = df.apply(calc_avg_salary, axis=1)
    monthly_count = df["avg_salary"].notna().sum()
    print(f"  计算了 {monthly_count} 条月薪岗位的平均薪资")

    # 2. 增加薪资分层
    def salary_level(salary):
        if pd.isna(salary):
            return "未统计"
        if salary < 25000:
            return "初级(<25K)"
        elif salary < 40000:
            return "中级(25-40K)"
        elif salary < 55000:
            return "高级(40-55K)"
        else:
            return "专家(55K+)"

    df["salary_level"] = df["avg_salary"].apply(salary_level)
    print(f"  完成薪资分层")

    # 3. 展开技能标签(为技能分析准备)
    # 每条岗位可能有多个 tag,我们把它们全部收集起来
    all_tags = []
    for tags in df["tags"]:
        if isinstance(tags, list):
            all_tags.extend(tags)

    tag_counter = Counter(all_tags)
    print(f"  技能标签展开:共 {len(all_tags)} 个标签,去重后 {len(tag_counter)} 个")

    # 4. 增加技能数量列(每条岗位要求的技能数)
    df["tag_count"] = df["tags"].apply(lambda x: len(x) if isinstance(x, list) else 0)

    return df, tag_counter

4.5 数据分析

现在数据已经干净了,可以开始做真正的分析。我们从六个维度来分析 AI 测试岗位的需求。

【追加到 data_analysis.py】 在上一段代码末尾继续添加:

python 复制代码
def analyze_data(df, tag_counter):
    """
    多维度数据分析
    返回分析结果字典
    """
    results = {}

    # ---- 1. 薪资分析 ----
    monthly = df[df["avg_salary"].notna()]
    results["salary"] = {
        "月薪岗位数": len(monthly),
        "平均薪资": monthly["avg_salary"].mean(),
        "最高薪资": monthly["avg_salary"].max(),
        "最低薪资": monthly["avg_salary"].min(),
        "中位数薪资": monthly["avg_salary"].median(),
    }

    # 按薪资分层统计
    results["salary_level_dist"] = df["salary_level"].value_counts().to_dict()

    print(f"\n薪资分析完成")

    # ---- 2. 技能需求分析(最核心) ----
    results["top_skills"] = tag_counter.most_common(15)

    # 技能分类统计
    skill_categories = {
        "编程语言": ["Python"],
        "AI/ML技术": ["机器学习", "深度学习", "大模型", "LLM", "NLP", "计算机视觉", "TensorFlow", "PyTorch"],
        "测试技能": ["自动化测试", "pytest", "测试框架", "模型测试", "测试开发", "测试基础", "数据标注"],
        "评测方法": ["LLM评测", "模型评测", "Prompt Engineering", "RAG", "BLEU", "ROUGE", "文本评测"],
        "数据技能": ["数据分析", "Pandas", "SQL", "数据清洗", "数据标注"],
        "工程技能": ["Docker", "CI/CD", "Selenium", "Red Team", "对抗攻击", "大模型安全", "质量保障", "A/B测试"],
    }

    category_counts = {}
    for category, keywords in skill_categories.items():
        count = sum(tag_counter.get(kw, 0) for kw in keywords)
        if count > 0:
            category_counts[category] = count

    results["skill_categories"] = category_counts
    print(f"技能分析完成")

    # ---- 3. 地区分析 ----
    results["location_dist"] = df["location"].value_counts().to_dict()

    # 按城市分组的平均薪资
    location_salary = monthly.groupby("location")["avg_salary"].agg(["mean", "count"])
    results["location_salary"] = location_salary.to_dict("index")
    print(f"地区分析完成")

    # ---- 4. 经验要求分析 ----
    results["experience_dist"] = df["experience"].value_counts().to_dict()

    # 按经验分组的平均薪资
    exp_salary = monthly.groupby("experience")["avg_salary"].agg(["mean", "count"])
    results["experience_salary"] = exp_salary.to_dict("index")
    print(f"经验分析完成")

    # ---- 5. 学历要求分析 ----
    results["education_dist"] = df["education"].value_counts().to_dict()
    print(f"学历分析完成")

    # ---- 6. 薪资与技能关联分析 ----
    # 找出高薪岗位(平均薪资前 25%)的技能共性
    if len(monthly) > 4:
        threshold = monthly["avg_salary"].quantile(0.75)
        high_salary_jobs = df[df["avg_salary"] >= threshold]
        high_salary_tags = []
        for tags in high_salary_jobs["tags"]:
            if isinstance(tags, list):
                high_salary_tags.extend(tags)
        results["high_salary_skills"] = Counter(high_salary_tags).most_common(10)
        results["high_salary_threshold"] = threshold
        results["high_salary_count"] = len(high_salary_jobs)
        print(f"高薪岗位技能分析完成")

    return results

4.6 生成报告

分析做完了,把结果整理成一份可读性好的报告。

【追加到 data_analysis.py】 在上一段代码末尾继续添加:

python 复制代码
def generate_report(results, df):
    """
    生成分析报告,保存到文本文件并打印到终端
    """
    lines = []

    def add(text=""):
        lines.append(text)

    add("=" * 60)
    add("AI 测试岗位技能需求分析报告")
    add("=" * 60)
    add(f"\n数据范围:{len(df)} 条 AI 测试相关岗位")
    add(f"分析维度:薪资、技能、地区、经验、学历、高薪岗位")

    # ---- 薪资分析 ----
    s = results.get("salary", {})
    add(f"\n{'─' * 60}")
    add(f"一、薪资分析")
    add(f"{'─' * 60}")
    add(f"  月薪岗位数:{s.get('月薪岗位数', 0)}")
    add(f"  平均薪资:{s.get('平均薪资', 0):,.0f} 元/月")
    add(f"  中位数薪资:{s.get('中位数薪资', 0):,.0f} 元/月")
    add(f"  薪资范围:{s.get('最低薪资', 0):,.0f} ~ {s.get('最高薪资', 0):,.0f} 元/月")

    add(f"\n  薪资分层分布:")
    for level, count in results.get("salary_level_dist", {}).items():
        bar = "█" * (count * 3)
        add(f"    {level:<16s} {count:>2d} 个岗位  {bar}")

    # ---- 技能分析 ----
    add(f"\n{'─' * 60}")
    add(f"二、技能需求分析(最重要的部分)")
    add(f"{'─' * 60}")

    add(f"\n  技能需求 Top 15:")
    for i, (tag, count) in enumerate(results.get("top_skills", []), 1):
        bar = "█" * (count * 2)
        add(f"    {i:>2d}. {tag:<24s} {count:>2d} 次  {bar}")

    add(f"\n  按技能类别统计:")
    for category, count in sorted(results.get("skill_categories", {}).items(),
                                   key=lambda x: x[1], reverse=True):
        bar = "█" * (count * 2)
        add(f"    {category:<12s} {count:>3d} 次  {bar}")

    # ---- 地区分析 ----
    add(f"\n{'─' * 60}")
    add(f"三、地区分布")
    add(f"{'─' * 60}")

    add(f"\n  岗位地区分布:")
    for location, count in results.get("location_dist", {}).items():
        bar = "█" * (count * 3)
        add(f"    {location:<8s} {count:>2d} 个岗位  {bar}")

    loc_salary = results.get("location_salary", {})
    if loc_salary:
        add(f"\n  各城市平均薪资:")
        for loc, stats in sorted(loc_salary.items(), key=lambda x: x[1].get("mean", 0), reverse=True):
            mean_salary = stats.get("mean", 0)
            count = stats.get("count", 0)
            add(f"    {loc:<8s} {mean_salary:>10,.0f} 元/月  ({int(count)} 个岗位)")

    # ---- 经验分析 ----
    add(f"\n{'─' * 60}")
    add(f"四、经验要求分析")
    add(f"{'─' * 60}")

    add(f"\n  经验要求分布:")
    for exp, count in results.get("experience_dist", {}).items():
        bar = "█" * (count * 3)
        add(f"    {exp:<10s} {count:>2d} 个岗位  {bar}")

    exp_salary = results.get("experience_salary", {})
    if exp_salary:
        add(f"\n  各经验等级平均薪资:")
        for exp, stats in exp_salary.items():
            mean_salary = stats.get("mean", 0)
            add(f"    {exp:<10s} {mean_salary:>10,.0f} 元/月")

    # ---- 学历分析 ----
    add(f"\n{'─' * 60}")
    add(f"五、学历要求分析")
    add(f"{'─' * 60}")

    add(f"\n  学历要求分布:")
    for edu, count in results.get("education_dist", {}).items():
        bar = "█" * (count * 3)
        add(f"    {edu:<8s} {count:>2d} 个岗位  {bar}")

    # ---- 高薪岗位分析 ----
    if "high_salary_skills" in results:
        add(f"\n{'─' * 60}")
        add(f"六、高薪岗位技能分析")
        add(f"{'─' * 60}")
        threshold = results.get("high_salary_threshold", 0)
        count = results.get("high_salary_count", 0)
        add(f"\n  高薪门槛:平均薪资 >= {threshold:,.0f} 元/月(前 25%)")
        add(f"  高薪岗位数:{count}")

        add(f"\n  高薪岗位最看重的技能 Top 10:")
        for i, (tag, freq) in enumerate(results.get("high_salary_skills", []), 1):
            bar = "█" * freq
            add(f"    {i:>2d}. {tag:<24s} {freq:>2d} 次  {bar}")

    # ---- 总结建议 ----
    add(f"\n{'─' * 60}")
    add(f"七、学习建议总结")
    add(f"{'─' * 60}")
    add(f"")
    add(f"  根据以上数据,AI 测试工程师的技能优先级建议如下:")
    add(f"")
    add(f"  第一优先级(必备):")
    add(f"    Python 编程")
    add(f"    基本测试方法论")
    add(f"    数据分析基础")
    add(f"")
    add(f"  第二优先级(核心竞争力):")
    add(f"    AI/ML 基础知识")
    add(f"    模型评测方法")
    add(f"    自动化测试框架")
    add(f"")
    add(f"  第三优先级(差异化优势):")
    add(f"    大模型评测(LLM)")
    add(f"    Prompt Engineering")
    add(f"    RAG 系统测试")
    add(f"    安全测试")

    add(f"\n{'=' * 60}")
    add(f"报告生成完毕")

    # 输出报告
    report_text = "\n".join(lines)

    # 打印到终端
    print(report_text)

    # 保存到文件
    with open(OUTPUT_REPORT, "w", encoding="utf-8") as f:
        f.write(report_text)
    print(f"\n报告已保存到 {OUTPUT_REPORT}")

    return report_text

4.7 主程序

【追加到 data_analysis.py】 在上一段代码末尾继续添加:

python 复制代码
# ========== 主程序 ==========
if __name__ == "__main__":
    # 第一步:读取数据
    df = load_data()
    if df is None:
        exit()

    # 第二步:数据清洗
    df = clean_data(df)

    # 第三步:数据转换
    df, tag_counter = transform_data(df)

    # 第四步:数据分析
    results = analyze_data(df, tag_counter)

    # 第五步:生成报告
    generate_report(results, df)

    print(f"\n全部完成!")

【运行】 在终端执行:

bash 复制代码
python data_analysis.py

4.8 运行结果

运行后终端会输出完整的分析报告,同时生成 analysis_report.txt 文件。大致输出如下:

复制代码
AI 测试岗位数据分析启动
============================================================
数据加载成功:12 条岗位记录

--- 数据清洗 ---
原始数据量:12 条
列名:['title', 'salary_raw', 'salary_low', 'salary_high', 'salary_type', 'company', 'location', 'experience', 'education', 'tags', 'description']

缺失值统计:
清洗后数据量:12 条

--- 数据转换 ---
  计算了 11 条月薪岗位的平均薪资
  完成薪资分层
  技能标签展开:共 53 个标签,去重后 37 个

薪资分析完成
技能分析完成
地区分析完成
经验分析完成
学历分析完成
高薪岗位技能分析完成
============================================================
AI 测试岗位技能需求分析报告
============================================================

数据范围:12 条 AI 测试相关岗位
分析维度:薪资、技能、地区、经验、学历、高薪岗位

────────────────────────────────────────────────────────────
一、薪资分析
────────────────────────────────────────────────────────────
  月薪岗位数:11
  平均薪资:40,818 元/月
  中位数薪资:36,500 元/月
  薪资范围:27,500 ~ 60,000 元/月

  薪资分层分布:
    中级(25-40K)        6 个岗位  ██████████████████
    高级(40-55K)        3 个岗位  █████████
    专家(55K+)          2 个岗位  ██████
    未统计               1 个岗位  ███

────────────────────────────────────────────────────────────
二、技能需求分析(最重要的部分)
────────────────────────────────────────────────────────────

  技能需求 Top 15:
     1. Python                   12 次  ████████████████████████
     2. pytest                    2 次  ████
     3. NLP                       2 次  ████
     4. RAG                       2 次  ████
     5. 数据分析                      2 次  ████
     6. 模型测试                      2 次  ████
     7. 机器学习                      1 次  ██
     8. 自动化测试                     1 次  ██
     9. 大模型                       1 次  ██
    10. LLM评测                     1 次  ██
    11. 测试开发                      1 次  ██
    12. Prompt Engineering        1 次  ██
    13. 评测框架                      1 次  ██
    14. TensorFlow                1 次  ██
    15. 模型评测                      1 次  ██

  按技能类别统计:
    编程语言          12 次  ████████████████████████
    测试技能           9 次  ██████████████████
    评测方法           8 次  ████████████████
    工程技能           8 次  ████████████████
    AI/ML技术        7 次  ██████████████
    数据技能           6 次  ████████████

────────────────────────────────────────────────────────────
三、地区分布
────────────────────────────────────────────────────────────

  岗位地区分布:
    北京        5 个岗位  ███████████████
    上海        2 个岗位  ██████
    杭州        2 个岗位  ██████
    深圳        1 个岗位  ███
    广州        1 个岗位  ███
    成都        1 个岗位  ███

  各城市平均薪资:
    上海           47,500 元/月  (1 个岗位)
    北京           47,300 元/月  (5 个岗位)
    杭州           36,250 元/月  (2 个岗位)
    广州           35,000 元/月  (1 个岗位)
    成都           30,000 元/月  (1 个岗位)
    深圳           27,500 元/月  (1 个岗位)

────────────────────────────────────────────────────────────
四、经验要求分析
────────────────────────────────────────────────────────────

  经验要求分布:
    3-5年        6 个岗位  ██████████████████
    5-10年       3 个岗位  █████████
    1-3年        2 个岗位  ██████
    在校生         1 个岗位  ███

  各经验等级平均薪资:
    1-3年           28,750 元/月
    3-5年           38,167 元/月
    5-10年          54,167 元/月

────────────────────────────────────────────────────────────
五、学历要求分析
────────────────────────────────────────────────────────────

  学历要求分布:
    本科        8 个岗位  ████████████████████████
    硕士        4 个岗位  ████████████

────────────────────────────────────────────────────────────
六、高薪岗位技能分析
────────────────────────────────────────────────────────────

  高薪门槛:平均薪资 >= 50,000 元/月(前 25%)
  高薪岗位数:3

  高薪岗位最看重的技能 Top 10:
     1. Python                    3 次  ███
     2. 模型评测                      1 次  █
     3. A/B测试                     1 次  █
     4. CI/CD                     1 次  █
     5. 质量保障                      1 次  █
     6. 大模型安全                     1 次  █
     7. 对抗攻击                      1 次  █
     8. Red Team                  1 次  █
     9. LLM                       1 次  █
    10. 评测方法论                     1 次  █

────────────────────────────────────────────────────────────
七、学习建议总结
────────────────────────────────────────────────────────────

  根据以上数据,AI 测试工程师的技能优先级建议如下:

  第一优先级(必备):
    Python 编程
    基本测试方法论
    数据分析基础

  第二优先级(核心竞争力):
    AI/ML 基础知识
    模型评测方法
    自动化测试框架

  第三优先级(差异化优势):
    大模型评测(LLM)
    Prompt Engineering
    RAG 系统测试
    安全测试

============================================================
报告生成完毕

报告已保存到 analysis_report.txt

全部完成!

五、从数据中提炼的洞察

上面的分析只是数字。但作为 AI 测试工程师,你更重要的是从数字中看出结论。以下是我从这份数据中提炼的核心洞察:

5.1 Python 是绝对的第一技能

12 个岗位中有 10 个要求 Python,出现率 83%。不管你是做传统测试转 AI 测试,还是从零开始学,Python 都是第一优先级------这验证了我们系列前两篇从 Python 开始学的决策是正确的。

5.2 薪资差距很大,技能决定天花板

薪资从 2 万到 7.5 万,差距接近 4 倍。高薪岗位(55K+)的共同特点是:要求大模型相关经验、需要方法论设计能力(评测体系建设、质量标准制定)、不只是执行测试。也就是说,做执行的人薪资在 2-4 万,能设计体系的人薪资在 5-7 万。

5.3 大模型是新的增长点

LLM、Prompt Engineering、RAG 这些技能在传统的 AI 测试岗位里几乎看不到,但在这批数据中频繁出现。这说明大模型测试是一个新兴方向,竞争少、需求增长快、薪资高。提前布局这个方向是非常值得的。

5.4 北京是机会最多的城市

北京岗位数最多,平均薪资也最高。上海和杭州紧随其后。如果你在这些城市找 AI 测试工作,机会会更多。

5.5 学历门槛不高

8 个岗位要求本科,只有 3 个要求硕士。大专也有机会。说明 AI 测试看重的是实际能力(Python、测试经验、AI 认知),学历不是硬门槛。


六、Pandas 常用操作速查表

留一份速查表,后面写分析代码时直接翻:

读取数据:

python 复制代码
pd.read_csv("file.csv")           # 读 CSV
pd.read_json("file.json")          # 读 JSON
pd.read_excel("file.xlsx")         # 读 Excel
df.to_csv("output.csv", index=False)    # 保存 CSV
df.to_json("output.json", orient="records", force_ascii=False)  # 保存 JSON

查看数据:

python 复制代码
df.head()           # 前 5 行
df.tail()           # 后 5 行
df.shape            # (行数, 列数)
df.info()           # 列名、类型、缺失值
df.describe()       # 数值列统计摘要
df["列名"].unique()  # 该列的所有唯一值
df["列名"].value_counts()  # 每个值出现的次数

筛选和排序:

python 复制代码
df[df["列名"] == "值"]                   # 条件筛选
df[(df["A"] > 10) & (df["B"] == "x")]   # 组合条件
df.sort_values("列名")                    # 排序
df.sort_values(["列A", "列B"], ascending=[True, False])  # 多列排序

分组聚合:

python 复制代码
df.groupby("分组列")["数值列"].mean()              # 分组求均值
df.groupby("分组列")["数值列"].agg(["mean", "max"])  # 多个统计量
df.groupby(["列A", "列B"])["数值列"].sum()           # 多级分组

数据清洗:

python 复制代码
df.isnull().sum()       # 每列缺失值数量
df.dropna()             # 删除有缺失值的行
df.fillna("默认值")      # 填充缺失值
df.drop_duplicates()    # 去重
df["列名"].str.strip()   # 去首尾空白
df["列名"].str.replace("旧", "新")  # 字符串替换

七、本篇小结

本篇的核心收获:

  • Pandas 两大核心结构:Series(一列数据)和 DataFrame(一张表格),理解了这两个概念就理解了 Pandas 的 80%
  • 数据清洗流程:检查缺失值 → 填充默认值 → 去重 → 标准化格式,五步走
  • 数据转换技巧:薪资解析成数字、技能标签展开、增加分析维度列
  • 分组统计能力:groupby + agg 完成多维度分析
  • Counter 统计:频次排名一行搞定
  • 报告生成:把分析结果整理成可读性好的文本报告
  • 数据洞察力:不只是输出数字,还要提炼出有价值的结论

这份"AI 测试岗位技能需求分析报告"就是本系列后续学习路线的数据依据。后面我们学的每一块知识,都是在回答报告中提出的问题。


下一篇:【测AI】第07篇:数据可视化 ------ Matplotlib 让分析结果一目了然

  • Matplotlib 基础:画出你的第一张图
  • 常用图表类型:柱状图、饼图、散点图、热力图
  • 实战:把岗位分析结果做成可视化图表
  • 技能需求词云图
  • 薪资分布图与趋势分析
  • 产出一套完整的数据分析可视化报告
相关推荐
全栈练习生1 小时前
大模型反向传播与梯度下降
python·ai
袁俪1 小时前
视频生成技术
人工智能
孟健1 小时前
Gemini 4 Argon 对比 GPT-6 Astra:百万 Token 输出很诱人,但我劝你先别迁编程工作流
人工智能·llm·ai编程
上弦外贸新观察1 小时前
为什么你的外贸官网内容很多,ChatGPT却不推荐?海外GEO优化的几个关键问题
人工智能·chatgpt
火山引擎边缘云1 小时前
从“看见文字”到“读懂画面”:AI MediaKit 如何实现视频字幕无痕擦除
人工智能·音视频
喵叔哟1 小时前
企业知识库 RAG 检索设计,三层召回如何兼顾精度与召回率
人工智能
算家云2 小时前
doubao-seed-2-1 系列模型上架算桥 API | 多模态理解能力继续加强!
人工智能·api·token·算力租赁·算力平台
程序员清风2 小时前
企业级 AI 中台架构:模型、知识库、Agent 与业务系统
人工智能·架构
甲维斯2 小时前
“凤雏”MiniMax3.1和 “太空兔”一锅炖了吧!!
人工智能