爬虫与数据处理

难度等级:⭐⭐ 前置知识:Python 基础 后续衔接:AI Agent 开发、数据存储

学习路径


一、HTTP 请求基础

1.1 requests 库

requests 是 Python 生态中最流行的 HTTP 客户端库,以其简洁的 API 设计著称。它封装了底层 urllib 的复杂性,提供了直观的方法来发送 HTTP 请求。

GET 请求是最常用的请求方式,用于从服务器获取数据:

import requests

response = requests.get('https://api.example.com/data', params={'page': 1, 'limit': 20})
print(response.status_code)  # HTTP 状态码
print(response.json())       # 解析 JSON 响应
print(response.text)         # 文本响应

POST 请求用于向服务器提交数据,常用于表单提交或 API 调用:

data = {'username': 'test', 'password': '123456'}
response = requests.post('https://api.example.com/login', json=data)

Session 对象可以跨请求保持 Cookie 和连接池,适合需要维持登录状态的场景:

session = requests.Session()
session.post('https://example.com/login', json={'user': 'admin', 'pass': 'secret'})
# 后续请求自动携带登录后的 Cookie
response = session.get('https://example.com/profile')

代理设置在爬虫中非常关键,可以隐藏真实 IP:

proxies = {'http': 'http://127.0.0.1:7890', 'https': 'http://127.0.0.1:7890'}
response = requests.get('https://example.com', proxies=proxies, timeout=10)

timeout 参数建议始终设置,避免请求无限期挂起。实际项目中常将超时设为 10-30 秒,并结合重试机制(requests.adapters.HTTPAdapter)提高稳定性。

1.2 httpx 异步请求

httpxrequests 的现代替代品,原生支持异步 I/O 和 HTTP/2。在需要高并发请求的场景下,httpx 的性能优势显著。

AsyncClient 基础用法

import httpx
import asyncio

async def fetch(url):
    async with httpx.AsyncClient() as client:
        response = await client.get(url)
        return response.json()

async def main():
    urls = ['https://api.example.com/1', 'https://api.example.com/2']
    tasks = [fetch(url) for url in urls]
    results = await asyncio.gather(*tasks)
    print(results)

asyncio.run(main())

并发控制:使用 asyncio.Semaphore 限制并发数量,避免对目标服务器造成过大压力:

semaphore = asyncio.Semaphore(10)

async def fetch_with_limit(url):
    async with semaphore:
        return await fetch(url)

HTTP/2 支持httpx 默认支持 HTTP/2,只需安装 http2 额外依赖(pip install httpx[http2]),然后设置 http2=True

async with httpx.AsyncClient(http2=True) as client:
    response = await client.get('https://example.com')

HTTP/2 的多路复用特性可以在单个连接上并行发送多个请求,减少连接建立开销,对于需要频繁请求同一域名的爬虫场景尤为有用。

1.3 请求头与反爬

网站通常通过分析请求头来识别爬虫。合理的请求头配置是绕过基础反爬的第一步。

User-Agent 是最基础的请求头,用于标识客户端身份:

headers = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
}
response = requests.get('https://example.com', headers=headers)

Referer 表示请求来源页面,某些网站会校验此字段:

headers = {
    'Referer': 'https://www.google.com/',
    'User-Agent': 'Mozilla/5.0 ...'
}

Cookie 池是应对账号登录型反爬的常见方案。通过维护多个已登录账号的 Cookie,在请求时随机选取,分散请求频率:

import random

COOKIE_POOL = [
    {'sessionid': 'abc123'},
    {'sessionid': 'def456'},
    {'sessionid': 'ghi789'},
]

def get_random_cookie():
    return random.choice(COOKIE_POOL)

headers = {'Cookie': f"sessionid={get_random_cookie()['sessionid']}"}

此外,AcceptAccept-LanguageAccept-Encoding 等请求头也应尽量模拟真实浏览器行为,降低被识别的概率。


二、HTML 解析

2.1 BeautifulSoup

