对新手来说,学爬虫最怕“知识点零散+踩坑无数”——看了一堆教程,却连一个完整的爬取流程都走不通;好不容易爬点数据,要么被反爬封禁,要么存储时乱码。这份3天速成指南,专为新手设计:每天聚焦1个核心工具,从基础Requests爬静态页,到Scrapy框架结构化爬取,再到Scrapy-Redis分布式提速,全程围绕“豆瓣影评爬取+MySQL存储”实战,每个步骤附可复制的代码,关键坑点提前标注,3天就能从“爬虫小白”到“能独立爬取分布式数据”。
前言:3天学习规划(新手友好,每天2-3小时)
| 天数 | 核心目标 | 实战内容 | 掌握技能 |
|---|---|---|---|
| 第1天 | 基础静态爬取(Requests) | 爬豆瓣电影影评列表→提取文本→存TXT | 环境搭建、Requests请求、BeautifulSoup解析 |
| 第2天 | 结构化爬取(Scrapy) | 爬豆瓣影评详情→提取多字段→存MySQL | Scrapy框架使用、MySQL数据持久化 |
| 第3天 | 分布式爬取(Scrapy-Redis) | 多节点爬多电影影评→分布式调度 | 分布式原理、Redis任务管理、反爬优化 |
第1天:入门Requests——爬豆瓣影评静态页(基础篇)
目标:用Requests爬取单部电影的豆瓣短评,提取“用户名+评论内容+评分”,存到文本文件。
1.1 环境搭建(10分钟搞定)
# 安装核心库:请求库+解析库
pip install requests==2.31.0 beautifulsoup4==4.12.3 lxml==4.9.3
验证安装:打开Python终端,输入,能显示版本号即成功。
import requests; print(requests.__version__)
1.2 核心代码:爬豆瓣短评(可直接运行)
以电影《肖申克的救赎》(短评URL:)为例:
https://movie.douban.com/subject/1292052/comments?status=P
import requests
from bs4 import BeautifulSoup
import time
def crawl_douban_comments(movie_url, page_num=1):
# 1. 配置请求头(关键!新手常忘加UA,直接被封)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/128.0.0.0 Safari/537.36",
"Referer": movie_url, # 模拟从电影详情页跳转,降低被封概率
"Cookie": "你的豆瓣Cookie" # 可选:登录豆瓣后复制Cookie,避免爬2页就被限制(怎么复制看“避坑1”)
}
# 2. 循环爬取多页(这里爬前3页)
all_comments = []
for page in range(1, page_num+1):
# 构造分页URL(豆瓣短评每页20条,page=1对应start=0,page=2对应start=20)
page_url = f"{movie_url}comments?status=P&start={(page-1)*20}"
try:
# 发送请求(加超时,避免卡壳)
response = requests.get(page_url, headers=headers, timeout=10)
# 解决编码乱码(新手高频坑!豆瓣用utf-8)
response.encoding = "utf-8" # 或用response.apparent_encoding自动识别
# 3. 解析页面(BeautifulSoup+CSS选择器)
soup = BeautifulSoup(response.text, "lxml")
# 找到所有短评节点(每个评论在class="comment-item"的div里)
comment_items = soup.find_all("div", class_="comment-item")
for item in comment_items:
# 提取用户名(class="comment-info"下的a标签)
username = item.find("div", class_="comment-info").find("a").text.strip()
# 提取评分(class="rating"的span,用attrs匹配属性)
rating = item.find("span", attrs={"class": "rating"})
rating = rating["title"] if rating else "未评分" # 处理无评分的情况
# 提取评论内容(class="short"的span)
content = item.find("span", class_="short").text.strip()
# 存储到列表
all_comments.append({
"username": username,
"rating": rating,
"content": content
})
print(f"第{page}页 - {username}:{content[:20]}...") # 打印前20字预览
# 4. 控制频率(新手必加!避免高频请求被封)
time.sleep(2) # 每爬1页停2秒
except Exception as e:
print(f"爬取第{page}页失败:{str(e)[:50]}")
continue
# 5. 保存到文本文件
with open("douban_comments.txt", "w", encoding="utf-8") as f:
for idx, comment in enumerate(all_comments, 1):
f.write(f"第{idx}条
")
f.write(f"用户名:{comment['username']}
")
f.write(f"评分:{comment['rating']}
")
f.write(f"评论:{comment['content']}
")
f.write("-"*50 + "
")
print(f"
爬取完成!共{len(all_comments)}条评论,保存到douban_comments.txt")
# ------------------- 执行爬取 -------------------
if __name__ == "__main__":
# 电影《肖申克的救赎》短评首页URL
MOVIE_URL = "https://movie.douban.com/subject/1292052/"
crawl_douban_comments(MOVIE_URL, page_num=3) # 爬前3页(60条评论)
1.3 新手避坑指南(第一天最易踩的3个坑)
坑1:请求被拒(403 Forbidden)
原因:没加,被豆瓣识别为爬虫;或未登录爬取页数过多。解决:① 必加
User-Agent(复制自己浏览器的UA:Chrome→F12→Network→随便点一个请求→Headers→User-Agent);② 登录豆瓣后,复制Cookie(同样在Headers的Cookie字段),加到请求头中,能爬更多页。
User-Agent
坑2:中文乱码(如“æµå½±”)
原因:response编码未设置为utf-8,豆瓣页面默认utf-8编码。解决:爬取后加,或用
response.encoding = "utf-8"自动识别编码。
response.encoding = response.apparent_encoding
坑3:解析不到数据(返回空列表)
原因:CSS选择器或XPath写错,比如豆瓣页面class名变化(如“comment-item”写成“comment”)。解决:用浏览器“检查”功能核对节点:打开目标页面→F12→Elements→Ctrl+F搜索“comment-item”,确认节点存在;复制正确的class名或XPath。
第2天:进阶Scrapy——结构化爬豆瓣影评+MySQL存储(框架篇)
目标:用Scrapy框架爬取豆瓣影评详情(多字段:用户名、评分、评论时间、评论内容、有用数),结构化存储到MySQL,比Requests更高效、更易维护。
2.1 环境搭建(15分钟)
# 安装Scrapy+MySQL驱动
pip install scrapy==2.11.0 pymysql==1.1.0
2.2 核心步骤:Scrapy项目完整实现
步骤1:创建Scrapy项目
打开命令行,执行以下命令(路径自己选,比如D盘根目录):
scrapy startproject douban_crawler
cd douban_crawler
scrapy genspider movie_comment movie.douban.com # 创建爬虫:名字movie_comment,域名movie.douban.com
步骤2:定义数据结构(items.py)
在中定义要爬取的字段,结构化存储:
items.py
import scrapy
class DoubanCrawlerItem(scrapy.Item):
# 用户名
username = scrapy.Field()
# 评分(如“力荐”“推荐”,对应星级)
rating = scrapy.Field()
# 评论时间(如“2024-05-20 14:30”)
comment_time = scrapy.Field()
# 评论内容
content = scrapy.Field()
# 有用数(多少人觉得该评论有用)
useful_num = scrapy.Field()
# 电影ID(区分不同电影)
movie_id = scrapy.Field()
步骤3:写爬虫逻辑(spiders/movie_comment.py)
修改自动生成的,爬取影评详情:
movie_comment.py
import scrapy
from douban_crawler.items import DoubanCrawlerItem
from scrapy.http import Request
class MovieCommentSpider(scrapy.Spider):
name = 'movie_comment'
allowed_domains = ['movie.douban.com']
# 起始URL:《肖申克的救赎》短评第1-3页(start=0,20,40)
start_urls = [
'https://movie.douban.com/subject/1292052/comments?status=P&start=0',
'https://movie.douban.com/subject/1292052/comments?status=P&start=20',
'https://movie.douban.com/subject/1292052/comments?status=P&start=40'
]
# 电影ID(从URL中提取:subject/后的数字)
movie_id = '1292052'
def parse(self, response):
# 1. 提取当前页所有评论节点
comment_items = response.xpath('//div[@class="comment-item"]')
for item in comment_items:
# 实例化Item
comment = DoubanCrawlerItem()
# 2. 提取字段(用XPath,比BeautifulSoup更灵活)
# 用户名
comment['username'] = item.xpath('.//div[@class="comment-info"]/a/text()').get().strip()
# 评分(注意:有些评论无评分,用get()或默认值)
rating_class = item.xpath('.//span[@class="rating"]/@class').get()
if rating_class:
# 评分class名如“rating5-t”对应“力荐”,“rating4-t”对应“推荐”
rating_map = {
'rating5-t': '力荐', 'rating4-t': '推荐',
'rating3-t': '还行', 'rating2-t': '较差', 'rating1-t': '很差'
}
comment['rating'] = rating_map.get(rating_class, '未评分')
else:
comment['rating'] = '未评分'
# 评论时间(提取文本后清理空格和换行)
comment['comment_time'] = item.xpath('.//div[@class="comment-info"]/span[last()]/text()').get().strip()
# 评论内容
comment['content'] = item.xpath('.//span[@class="short"]/text()').get().strip()
# 有用数(提取文本后转整数,默认0)
useful_num = item.xpath('.//span[@class="votes"]/text()').get()
comment['useful_num'] = int(useful_num) if useful_num else 0
# 电影ID
comment['movie_id'] = self.movie_id
# 3. 提交Item到Pipeline(后续存MySQL)
yield comment
# (可选)自动爬取下一页(新手可先不做,熟悉后再扩展)
# next_page = response.xpath('//a[text()="后页 >"]/@href').get()
# if next_page:
# next_url = response.urljoin(next_page)
# yield Request(next_url, callback=self.parse)
def start_requests(self):
# 发送请求时加Headers(避免403)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/128.0.0.0 Safari/537.36",
"Cookie": "你的豆瓣Cookie(同第一天,登录后复制)"
}
for url in self.start_urls:
yield Request(url, headers=headers, callback=self.parse)
步骤4:配置MySQL存储(pipelines.py)
在中写数据存储逻辑,将Item存入MySQL:
pipelines.py
import pymysql
from scrapy.exceptions import DropItem
class DoubanMysqlPipeline:
def __init__(self):
# 1. 初始化MySQL连接(替换为你的MySQL信息)
self.conn = pymysql.connect(
host='localhost', # 本地MySQL默认localhost
port=3306, # 端口默认3306
user='root', # 用户名(默认root)
password='123456',# 你的MySQL密码(安装时设置的)
db='douban_db', # 数据库名(需提前创建)
charset='utf8mb4' # 支持中文(必设utf8mb4,避免表情符号乱码)
)
self.cursor = self.conn.cursor()
# 2. 创建评论表(若不存在)
self.create_table()
def create_table(self):
# 创建douban_comments表的SQL语句
create_sql = """
CREATE TABLE IF NOT EXISTS douban_comments (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL COMMENT '用户名',
rating VARCHAR(10) COMMENT '评分',
comment_time VARCHAR(20) COMMENT '评论时间',
content TEXT NOT NULL COMMENT '评论内容',
useful_num INT DEFAULT 0 COMMENT '有用数',
movie_id VARCHAR(20) NOT NULL COMMENT '电影ID',
UNIQUE KEY unique_comment (username, comment_time, movie_id) # 避免重复插入
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='豆瓣影评表';
"""
self.cursor.execute(create_sql)
self.conn.commit()
def process_item(self, item, spider):
try:
# 3. 插入数据到MySQL
insert_sql = """
INSERT INTO douban_comments (username, rating, comment_time, content, useful_num, movie_id)
VALUES (%s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE useful_num = VALUES(useful_num); # 重复时更新有用数
"""
# 提取Item字段值
values = (
item['username'],
item['rating'],
item['comment_time'],
item['content'],
item['useful_num'],
item['movie_id']
)
self.cursor.execute(insert_sql, values)
self.conn.commit()
self.logger.info(f"成功插入评论:{item['username']} - {item['content'][:20]}...")
return item
except Exception as e:
self.conn.rollback()
self.logger.error(f"插入数据失败:{str(e)},Item:{item}")
raise DropItem(f"插入数据失败:{str(e)}")
def close_spider(self, spider):
# 4. 关闭爬虫时,关闭MySQL连接
self.cursor.close()
self.conn.close()
self.logger.info("MySQL连接已关闭")
步骤5:启用Pipeline(settings.py)
修改,启用MySQL Pipeline,关闭robots协议(豆瓣robots允许爬取公开评论):
settings.py
# 1. 启用MySQL Pipeline(优先级300,数字越小越先执行)
ITEM_PIPELINES = {
'douban_crawler.pipelines.DoubanMysqlPipeline': 300,
}
# 2. 关闭robots协议(新手必改,否则Scrapy会遵守robots,爬不到数据)
ROBOTSTXT_OBEY = False
# 3. 配置日志级别(方便调试,生产环境可改INFO)
LOG_LEVEL = 'DEBUG'
# 4. 控制请求频率(避免被封,每1秒1次请求)
DOWNLOAD_DELAY = 1
RANDOMIZE_DOWNLOAD_DELAY = True # 随机延迟(0.5-1.5倍DOWNLOAD_DELAY)
# 5. 禁用Cookie(可选,若已在start_requests加了Cookie,这里可禁用)
# COOKIES_ENABLED = False
步骤6:执行爬虫
在命令行(当前路径为douban_crawler)执行:
scrapy crawl movie_comment
执行成功后,打开MySQL,进入数据库,查询
douban_db表,就能看到爬取的评论数据。
douban_comments
2.3 新手避坑指南(第二天最易踩的3个坑)
坑1:MySQL连接失败(OperationalError)
原因:① MySQL未启动;② 用户名/密码错误;③ 数据库未创建。解决:① 启动MySQL服务(Windows:服务→找到MySQL→启动;Linux:
douban_db);② 核对
systemctl start mysql中的
pipelines.py和
user(与MySQL登录信息一致);③ 先在MySQL中创建数据库:
password。
CREATE DATABASE IF NOT EXISTS douban_db DEFAULT CHARSET utf8mb4;
坑2:Item字段未提取到(返回None,插入数据库失败)
原因:XPath写错,或豆瓣页面结构变化(如评分节点位置变了)。解决:① 用Scrapy Shell调试:命令行执行,然后在Shell中输入XPath(如
scrapy shell "https://movie.douban.com/subject/1292052/comments?status=P&start=0"),看是否能获取到值;② 调试通过后,再修改Spider代码。
response.xpath('//div[@class="comment-item"]//a/text()').get()
坑3:爬虫不执行Pipeline(数据没存到MySQL)
原因:中未启用Pipeline,或
settings.py配置错误。解决:① 确认
ITEM_PIPELINES的配置格式正确(键是Pipeline类的完整路径,值是优先级);② 检查Pipeline类名是否与
ITEM_PIPELINES中的一致(如
pipelines.py)。
DoubanMysqlPipeline
第3天:高阶Scrapy-Redis——分布式爬取多电影影评(提速篇)
目标:用Scrapy-Redis实现分布式爬取,多台电脑同时爬多个电影的影评(如《肖申克的救赎》《阿甘正传》),解决单节点爬取慢、任务堆积的问题。
3.1 环境搭建(20分钟)
# 安装Scrapy-Redis+Redis客户端
pip install scrapy-redis==0.7.2 redis==5.0.1
安装Redis:Windows下载Redis压缩包(https://github.com/tporadowski/redis/releases),解压后双击启动;Linux执行
redis-server.exe,然后
sudo apt install redis-server启动。
redis-server
3.2 核心步骤:修改Scrapy为分布式
步骤1:修改settings.py(添加Scrapy-Redis配置)
在末尾添加以下配置:
douban_crawler/settings.py
# ------------------- Scrapy-Redis分布式配置 -------------------
# 1. 启用Redis调度器(替代默认调度器,实现任务分布式)
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 2. 启用Redis去重(替代默认去重,避免多节点重复爬取)
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 3. Redis连接配置(替换为你的Redis地址,本地默认127.0.0.1:6379)
REDIS_URL = "redis://127.0.0.1:6379/0" # 0是Redis的db编号
# 4. 任务完成后是否保留Redis中的请求队列(False=完成后清空,True=保留,新手设False)
SCHEDULER_PERSIST = False
# 5. 优先级队列(默认是PriorityQueue,适合分布式)
SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.PriorityQueue"
# 6. 配置Redis Pipeline(可选,将Item存到Redis,方便后续处理)
# ITEM_PIPELINES.update({
# 'scrapy_redis.pipelines.RedisPipeline': 400,
# })
步骤2:修改Spider为分布式(spiders/movie_comment_redis.py)
新建,支持多电影URL入队,多节点共同爬取:
movie_comment_redis.py
import scrapy
from douban_crawler.items import DoubanCrawlerItem
from scrapy.http import Request
from scrapy_redis.spiders import RedisSpider # 继承RedisSpider,而非普通Spider
class MovieCommentRedisSpider(RedisSpider):
name = 'movie_comment_redis' # 爬虫名,分布式节点需用同一个名
allowed_domains = ['movie.douban.com']
# 关键:不再写start_urls,而是从Redis的key中获取起始URL(默认key是“movie_comment_redis:start_urls”)
redis_key = 'movie_comment_redis:start_urls'
def parse(self, response):
# 1. 提取电影ID(从URL中提取:subject/后的数字,如“1292052”)
movie_id = response.url.split('/subject/')[1].split('/')[0]
# 2. 提取当前页评论
comment_items = response.xpath('//div[@class="comment-item"]')
for item in comment_items:
comment = DoubanCrawlerItem()
# 提取字段(同第2天,略,直接复用之前的逻辑)
comment['username'] = item.xpath('.//div[@class="comment-info"]/a/text()').get().strip()
rating_class = item.xpath('.//span[@class="rating"]/@class').get()
comment['rating'] = {
'rating5-t': '力荐', 'rating4-t': '推荐',
'rating3-t': '还行', 'rating2-t': '较差', 'rating1-t': '很差'
}.get(rating_class, '未评分') if rating_class else '未评分'
comment['comment_time'] = item.xpath('.//div[@class="comment-info"]/span[last()]/text()').get().strip()
comment['content'] = item.xpath('.//span[@class="short"]/text()').get().strip()
comment['useful_num'] = int(item.xpath('.//span[@class="votes"]/text()').get()) if item.xpath('.//span[@class="votes"]/text()').get() else 0
comment['movie_id'] = movie_id
yield comment
# 3. 自动爬取下一页(分布式核心:下一页URL自动入Redis队列,多节点共同处理)
next_page = response.xpath('//a[text()="后页 >"]/@href').get()
if next_page:
next_url = response.urljoin(next_page)
yield Request(next_url, headers=self.get_headers(), callback=self.parse)
def get_headers(self):
# 统一请求头(含UA和Cookie)
return {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/128.0.0.0 Safari/537.36",
"Cookie": "你的豆瓣Cookie",
"Referer": "https://movie.douban.com/"
}
def make_request_from_data(self, data):
# 从Redis获取URL时,添加请求头
url = bytes(data).decode('utf-8')
return Request(url, headers=self.get_headers(), callback=self.parse)
步骤3:启动分布式爬虫
启动Redis服务:双击(Windows),确保Redis正常运行(显示“Ready to accept connections”)。
redis-server.exe
向Redis添加起始URL:
打开新的命令行,启动Redis客户端(Windows:双击;Linux:
redis-cli.exe),执行以下命令,添加2部电影的短评首页URL:
redis-cli
# 向“movie_comment_redis:start_urls”队列添加URL(Scrapy-Redis会从这个队列取任务)
lpush movie_comment_redis:start_urls https://movie.douban.com/subject/1292052/comments?status=P&start=0
lpush movie_comment_redis:start_urls https://movie.douban.com/subject/1291546/comments?status=P&start=0
(注:是《阿甘正传》的豆瓣ID)
1291546
启动多个爬虫节点:
在本地启动第一个节点:命令行进入目录,执行
douban_crawler。(可选)在另一台电脑(需与Redis在同一局域网,或Redis允许远程连接)启动第二个节点:同样部署
scrapy crawl movie_comment_redis项目,执行相同命令,两个节点会从Redis共同获取任务,分布式爬取。
douban_crawler
查看Redis任务状态:
在Redis客户端执行,查看待爬取的请求队列长度;执行
llen movie_comment_redis:requests,查看已去重的URL(避免重复爬取)。
smembers movie_comment_redis:dupefilter
3.3 新手避坑指南(第三天最易踩的3个坑)
坑1:多节点无法获取Redis任务
原因:① Redis未允许远程连接(仅本地可访问);② Redis连接配置错误。解决:① 允许Redis远程连接:修改Redis配置文件(),注释
redis.windows.conf,添加
bind 127.0.0.1,然后重启Redis;② 核对
protected-mode no中的
settings.py,确保IP和端口正确(远程节点需填Redis服务器的IP,如
REDIS_URL)。
redis://192.168.1.100:6379/0
坑2:分布式爬取重复数据
原因:① 未启用Scrapy-Redis的去重(配置错误);② MySQL表未加唯一键。解决:① 确认
DUPEFILTER_CLASS中
settings.py配置正确(
DUPEFILTER_CLASS);② 确保MySQL表的
scrapy_redis.dupefilter.RFPDupeFilter唯一键存在(用户名+评论时间+电影ID,避免重复插入)。
unique_comment
坑3:Redis任务堆积(爬虫不处理队列)
原因:① 爬虫代码错误(如XPath解析失败,导致下一页URL未入队);② 单个任务处理时间过长(如MySQL插入慢)。解决:① 用Scrapy Shell调试爬虫代码,确保下一页URL能正常提取并入队;② 优化MySQL插入(如批量插入),或增加爬虫节点数量,分担任务压力。
总结:3天速成后的进阶方向
3天时间,你已经掌握了爬虫的核心流程:从基础Requests到框架Scrapy,再到分布式Scrapy-Redis,还实现了MySQL结构化存储。接下来可以从这3个方向进阶:
反爬优化:学习代理池(如BrightData住宅代理)、浏览器指纹伪装(Playwright),应对豆瓣更严格的反爬;数据分析:用Pandas分析爬取的影评数据,比如统计电影评分分布、提取热门评论关键词;可视化:用Matplotlib或ECharts绘制影评评分趋势图,或用Flask搭建简单的影评查询网页。
记住:爬虫的核心是“模拟真实用户行为+遵守平台规则”,新手不要高频爬取、不要爬取非公开数据,避免法律风险。遇到问题时,多查官方文档(Requests、Scrapy、Scrapy-Redis),多调试,很快就能熟练掌握!