爬虫与数据处理
难度等级:⭐⭐ 前置知识:Python 基础 后续衔接:AI Agent 开发、数据存储
学习路径
- 入门阶段:掌握 HTTP 请求和 HTML 解析
- 进阶阶段:能够使用 Scrapy 构建分布式爬虫
- 精通阶段:能够处理反爬策略和大规模数据采集
一、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 的前提。
核心组件:
- Engine(引擎):负责控制整个系统的数据流,触发事件
- Scheduler(调度器):接收引擎发送的请求,入队并去重,后续返回给引擎
- Downloader(下载器):负责下载网页,返回给 Spider
- Spider(爬虫):用户编写的解析逻辑,提取数据和新的请求
- Item Pipeline(管道):处理 Spider 提取的 Item,进行清洗、验证、存储
数据流:
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 实现去重队列和任务队列,支持多机协同爬取。
核心原理:
- 使用 Redis 的
SET数据结构实现全局去重(request_fingerprint) - 使用 Redis 的
LIST作为任务队列,多个 Spider 实例共享 - 支持断点续爬,暂停后恢复不会丢失任务
配置示例:
# 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)
常见加密方式:
- MD5/SHA:常用于参数签名,不可逆
- AES/DES:对称加密,用于数据体加密传输
- RSA:非对称加密,用于密钥交换
- Base64:编码方式,非加密,常用于混淆
JS 逆向:当加密逻辑在 JavaScript 中时,可以使用以下方法:
- 阅读混淆后的 JS 代码,还原加密逻辑
- 使用
PyExecJS或node直接执行 JS 代码 - 使用浏览器自动化模拟请求流程
逆向工程的难点在于网站可能随时更新加密逻辑,需要持续维护。但对于数据量大、更新频繁的场景,逆向 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})
代理类型:
- 透明代理:目标服务器可以获取真实 IP,仅用于加速访问
- 匿名代理:隐藏真实 IP,但会标识为代理
- 高匿代理:完全隐藏代理身份,最安全
代理池维护:需要定期检测代理可用性,剔除失效代理。可以结合代理 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: /
User-agent:指定规则适用的爬虫名称,*表示所有爬虫Disallow:禁止抓取的路径Allow:允许抓取的路径(优先级高于 Disallow)
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 数据使用边界
爬虫技术本身是中性的,但数据的使用方式可能涉及法律风险。了解并遵守相关法规是每个开发者的责任。
个人隐私保护:
- 不爬取包含个人身份信息(PII)的数据,如身份证号、手机号、住址
- 即使数据公开,也应尊重隐私,避免大规模采集个人信息
- 遵守《个人信息保护法》(PIPL)和 GDPR 等法规
商业数据:
- 不爬取需要付费访问的内容
- 不绕过付费墙或登录验证获取商业数据
- 不将爬取的数据用于直接竞争或转售
版权保护:
- 不复制受版权保护的内容(如文章全文、图片)
- 爬取的数据仅用于个人学习、研究或合理使用
- 商业用途需获得数据所有者的授权
合规建议:
- 明确爬取目的,仅采集必要的数据
- 控制爬取频率,避免对目标服务器造成负担
- 尊重网站的
robots.txt和服务条款 - 对爬取的数据妥善保管,避免泄露
- 如涉及商业用途,建议咨询法律专业人士
八、学习资源推荐
官方文档
推荐书籍
- 《Python 3 网络爬虫开发实战》- 崔庆才
- 《Python 爬虫开发与项目实战》- 范传辉
- 《利用 Python 进行数据分析》- Wes McKinney(Pandas 作者)
在线课程
- 慕课网:Python 爬虫入门到精通
- Coursera:Using Python to Access Web Data
- B 站:Scrapy 框架实战教程
实践项目建议
- 从简单的静态网站开始,练习 requests + BeautifulSoup
- 尝试爬取需要登录的网站,理解 Session 和 Cookie
- 学习 Scrapy 框架,构建中等规模的爬虫项目
- 处理动态页面,掌握 Playwright/Selenium
- 研究反爬策略,学习代理池和验证码处理
- 使用 Pandas 进行数据清洗和分析
- 将爬取结果可视化,形成完整的数据处理流程