🌐 数据采集与处理

网络爬虫、API调用与数据清洗技术

难度等级 ⭐⭐ 进阶级
预计学习周期 2-3 周(每天 1-2 小时)
前置知识 Python 基础 + HTTP 基础
课程章节 4 章 · 20+ 知识点

课程亮点

🌐
爬虫实战
Requests + BeautifulSoup 完整爬虫开发流程
🧹
数据清洗
掌握缺失值、异常值、重复值处理方法
💾
存储方案
CSV/JSON/数据库多种存储方式实战
🗺️

学习路径

阶段 1:网络请求基础
HTTP原理、URL结构、Requests库基本使用
阶段 2:网页解析技术
HTML结构、CSS选择器、XPath、BeautifulSoup
阶段 3:数据清洗与处理
Pandas清洗、格式化、数据质量检查
阶段 4:综合爬虫项目
完成一个完整的网站数据采集项目
👥

适合人群

✅ 适合学习:
· 会Python基础,想做数据获取的同学
· 需要批量采集网络数据的同学
· 对爬虫技术感兴趣的同学
⚠️ 建议先补基础:
· Python基础语法和函数
· 基础HTML/CSS知识
· 了解HTTP请求的基本概念
🎓

学完能做什么

学完本课程,你将能够:

  • 独立开发Python爬虫程序采集网页数据
  • 使用API接口获取结构化数据
  • 对采集的原始数据进行清洗和预处理
  • 将处理后的数据存储到CSV/JSON/数据库
01

数据采集基础

1.1 数据来源分类

在进行数据采集之前,首先需要了解数据的来源和类型。根据数据的组织方式,可以将数据分为以下三大类:

数据类型说明典型示例
结构化数据有固定的模式或 schema,可以用二维表格表示数据库表、Excel、CSV
半结构化数据有一定的层次结构,但没有严格的 schemaJSON、XML、HTML
非结构化数据没有预定义的结构或格式文本、图片、视频、音频
💡 提示:在实际项目中,我们采集的数据往往是半结构化的(如网页 HTML),需要经过解析和清洗后才能转化为结构化数据加以利用。

1.2 网络请求原理:HTTP/HTTPS

HTTP(HyperText Transfer Protocol)是互联网上应用最为广泛的协议,用于客户端与服务器之间的通信。HTTPS 则是在 HTTP 的基础上加入了 SSL/TLS 加密层,保障数据传输安全。

一次完整的 HTTP 通信过程如下:

  1. 客户端(浏览器/爬虫)向服务器发送 请求(Request)
  2. 服务器接收请求并进行处理
  3. 服务器返回 响应(Response) 给客户端
  4. 客户端解析响应内容并展示给用户

1.3 URL 结构详解

URL(Uniform Resource Locator)是互联网上资源的唯一地址。一个完整的 URL 由以下部分组成:

Text https://www.example.com:443/path/to/page?name=value&sort=asc#section1 \___/ \_____________/ \_/ \___________/ \_____________________/ \______/ | | | | | | 协议 域名 端口 路径 查询参数 锚点
  • 协议(Scheme):http 或 https,指定通信协议
  • 域名(Host):服务器的地址,如 www.example.com
  • 端口(Port):HTTP 默认 80,HTTPS 默认 443
  • 路径(Path):服务器上资源的路径
  • 查询参数(Query):以 ? 开头,键值对用 & 分隔
  • 锚点(Fragment):以 # 开头,指向页面内的某个位置

1.4 HTTP 请求与响应

HTTP 请求由请求行、请求头和请求体三部分组成。常用的请求方法如下:

方法用途是否包含请求体
GET获取资源
POST提交数据
PUT更新资源(全量替换)
DELETE删除资源通常否
HEAD获取响应头(不返回响应体)

HTTP 响应同样由状态行、响应头和响应体组成。常见的状态码包括:

Text 1xx 信息类 — 请求已接收,继续处理 2xx 成功类 — 请求已成功处理(200 OK, 201 Created) 3xx 重定向类 — 需要进一步操作(301 永久重定向, 302 临时重定向) 4xx 客户端错误 — 请求有误(400 Bad Request, 404 Not Found, 403 Forbidden) 5xx 服务器错误 — 服务器处理失败(500 Internal Server Error, 502 Bad Gateway)
✅ 小技巧:在浏览器中按 F12 打开开发者工具,切换到 Network(网络)选项卡,可以实时查看页面加载过程中的所有 HTTP 请求和响应,这是学习网络请求原理的最佳方式。
02