BeautifulSoup 是 Python 中最常用的 HTML/XML 解析库,配合 lxmlhtml.parser 使用。它的 API 设计直观,适合快速提取页面数据。

基础选择器

from bs4 import BeautifulSoup

html = '<div class="item"><h2>标题</h2><p>内容</p></div>'
soup = BeautifulSoup(html, 'lxml')

# 通过标签名查找
title = soup.find('h2').text  # '标题'

# 通过 class 查找
items = soup.find_all('div', class_='item')

# CSS 选择器
links = soup.select('div.item > a[href]')

遍历文档树

for child in soup.div.children:
    print(child.name, child.text)

# 获取属性
link = soup.find('a')
href = link.get('href')

修改文档树:BeautifulSoup 支持动态修改 HTML 结构,可用于数据清洗或模板生成:

soup.find('h2').string = '新标题'
soup.find('p').decompose()  # 删除节点

BeautifulSoup 的优点是容错能力强,即使 HTML 结构不规范也能正确解析。缺点是性能相对 lxml 的 XPath 较慢,在大规模数据处理时需要注意效率问题。

2.2 lxml / XPath

lxml 是基于 C 语言实现的 XML/HTML 解析库,性能远超 BeautifulSoup。其核心优势在于支持 XPath 表达式,可以用简洁的语法定位复杂节点。

XPath 基础语法

from lxml import etree

html = '<div class="list"><a href="/page1">第一页</a><a href="/page2">第二页</a></div>'
tree = etree.HTML(html)

# 绝对路径
links = tree.xpath('/html/body/div/a/@href')

# 相对路径(推荐)
links = tree.xpath('//div[@class="list"]/a/@href')

# 文本提取
texts = tree.xpath('//a/text()')  # ['第一页', '第二页']

常用 XPath 表达式

表达式 含义
//div 所有 div 元素
//div[@class="test"] class 为 test 的 div
//a[@href] 所有带 href 属性的 a 标签
//ul/li[1] 第一个 li 子元素
//a[contains(@href, "page")] href 包含 “page” 的 a 标签

命名空间处理:解析 XML(如 RSS、SVG)时,命名空间是常见障碍:

namespaces = {'atom': 'http://www.w3.org/2005/Atom'}
titles = tree.xpath('//atom:title/text()', namespaces=namespaces)

lxml 的性能优势在于底层 C 实现,解析速度通常是 BeautifulSoup 的 5-10 倍。对于需要解析大量页面的爬虫项目,推荐使用 lxml 作为解析引擎。

2.3 正则表达式

正则表达式用于处理复杂的文本匹配和提取场景,是 HTML 解析的有力补充。Python 的 re 模块提供了完整的正则支持。

基础匹配

import re

text = '联系电话:138-1234-5678,邮箱:test@example.com'

# 提取手机号
phones = re.findall(r'\d{3}-\d{4}-\d{4}', text)
# ['138-1234-5678']

# 提取邮箱
emails = re.findall(r'[\w.-]+@[\w.-]+\.\w+', text)
# ['test@example.com']

分组提取

pattern = r'(\d{4})年(\d{1,2})月(\d{1,2})日'
match = re.search(pattern, '发布日期:2024年3月15日')
if match:
    year, month, day = match.groups()  # ('2024', '3', '15')

编译优化:对于重复使用的正则,建议预先编译:

phone_pattern = re.compile(r'\d{3}-\d{4}-\d{4}')
phones = phone_pattern.findall(text)

正则表达式的强大之处在于灵活性,但过度使用会导致代码难以维护。建议优先使用 HTML 解析器提取结构化数据,仅在纯文本处理或复杂模式匹配时使用正则。


三、Scrapy 框架

3.1 Scrapy 架构

Scrapy 是一个基于 Twisted 的异步爬虫框架,采用事件驱动架构,天然支持高并发。理解其组件交互是高效使用 Scrapy 的前提。

核心组件

数据流

