1. 环境准备与Python安装

1.1 检查系统要求

Scrapy是一个基于Python的开源网络爬虫框架,需要在Windows系统上安装Python环境。首先确认你的Windows系统版本(Windows 7/8/10/11均可,但推荐Windows 10或11)。

1.2 安装Python

  1. 下载Python安装包

    • 访问Python官方网站:https://www.python.org/downloads/
    • 下载最新的Python 3.x版本(推荐Python 3.9或更高版本)
    • 注意:下载时选择”Windows installer (64-bit)“(如果你的系统是64位)
  2. 安装Python

    • 运行下载的安装程序
    • 重要:在安装界面勾选”Add Python to PATH”选项
    • 选择”Install Now”进行默认安装
    • 安装完成后,按Win+R,输入cmd打开命令提示符,输入以下命令验证安装:
      
      python --version
      
      应显示类似Python 3.10.6的版本信息
  3. 验证pip安装

    • 在命令提示符中输入:
      
      pip --version
      
      应显示pip的版本信息,如pip 23.2.1 from ...

1.3 配置虚拟环境(推荐)

为避免项目依赖冲突,建议为每个项目创建独立的虚拟环境:

  1. 安装virtualenv(如果尚未安装):

    pip install virtualenv
    
  2. 创建项目目录

    mkdir C:\scrapy_projects
    cd C:\scrapy_projects
    
  3. 创建虚拟环境

    virtualenv scrapy_env
    
  4. 激活虚拟环境

    scrapy_env\Scripts\activate
    

    激活后命令提示符前会显示(scrapy_env)前缀

2. 安装Scrapy框架

2.1 通过pip安装Scrapy

在激活的虚拟环境中执行:

pip install scrapy

2.2 验证Scrapy安装

安装完成后,输入以下命令验证:

scrapy --version

应显示Scrapy的版本信息,如Scrapy 2.11.0

2.3 解决常见安装问题

问题1:安装速度慢或超时

使用国内镜像源加速安装:

pip install scrapy -i https://pypi.tuna.tsinghua.edu.cn/simple

问题2:缺少依赖库

Scrapy依赖Twisted、lxml等库,如果安装失败,可以尝试:

pip install twisted lxml cryptography
pip install scrapy

问题3:Windows系统缺少C++编译工具

某些依赖需要编译,如果遇到错误:

  1. 安装Microsoft Visual C++ Build Tools:https://visualstudio.microsoft.com/visual-cpp-build-tools/
  2. 或者安装预编译的wheel包:
    
    pip install scrapy --only-binary :all:
    

3. 创建Scrapy爬虫项目

3.1 创建项目结构

在项目目录下(如C:\scrapy_projects)执行:

scrapy startproject myspider

这将创建以下目录结构:

myspider/
├── scrapy.cfg          # 项目配置文件
└── myspider/           # 项目Python包
    ├── __init__.py
    ├── items.py        # 定义数据结构
    ├── middlewares.py  # 中间件
    ├── pipelines.py    # 数据处理管道
    ├── settings.py     # 项目设置
    └── spiders/        # 爬虫存放目录
        └── __init__.py

3.2 创建第一个爬虫

进入项目目录:

cd myspider

创建爬虫文件(以爬取豆瓣电影Top250为例):

scrapy genspider douban_spider movie.douban.com

这将在spiders/目录下生成douban_spider.py文件,初始内容如下:

import scrapy

class DoubanSpiderSpider(scrapy.Spider):
    name = "douban_spider"
    allowed_domains = ["movie.douban.com"]
    start_urls = ["https://movie.douban.com/top250"]
    
    def parse(self, response):
        pass

4. 编写爬虫代码

4.1 完整爬虫示例

修改douban_spider.py文件,实现爬取豆瓣电影Top250:

import scrapy
from scrapy.http import Request
import re

