admin 管理员组

文章数量: 1087649

我的第1个爬虫程序——豆瓣Top250爬虫的详细步骤指南


一、创建隔离开发环境

1. 使用虚拟环境(推荐venv
# 在项目目录打开终端执行
python -m venv douban_env      # 创建虚拟环境
source douban_env/bin/activate # Linux/macOS激活
douban_env\Scripts\activate   # Windows激活
2. 安装依赖库
pip install requests beautifulsoup4 lxml
3. 生成依赖清单
pip freeze > requirements.txt

二、项目架构设计

douban_top250/
├── config/               # 配置文件
│   └── settings.py
├── core/                 # 核心逻辑
│   ├── spider.py
│   └── storage.py
├── utils/                # 工具函数
│   └── helper.py
├── output/               # 输出目录
├── main.py               # 主入口
└── requirements.txt      # 依赖清单

三、分步实现

步骤1:创建配置文件 config/settings.py
# 请求配置
HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Referer': 'https://movie.douban/'
}

# 目标URL配置
BASE_URL = 'https://movie.douban/top250'

# 存储配置
OUTPUT_DIR = './output'
CSV_HEADERS = ['标题', '评分', '年份', '国家', '类型', '链接']

# 容错配置
SAFE_MODE = True  # 遇到错误时跳过条目而不是终止
UNKNOWN_PLACEHOLDER = "未知"  # 数据缺失时的占位符
步骤2:编写工具类 utils/helper.py
import random
import time

def random_delay(min=1, max=3):
    """随机延迟防止被封"""
    time.sleep(random.uniform(min, max))

def make_soup(html):
    """创建BeautifulSoup对象"""
    from bs4 import BeautifulSoup
    return BeautifulSoup(html, 'lxml')
步骤3:核心爬虫逻辑 core/spider.py
import requests
from config import settings
from utils.helper import random_delay, make_soup

class DoubanSpider:
    def __init__(self):
        self.session = requests.Session()
        self.session.headers.update(settings.HEADERS)

    def fetch_page(self, url):
        """获取页面内容"""
        try:
            random_delay()
            response = self.session.get(url)
            response.raise_for_status()  # 自动处理HTTP错误
            return response.text
        except requests.RequestException as e:
            print(f"请求失败: {str(e)}")
            return None

    def parse_page(self, html):
        """改进后的解析方法"""
        soup = make_soup(html)
        movies = []
        
        for item in soup.find_all('div', class_='item'):
            try:
                # 标题与链接
                title = item.find('span', class_='title').text.strip()
                rating = item.find('span', class_='rating_num').text.strip()
                link = item.find('a')['href']
                
                # 详细信息解析(稳健版)
                info_div = item.find('div', class_='bd')
                info_text = info_div.p.get_text(" ", strip=True)  # 用空格替代换行
                
                # 使用正则表达式提取年份/国家/类型
                import re
                pattern = r'(\d{4})[^/]*(.*?)\s+/\s+(.*?)$'
                match = re.search(pattern, info_text)
                
                if match:
                    year = match.group(1).strip()
                    country = match.group(2).strip().replace('/', ' ')  # 处理国家中的斜杠
                    genre = match.group(3).strip()
                else:
                    year = country = genre = "N/A"  # 无法解析时填充默认值
                    
                movies.append({
                    '标题': title,
                    '评分': rating,
                    '年份': year,
                    '国家': country,
                    '类型': genre,
                    '链接': link
                })
                
            except Exception as e:
                print(f"解析条目失败: {str(e)}")
                continue  # 跳过当前条目
                
        return movies

    def get_all_pages(self):
        """处理分页"""
        all_movies = []
        start = 0
        
        while True:
            url = f"{settings.BASE_URL}?start={start}"
            html = self.fetch_page(url)
            if not html:
                break
                
            movies = self.parse_page(html)
            if not movies:
                break
                
            all_movies.extend(movies)
            start += 25
            # 检查是否还有下一页
            if start >= 250:  # Top250最多250条
                break
        
        return all_movies
步骤4:数据存储模块 core/storage.py
import csv
import json
import os
from config import settings

class DataStorage:
    @staticmethod
    def save_csv(data, filename='douban_top250.csv'):
        os.makedirs(settings.OUTPUT_DIR, exist_ok=True)
        path = os.path.join(settings.OUTPUT_DIR, filename)
        
        with open(path, 'w', newline='', encoding='utf-8') as f:
            writer = csv.DictWriter(f, fieldnames=settings.CSV_HEADERS)
            writer.writeheader()
            writer.writerows(data)
        print(f"数据已保存至 {path}")

    @staticmethod
    def save_json(data, filename='douban_top250.json'):
        os.makedirs(settings.OUTPUT_DIR, exist_ok=True)
        path = os.path.join(settings.OUTPUT_DIR, filename)
        
        with open(path, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=2)
        print(f"数据已保存至 {path}")
步骤5:主程序 main.py
from core.spider import DoubanSpider
from core.storage import DataStorage

def main():
    # 检查robots协议
    print("豆瓣 robots.txt 重要条款:")
    print("User-agent: *")
    print("Disallow: /search")  # 实际需查看最新内容
    
    # 执行爬虫
    spider = DoubanSpider()
    movies_data = spider.get_all_pages()
    
    # 存储数据
    if movies_data:
        DataStorage.save_csv(movies_data)
        DataStorage.save_json(movies_data)
    else:
        print("未获取到有效数据")

if __name__ == '__main__':
    main()

四、运行与验证

  1. 在激活的虚拟环境中执行:
python main.py
  1. 检查 output/ 目录生成的 CSV 和 JSON 文件

五、高级优化建议

  1. 异常处理增强
# 在spider类中添加重试机制
def fetch_page(self, url, retries=3):
    for attempt in range(retries):
        try:
            # ...原有代码...
        except requests.RequestException as e:
            if attempt == retries - 1:
                raise
            print(f"重试中 ({attempt+1}/{retries})...")
            time.sleep(2 ** attempt)  # 指数退避
  1. 请求头轮换
# 在settings.py中添加多个User-Agent
USER_AGENTS = [
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15',
    'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
]

# 在helper.py中添加选择函数
def get_random_user_agent():
    return random.choice(settings.USER_AGENTS)
  1. 代理设置(如果需要):
# 在spider初始化时添加
def __init__(self, proxy=None):
    if proxy:
        self.session.proxies = {'http': proxy, 'https': proxy}

六、法律合规检查

  1. 访问 https://www.douban/robots.txt 查看协议
  2. 重点条款:
User-agent: *
Disallow: /subject_search
Disallow: /amazon_search
Disallow: /search
Disallow: /group/search
Disallow: /event/search
Disallow: /forum/search
Disallow: /game/search
  1. 合规措施:
  • 限制请求频率(代码中已实现随机延迟)
  • 不绕过反爬机制
  • 仅用于学习用途
  • 不存储敏感信息

通过这个结构化的项目实现,你可以:

  • 保持代码的可维护性
  • 方便后续扩展功能(如添加代理支持)
  • 符合Python最佳实践
  • 有效管理依赖项

下一步可以尝试:

  1. 添加日志记录模块
  2. 实现数据库存储(MySQL/MongoDB)
  3. 使用Scrapy框架重构项目
  4. 部署到服务器定时运行

本文标签: 爬虫 豆瓣 步骤 指南 程序