Spider 发起 Request → Engine → Scheduler 排队
→ Engine → Downloader 下载 → 返回 Response
→ Engine → Spider 解析 → 产出 Item 和新的 Request
→ Item → Pipeline 处理
→ Request → Scheduler 继续排队

Scrapy 的异步特性使其在单机上可以轻松处理数百个并发请求,远超 requests 等同步库的并发能力。

3.2 Spider 开发

Spider 是 Scrapy 的核心,用户在此定义爬取逻辑。Scrapy 提供了多种 Spider 类型以适应不同场景。

基础 Spider

import scrapy

class ExampleSpider(scrapy.Spider):
    name = 'example'
    start_urls = ['https://example.com/page1']

    def parse(self, response):
        title = response.css('h1::text').get()
        yield {'title': title}
        
        # 提取下一页链接
        next_page = response.css('a.next::attr(href)').get()
        if next_page:
            yield response.follow(next_page, self.parse)

CrawlSpider 适用于需要跟随链接爬取全站内容的场景,通过 Rule 定义自动化的链接提取:

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class SiteSpider(CrawlSpider):
    name = 'site'
    start_urls = ['https://example.com']
    rules = (
        Rule(LinkExtractor(allow=r'/article/\d+'), callback='parse_article'),
        Rule(LinkExtractor(allow=r'/page/\d+'), follow=True),  # 只跟进,不回调
    )

    def parse_article(self, response):
        yield {
            'url': response.url,
            'title': response.css('h1::text').get(),
            'content': response.css('.content::text').getall(),
        }

LinkExtractor 支持正则表达式、CSS 选择器等多种方式过滤链接,follow=True 表示继续追踪该页面中的其他链接。

3.3 中间件

中间件是 Scrapy 的扩展点,可以在请求和响应的处理链中插入自定义逻辑。

Downloader Middleware 在请求发出前和响应返回后执行,常用于设置代理、处理 Cookie、重试等:

class ProxyMiddleware:
    def process_request(self, request, spider):
        request.meta['proxy'] = 'http://127.0.0.1:7890'
        return None  # 继续处理

    def process_response(self, request, response, spider):
        if response.status == 403:
            # 被封禁,更换代理后重试
            request.meta['proxy'] = 'http://new-proxy:7890'
            return request
        return response

Spider Middleware 在 Spider 的 parse 方法前后执行,用于过滤或修改 Spider 的输出:

class FilterMiddleware:
    def process_spider_output(self, response, result, spider):
        for item in result:
            if isinstance(item, dict) and item.get('title'):
                yield item

settings.py 中注册中间件并设置优先级(数值越小越先执行):

DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.ProxyMiddleware': 543,
}

3.4 数据存储

Scrapy 通过 Item 定义数据结构,通过 Pipeline 处理数据的存储。

Item 定义

import scrapy

class ArticleItem(scrapy.Item):
    title = scrapy.Field()
    url = scrapy.Field()
    publish_date = scrapy.Field()
    content = scrapy.Field()

Pipeline 示例:将数据写入 JSON 文件:

import json

class JsonPipeline:
    def open_spider(self, spider):
        self.file = open('articles.json', 'w', encoding='utf-8')
        self.file.write('[\n')
        self.first = True

    def close_spider(self, spider):
        self.file.write('\n]')
        self.file.close()

    def process_item(self, item, spider):
        if not self.first:
            self.file.write(',\n')
        self.file.write(json.dumps(dict(item), ensure_ascii=False))
        self.first = False
        return item

导出到数据库

import pymysql

class MySQLPipeline:
    def open_spider(self, spider):
        self.conn = pymysql.connect(
            host='localhost', user='root', password='pwd',
            database='crawler', charset='utf8mb4'
        )
        self.cursor = self.conn.cursor()

    def process_item(self, item, spider):
        sql = 'INSERT INTO articles (title, url, content) VALUES (%s, %s, %s)'
        self.cursor.execute(sql, (item['title'], item['url'], item['content']))
        self.conn.commit()
        return item

    def close_spider(self, spider):
        self.conn.close()