class DoubanSpiderSpider(scrapy.Spider):
    name = "douban_spider"
    allowed_domains = ["movie.douban.com"]
    start_urls = ["https://movie.douban.com/top250"]
    
    # 设置请求头,模拟浏览器访问
    custom_settings = {
        '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',
        'ROBOTSTXT_OBEY': False,  # 不遵守robots.txt(仅用于学习)
        'DOWNLOAD_DELAY': 1,  # 下载延迟,避免被封
    }
    
    def parse(self, response):
        """解析豆瓣电影Top250列表页"""
        # 提取所有电影条目
        movie_items = response.css('div.item')
        
        for movie in movie_items:
            # 提取电影信息
            title = movie.css('span.title::text').get()
            rating = movie.css('span.rating_num::text').get()
            quote = movie.css('span.inq::text').get()
            
            # 提取电影详情页链接
            detail_url = movie.css('a::attr(href)').get()
            
            # 构造详情页请求,传递电影标题作为参数
            yield Request(
                url=detail_url,
                callback=self.parse_detail,
                meta={'title': title, 'rating': rating, 'quote': quote}
            )
        
        # 处理分页
        next_page = response.css('span.next a::attr(href)').get()
        if next_page:
            yield Request(
                url=response.urljoin(next_page),
                callback=self.parse
            )
    
    def parse_detail(self, response):
        """解析电影详情页"""
        title = response.meta['title']
        rating = response.meta['rating']
        quote = response.meta['quote']
        
        # 提取导演、演员等信息
        director = response.css('a[href*="celebrity"]::text').getall()
        director = [d.strip() for d in director if d.strip()]
        
        # 提取电影类型
        genres = response.css('span[property="v:genre"]::text').getall()
        
        # 提取上映时间
        release_date = response.css('span[property="v:initialReleaseDate"]::text').get()
        
        # 提取电影时长
        duration = response.css('span[property="v:runtime"]::text').get()
        
        # 提取剧情简介
        summary = response.css('span[property="v:summary"]::text').get()
        if summary:
            summary = summary.strip()
        
        # 提取评分人数
        votes = response.css('span[property="v:votes"]::text').get()
        
        # 返回结构化数据
        yield {
            'title': title,
            'rating': rating,
            'quote': quote,
            'director': director,
            'genres': genres,
            'release_date': release_date,
            'duration': duration,
            'summary': summary,
            'votes': votes,
            'url': response.url
        }

4.2 定义数据结构(可选)

items.py中定义数据结构,使数据更规范:

import scrapy

class MovieItem(scrapy.Item):
    title = scrapy.Field()
    rating = scrapy.Field()
    quote = scrapy.Field()
    director = scrapy.Field()
    genres = scrapy.Field()
    release_date = scrapy.Field()
    duration = scrapy.Field()
    summary = scrapy.Field()
    votes = scrapy.Field()
    url = scrapy.Field()

然后修改爬虫代码,使用Item对象:

from myspider.items import MovieItem

# 在parse_detail方法中:
item = MovieItem()
item['title'] = title
item['rating'] = rating
# ... 其他字段
yield item

5. 运行爬虫并保存数据

5.1 运行爬虫

在项目根目录(C:\scrapy_projects\myspider)下执行:

scrapy crawl douban_spider

5.2 保存数据到JSON文件

scrapy crawl douban_spider -o movies.json

5.3 保存数据到CSV文件

scrapy crawl douban_spider -o movies.csv

5.4 保存数据到JSON Lines格式(推荐)

scrapy crawl douban_spider -o movies.jsonl

5.5 运行爬虫并查看输出

scrapy crawl douban_spider -L INFO

6. 配置文件详解

6.1 修改settings.py配置

myspider/settings.py中添加以下配置:

# 禁用robots.txt遵守(仅用于学习)
ROBOTSTXT_OBEY = False

# 设置请求头
DEFAULT_REQUEST_HEADERS = {
    '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',
    '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',
}

# 下载延迟(避免被封)
DOWNLOAD_DELAY = 1

# 并发请求数
CONCURRENT_REQUESTS = 16

# 启用管道(用于数据处理)
ITEM_PIPELINES = {
    'myspider.pipelines.MyspiderPipeline': 300,
}

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

# 启用扩展
EXTENSIONS = {
    'scrapy.extensions.telnet.TelnetConsole': None,
}

# 日志级别
LOG_LEVEL = 'INFO'

# 数据导出编码
FEED_EXPORT_ENCODING = 'utf-8'

6.2 创建数据处理管道

