19  案例:当当网 Python 类图书销售数据分析

19.1 背景

我想写一本有关 Python 应用的书。目前我有很多想法,拿不定主意。比如,

  • 目标读者是谁?经管类博士生,还是中小学生?
  • 书的内容是什么?是 Python 基础知识,还是 Python 数据分析?亦或是机器学习?
  • 书的形式是什么?是电子书,还是纸质书?亦或是两者都有?
  • 书的价格是多少?是 50 元,还是 100 元?亦或是 200 元?
  • 找哪家出版社?

在不了解这些信息的情况下,贸然画一年的时间写一本书是一个非常冒险的决定。为了降低风险,我决定先做一些市场调研,看看读者对 Python 书籍的需求。

我先通过百度搜索和 Google 搜索,了解到当当网是中国最大的图书销售网站之一,拥有丰富的图书数据。我决定从当当网获取一些 Python 类图书的销售数据,进行分析,看看读者对 Python 书籍的需求。

问题来了,我只有一些非常基础的 Python 知识 (大体上限于能对着课本或帮助文档提供的实例进行一些微小的修改,然后运行)。而且,我目前掌握的 Python 知识基本上进局限于回归分析。对于网络爬虫、数据分析、数据可视化等知识,我几乎一无所知。

我听说 ChatGPT, DeepSeek 等 AI 工具可以帮助我完成一些复杂的任务,比如网络爬虫、数据分析、数据可视化等。我决定试试这些工具,看看它们能否帮助我完成我的市场调研。

于是,我写一个提示词,发给了 ChatGPT:

与 ChatGPT 的完整对话

