Python Scrapy 库(一文讲透)

Scrapy 是什么?为什么你需要学习它

Scrapy 是 Python 生态中最成熟、最强大的网页抓取框架之一。它像一个自动化程度极高的图书管理员,可以自动浏览网页、提取数据,并将这些数据整理成结构化格式。对于想要掌握数据采集技术的开发者来说,Python Scrapy 库是入门和进阶的必备工具。相比原生 requests + BeautifulSoup 组合,Scrapy 提供了完整的异步处理能力、内置的去重机制、自动的请求调度等特性,能将抓取效率提升 3-5 倍。本文将通过完整案例,带你从零开始掌握这个高效爬虫工具。

安装与基础配置

安装 Python Scrapy 库

在终端输入以下命令即可安装:

pip install scrapy

⚠️ 注意:Windows 用户建议使用 pip install -U Twisted[windows] 预处理,避免安装失败

创建项目结构

Scrapy 项目包含多个核心组件,通过如下命令创建项目:

scrapy startproject douban_movies
cd douban_movies

这会生成包含以下文件的项目框架:

douban_movies/
├── douban_movies/
│   ├── __init__.py
│   ├── items.py      # 数据模型定义
│   ├── middlewares.py # 中间件配置
│   ├── pipelines.py  # 数据处理流程
│   └── settings.py   # 全局配置
└── scrapy.cfg        # 项目配置

基本使用流程

创建第一个 Spider

spiders 目录新建 movie_spider.py

import scrapy

class DoubanMovieSpider(scrapy.Spider):
    name = "douban_movies"  # Spider 名称
    start_urls = ['https://movie.douban.com/top250']  # 起始URL

    def parse(self, response):
        """解析豆瓣电影列表页面的核心方法"""
        for movie in response.css('div.item'):
            yield {
                'title': movie.css('span.title::text').get(),  # 提取电影标题
                'rating': movie.css('span.rating_num::text').get(),  # 提取评分
                'year': movie.css('div.bd p::text')[1].re(r'(\d{4})'),  # 提取年份
            }

        next_page = response.css('span.next a::attr(href)').get()  # 获取下一页链接
        if next_page:
            yield response.follow(next_page, self.parse)  # 递归抓取

✅ 关键点解析:parse 方法中的 yield 语句会生成字典格式的数据,response.follow 能自动拼接相对路径

运行爬虫并导出数据

使用以下命令启动爬虫并保存为 JSON 文件:

scrapy crawl douban_movies -o movies.json

这将执行:

  1. 发起初始请求
  2. 解析 CSS 选择器定位的元素
  3. 递归抓取所有分页
  4. 将结果保存到 JSON 文件

数据解析进阶技巧

使用选择器定位元素

Scrapy 提供两种主要的选择器:

movies = response.css('div.item')

director = movies[0].xpath('.//div/p/text()').getall()[1]
选择器类型 语法示例 匹配结果
CSS .class #id 类选择器、ID选择器
XPath //div[@class='x'] 路径选择器、属性筛选

构建结构化数据模型

items.py 中定义数据结构:

import scrapy

class DoubanMoviesItem(scrapy.Item):
    title = scrapy.Field()      # 电影标题
    rating = scrapy.Field()     # 评分信息
    year = scrapy.Field()       # 上映年份
    intro = scrapy.Field()      # 简介内容

修改 Spider 文件使其使用定义的 Item:

from douban_movies.items import DoubanMoviesItem

class DoubanMovieSpider(scrapy.Spider):
    # ...其他配置保持不变...
    def parse(self, response):
        item = DoubanMoviesItem()
        item['title'] = response.css('span.title::text').get()
        return item

高级功能实践

自定义请求头与中间件

修改 settings.py 配置请求头:

USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'

DOWNLOADER_MIDDLEWARES = {
    'douban_movies.middlewares.DoubanMoviesSpiderMiddleware': 543,  # 启用中间件
}

编写中间件处理请求头:

from scrapy import signals

class DoubanMoviesSpiderMiddleware:
    def process_request(self, request, spider):
        """在请求前添加自定义Header"""
        request.headers['Accept-Language'] = 'en-US,en;q=0.9,zh-TW;q=0.8,zh;q=0.7'
        request.headers['Referer'] = 'https://movie.douban.com/'