Scrapy 也内置了导出命令:scrapy crawl example -o output.json-o output.csv,适合快速导出数据。

3.5 分布式爬虫

单机爬虫在数据量达到百万级时会遇到瓶颈。Scrapy-Redis 通过 Redis 实现去重队列和任务队列,支持多机协同爬取。

核心原理

配置示例

# settings.py
SCHEDULER = 'scrapy_redis.scheduler.Scheduler'
SCHEDULER_PERSIST = True  # 持久化队列
DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter'
REDIS_URL = 'redis://localhost:6379/0'

ITEM_PIPELINES = {
    'scrapy_redis.pipelines.RedisPipeline': 300,
}

RedisSpider

from scrapy_redis.spiders import RedisSpider

class DistributedSpider(RedisSpider):
    name = 'distributed'
    redis_key = 'distributed:start_urls'  # 从 Redis 获取起始 URL

    def parse(self, response):
        # 解析逻辑
        pass

启动方式:先运行 Spider,然后通过 LPUSH distributed:start_urls https://example.com 向 Redis 推送起始 URL,多个 Spider 实例会自动从队列中获取任务并执行。


四、动态页面处理

4.1 Selenium

Selenium 是浏览器自动化工具,通过 WebDriver 控制真实浏览器,适合处理 JavaScript 渲染的动态页面。

基础配置

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('--headless')  # 无头模式
options.add_argument('--disable-gpu')
driver = webdriver.Chrome(options=options)

driver.get('https://example.com')
content = driver.page_source
driver.quit()

等待机制是 Selenium 的核心。动态页面中元素加载时间不确定,必须使用等待避免竞态条件:

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 显式等待
wait = WebDriverWait(driver, 10)
element = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.content')))

# 隐式等待(全局设置)
driver.implicitly_wait(10)

元素交互

# 点击
driver.find_element(By.CSS_SELECTOR, 'button.submit').click()

# 输入
driver.find_element(By.ID, 'search').send_keys('关键词')

# 下拉选择
from selenium.webdriver.support.ui import Select
select = Select(driver.find_element(By.ID, 'category'))
select.select_by_visible_text('技术')

Selenium 的缺点是性能开销大,每个实例都需要启动完整的浏览器进程。适合页面交互复杂、无法通过 API 逆向的场景。

4.2 Playwright

Playwright 是微软推出的浏览器自动化库,支持 Chromium、Firefox、WebKit。相比 Selenium,它具有更好的性能和更现代的 API。

基础用法

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto('https://example.com')
    content = page.content()
    browser.close()

自动等待是 Playwright 的核心特性,大多数操作会自动等待元素可见、可交互,无需手动设置等待:

# 自动等待元素出现并点击
page.click('button.submit')

# 等待网络空闲
page.wait_for_load_state('networkidle')

请求拦截:可以在请求发出前修改或阻止:

def handle_route(route):
    if 'ads' in route.request.url:
        route.abort()  # 阻止广告请求
    else:
        route.continue_()

page.route('**/*', handle_route)
page.goto('https://example.com')

异步 API

from playwright.async_api import async_playwright
import asyncio

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()
        page = await browser.new_page()
        await page.goto('https://example.com')
        await browser.close()

asyncio.run(main())

Playwright 的性能优于 Selenium,且 API 更加简洁,是现代动态页面爬取的首选工具。

4.3 逆向工程

逆向工程是指通过分析网页的网络请求,找到隐藏的数据接口,从而绕过浏览器直接调用 API。这是效率最高的爬取方式。

API 抓包:使用浏览器开发者工具的 Network 面板,过滤 XHR/Fetch 请求,找到返回 JSON 数据的接口。

参数分析

# 假设抓包发现 API 需要签名参数
# 原始请求:GET /api/data?page=1&sign=a1b2c3d4

import hashlib
import time