pipelines.py中创建数据清洗管道:

import json
import csv

class MyspiderPipeline:
    def __init__(self):
        self.file = open('movies_cleaned.json', 'w', encoding='utf-8')
    
    def process_item(self, item, spider):
        # 数据清洗:去除空格
        if item.get('title'):
            item['title'] = item['title'].strip()
        
        # 数据验证:确保评分是数字
        if item.get('rating'):
            try:
                item['rating'] = float(item['rating'])
            except:
                item['rating'] = None
        
        # 数据转换:将导演列表转为字符串
        if item.get('director'):
            item['director'] = ', '.join(item['director'])
        
        # 写入JSON文件
        line = json.dumps(dict(item), ensure_ascii=False) + "\n"
        self.file.write(line)
        
        return item
    
    def close_spider(self, spider):
        self.file.close()

7. 调试与测试

7.1 使用Scrapy Shell调试

在项目目录下执行:

scrapy shell "https://movie.douban.com/top250"

在Shell中测试选择器:

# 查看页面标题
response.css('title::text').get()

# 提取所有电影标题
response.css('div.item span.title::text').getall()

# 提取第一个电影的评分
response.css('div.item span.rating_num::text').get()

# 查看页面结构
response.body

7.2 使用浏览器开发者工具

  1. 打开豆瓣电影Top250页面
  2. 按F12打开开发者工具
  3. 选择”Elements”标签,右键点击元素,选择”Copy” → “Copy selector”
  4. 在Scrapy中使用该选择器测试

7.3 常见问题排查

问题1:爬虫没有返回任何数据

  • 检查选择器是否正确
  • 使用Scrapy Shell测试选择器
  • 检查网络连接和网站是否可访问

问题2:被网站封禁

  • 增加下载延迟:DOWNLOAD_DELAY = 2
  • 使用代理IP:在settings.py中添加:
    
    PROXY = 'http://your-proxy-ip:port'
    DOWNLOADER_MIDDLEWARES = {
      'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 110,
    }
    
  • 使用随机User-Agent:
    
    pip install scrapy-user-agents
    
    在settings.py中添加:
    
    DOWNLOADER_MIDDLEWARES = {
      'scrapy_user_agents.middlewares.RandomUserAgentMiddleware': 400,
    }
    

问题3:数据格式错误

  • 检查数据类型转换
  • 在pipeline中添加异常处理
  • 使用try-except捕获错误

8. 项目结构优化

8.1 创建多个爬虫

scrapy genspider baidu_spider www.baidu.com
scrapy genspider jd_spider www.jd.com

8.2 创建通用爬虫模板

spiders/目录下创建base_spider.py

import scrapy
from scrapy.http import Request

class BaseSpider(scrapy.Spider):
    """通用爬虫基类"""
    
    def start_requests(self):
        """生成初始请求"""
        for url in self.start_urls:
            yield Request(
                url=url,
                callback=self.parse,
                headers={'User-Agent': self.get_random_user_agent()}
            )
    
    def get_random_user_agent(self):
        """获取随机User-Agent"""
        import random
        user_agents = [
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0',
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15',
        ]
        return random.choice(user_agents)

8.3 创建数据存储模块

在项目根目录下创建data_storage.py

import json
import csv
import sqlite3
from datetime import datetime