Requests库入门

2.1 安装与基本使用

Requests 是 Python 中最流行的 HTTP 库,以其简洁优雅的 API 著称。使用 pip 即可安装:

Bash pip install requests

安装完成后,即可在 Python 中导入并使用:

Python import requests # 发送一个简单的 GET 请求 response = requests.get('https://www.example.com') # 查看响应内容 print(response.text) # 响应体的文本内容 print(response.status_code) # 状态码,如 200 print(response.url) # 最终请求的 URL print(response.encoding) # 响应编码
💡 提示:如果网络环境需要代理,可以通过 proxies 参数设置。例如:requests.get(url, proxies={'http': 'http://127.0.0.1:7890'})

2.2 GET 与 POST 请求

GET 请求用于从服务器获取数据,参数通过 URL 传递;POST 请求用于向服务器提交数据,参数放在请求体中。

Python import requests # --- GET 请求:通过 params 传递查询参数 --- payload = {'q': 'python爬虫', 'page': 1} response = requests.get('https://httpbin.org/get', params=payload) print("GET 请求的完整 URL:", response.url) # 输出: https://httpbin.org/get?q=python%E7%88%AC%E8%99%AB&page=1 # --- POST 请求:通过 data 或 json 传递请求体 --- form_data = {'username': 'admin', 'password': '123456'} response = requests.post('https://httpbin.org/post', data=form_data) print("POST 表单响应:", response.json()['form']) json_data = {'title': '测试文章', 'content': '这是正文内容'} response = requests.post('https://httpbin.org/post', json=json_data) print("POST JSON 响应:", response.json()['json'])

2.3 请求头设置

很多网站会通过 User-Agent 等请求头来判断访问来源。设置合理的请求头可以模拟浏览器行为,避免被服务器拒绝。

Python import requests # 自定义请求头 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/120.0.0.0 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Referer': 'https://www.google.com/' } response = requests.get('https://httpbin.org/headers', headers=headers) print(response.json())
✅ 小技巧:可以在浏览器开发者工具的 Network 面板中找到任意请求的完整请求头信息,直接复制到代码中使用,这样能最大程度地模拟真实浏览器。

2.4 响应处理

Requests 的 Response 对象提供了丰富的属性和方法来处理服务器返回的数据:

Python import requests response = requests.get('https://httpbin.org/json') # --- 状态码判断 --- if response.status_code == 200: print("请求成功!") elif response.status_code == 404: print("页面不存在!") # 更推荐使用内置的断言方法 response.raise_for_status() # 如果状态码不是 2xx,会抛出 HTTPError # --- 编码处理 --- print("自动检测编码:", response.encoding) response.encoding = 'utf-8' # 手动指定编码 print("响应文本前100字符:", response.text[:100]) # --- JSON 响应解析 --- data = response.json() print("解析后的数据类型:", type(data)) print("幻灯片标题:", data['slideshow']['title']) # --- 二进制内容(如图片) --- img_response = requests.get('https://httpbin.org/image/png') with open('image.png', 'wb') as f: f.write(img_response.content) print("图片已保存,大小:", len(img_response.content), "字节")

2.5 Session 会话

Session 对象可以跨请求保持某些参数(如 Cookie),在需要登录或维持会话状态的场景中非常有用。

Python import requests # 创建 Session 对象 session = requests.Session() # 设置全局请求头(后续所有请求都会携带) session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 'AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36' }) # 模拟登录 login_data = {'username': 'your_user', 'password': 'your_pass'} session.post('https://example.com/login', data=login_data) # 登录后访问需要认证的页面(自动携带 Cookie) response = session.get('https://example.com/dashboard') print("登录后页面内容:", response.text[:200]) # 使用完毕后关闭 Session session.close()
💡 提示:推荐使用 with 语句管理 Session,这样可以确保即使发生异常也能正确关闭连接:with requests.Session() as session: ...
03

网页解析技术

3.1 HTML 结构基础

HTML(HyperText Markup Language)是网页的基本结构语言。理解 HTML 的层级关系是进行网页解析的前提。