def generate_sign(params, secret='my_secret'):
    sorted_params = sorted(params.items())
    query_string = '&'.join(f'{k}={v}' for k, v in sorted_params)
    return hashlib.md5(f'{query_string}{secret}'.encode()).hexdigest()

params = {'page': 1, 'timestamp': int(time.time())}
params['sign'] = generate_sign(params)

常见加密方式

JS 逆向:当加密逻辑在 JavaScript 中时,可以使用以下方法:

  1. 阅读混淆后的 JS 代码,还原加密逻辑
  2. 使用 PyExecJSnode 直接执行 JS 代码
  3. 使用浏览器自动化模拟请求流程

逆向工程的难点在于网站可能随时更新加密逻辑,需要持续维护。但对于数据量大、更新频繁的场景,逆向 API 仍然是最优解。


五、反爬策略与应对

5.1 IP 封禁

IP 封禁是最基础的反爬手段。当同一 IP 在短时间内发起大量请求时,服务器会将其加入黑名单。

代理 IP 池是应对 IP 封禁的核心方案:

import requests

class ProxyPool:
    def __init__(self):
        self.proxies = []
        self.index = 0

    def add_proxy(self, proxy):
        self.proxies.append(proxy)

    def get_proxy(self):
        proxy = self.proxies[self.index % len(self.proxies)]
        self.index += 1
        return proxy

pool = ProxyPool()
pool.add_proxy('http://proxy1:8080')
pool.add_proxy('http://proxy2:8080')

# 使用代理
proxy = pool.get_proxy()
response = requests.get('https://example.com', proxies={'http': proxy})

代理类型

代理池维护:需要定期检测代理可用性,剔除失效代理。可以结合代理 API 服务(如快代理、芝麻代理)自动获取和更新代理列表。

5.2 验证码

验证码用于区分人类和机器,是常见的反爬手段。

打码平台:将验证码图片发送给第三方平台,由人工或 AI 识别后返回结果:

import base64
import requests

def solve_captcha(image_path):
    with open(image_path, 'rb') as f:
        image_data = base64.b64encode(f.read()).decode()
    
    # 调用打码平台 API(以某平台为例)
    response = requests.post('https://api.dama.com/recognize', json={
        'image': image_data,
        'type': 'normal'
    })
    return response.json()['result']

OCR 识别:对于简单的图形验证码,可以使用 Tesseractddddocr 本地识别:

import ddddocr

ocr = ddddocr.DdddOcr()
with open('captcha.png', 'rb') as f:
    result = ocr.classification(f.read())
print(result)  # 输出识别结果

行为模拟:对于滑块验证码,需要模拟人类的拖动轨迹:

import time
import random

def simulate_drag(distance):
    """模拟人类拖动滑块"""
    track = []
    current = 0
    mid = distance * 3 / 4
    t = 0.2
    v = 0
    
    while current < distance:
        if current < mid:
            a = random.randint(2, 5)
        else:
            a = -random.randint(3, 6)
        v0 = v
        v = v0 + a * t
        move = v0 * t + 0.5 * a * t * t
        current += move
        track.append(round(move))
    return track

5.3 频率限制

服务器会统计单位时间内的请求次数,超过阈值则拒绝服务(返回 429 状态码)。

随机延迟

import time
import random

def random_delay(min_sec=1, max_sec=5):
    time.sleep(random.uniform(min_sec, max_sec))

for url in urls:
    requests.get(url)
    random_delay()

并发控制:使用信号量或队列限制并发数:

import threading

semaphore = threading.Semaphore(5)  # 最多 5 个并发

def fetch(url):
    with semaphore:
        return requests.get(url)

指数退避:遇到 429 时,逐步增加等待时间:

def fetch_with_retry(url, max_retries=3):
    for i in range(max_retries):
        response = requests.get(url)
        if response.status_code != 429:
            return response
        wait_time = (2 ** i) + random.random()
        time.sleep(wait_time)
    raise Exception('Max retries exceeded')

5.4 指纹检测

高级反爬系统会检测客户端的指纹特征,包括 TLS 指纹和浏览器指纹。

