1. 爬虫基础:HTTP请求与网页抓取入门
当我们需要从互联网获取数据时,爬虫技术是最直接有效的手段之一。作为从业十余年的数据工程师,我处理过各种规模的爬虫项目,从简单的单页抓取到分布式爬虫系统。今天就从最基础的HTTP请求开始,分享如何正确编写爬虫的基本请求模块。
HTTP请求是爬虫与网站服务器对话的基础语言。就像去餐厅点餐,你需要用服务员能听懂的方式表达需求(GET/POST请求),然后等待厨房(服务器)准备菜品(返回响应)。一个完整的爬虫请求流程包括:建立连接→发送请求→接收响应→解析内容。这看似简单,但实际开发中会遇到各种"意外情况"——反爬机制、网络波动、页面结构变化等。
提示:在开始编写爬虫前,务必检查目标网站的robots.txt文件(如https://www.example.com/robots.txt),了解网站允许抓取的范围和频率限制。这是爬虫开发者的基本职业操守。
2. 核心工具选型:主流爬虫框架对比
2.1 Python生态中的爬虫框架
目前主流的Python爬虫框架主要有以下几种:
Scrapy:工业级框架,适合中大型项目
- 优势:异步处理、中间件扩展、内置选择器
- 缺点:学习曲线较陡,对小项目略显笨重
Requests + BeautifulSoup:轻量级组合
- 优势:简单易用,适合快速开发
- 缺点:缺乏自动化调度能力
Playwright/Selenium:浏览器自动化工具
- 优势:能处理JavaScript渲染的页面
- 缺点:资源消耗大,速度较慢
对于初学者,我建议从Requests库入手。它提供了人性化的API,比如一个基本的GET请求只需要:
import requests response = requests.get('https://example.com') print(response.text)2.2 框架选择的核心考量因素
选择框架时需要评估:
- 项目规模(小规模抓取还是持续采集)
- 目标网站技术栈(静态HTML还是动态渲染)
- 反爬强度(是否需要模拟浏览器行为)
- 团队熟悉度(优先选择团队熟悉的技术)
在我的实战经验中,约70%的常规爬取任务用Requests+BeautifulSoup就能解决,20%需要Scrapy这样的专业框架,剩下10%复杂场景才需要动用浏览器自动化工具。
3. HTTP请求实战详解
3.1 请求头(Headers)的精心配置
请求头是爬虫与服务器"沟通礼仪"的关键。常见的必要头信息包括:
headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Referer': 'https://www.google.com/', 'Accept-Encoding': 'gzip, deflate' }注意:User-Agent不要直接使用python-requests库的默认值,这等于告诉网站你是个爬虫。建议模拟主流浏览器的UA字符串。
3.2 请求参数与超时设置
GET请求传参有两种方式:
- URL直接拼接参数:
url = 'https://api.example.com/search?q=python&page=1' - 使用params参数:
params = {'q': 'python', 'page': 1} response = requests.get(url, params=params)
POST请求通常需要设置请求体:
data = {'username': 'admin', 'password': '123456'} response = requests.post(url, data=data)务必设置合理的超时时间(连接超时和读取超时):
# 连接超时3秒,读取超时10秒 response = requests.get(url, timeout=(3, 10))3.3 响应处理与状态码解析
HTTP状态码是服务器给出的"应答信号",常见的有:
- 200:请求成功
- 301/302:重定向
- 403:禁止访问(可能触发了反爬)
- 404:页面不存在
- 500:服务器内部错误
完整的响应处理流程应该包括:
try: response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() # 自动检查4xx/5xx错误 if response.status_code == 200: content = response.content.decode('utf-8') # 进一步处理内容... except requests.exceptions.RequestException as e: print(f"请求失败: {e}")4. 反爬应对策略与伦理规范
4.1 常见反爬机制及应对方案
- User-Agent检测:随机轮换UA字符串
- 请求频率限制:添加延迟(time.sleep)
- IP封禁:使用代理IP池
- 验证码:OCR识别或人工打码
- 行为分析:模拟人类操作间隔
建议的请求间隔设置:
import random import time # 随机延迟1-3秒 time.sleep(random.uniform(1, 3))4.2 爬虫开发者的伦理守则
- 遵守robots.txt协议
- 控制请求频率(单IP每秒不超过1-2次)
- 不抓取敏感/隐私数据
- 设置明显的User-Agent标识
- 尊重网站的服务条款
我曾经参与的一个电商项目,因为未控制好爬取频率,导致对方服务器负载激增,最终被永久封禁IP段。这个教训让我深刻认识到:爬虫不仅要考虑技术实现,更要考虑对目标网站的影响。
5. 实战案例:抓取新闻网站数据
5.1 目标分析
以抓取某新闻网站头条为例,我们需要:
- 分析页面结构(使用浏览器开发者工具)
- 确定数据定位方式(CSS选择器/XPath)
- 设计异常处理机制
5.2 完整代码实现
import requests from bs4 import BeautifulSoup import time import random def fetch_news(url): headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)', 'Accept': 'text/html,application/xhtml+xml' } try: # 随机延迟 time.sleep(random.uniform(1, 2)) response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() soup = BeautifulSoup(response.text, 'html.parser') news_list = [] # 假设新闻条目有class="news-item" for item in soup.select('.news-item'): title = item.select_one('h2').text.strip() link = item.find('a')['href'] news_list.append({'title': title, 'url': link}) return news_list except Exception as e: print(f"抓取失败: {e}") return [] # 使用示例 news = fetch_news('https://news.example.com/latest') for item in news: print(item['title'], item['url'])5.3 常见问题排查
- 返回空数据:检查选择器是否匹配当前页面结构
- 403禁止访问:尝试更新请求头或使用代理
- 连接超时:检查网络或增加超时时间
- 编码错误:明确指定响应编码(如response.encoding='utf-8')
6. 进阶技巧与性能优化
6.1 会话(Session)保持
对于需要登录或保持状态的网站,使用Session对象:
session = requests.Session() # 登录 login_data = {'user': 'name', 'pass': 'word'} session.post(login_url, data=login_data) # 后续请求会自动保持cookies profile = session.get(profile_url)6.2 异步请求加速
对于IO密集型任务,可以使用aiohttp实现异步:
import aiohttp import asyncio async def fetch(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.text() # 批量抓取 urls = ['url1', 'url2', 'url3'] tasks = [fetch(url) for url in urls] results = await asyncio.gather(*tasks)6.3 代理IP的使用
当遇到IP限制时,可以通过代理发送请求:
proxies = { 'http': 'http://proxy_ip:port', 'https': 'http://proxy_ip:port' } response = requests.get(url, proxies=proxies)7. 数据存储与后续处理
7.1 常见存储方案
文件存储:
import json with open('data.json', 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False)数据库存储:
import sqlite3 conn = sqlite3.connect('data.db') cursor = conn.cursor() cursor.execute('INSERT INTO news VALUES (?, ?)', (title, url)) conn.commit()
7.2 数据清洗建议
原始网页数据通常需要:
- 去除空白字符(strip())
- 处理特殊编码(HTML实体解码)
- 验证URL有效性
- 去重处理
from html import unescape clean_text = unescape(raw_text).strip()在长期爬虫项目中,我建议采用"采集→清洗→存储"的分阶段架构,每个环节独立处理,这样当某个环节需要调整时不会影响整体流程。
编写生产级爬虫时,一定要加入完善的日志记录:
import logging logging.basicConfig( filename='spider.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) try: # 爬取代码... logging.info(f"成功抓取 {url}") except Exception as e: logging.error(f"抓取失败 {url}: {str(e)}")最后提醒一点:随着网站前端技术发展,越来越多的内容通过JavaScript动态加载。对于这类网站,单纯的Requests可能无法获取完整内容,此时需要考虑使用Selenium或Playwright等浏览器自动化工具。但这也意味着更高的资源消耗和更复杂的维护成本,所以技术选型需要根据实际需求权衡。