HTML <!DOCTYPE html> <html> <head> <title>示例页面</title> </head> <body> <div class="container"> <h1 id="title">文章标题</h1> <p class="intro">这是一段介绍文字。</p> <ul class="item-list"> <li data-id="1"><a href="/item/1">项目一</a></li> <li data-id="2"><a href="/item/2">项目二</a></li> <li data-id="3"><a href="/item/3">项目三</a></li> </ul> </div> </body> </html>

HTML 中的关键概念:

  • 标签(Tag):如 <div>、<p>、<a> 等,定义元素类型
  • 属性(Attribute):如 class、id、href、data-* 等,提供附加信息
  • 层级关系:父元素包含子元素,形成 DOM 树结构

3.2 BeautifulSoup 安装与使用

BeautifulSoup 是 Python 中最常用的 HTML/XML 解析库,配合 lxml 解析器使用效果最佳。

Bash pip install beautifulsoup4 lxml
Python from bs4 import BeautifulSoup html_doc = """ <div class="article"> <h2 class="title">Python 数据采集入门</h2> <div class="meta"> <span class="author">张三</span> <span class="date">2025-01-15</span> </div> <p class="content">本文介绍如何使用 Python 进行数据采集。</p> </div> """ # 创建 BeautifulSoup 对象 soup = BeautifulSoup(html_doc, 'lxml') # --- 基本查找 --- print("标题:", soup.h2.string) # Python 数据采集入门 print("作者:", soup.find('span', class_='author').string) # 张三 print("日期:", soup.find('span', class_='date').string) # 2025-01-15 # --- find_all 查找所有匹配元素 --- all_spans = soup.find_all('span') for span in all_spans: print(f"{span.get('class')}: {span.string}") # --- 获取属性 --- print("div 的 class:", soup.div.get('class')) # ['article']
✅ 小技巧:BeautifulSoup 的 prettify() 方法可以将解析后的 HTML 以格式化的方式输出,在调试时非常方便查看文档结构。

3.3 CSS 选择器

BeautifulSoup 的 select() 方法支持 CSS 选择器语法,对于熟悉前端开发的开发者来说非常直观。

Python from bs4 import BeautifulSoup html_doc = """ <div id="products"> <div class="product"> <h3 class="name">商品A</h3> <span class="price">99.00</span> </div> <div class="product"> <h3 class="name">商品B</h3> <span class="price">199.00</span> </div> <div class="product featured"> <h3 class="name">商品C</h3> <span class="price">299.00</span> </div> </div> """ soup = BeautifulSoup(html_doc, 'lxml') # 通过 class 选择 names = soup.select('.product .name') for name in names: print("商品名:", name.string) # 通过 id 选择 products = soup.select('#products .product') print(f"共找到 {len(products)} 个商品") # 组合选择器:选择带有 featured 类的商品 featured = soup.select('.product.featured .price') print("推荐商品价格:", featured[0].string) # 属性选择器 links = soup.select('a[href^="https"]') # href 以 https 开头的 a 标签

3.4 XPath 语法

XPath 是另一种强大的 XML/HTML 路径查询语言,配合 lxml 库使用。相比 CSS 选择器,XPath 支持更复杂的查询条件。

Python from lxml import etree html_doc = """ <table> <tr><th>姓名</th><th>年龄</th><th>城市</th></tr> <tr><td>张三</td><td>25</td><td>北京</td></tr> <tr><td>李四</td><td>30</td><td>上海</td></tr> <tr><td>王五</td><td>28</td><td>广州</td></tr> </table> """ tree = etree.HTML(html_doc) # --- 基本路径表达式 --- # 选取所有 td 元素 tds = tree.xpath('//td') for td in tds: print("单元格:", td.text) # --- 带条件筛选 --- # 选取第二个 tr 中的所有 td row2 = tree.xpath('//tr[2]/td/text()') print("第二行数据:", row2) # --- 属性筛选 --- # 选取所有含 class="highlight" 的元素 highlights = tree.xpath('//*[@class="highlight"]') # --- 轴选择(高级用法) --- # 选取 td 的父元素 tr parents = tree.xpath('//td/parent::tr') print(f"共有 {len(parents)} 行数据") # --- 内置函数 --- # 获取最后一个 tr last_row = tree.xpath('//tr[last()]/td/text()') print("最后一行:", last_row)