TLS 指纹:不同 HTTP 客户端在 TLS 握手时的行为特征不同。requests 的 TLS 指纹与真实浏览器差异明显,容易被识别。可以使用 curl_cffi 模拟浏览器的 TLS 指纹:

from curl_cffi import requests

# 模拟 Chrome 浏览器的 TLS 指纹
response = requests.get('https://example.com', impersonate='chrome110')

浏览器指纹:包括 Canvas 指纹、WebGL、字体列表、屏幕分辨率等。使用 Selenium 或 Playwright 时,可以通过配置降低指纹特征:

from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('--disable-blink-features=AutomationControlled')
options.add_experimental_option('excludeSwitches', ['enable-automation'])

Cloudflare 防护:Cloudflare 的 5 秒盾会检查 JavaScript 执行能力和 Cookie 设置。可以使用 cloudscraper 库绕过:

import cloudscraper

scraper = cloudscraper.create_scraper()
response = scraper.get('https://example.com')

六、数据处理

6.1 Pandas

Pandas 是 Python 中最强大的数据分析库,提供了高效的数据结构和丰富的数据处理功能。爬虫获取的数据通常需要经过 Pandas 清洗和转换后才能使用。

DataFrame 基础

import pandas as pd

# 从字典创建
data = {
    'name': ['Alice', 'Bob', 'Charlie'],
    'age': [25, 30, 35],
    'city': ['Beijing', 'Shanghai', 'Guangzhou']
}
df = pd.DataFrame(data)

# 从 CSV 读取
df = pd.read_csv('data.csv', encoding='utf-8')

# 查看数据
print(df.head())      # 前 5 行
print(df.info())      # 列信息
print(df.describe())  # 统计摘要

数据清洗

# 删除缺失值
df = df.dropna()

# 填充缺失值
df['age'] = df['age'].fillna(df['age'].median())

# 去重
df = df.drop_duplicates()

# 类型转换
df['age'] = df['age'].astype(int)
df['date'] = pd.to_datetime(df['date'])

分组聚合

# 按城市分组,计算平均年龄
result = df.groupby('city')['age'].mean()

# 多列聚合
result = df.groupby('city').agg({
    'age': ['mean', 'max', 'min'],
    'name': 'count'
})

时间序列

# 设置时间索引
df['date'] = pd.to_datetime(df['date'])
df = df.set_index('date')

# 按周重采样
weekly = df.resample('W').mean()

# 滚动平均
df['rolling_avg'] = df['value'].rolling(window=7).mean()

Pandas 的链式调用风格使代码更加简洁:

result = (
    pd.read_csv('data.csv')
    .dropna()
    .query('age > 18')
    .groupby('city')
    .mean()
)

6.2 NumPy

NumPy 是 Python 科学计算的基础库,提供了高性能的多维数组对象和运算功能。Pandas 的底层数据结构就是基于 NumPy 构建的。

数组创建

import numpy as np

a = np.array([1, 2, 3])
b = np.zeros((3, 3))
c = np.ones((2, 4))
d = np.random.rand(3, 3)  # 随机数组
e = np.arange(0, 10, 2)   # [0, 2, 4, 6, 8]

矩阵运算

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])

# 矩阵乘法
c = np.dot(a, b)

# 逐元素运算
d = a * b
e = a + 2

# 转置
f = a.T

广播机制:不同形状的数组可以自动扩展后进行运算:

a = np.array([[1, 2, 3], [4, 5, 6]])  # shape (2, 3)
b = np.array([10, 20, 30])            # shape (3,)

result = a + b  # b 自动广播到 (2, 3)
# [[11, 22, 33], [14, 25, 36]]

统计函数

data = np.random.randn(1000)
print(data.mean())    # 均值
print(data.std())     # 标准差
print(np.median(data))  # 中位数
print(np.percentile(data, 95))  # 95 分位数

NumPy 的性能优势在于底层 C 实现和向量化运算,避免了 Python 循环的开销。在数据处理中,尽量使用 NumPy 的内置函数代替循环是提升性能的关键。

