3天速成Python爬虫:从Requests到Scrapy-Redis,爬豆瓣影评+MySQL存储(新手避坑手册)

对新手来说,学爬虫最怕“知识点零散+踩坑无数”——看了一堆教程,却连一个完整的爬取流程都走不通;好不容易爬点数据,要么被反爬封禁,要么存储时乱码。这份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
,被豆瓣识别为爬虫;或未登录爬取页数过多。解决:① 必加
User-Agent
(复制自己浏览器的UA:Chrome→F12→Network→随便点一个请求→Headers→User-Agent);② 登录豆瓣后,复制Cookie(同样在Headers的Cookie字段),加到请求头中,能爬更多页。

坑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)


pipelines.py
中写数据存储逻辑,将Item存入MySQL:


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)

修改
settings.py
,启用MySQL Pipeline,关闭robots协议(豆瓣robots允许爬取公开评论):


# 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未启动;② 用户名/密码错误;③ 数据库
douban_db
未创建。解决:① 启动MySQL服务(Windows:服务→找到MySQL→启动;Linux:
systemctl start mysql
);② 核对
pipelines.py
中的
user

password
(与MySQL登录信息一致);③ 先在MySQL中创建数据库:
CREATE DATABASE IF NOT EXISTS douban_db DEFAULT CHARSET utf8mb4;

坑2:Item字段未提取到(返回None,插入数据库失败)

原因:XPath写错,或豆瓣页面结构变化(如评分节点位置变了)。解决:① 用Scrapy Shell调试:命令行执行
scrapy shell "https://movie.douban.com/subject/1292052/comments?status=P&start=0"
,然后在Shell中输入XPath(如
response.xpath('//div[@class="comment-item"]//a/text()').get()
),看是否能获取到值;② 调试通过后,再修改Spider代码。

坑3:爬虫不执行Pipeline(数据没存到MySQL)

原因:
settings.py
中未启用Pipeline,或
ITEM_PIPELINES
配置错误。解决:① 确认
ITEM_PIPELINES
的配置格式正确(键是Pipeline类的完整路径,值是优先级);② 检查Pipeline类名是否与
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),解压后双击
redis-server.exe
启动;Linux执行
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)

新建
movie_comment_redis.py
,支持多电影URL入队,多节点共同爬取:


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服务:双击
redis-server.exe
(Windows),确保Redis正常运行(显示“Ready to accept connections”)。

向Redis添加起始URL
打开新的命令行,启动Redis客户端(Windows:双击
redis-cli.exe
;Linux:
redis-cli
),执行以下命令,添加2部电影的短评首页URL:


# 向“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

(注:
1291546
是《阿甘正传》的豆瓣ID)

启动多个爬虫节点

在本地启动第一个节点:命令行进入
douban_crawler
目录,执行
scrapy crawl movie_comment_redis
。(可选)在另一台电脑(需与Redis在同一局域网,或Redis允许远程连接)启动第二个节点:同样部署
douban_crawler
项目,执行相同命令,两个节点会从Redis共同获取任务,分布式爬取。

查看Redis任务状态
在Redis客户端执行
llen movie_comment_redis:requests
,查看待爬取的请求队列长度;执行
smembers movie_comment_redis:dupefilter
,查看已去重的URL(避免重复爬取)。

3.3 新手避坑指南(第三天最易踩的3个坑)

坑1:多节点无法获取Redis任务

原因:① Redis未允许远程连接(仅本地可访问);② Redis连接配置错误。解决:① 允许Redis远程连接:修改Redis配置文件(
redis.windows.conf
),注释
bind 127.0.0.1
,添加
protected-mode no
,然后重启Redis;② 核对
settings.py
中的
REDIS_URL
,确保IP和端口正确(远程节点需填Redis服务器的IP,如
redis://192.168.1.100:6379/0
)。

坑2:分布式爬取重复数据

原因:① 未启用Scrapy-Redis的去重(
DUPEFILTER_CLASS
配置错误);② MySQL表未加唯一键。解决:① 确认
settings.py

DUPEFILTER_CLASS
配置正确(
scrapy_redis.dupefilter.RFPDupeFilter
);② 确保MySQL表的
unique_comment
唯一键存在(用户名+评论时间+电影ID,避免重复插入)。

坑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),多调试,很快就能熟练掌握!

© 版权声明

相关文章

暂无评论

none
暂无评论...