class DataStorage:
    """数据存储管理器"""
    
    def __init__(self, storage_type='json'):
        self.storage_type = storage_type
        self.file = None
        self.conn = None
        
    def open(self, filename):
        """打开存储文件"""
        if self.storage_type == 'json':
            self.file = open(filename, 'w', encoding='utf-8')
        elif self.storage_type == 'csv':
            self.file = open(filename, 'w', newline='', encoding='utf-8')
            self.writer = csv.writer(self.file)
            # 写入表头
            self.writer.writerow(['title', 'rating', 'quote', 'director', 'genres', 
                                'release_date', 'duration', 'summary', 'votes', 'url'])
        elif self.storage_type == 'sqlite':
            self.conn = sqlite3.connect(filename)
            self.create_table()
    
    def create_table(self):
        """创建数据库表"""
        cursor = self.conn.cursor()
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS movies (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                title TEXT,
                rating REAL,
                quote TEXT,
                director TEXT,
                genres TEXT,
                release_date TEXT,
                duration TEXT,
                summary TEXT,
                votes TEXT,
                url TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        ''')
        self.conn.commit()
    
    def save(self, data):
        """保存数据"""
        if self.storage_type == 'json':
            line = json.dumps(data, ensure_ascii=False) + "\n"
            self.file.write(line)
        elif self.storage_type == 'csv':
            row = [
                data.get('title', ''),
                data.get('rating', ''),
                data.get('quote', ''),
                ', '.join(data.get('director', [])),
                ', '.join(data.get('genres', [])),
                data.get('release_date', ''),
                data.get('duration', ''),
                data.get('summary', ''),
                data.get('votes', ''),
                data.get('url', '')
            ]
            self.writer.writerow(row)
        elif self.storage_type == 'sqlite':
            cursor = self.conn.cursor()
            cursor.execute('''
                INSERT INTO movies (title, rating, quote, director, genres, 
                                   release_date, duration, summary, votes, url)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ''', (
                data.get('title'),
                data.get('rating'),
                data.get('quote'),
                ', '.join(data.get('director', [])),
                ', '.join(data.get('genres', [])),
                data.get('release_date'),
                data.get('duration'),
                data.get('summary'),
                data.get('votes'),
                data.get('url')
            ))
            self.conn.commit()
    
    def close(self):
        """关闭存储"""
        if self.file:
            self.file.close()
        if self.conn:
            self.conn.close()

9. 高级功能

9.1 使用代理IP

# 在settings.py中添加
PROXY_LIST = [
    'http://proxy1.example.com:8080',
    'http://proxy2.example.com:8080',
]

DOWNLOADER_MIDDLEWARES = {
    'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 110,
}

9.2 使用分布式爬虫

安装Scrapy-Redis:

pip install scrapy-redis

修改settings.py

SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
REDIS_URL = 'redis://localhost:6379/0'

9.3 使用Scrapy-Cloud

pip install scrapy-cloud

10. 部署与自动化

10.1 创建批处理脚本

在项目根目录创建run_spider.bat

@echo off
cd /d C:\scrapy_projects\myspider
scrapy crawl douban_spider -o movies_%date:~0,4%%date:~5,2%%date:~8,2%.jsonl
echo 爬虫执行完成
pause

10.2 使用Windows任务计划程序

  1. 打开”任务计划程序”
  2. 创建基本任务
  3. 设置触发器(每天/每周)
  4. 操作:启动程序
  5. 程序:C:\scrapy_projects\myspider\run_spider.bat

10.3 使用Docker容器化(可选)

创建Dockerfile

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["scrapy", "crawl", "douban_spider"]

11. 最佳实践与注意事项

11.1 遵守法律法规

  • 仅用于学习和研究目的
  • 遵守目标网站的robots.txt协议
  • 不要对网站造成过大访问压力
  • 尊重网站的版权和数据使用条款

11.2 性能优化

  • 合理设置并发数和下载延迟
  • 使用缓存机制避免重复请求
  • 优化选择器性能
  • 使用异步IO处理大量数据

11.3 错误处理

  • 添加重试机制
  • 处理网络超时
  • 处理页面解析错误
  • 记录日志便于调试

11.4 数据安全

  • 不要存储敏感信息
  • 加密存储重要数据
  • 定期清理临时文件

12. 总结

通过以上步骤,你已经成功在Windows系统上安装了Scrapy并创建了一个完整的爬虫项目。从环境准备、框架安装、项目创建、代码编写到运行调试,每个环节都有详细的说明和示例代码。

记住,爬虫技术是一把双刃剑,务必合法合规使用。在实际应用中,你可能需要根据具体网站的结构和反爬机制调整代码。建议先从简单的静态网站开始练习,逐步掌握更复杂的爬虫技术。

如果你在实践过程中遇到任何问题,可以:

  1. 查看Scrapy官方文档:https://docs.scrapy.org/
  2. 使用Scrapy Shell调试选择器
  3. 在Stack Overflow搜索相关问题
  4. 加入Scrapy社区获取帮助

祝你爬虫学习顺利!