6.3 数据可视化

数据可视化帮助理解数据分布和趋势。Matplotlib 是 Python 最基础的可视化库,Seaborn 在其之上提供了更高级的统计图表。

Matplotlib 基础

import matplotlib.pyplot as plt

x = [1, 2, 3, 4, 5]
y = [2, 4, 6, 8, 10]

plt.figure(figsize=(8, 6))
plt.plot(x, y, marker='o', linestyle='-', color='b')
plt.title('Line Chart')
plt.xlabel('X')
plt.ylabel('Y')
plt.grid(True)
plt.savefig('chart.png', dpi=300)
plt.show()

常用图表类型

# 柱状图
plt.bar(['A', 'B', 'C'], [10, 20, 15])

# 散点图
plt.scatter(x, y, c='red', alpha=0.5)

# 直方图
plt.hist(data, bins=30, edgecolor='black')

# 饼图
plt.pie([30, 40, 30], labels=['A', 'B', 'C'], autopct='%1.1f%%')

Seaborn 统计图表

import seaborn as sns

# 设置主题
sns.set_theme(style='whitegrid')

# 加载示例数据集
tips = sns.load_dataset('tips')

# 散点图 + 回归线
sns.lmplot(x='total_bill', y='tip', data=tips)

# 箱线图
sns.boxplot(x='day', y='total_bill', data=tips)

# 热力图(相关性矩阵)
corr = tips.corr(numeric_only=True)
sns.heatmap(corr, annot=True, cmap='coolwarm')

子图布局

fig, axes = plt.subplots(2, 2, figsize=(12, 10))
axes[0, 0].plot(x, y)
axes[0, 1].bar(['A', 'B'], [1, 2])
axes[1, 0].scatter(x, y)
axes[1, 1].hist(data)
plt.tight_layout()

可视化不仅是展示工具,也是数据分析的重要手段。通过图表可以快速发现异常值、分布特征和变量间的关系,为后续建模提供参考。


七、爬虫伦理与合规

7.1 robots.txt

robots.txt 是网站用来告知爬虫哪些页面可以或不可以被抓取的协议文件,位于网站根目录下(如 https://example.com/robots.txt)。

协议格式

User-agent: *
Disallow: /admin/
Disallow: /private/
Allow: /public/

User-agent: Googlebot
Allow: /

Python 解析

from urllib.robotparser import RobotFileParser

rp = RobotFileParser()
rp.set_url('https://example.com/robots.txt')
rp.read()

print(rp.can_fetch('*', 'https://example.com/public/page'))  # True
print(rp.can_fetch('*', 'https://example.com/admin/login'))  # False

遵守 robots.txt 是爬虫的基本礼仪。虽然技术上可以忽略该文件,但从法律和道德角度,违反协议可能导致 IP 被封甚至面临法律风险。

7.2 数据使用边界

爬虫技术本身是中性的,但数据的使用方式可能涉及法律风险。了解并遵守相关法规是每个开发者的责任。

个人隐私保护

商业数据

版权保护

合规建议

  1. 明确爬取目的,仅采集必要的数据
  2. 控制爬取频率,避免对目标服务器造成负担
  3. 尊重网站的 robots.txt 和服务条款
  4. 对爬取的数据妥善保管,避免泄露
  5. 如涉及商业用途,建议咨询法律专业人士

八、学习资源推荐

官方文档

推荐书籍

在线课程

实践项目建议

  1. 从简单的静态网站开始,练习 requests + BeautifulSoup
  2. 尝试爬取需要登录的网站,理解 Session 和 Cookie
  3. 学习 Scrapy 框架,构建中等规模的爬虫项目
  4. 处理动态页面,掌握 Playwright/Selenium
  5. 研究反爬策略,学习代理池和验证码处理
  6. 使用 Pandas 进行数据清洗和分析
  7. 将爬取结果可视化,形成完整的数据处理流程