提示词
- 目的:从当当网获取Python相关书籍的销售量信息,最终输出一份市场调研报告。
- 语言:Python
- 网站:[https://search.dangdang.com/](https://search.dangdang.com/)
- 关键词:'python'
- 排序关键词:'销量'
- 排序方式:从高到低
- 信息条数:前 20 条
- 目前搜索结果页面URL:[https://search.dangdang.com/?key=python\&act=input\&sort\_type=sort\_sale\_amt\_desc#J\_tab](https://search.dangdang.com/?key=python&act=input&sort_type=sort_sale_amt_desc#J_tab)

- 预期输出: 
  - 书名,作者,年份,出版社,评论数,原价、折后价

以上是我目前想到的。
请帮我做一个全面的规划,包括分析目标、收集那些变量,分析流程,以及最终的报告形式、核心内容等。

以下是 ChatGPT 的回复:

AI 的回复

好的,以下是一份较为系统的规划方案,适用于以 Python 编写程序从当当网采集“Python”相关书籍的销售量数据,并撰写一份结构完整的市场调研报告。


Python 相关图书市场调研项目规划

一、分析目标

  • 核心问题:当前在当当网上热销的 Python 图书有哪些?主要出版时间集中在哪些年份?哪些出版社主导了市场?

  • 业务用途

    • 为出版商、培训机构、书店提供选书参考;
    • 了解当前市场偏好(入门/进阶、编程/数据分析/人工智能);
    • 为课程教材选择提供数据支持。

二、数据采集计划

1. 数据来源

2. 待采集字段(建议)

字段名 说明
书名 完整书名
作者 主作者及其他合著者
出版社 出版机构
出版时间 年份或具体日期
原价 划线价
折后价 实际售价
评论数 用户评论数,反映活跃度
星级评分 若有星级则采集
封面图链接 便于后续可视化展示
图书详情页链接 为后续扩展字段或人工校验提供入口

三、分析流程

Step 1:数据采集(Python 编程)

  • 使用库:

    • requests 模拟访问
    • BeautifulSoup 解析 HTML
    • relxml 辅助提取字段
  • 反爬措施应对:

    • 合理设置 headers(如模拟浏览器 UA)
    • 增加 time.sleep() 随机延迟
    • 如遇 JavaScript 动态加载,可考虑 selenium

Step 2:数据清洗与规范化

  • 作者、出版社等字段统一格式
  • 出版时间转为 datetime
  • 价格字段转换为 float
  • 评论数转换为整数(去除“条评论”字样)

Step 3:数据分析

3.1 描述性统计

  • 平均价格、评论数中位数
  • 出版年份分布(近几年为主)
  • 高频作者、出版社统计

3.2 分类分析

  • 入门类 vs 进阶类
  • 数据分析 vs Web 编程 vs 机器学习
  • 是否为教材或考试参考书

3.3 排行榜可视化

  • 前 10 图书销量/评论/价格对比柱状图
  • 出版时间折线图
  • 价格 vs 评论数散点图

四、报告输出格式

1. 技术报告形式(推荐)

  • 格式:Markdown / Quarto / PDF(可由 Jupyter Notebook 或 Quarto 生成)
  • 结构建议
# Python 图书销售市场调研报告(当当网) {.unnumbered}

## 1. 项目背景与目标 {.unnumbered}
## 2. 数据采集与字段说明 {.unnumbered}
## 3. 市场总体概况 {.unnumbered}
   - 销售 Top20 图书一览
   - 平均价格、评分、评论数等统计
## 4. 出版特征分析 {.unnumbered}
   - 出版社分布、出版时间分布
   - 作者贡献度分析
## 5. 图书类型对比分析 {.unnumbered}
   - 入门、进阶分布
   - 专业方向分布
## 6. 结论与建议 {.unnumbered}
   - 当前趋势总结
   - 出版或选书建议

2. 附录部分

  • 原始数据表格(20 本书)
  • 数据采集代码(附说明)
  • 图形可视化汇总图(Matplotlib / Seaborn / Plotly)

五、可扩展方向(可选)

  • 同步采集京东、淘宝图书频道以对比
  • 分析定价策略(原价 vs 折扣价规律)
  • 抽取关键词构建词云图
  • 构建图书推荐系统雏形

如你同意,我可以继续为你编写:

  1. Python 抓取前 20 条当当网图书数据的爬虫脚本;
  2. 可视化分析和 Markdown 报告生成模板;
  3. 教学型讲义或课程材料用版本。

是否现在开始写代码部分?是否使用 requests + BeautifulSoup,还是你希望使用 selenium 模拟浏览器?

19.2 爬取数据

我们让 ChatGPT 执行第一步:

Step 1:使用 Python 抓取当当网「Python」相关图书销量前 20 名信息 的完整采集代码。

19.2.1 技术选择说明

19.2.2 Python 代码

# 设置项目路径
#   [./data]: 存储数据文件
#   [./output]: 存储输出文件


#---------------- 项目路径,酌情修改 -------------------
#
path = r'D:\Github\dslian\cases\web_dangdang_book' 
#
#------------------------------------------------------

import os
import sys
path_data = os.path.join(path, 'data')
path_output = os.path.join(path, 'output')

os.chdir(path)
os.getcwd()
'd:\\Github\\dslian\\cases\\web_dangdang_book'
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import os

# 设置目标搜索 URL(销量降序排序)
url = "https://search.dangdang.com/?key=python&sort_type=sort_sale_amt_desc"

# 设置请求头,模拟浏览器访问
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
                   (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"
}

# 发送请求并获取页面内容
response = requests.get(url, headers=headers)
response.encoding = "gb2312"

# 使用 BeautifulSoup 解析 HTML 内容
soup = BeautifulSoup(response.text, "html.parser")

# 所有图书的信息在 ul.bigimg li 中
book_list = soup.select("ul.bigimg > li")

# 用于保存每本书的信息
books_data = []

# 遍历每本书的条目,提取信息
for item in book_list:
    try:
        title_tag = item.select_one("a[name='itemlist-title']")
        title = title_tag.get("title").strip() if title_tag else ""
        link = title_tag.get("href") if title_tag else ""

        author_tag = item.select_one("p.name a")
        author = author_tag.text.strip() if author_tag else ""

        pub_info_tag = item.select_one("p.search_book_author")
        pub_info = pub_info_tag.text.strip() if pub_info_tag else ""
        pub_info_parts = pub_info.split("\xa0\xa0")

        publisher = pub_info_parts[1].strip() if len(pub_info_parts) > 1 else ""
        pubdate = pub_info_parts[2].strip() if len(pub_info_parts) > 2 else ""

        comment_tag = item.select_one("p.search_star_line a")
        comment = comment_tag.text.strip() if comment_tag else ""

        price_tag = item.select_one("p.price")
        orig_price_tag = price_tag.select_one("span.search_pre_price") if price_tag else None
        disc_price_tag = price_tag.select_one("span.search_now_price") if price_tag else None
        orig_price = orig_price_tag.text.strip("¥") if orig_price_tag else ""
        disc_price = disc_price_tag.text.strip("¥") if disc_price_tag else ""

        books_data.append({
            "书名": title,
            "作者": author,
            "出版社": publisher,
            "出版时间": pubdate,
            "原价": orig_price,
            "折后价": disc_price,
            "评论数": comment,
            "详情页链接": link
        })

        time.sleep(0.5)  # 加入短暂停顿防止访问过快

    except Exception as e:
        print(f"解析失败:{e}")
        continue

# 转换为 DataFrame
df_books = pd.DataFrame(books_data)

# 确保保存路径存在
output_path = "data"
os.makedirs(output_path, exist_ok=True)
output_file = os.path.join(output_path, "dangdang_python_books_top20.csv")

# 保存为 CSV 文件
df_books.to_csv(output_file, index=False, encoding="utf-8-sig")

# 打印成功信息
print(f"已成功下载 {len(df_books)} 条数据,
        点击 [file://{os.path.abspath(output_file)}] 
        查看 dangdang_python_books_top20.csv 文件")
已成功下载 60 条数据,点击 [file://d:\Github\dslian\cases\web_dangdang_book\data\dangdang_python_books_top20.csv] 查看 dangdang_python_books_top20.csv 文件
import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import os

url = "https://search.dangdang.com/?key=python&sort_type=sort_sale_amt_desc"
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
                   (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"
}

response = requests.get(url, headers=headers)
response.encoding = "gb2312"
soup = BeautifulSoup(response.text, "html.parser")
book_list = soup.select("ul.bigimg > li")

books_data = []

for item in book_list:
    try:
        # 1. 主标题和简介
        title_tag = item.select_one("a[name='itemlist-title']")
        full_title = title_tag.get("title").strip() if title_tag else ""
        if ":" in full_title:
            title, brief = full_title.split(":", 1)
        elif "(" in full_title:
            title, brief = full_title.split("(", 1)
            brief = "(" + brief
        else:
            title, brief = full_title, ""

        # 2. 详情页链接
        link = title_tag.get("href") if title_tag else ""

        # 3. 作者 / 出版社 / 出版时间
        pub_info_tag = item.select_one("p.search_book_author")
        pub_info_parts = pub_info_tag.text.strip().split("\xa0\xa0") if pub_info_tag else []
        author = pub_info_parts[0].replace("作者", "").strip() if len(pub_info_parts) > 0 else ""
        publisher = pub_info_parts[1].strip() if len(pub_info_parts) > 1 else ""
        pubdate = pub_info_parts[2].strip() if len(pub_info_parts) > 2 else ""

        # 4. 评论数
        comment_tag = item.select_one("p.search_star_line a")
        comment = comment_tag.text.strip() if comment_tag else ""

        # 5. 价格
        price_tag = item.select_one("p.price")
        orig_price_tag = price_tag.select_one("span.search_pre_price") if price_tag else None
        disc_price_tag = price_tag.select_one("span.search_now_price") if price_tag else None
        orig_price = orig_price_tag.text.strip("¥") if orig_price_tag else ""
        disc_price = disc_price_tag.text.strip("¥") if disc_price_tag else ""

        books_data.append({
            "书名": title,
            "简介": brief,
            "作者": author,
            "出版社": publisher,
            "出版时间": pubdate,
            "原价": orig_price,
            "折后价": disc_price,
            "评论数": comment,
            "详情页链接": link
        })

        time.sleep(0.5)  # 加入短暂停顿防止访问过快

    except Exception as e:
        print(f"解析失败:{e}")
        continue

df_books = pd.DataFrame(books_data)

output_path = "data"
os.makedirs(output_path, exist_ok=True)
output_file = os.path.join(output_path, "dangdang_python_books_cleaned.csv")
df_books.to_csv(output_file, index=False, encoding="utf-8-sig")

print(f"已成功下载 {len(df_books)} 条数据,点击 [file://{os.path.abspath(output_file)}] 查看 dangdang_python_books_cleaned.csv 文件")
已成功下载 60 条数据,点击 [file://d:\Github\dslian\cases\web_dangdang_book\data\dangdang_python_books_cleaned.csv] 查看 dangdang_python_books_cleaned.csv 文件
# 读入本地数据
df_books = pd.read_csv(r"data\dangdang_python_books_cleaned.csv", encoding="utf-8-sig")

# 展示前几行结果
print(df_books.columns)
print(df_books.head(3))
print(df_books.info())
Index(['书名', '简介', '作者', '出版社', '出版时间', '原价', '折后价', '评论数', '详情页链接'], dtype='object')
                                                  书名  \
0                                     Python编程三剑客第3版   
1                                      小学生Python创意编程   
2  Python编程从入门到实践 第3版 Python编程入门经典,自学利器,数据分析、网络爬虫...   

                                                  简介  \
0          Python编程从入门到实践第3版+快速上手第2版+极客项目编程(当当套装共3册)   
1  (视频教学版) 本书按照入门开发者的思维方式编写,非常适合孩子学习Python编程的基础知识...   
2                                                NaN   

                                                  作者  出版社  出版时间     原价   折后价  \
0                                            加入购物车收藏  NaN   NaN  267.8  21.6   
1              刘凤飞 /2024-01-01  /清华大学出版社加入购物车购买电子书收藏  NaN   NaN   89.0  45.2   
2  [美]埃里克・马瑟斯(Eric Matthes) /2023-05-01  /人民邮电出版社...  NaN   NaN  109.8  69.8   

         评论数                                    详情页链接  
0  124999条评论  //product.dangdang.com/12100010244.html  
1    7157条评论     //product.dangdang.com/29666296.html  
2   19970条评论     //product.dangdang.com/29564789.html  
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 60 entries, 0 to 59
Data columns (total 9 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   书名      60 non-null     object 
 1   简介      29 non-null     object 
 2   作者      60 non-null     object 
 3   出版社     0 non-null      float64
 4   出版时间    0 non-null      float64
 5   原价      57 non-null     float64
 6   折后价     60 non-null     float64
 7   评论数     60 non-null     object 
 8   详情页链接   60 non-null     object 
dtypes: float64(4), object(5)
memory usage: 4.3+ KB
None

19.3 后续

我花了 30 分钟做到目前的样子,基本上搞懂了使用 Copilot 编写 Python 代码实现爬虫的流程。

刚好当时在教授「数据分析与 Python 应用」的课程,我索性把这个案例作为课堂作业,要求学生们完成。