数据持久化方案

Scrapy 支持多种数据导出格式:

scrapy crawl douban_movies -o movies.csv  # 导出CSV
scrapy crawl douban_movies -o movies.xml  # 导出XML
scrapy crawl douban_movies -o movies.json # 导出JSON

进阶存储到数据库的实现方式:

import sqlite3

class DoubanMoviesPipeline:
    def open_spider(self, spider):
        """爬虫启动时创建数据库连接"""
        self.conn = sqlite3.connect('movies.db')
        self.cur = self.conn.cursor()
        self.cur.execute('''
            CREATE TABLE IF NOT EXISTS movies
            (title TEXT, rating TEXT, year TEXT, intro TEXT)
        ''')

    def close_spider(self, spider):
        """爬虫结束时关闭连接"""
        self.conn.commit()
        self.conn.close()

    def process_item(self, item, spider):
        """将数据写入数据库"""
        self.cur.execute('''
            INSERT INTO movies (title, rating, year, intro)
            VALUES (?, ?, ?, ?)
        ''', (
            item['title'][0].strip(),  # 去除标题首尾空格
            item['rating'][0],         # 评分数据
            item['year'][0],           # 年份数据
            item['intro'][0]           # 简介内容
        ))
        return item

常见问题解决方案

处理动态加载内容

对于需要 JavaScript 渲染的页面,可以使用 Splash 中间件:

from scrapy_splash import SplashRequest

class MySpider(scrapy.Spider):
    def start_requests(self):
        yield SplashRequest(
            url='https://example.com/ajax',
            callback=self.parse,
            args={'wait': 2},  # 等待2秒确保JS执行完成
            endpoint='render.html'
        )

识别与处理验证码

虽然 Scrapy 本身无法处理验证码,但可以配合第三方服务:

import requests

def handle_captcha(image_url):
    """将验证码发送到识别服务"""
    response = requests.post(
        'https://api.captcha-service.com/recognize',
        files={'image': requests.get(image_url).content}
    )
    return response.json()['text']

实战案例:豆瓣电影Top250爬虫

完整代码示例

以下是一个完整的豆瓣电影爬取示例:

import scrapy
from douban_movies.items import DoubanMoviesItem

class DoubanTop250Spider(scrapy.Spider):
    name = "douban_top250"
    start_urls = ['https://movie.douban.com/top250']

    def parse(self, response):
        """主解析方法"""
        for movie in response.css('div.item'):
            item = DoubanMoviesItem()
            item['title'] = movie.css('span.title::text').getall()
            item['rating'] = movie.css('span.rating_num::text').get()
            item['year'] = movie.css('div.bd p::text')[1].re(r'(\d{4})')
            item['intro'] = movie.css('p.quote span::text').get()
            yield item

        next_page = response.css('span.next a::attr(href)').get()
        if next_page:
            yield scrapy.Request(url=response.urljoin(next_page), callback=self.parse)

数据处理流程

pipelines.py 添加数据清洗逻辑:

class DoubanMoviesPipeline:
    def process_item(self, item, spider):
        # 清洗标题数据
        item['title'] = [t.strip() for t in item['title']]
        
        # 处理中文/英文标题
        if len(item['title']) > 1:
            item['title'] = f"{item['title'][0]} / {item['title'][1]}"
        else:
            item['title'] = item['title'][0]
        
        # 简介为空时设置默认值
        if not item['intro']:
            item['intro'] = '暂无简介'
        
        return item

结语

通过本文的讲解,你应该已经掌握了 Python Scrapy 库的核心使用方法。从基础安装到实战案例,我们逐步构建了一个完整的电影数据爬虫系统。记住 Scrapy 的设计哲学:高内聚低耦合,每个组件都专注于特定功能。当你需要处理更复杂的场景时,可以:

  1. 使用 scrapy-splash 处理 JS 渲染
  2. 配合 scrapy-redis 实现分布式爬虫
  3. 利用 Item Loaders 简化数据处理

建议通过 scrapy shell <url> 工具进行实时调试,这能显著提升开发效率。现在就动手实践本文的代码示例,在实际项目中体验 Python Scrapy 库的魅力吧!