3.5 数据提取实战

下面是一个综合实战案例,演示如何采集一个图书列表页面并提取结构化数据:

Python import requests from bs4 import BeautifulSoup import json # 1. 发送请求获取网页 url = 'https://books.toscrape.com/' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 'AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36' } response = requests.get(url, headers=headers) response.encoding = 'utf-8' # 2. 解析 HTML soup = BeautifulSoup(response.text, 'lxml') # 3. 提取数据 books = [] book_items = soup.select('article.product_pod') for item in book_items: title = item.h3.a['title'] # 书名 price = item.select_one('.price_color').string # 价格 rating = item.select_one('p.star-rating')['class'][1] # 评分 availability = item.select_one('.availability').string.strip() # 库存 books.append({ 'title': title, 'price': price, 'rating': rating, 'availability': availability }) # 4. 输出结果 print(f"共采集到 {len(books)} 本书") for book in books[:5]: print(f" 《{book['title']}》 - {book['price']} - 评分: {book['rating']}") # 5. 保存为 JSON with open('books.json', 'w', encoding='utf-8') as f: json.dump(books, f, ensure_ascii=False, indent=2) print("数据已保存到 books.json")
💡 提示:books.toscrape.com 是一个专门用于爬虫练习的网站,不会触发反爬机制,非常适合初学者练手。在实际项目中,请务必遵守目标网站的 robots.txt 协议和相关法律法规。
04

数据清洗与存储

4.1 数据清洗概述

从网络采集到的原始数据通常存在各种质量问题,直接使用会导致分析结果不准确。数据清洗是数据处理流程中至关重要的一步,主要解决以下三类问题:

问题类型描述常见处理方式
缺失值某些字段为空或 NaN填充默认值、删除、插值
异常值数据明显偏离正常范围过滤、截断、替换
重复值存在完全相同的记录去重(保留第一条或最后一条)

此外,数据清洗还包括格式统一(日期格式、字符串大小写)、类型转换(字符串转数值)、去除空白字符和特殊符号等操作。

💡 提示:业界有一个著名的说法:"Garbage In, Garbage Out"(垃圾进,垃圾出)。无论分析模型多么先进,如果输入的数据质量差,输出的结果也不会可靠。因此,数据清洗是数据分析中最值得投入时间的环节。

4.2 Pandas 清洗操作

Pandas 是 Python 数据处理的核心库,提供了丰富的数据清洗功能。以下演示常见的数据清洗操作:

Python import pandas as pd import numpy as np # --- 创建示例数据 --- data = { '姓名': ['张三', '李四', '王五', '赵六', '张三', '钱七'], '年龄': [25, 30, np.nan, 150, 25, 28], '城市': ['北京', '上海', '广州', '北京', '北京', '深圳'], '薪资': ['8000', '12000', '9500', 'abc', '8000', '11000'] } df = pd.DataFrame(data) print("原始数据:") print(df) print() # --- 1. 处理缺失值 --- print("缺失值统计:") print(df.isnull().sum()) # 填充缺失值 df['年龄'] = df['年龄'].fillna(df['年龄'].mean()) print("\n填充缺失值后:") print(df) # --- 2. 处理异常值 --- # 年龄范围:18-65,超出范围的替换为 NaN 再填充 df.loc[(df['年龄'] < 18) | (df['年龄'] > 65), '年龄'] = np.nan df['年龄'] = df['年龄'].fillna(df['年龄'].mean()) # --- 3. 处理重复值 --- print(f"\n去重前: {len(df)} 条记录") df = df.drop_duplicates() print(f"去重后: {len(df)} 条记录") # --- 4. 类型转换 --- df['薪资'] = pd.to_numeric(df['薪资'], errors='coerce') print("\n类型转换后:") print(df.dtypes)
✅ 小技巧:pd.to_numeric()errors='coerce' 参数可以将无法转换的值设为 NaN,而不是报错。这在处理混合类型数据时非常实用。

4.3 数据格式化

数据格式化确保数据的一致性和可读性,常见的格式化操作包括字符串处理、日期格式化和数值格式化。

