你是不是也遇到过这样的场景:想分析某个行业的数据,却发现没有现成的API;想追踪竞品价格,只能每天手动复制粘贴;想批量下载一些公开资料,却要一个个点击保存。这些重复、低效的数据收集工作,正是Python爬虫要解决的痛点。
很多人对爬虫的第一印象是“能自动抓取网页数据”,这没错,但理解仅停留于此,往往会踩进大坑。爬虫的真正价值,在于将非结构化的网页信息,转化为结构化、可分析的数据资产。它不只是“抓取”,更核心的是“解析”和“存储”。从技术角度看,一个健壮的爬虫项目,70%的精力可能花在处理反爬机制、数据清洗和异常流程上,而不仅仅是写几行请求代码。
本文不会教你如何“暴力”抓取所有网站,那既不道德也不可持续。相反,我们会聚焦于合法、合规、稳健的Python爬虫实践。你将了解到,如何像一名专业的开发者那样,从零构建一个具备良好工程素养的爬虫:包括环境搭建、请求发送、数据解析、反反爬策略、数据存储,以及最重要的——如何设计一个易于维护和扩展的爬虫架构。
无论你是想入门数据分析、做市场调研,还是希望自动化一些日常工作,掌握爬虫都能让你事半功倍。接下来,我们从最核心的问题开始:在动手写代码之前,你必须知道哪些规则和原理?
1. 爬虫的核心:不只是技术,更是规则与伦理
在写下第一行import requests之前,我们必须先划定边界。爬虫技术本身是中立的,但使用方式决定了它的性质。无视规则的爬虫行为,轻则导致IP被封、数据获取失败,重则可能面临法律风险。
1.1 必须遵守的规则:Robots协议与法律法规
Robots协议(robots.txt)是网站与爬虫之间的“君子协定”。它存放在网站根目录下(如https://example.com/robots.txt),明确告知爬虫哪些目录可以访问,哪些禁止抓取。
例如,查看淘宝的robots协议,你会发现大量目录对通用爬虫是禁止的:
User-agent: * Disallow: /这并不意味着完全不能抓取,但提示你需要非常谨慎,或寻求官方API。而像维基百科等网站则相对开放。一个负责任的爬虫,应该首先检查并尊重目标网站的robots.txt。
除了技术协议,还需关注:
- 《数据安全法》与《个人信息保护法》:严禁爬取涉及个人隐私、国家安全、商业秘密的数据。
- 网站服务条款(Terms of Service):很多网站明确禁止未经授权的自动化数据抓取。
- 访问频率限制:过于频繁的请求会对目标服务器造成压力,构成拒绝服务攻击(DoS)风险。
核心原则:只爬取公开、非敏感、且对服务器影响小的数据。对于商业网站,优先寻找官方API;对于学术或公益网站,也应控制抓取速度。
1.2 爬虫的基本工作流程
理解流程,才能写出结构清晰的代码。一个典型的爬虫任务遵循以下步骤:
- 明确目标:确定要抓取哪些网站、哪些页面、哪些具体数据(如商品标题、价格、评论)。
- 分析页面:使用浏览器开发者工具(F12)分析目标页面的网络请求(XHR/Fetch)和HTML结构,确定数据来源(是直接渲染在HTML中,还是通过Ajax接口加载的JSON)。
- 发送请求:使用HTTP库(如
requests)模拟浏览器,向目标URL发送请求,获取响应内容(HTML、JSON等)。 - 解析数据:从响应内容中提取所需信息。常用工具有:
- 正则表达式:灵活但复杂,适合简单或固定模式。
- BeautifulSoup:基于HTML/XML解析树,语法简单直观,适合初学者。
- lxml:解析速度快,功能强大,适合处理大规模数据。
- 直接处理JSON:如果数据来自API接口,直接用
json.loads()解析。
- 保存数据:将解析后的结构化数据持久化,如存入CSV文件、JSON文件、MySQL/MongoDB数据库等。
- 处理异常与反爬:处理网络超时、页面不存在、IP被封、验证码等异常情况,并采取相应策略(如使用代理IP、添加请求头、降低频率)。
2. 环境准备:搭建你的Python爬虫工作台
工欲善其事,必先利其器。一个稳定的开发环境能避免很多后续麻烦。
2.1 Python与包管理工具安装
Python版本:推荐使用Python 3.8及以上版本,它们有更好的异步支持和库兼容性。避免使用Python 2.x,其已停止维护。
安装步骤(以Windows为例):
- 访问 Python官网 下载安装包。
- 运行安装程序,务必勾选“Add Python 3.x to PATH”,这样可以在命令行直接使用
python和pip命令。 - 安装完成后,打开命令提示符(CMD)或PowerShell,验证安装:
成功显示版本号即表示安装正确。python --version pip --version
包管理工具pip:Python自带的包管理器,用于安装第三方库。建议立即升级到最新版:
python -m pip install --upgrade pip2.2 虚拟环境管理:隔离项目依赖
不同爬虫项目可能依赖不同版本的库。使用虚拟环境可以为每个项目创建独立的Python运行环境,避免依赖冲突。
使用venv(Python内置):
# 在项目目录下,创建名为‘venv’的虚拟环境 python -m venv venv # 激活虚拟环境 (Windows) venv\Scripts\activate # 激活虚拟环境 (macOS/Linux) source venv/bin/activate # 激活后,命令行提示符前会出现‘(venv)’字样 # 在此环境下安装的包仅对本项目有效 # 退出虚拟环境 deactivate2.3 核心库安装
激活虚拟环境后,安装爬虫最常用的几个库:
pip install requests beautifulsoup4 lxml pandas pymysql- requests:人性化的HTTP库,用于发送网络请求。
- beautifulsoup4:HTML/XML解析库,配合
lxml解析器速度更快。 - lxml:另一个高效的解析库,也是BeautifulSoup的解析器选项之一。
- pandas:数据处理和分析神器,可方便地将数据导出为Excel/CSV。
- pymysql:用于连接MySQL数据库。
IDE选择:推荐使用VS Code或PyCharm。VS Code轻量且插件丰富,PyCharm是专业的Python IDE,调试功能强大。确保为IDE配置好已激活的虚拟环境解释器。
3. 第一只爬虫:从静态网页抓取开始
让我们从一个最简单的例子开始:抓取一个静态网页的标题。我们以一个练习网站 http://httpbin.org 为例,它专门用于HTTP请求测试。
3.1 使用Requests获取网页内容
创建一个名为first_spider.py的文件:
import requests # 目标URL url = 'http://httpbin.org/get' # 设置请求头,模拟浏览器访问 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' } try: # 发送GET请求 response = requests.get(url, headers=headers) # 检查请求是否成功 (状态码200表示成功) response.raise_for_status() # 如果状态码不是200,将抛出HTTPError异常 # 打印状态码和响应内容(JSON格式) print(f"状态码: {response.status_code}") print("响应内容:") print(response.json()) # httpbin.org/get 返回的是JSON except requests.exceptions.RequestException as e: print(f"请求出错: {e}") except ValueError as e: print(f"解析JSON出错: {e}")运行这个脚本,你会看到返回的JSON数据,其中包含了你的请求头、IP等信息。User-Agent是告诉服务器你是什么客户端,不加的话容易被识别为爬虫。
3.2 使用BeautifulSoup解析HTML
静态网页的数据通常嵌套在HTML标签中。我们以抓取“豆瓣电影Top250”的首页电影名称为例(请注意,实际抓取前应查看其robots.txt)。
import requests from bs4 import BeautifulSoup url = 'https://movie.douban.com/top250' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } try: resp = requests.get(url, headers=headers) resp.raise_for_status() # 指定使用‘lxml’解析器,需要先安装 lxml 库 soup = BeautifulSoup(resp.text, 'lxml') # 查找所有class为‘title’的span标签(包含电影名) # 注意:实际豆瓣的HTML结构可能变化,此处仅为示例 title_tags = soup.find_all('span', class_='title') for idx, tag in enumerate(title_tags, 1): # 过滤掉英文/其他语言片名(通常第一个是中文片名) if '/' not in tag.text: # 简单的过滤逻辑 print(f"{idx}. {tag.text}") except Exception as e: print(f"抓取或解析失败: {e}")关键点解析:
BeautifulSoup(resp.text, 'lxml'):将HTML文本转化为一个可遍历的树形结构对象。soup.find_all(‘span’, class_=‘title’):查找所有标签名为span且class属性为title的元素。class_带下划线是为了避免与Python关键字class冲突。- 网页结构可能随时变更,因此选择器(如
‘span.title’)需要根据实际情况调整。熟练使用浏览器的“检查元素”功能是爬虫工程师的基本功。
4. 应对挑战:反爬虫机制与应对策略
如果所有网站都像上面那样简单,爬虫就毫无难度了。现实是,网站会使用各种反爬虫技术来保护数据和服务器资源。
4.1 常见的反爬虫手段
| 反爬手段 | 表现 | 目的 |
|---|---|---|
| User-Agent检测 | 检查请求头中的User-Agent,如果为空或为爬虫库默认值,则拒绝服务。 | 识别并拦截非浏览器流量。 |
| IP频率限制 | 同一IP在短时间内请求次数过多,会被暂时或永久封禁。 | 防止服务器过载和资源滥用。 |
| 验证码 | 在多次请求后弹出图片、滑块或点选验证码。 | 区分人类用户和自动化程序。 |
| 动态数据加载 | 数据通过JavaScript异步加载(Ajax),初始HTML中不包含有效数据。 | 增加直接解析HTML的难度。 |
| 请求参数签名 | 请求必须携带一个由特定算法生成的、会变化的参数(如token,sign)。 | 防止请求被轻易模拟和重放。 |
| Cookie/Session验证 | 需要先登录或完成一系列操作获得有效Cookie,才能访问数据。 | 确保用户状态和权限。 |
4.2 基础应对策略
策略一:伪装请求头
这是最基本的伪装。除了User-Agent,有时还需要携带Referer(来源页)、Accept-Language等。
headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/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/', # 模拟从谷歌跳转而来 'Connection': 'keep-alive', }策略二:设置请求间隔
在循环抓取时,在请求之间加入随机延时,模拟人类操作。
import time import random for page in range(1, 11): # ... 发送请求,解析数据 ... time.sleep(random.uniform(1, 3)) # 随机等待1-3秒策略三:处理Cookie
使用requests.Session()对象,它可以自动保存和发送Cookie,模拟一个浏览器会话。
import requests session = requests.Session() # 第一次请求,获取并保存Cookie login_resp = session.post(‘登录URL‘, data={‘username‘: ‘...‘, ‘password‘: ‘...‘}) # 后续请求,session会自动携带Cookie data_resp = session.get(‘数据URL‘)策略四:使用代理IP
当IP被封锁时,需要通过代理IP池来更换出口IP。可以使用付费代理服务,或寻找免费的HTTP代理(但稳定性差)。
proxies = { ‘http‘: ‘http://12.34.56.78:8080‘, ‘https‘: ‘http://12.34.56.78:8080‘, } try: response = requests.get(url, headers=headers, proxies=proxies, timeout=10) except requests.exceptions.ProxyError: print(‘代理失效,更换下一个代理IP‘)4.3 进阶挑战:动态页面与Selenium
当数据由JavaScript动态渲染时,requests获取的初始HTML是空的。此时需要能驱动浏览器的工具,如Selenium或Playwright。
使用Selenium示例:
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time # 配置Chrome浏览器选项(无头模式,不显示图形界面) options = webdriver.ChromeOptions() options.add_argument(‘--headless‘) # 无头模式 options.add_argument(‘--disable-gpu‘) options.add_argument(‘user-agent=Mozilla/5.0 ...‘) driver = webdriver.Chrome(options=options) # 确保已下载对应版本的ChromeDriver try: driver.get(‘https://example.com/dynamic-page‘) # 显式等待,直到某个元素加载出来 element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CLASS_NAME, “target-class“)) ) # 此时页面已完全渲染,可以获取HTML page_source = driver.page_source # 接下来可以用BeautifulSoup解析page_source # ... finally: driver.quit() # 务必关闭浏览器,释放资源注意:Selenium速度慢、资源消耗大,应仅作为获取动态数据的最后手段。优先尝试分析网页的网络请求,看能否直接找到数据接口(XHR/Fetch)。
5. 完整项目实战:爬取公开图书信息并存储
我们将构建一个相对完整的爬虫项目,目标是从一个公开的图书信息网站(例如:http://books.toscrape.com/,一个专为爬虫练习设计的网站)抓取图书列表,并将数据存储到CSV文件和MySQL数据库中。
5.1 项目结构规划
book_scraper/ ├── scraper.py # 主爬虫逻辑 ├── config.py # 配置文件(数据库连接等) ├── utils.py # 工具函数(请求、解析、存储) ├── requirements.txt # 项目依赖 └── data/ # 存放输出的CSV文件5.2 核心代码实现
1. 配置文件config.py
# 数据库配置 (请根据实际情况修改) DB_CONFIG = { ‘host‘: ‘localhost‘, ‘port‘: 3306, ‘user‘: ‘your_username‘, ‘password‘: ‘your_password‘, ‘database‘: ‘book_data‘, ‘charset‘: ‘utf8mb4‘, } # 爬虫配置 SPIDER_CONFIG = { ‘base_url‘: ‘http://books.toscrape.com/catalogue/page-{}.html‘, ‘start_page‘: 1, ‘end_page‘: 5, # 只爬取5页作为演示 ‘request_headers‘: { ‘User-Agent‘: ‘Mozilla/5.0 ...‘, }, ‘request_timeout‘: 10, ‘delay_range‘: (1, 2), # 请求延迟范围(秒) }2. 工具函数utils.py
import requests import time import random from bs4 import BeautifulSoup import pymysql from config import DB_CONFIG, SPIDER_CONFIG def get_page_html(url, headers=None): """发送请求,获取页面HTML""" if headers is None: headers = SPIDER_CONFIG[‘request_headers‘] try: resp = requests.get(url, headers=headers, timeout=SPIDER_CONFIG[‘request_timeout‘]) resp.raise_for_status() # 检查编码,避免乱码 resp.encoding = resp.apparent_encoding return resp.text except requests.exceptions.RequestException as e: print(f“请求 {url} 失败: {e}“) return None def parse_book_list(html): """解析图书列表页,提取每本书的详情链接和基本信息""" soup = BeautifulSoup(html, ‘lxml‘) books = [] # 根据books.toscrape.com的实际结构查找 book_elements = soup.select(‘article.product_pod‘) # CSS选择器 for elem in book_elements: book = {} # 提取书名 title_tag = elem.select_one(‘h3 a‘) book[‘title‘] = title_tag[‘title‘] if title_tag else ‘N/A‘ # 提取详情页链接 book[‘detail_url‘] = ‘http://books.toscrape.com/catalogue/‘ + title_tag[‘href‘] if title_tag else ‘N/A‘ # 提取价格 price_tag = elem.select_one(‘p.price_color‘) book[‘price‘] = price_tag.get_text(strip=True) if price_tag else ‘N/A‘ # 提取库存状态 stock_tag = elem.select_one(‘p.instock.availability‘) book[‘stock‘] = stock_tag.get_text(strip=True) if stock_tag else ‘N/A‘ books.append(book) return books def parse_book_detail(html): """解析图书详情页,提取更详细的信息""" soup = BeautifulSoup(html, ‘lxml‘) detail = {} # 提取产品描述 desc_tag = soup.select_one(‘article.product_page > p‘) detail[‘description‘] = desc_tag.get_text(strip=True) if desc_tag else ‘N/A‘ # 提取产品分类 category_tag = soup.select(‘ul.breadcrumb li a‘)[2] # 根据网站结构定位 detail[‘category‘] = category_tag.get_text(strip=True) if category_tag else ‘N/A‘ # 提取评价等级 rating_tag = soup.select_one(‘p.star-rating‘) if rating_tag: rating_class = rating_tag[‘class‘][1] # 例如 ‘Three‘ detail[‘rating‘] = rating_class else: detail[‘rating‘] = ‘N/A‘ return detail def save_to_csv(books, filename=‘data/books.csv‘): """将图书列表保存到CSV文件""" import csv import os os.makedirs(‘data‘, exist_ok=True) if not books: return keys = books[0].keys() with open(filename, ‘w‘, newline=‘‘, encoding=‘utf-8-sig‘) as f: # utf-8-sig支持Excel中文 writer = csv.DictWriter(f, fieldnames=keys) writer.writeheader() writer.writerows(books) print(f“数据已保存到 {filename}“) def init_database(): """初始化数据库连接和表结构""" connection = pymysql.connect(**DB_CONFIG) try: with connection.cursor() as cursor: # 创建表(如果不存在) create_table_sql = “““ CREATE TABLE IF NOT EXISTS books ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(500) NOT NULL, price VARCHAR(50), stock VARCHAR(100), description TEXT, category VARCHAR(200), rating VARCHAR(50), detail_url VARCHAR(500), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; “““ cursor.execute(create_table_sql) connection.commit() print(“数据库表初始化完成。“) finally: connection.close() def save_to_mysql(book_data): """将单条图书数据插入MySQL数据库""" connection = pymysql.connect(**DB_CONFIG) try: with connection.cursor() as cursor: sql = “““ INSERT INTO books (title, price, stock, description, category, rating, detail_url) VALUES (%s, %s, %s, %s, %s, %s, %s) “““ cursor.execute(sql, ( book_data.get(‘title‘), book_data.get(‘price‘), book_data.get(‘stock‘), book_data.get(‘description‘, ‘‘), book_data.get(‘category‘, ‘‘), book_data.get(‘rating‘, ‘‘), book_data.get(‘detail_url‘, ‘‘) )) connection.commit() except pymysql.Error as e: print(f“插入数据库失败: {e}“) connection.rollback() finally: connection.close()3. 主爬虫逻辑scraper.py
import time import random from utils import get_page_html, parse_book_list, parse_book_detail, save_to_csv, save_to_mysql, init_database from config import SPIDER_CONFIG def main(): print(“开始爬取图书信息...“) all_books = [] base_url = SPIDER_CONFIG[‘base_url‘] delay_min, delay_max = SPIDER_CONFIG[‘delay_range‘] # 初始化数据库表 init_database() for page in range(SPIDER_CONFIG[‘start_page‘], SPIDER_CONFIG[‘end_page‘] + 1): print(f“正在处理第 {page} 页...“) url = base_url.format(page) html = get_page_html(url) if not html: print(f“第 {page} 页获取失败,跳过。“) continue books_on_page = parse_book_list(html) print(f“第 {page} 页找到 {len(books_on_page)} 本书。“) for book in books_on_page: # 获取详情页信息 detail_html = get_page_html(book[‘detail_url‘]) if detail_html: detail_info = parse_book_detail(detail_html) book.update(detail_info) # 合并基本信息与详情信息 else: print(f“获取详情页失败: {book[‘detail_url‘]}“) # 保存到数据库 save_to_mysql(book) # 添加到总列表,用于后续保存CSV all_books.append(book) # 请求间隔,避免过快 time.sleep(random.uniform(delay_min, delay_max)) # 每爬完一页,稍作休息 time.sleep(random.uniform(1, 3)) # 所有数据爬取完成后,保存到CSV if all_books: save_to_csv(all_books) print(f“爬取完成!共获取 {len(all_books)} 条图书信息。“) else: print(“未获取到任何数据。“) if __name__ == ‘__main__‘: main()4. 依赖文件requirements.txt
requests>=2.28.0 beautifulsoup4>=4.11.0 lxml>=4.9.0 pymysql>=1.0.0 pandas>=1.5.0 # 可选,用于更强大的数据分析5.3 运行与验证
- 安装依赖:在项目根目录下,执行
pip install -r requirements.txt。 - 配置数据库:在
config.py中填写你的MySQL数据库信息,并确保数据库book_data已创建。 - 运行爬虫:执行
python scraper.py。 - 验证结果:
- 查看
data/books.csv文件,应包含爬取的数据。 - 连接MySQL数据库,查询
books表,应能看到插入的记录。
- 查看
这个项目演示了一个完整爬虫的骨架:配置管理、请求发送、数据解析、异常处理、数据持久化(文件+数据库)以及礼貌的爬取间隔。你可以在此基础上扩展,比如增加代理IP池、分布式任务队列、更复杂的异常重试机制等。
6. 常见问题与排查思路
在实际爬虫开发中,你几乎一定会遇到下面这些问题。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 返回状态码 403 | 请求被服务器拒绝。常见于未添加合法User-Agent、IP被封、或触发了风控。 | 1. 打印当前请求头。 2. 尝试在浏览器中访问同一URL。 3. 更换IP或使用代理测试。 | 1. 完善请求头,模拟真实浏览器。 2. 降低请求频率,增加随机延迟。 3. 使用代理IP。 |
| 返回状态码 404 | 页面不存在。URL拼写错误或页面已移除。 | 检查URL是否正确,在浏览器中验证。 | 修正URL或处理页面不存在的异常。 |
| 返回状态码 500/502/503 | 服务器内部错误或网关错误。可能是目标服务器问题,也可能是你的请求触发了防护。 | 1. 等待一段时间后重试。 2. 简化请求参数。 | 1. 实现重试机制(如tenacity库)。2. 检查请求参数是否合理。 |
| 数据解析为空 | 1. 网页结构已变更。 2. 数据是动态加载的(JS)。 3. 解析器或选择器写错。 | 1. 打印resp.text的前1000字符,查看是否包含目标数据。2. 使用浏览器开发者工具,检查元素是否在初始HTML中。 3. 检查CSS选择器或XPath路径。 | 1. 更新解析逻辑。 2. 改用Selenium或分析Ajax接口。 3. 使用更稳健的选择器。 |
| 乱码 | 响应内容的编码与解析编码不一致。 | 打印resp.encoding和resp.apparent_encoding。 | 设置resp.encoding = resp.apparent_encoding或指定正确的编码(如‘utf-8‘)。 |
| 连接超时 | 网络不稳定或服务器响应慢。 | 检查网络连接,增加timeout参数值。 | 使用try...except捕获超时异常,并加入重试逻辑。 |
| 被要求输入验证码 | 触发了网站的反爬验证。 | 观察在浏览器中手动操作是否会触发。 | 1. 大幅降低请求频率。 2. 尝试使用带有验证码识别功能的库(如 ddddocr),但复杂度高。3. 考虑是否应停止爬取。 |
| 数据库插入失败 | 1. 连接信息错误。 2. 数据格式与表结构不匹配(如超长、含特殊字符)。 3. 主键或唯一键冲突。 | 1. 检查数据库连接配置。 2. 打印待插入的数据。 3. 查看数据库错误日志。 | 1. 确保配置正确,数据库服务开启。 2. 对数据进行清洗和截断。 3. 使用 INSERT IGNORE或先查询后插入。 |
7. 工程化与最佳实践
当爬虫从脚本升级为需要长期稳定运行的系统时,你需要考虑更多工程化问题。
7.1 项目结构规范化
不要把所有代码写在一个文件里。参考前面的示例,按功能模块拆分:
spiders/:存放不同网站的爬虫类。items.py:定义统一的数据结构。middlewares.py:处理请求和响应的中间件(如代理、User-Agent轮换)。pipelines.py:数据处理管道(如清洗、去重、存储)。scheduler.py:任务调度器。utils/:公共工具函数。 这种结构清晰,易于维护和扩展,也是Scrapy等框架采用的设计思想。
7.2 使用成熟的爬虫框架:Scrapy
对于中型以上项目,强烈建议使用Scrapy框架。它提供了完整的爬虫开发体系:
- 异步处理:基于Twisted,速度快。
- 内置组件:下载器、调度器、中间件、管道一应俱全。
- 项目模板:
scrapy startproject myproject一键生成规范结构。 - 强大扩展:方便地添加代理、处理Cookie、导出数据。
一个简单的Scrapy爬虫示例:
# 安装 pip install scrapy # 创建项目 scrapy startproject book_scraper_scrapy cd book_scraper_scrapy scrapy genspider books books.toscrape.com然后编辑生成的books.py和pipelines.py即可。框架会帮你处理并发、去重、日志等繁杂事务。
7.3 数据存储策略
- 文件:CSV/JSON适合小批量、临时数据。注意文件读写锁和编码。
- 数据库:
- MySQL/PostgreSQL:适合关系型数据,便于复杂查询。
- MongoDB:适合半结构化、模式不固定的数据,写入速度快。
- SQLite:轻量级,适合单机小型项目,无需安装数据库服务。
- 去重:使用数据库唯一键,或布隆过滤器(Bloom Filter)在内存中快速判断URL是否已抓取。
7.4 日志与监控
不要只用print。使用Python内置的logging模块记录不同级别(DEBUG, INFO, WARNING, ERROR)的日志,便于问题追踪。
import logging logging.basicConfig(level=logging.INFO, format=‘%(asctime)s - %(name)s - %(levelname)s - %(message)s‘, filename=‘spider.log‘) logger = logging.getLogger(__name__) logger.info(‘开始爬取页面 %s‘, url)7.5 伦理、法律与风险控制再强调
- 控制速率:在
robots.txt中寻找Crawl-delay提示,若无,建议单域名请求间隔至少1-2秒。 - 识别敏感数据:绝不爬取个人隐私、账号密码、商业秘密、受版权保护的核心内容。
- 设置退出机制:当连续遇到大量错误(如403、验证码)时,应自动暂停或停止,并发出警报。
- 保留证据:对于公开数据的爬取,保留你遵守
robots.txt和合理使用原则的证据。
掌握Python爬虫,本质上是掌握了从互联网这片数据海洋中高效、精准、合法获取信息的能力。这项技能的价值不在于能抓取多少数据,而在于你能将这些数据转化为有意义的洞察和解决方案。从今天起,试着用爬虫的思路去观察你常浏览的网站,思考其数据结构和获取方式,这是迈向数据驱动决策的第一步。