爬虫与数据处理

难度等级:⭐⭐ 前置知识: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 异步请求

httpx 是 requests 的现代替代品,原生支持异步 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']}"}

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


二、HTML 解析

2.1 BeautifulSoup

BeautifulSoup 是 Python 中最常用的 HTML/XML 解析库,配合 lxml 或 html.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. 使用 PyExecJS 或 node 直接执行 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 识别:对于简单的图形验证码,可以使用 Tesseract 或 ddddocr 本地识别:

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. 将爬取结果可视化,形成完整的数据处理流程