Python import pandas as pd # 创建示例数据 data = { '商品名': [' 苹果手机 ', '华为笔记本', '小米平板', 'OPPO耳机 '], '价格': [5999.0, 6999.0, 2499.0, 399.0], '日期': ['2025/01/15', '2025-02-20', '2025.03.10', '2025/4/5'], '分类': ['手机', '手机', '平板', '耳机'] } df = pd.DataFrame(data) # --- 字符串清洗 --- df['商品名'] = df['商品名'].str.strip() # 去除首尾空格 df['分类'] = df['分类'].str.upper() # 转大写 # --- 日期格式化 --- df['日期'] = pd.to_datetime(df['日期'], format='mixed') df['年月'] = df['日期'].dt.strftime('%Y-%m') # 提取年月 df['星期'] = df['日期'].dt.day_name() # 获取星期几 # --- 数值格式化 --- df['价格(万元)'] = (df['价格'] / 10000).round(4) # 转换为万元 df['价格区间'] = pd.cut(df['价格'], bins=[0, 1000, 5000, 10000], labels=['低价', '中价', '高价']) print(df)

4.4 存储为 CSV / JSON

清洗完成的数据需要持久化存储。CSV 和 JSON 是两种最常用的轻量级数据存储格式。

Python import pandas as pd import json # 示例数据 data = { '姓名': ['张三', '李四', '王五'], '年龄': [25, 30, 28], '城市': ['北京', '上海', '广州'] } df = pd.DataFrame(data) # --- 存储为 CSV --- df.to_csv('output/people.csv', index=False, encoding='utf-8-sig') print("CSV 文件已保存") # 读取 CSV df_csv = pd.read_csv('output/people.csv', encoding='utf-8-sig') print("从 CSV 读取的数据:") print(df_csv) # --- 存储为 JSON --- # 方式1:使用 Pandas df.to_json('output/people.json', orient='records', force_ascii=False, indent=2) print("\nJSON 文件已保存") # 方式2:使用 json 模块(更灵活) records = df.to_dict(orient='records') with open('output/people_custom.json', 'w', encoding='utf-8') as f: json.dump({ 'total': len(records), 'data': records }, f, ensure_ascii=False, indent=2) # 读取 JSON df_json = pd.read_json('output/people.json', encoding='utf-8') print("从 JSON 读取的数据:") print(df_json)
💡 提示:保存 CSV 文件时推荐使用 encoding='utf-8-sig'(带 BOM 的 UTF-8),这样用 Excel 打开时不会出现中文乱码问题。

4.5 存储到数据库

对于大量结构化数据,存储到数据库是更好的选择。SQLite 是 Python 内置的轻量级数据库,无需额外安装服务。

Python import sqlite3 import pandas as pd # --- 示例数据 --- data = { '姓名': ['张三', '李四', '王五', '赵六'], '年龄': [25, 30, 28, 35], '城市': ['北京', '上海', '广州', '深圳'], '薪资': [8000, 12000, 9500, 15000] } df = pd.DataFrame(data) # --- 方式1:使用 Pandas 直接写入 SQLite --- conn = sqlite3.connect('output/data.db') df.to_sql('employees', conn, if_exists='replace', index=False) print("数据已写入 SQLite 数据库") # 使用 Pandas 读取 result = pd.read_sql('SELECT * FROM employees WHERE 薪资 > 10000', conn) print("\n薪资大于10000的员工:") print(result) # --- 方式2:使用 SQL 语句操作 --- cursor = conn.cursor() # 插入新记录 cursor.execute( "INSERT INTO employees (姓名, 年龄, 城市, 薪资) VALUES (?, ?, ?, ?)", ('钱七', 32, '杭州', 11000) ) conn.commit() # 更新记录 cursor.execute("UPDATE employees SET 薪资 = 薪资 * 1.1 WHERE 城市 = '北京'") conn.commit() # 查询并统计 cursor.execute("SELECT 城市, COUNT(*), AVG(薪资) FROM employees GROUP BY 城市") for row in cursor.fetchall(): print(f"城市: {row[0]}, 人数: {row[1]}, 平均薪资: {row[2]:.0f}") # 关闭连接 conn.close()
✅ 小技巧:Pandas 的 to_sql() 方法支持 if_exists 参数,可选值为 'fail'(默认,表已存在则报错)、'replace'(替换原表)和 'append'(追加数据)。在增量采集场景中,使用 'append' 可以方便地将新数据追加到已有表中。