爬豆瓣电影Top250是很多Python爬虫新手的入门练手项目,但真动手做起来会发现不少“坑”:请求被403拒绝、数据提取混乱、导出Excel格式错乱…… 我最近刚做完这个练手项目,从0到1踩了不少坑,也总结了一套能稳定运行的流程。这篇文章就带你一步步实现:从分析网页结构、处理反爬,到精准提取数据、清洗格式,最后用pandas导出Excel,全程附可直接运行的代码和避坑细节。

一、先搞清楚:我们要爬什么?怎么爬?

爬取前先明确目标:豆瓣电影Top250页面(https://movie.douban.com/top250)有250部电影,分10页展示(每页25部)。我们需要提取每部电影的排名、标题、导演/主演、年份/国家/类型、评分、评价人数、简介这7项信息,最后汇总成Excel。

先看网页结构(F12打开开发者工具):
每部电影的信息都包裹在<li>标签里,class是"item"。点进一个item能看到:排名在<em>标签里,标题在class="title"<span>里,导演主演在class="bd"<p>里,评分在class="rating_num"里…… 这些就是我们要定位的标签。

豆瓣电影Top250网页结构(实际写时可配自己抓的图)

爬取思路很简单:

  1. 循环请求10页数据(URL规律:第1页?start=0,第2页?start=25,…,第10页?start=225);
  2. 用BeautifulSoup解析每页HTML,提取7项信息;
  3. 清洗数据(比如从标题里拆年份、处理多语言标题);
  4. 用pandas导出到Excel。

二、环境准备:3个库就够了

不需要复杂的环境,安装3个基础库:

  • requests:发送HTTP请求获取网页内容;
  • beautifulsoup4:解析HTML提取数据;
  • pandas:处理数据并导出Excel(依赖openpyxl库)。

直接用pip安装:

pip install requests beautifulsoup4 pandas openpyxl

建议用Python3.8+版本,避免一些库的兼容性问题。

三、爬取数据:重点处理反爬和标签定位

3.1 发送请求:先解决“被豆瓣拒绝”的问题

第一次写的时候,直接用requests.get(url)请求,结果返回403 Forbidden——豆瓣有反爬机制,会识别“非浏览器”的请求。解决办法是给请求加User-Agent头,模拟浏览器访问。

User-Agent可以从浏览器开发者工具的“Network”里找(随便刷新一个页面,看请求头里的User-Agent),比如Chrome的可以用:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36

封装一个请求函数,自动处理10页URL:

import requests
from bs4 import BeautifulSoup
import time

# 模拟浏览器请求头
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
}

def get_page_html(page_num):
    """获取第page_num页的HTML内容(page_num从0到9)"""
    start = page_num * 25  # 计算start参数:0,25,...,225
    url = f"https://movie.douban.com/top250?start={start}&filter="
    try:
        # 发送GET请求,添加超时控制(避免卡壳)
        response = requests.get(url, headers=headers, timeout=10)
        response.raise_for_status()  # 如果状态码不是200,抛出异常
        # 豆瓣网页用utf-8编码,直接指定避免乱码
        response.encoding = "utf-8"
        return response.text
    except Exception as e:
        print(f"第{page_num+1}页请求失败:{e}")
        return None

注意加timeout=10response.raise_for_status(),避免请求超时或出错时程序崩溃。另外,爬多页时建议加个time.sleep(1),别太频繁请求(礼貌爬取,避免被封IP)。

3.2 解析数据:精准定位标签,提取7项信息

拿到HTML后,用BeautifulSoup解析。核心是从每个class="item"<li>里提取数据,逐个分析标签:

  1. 排名:在<em class="">标签里,直接取文本即可;
  2. 标题:有多个<span class="title">,第一个是中文标题,后面可能有外文标题(带/分隔),我们只取中文标题;
  3. 导演/主演:在<div class="bd">下的第一个<p>里,文本格式是“导演: 张艺谋 主演: 葛优 / 巩俐 …”,需要拆分;
  4. 年份/国家/类型:和导演信息在同一个<p>里,靠后的位置,格式是“1994 / 中国大陆 / 剧情”;
  5. 评分:在<span class="rating_num">里,文本是“9.7”这样的数字;
  6. 评价人数:在评分后面的<span>里,格式是“(123456人评价)”,需要提取数字;
  7. 简介:在<span class="inq">里,部分电影没有简介,需要处理空值。

直接上解析代码,每个提取步骤都加了注释:

def parse_html(html):
    """解析单页HTML,返回电影数据列表"""
    soup = BeautifulSoup(html, "html.parser")  # 用html.parser解析
    movie_list = []
    # 找到所有电影item
    items = soup.find_all("li", class_="item")
    
    for item in items:
        movie = {}
        # 1. 排名
        rank = item.find("em").text.strip()
        movie["排名"] = int(rank)  # 转成整数
        
        # 2. 标题(只取中文标题)
        title_tags = item.find_all("span", class_="title")
        # 中文标题通常是第一个,且不带/
        chinese_title = [t.text for t in title_tags if "/" not in t.text][0]
        movie["标题"] = chinese_title
        
        # 3. 导演和主演
        bd_p = item.find("div", class_="bd").find("p").text.strip()
        # 按换行分割,第一行是导演,第二行是年份等信息
        bd_lines = [line.strip() for line in bd_p.split("\n") if line.strip()]
        director_actor_line = bd_lines[0]
        # 拆分导演和主演(格式:"导演: 张艺谋 主演: 葛优 / 巩俐")
        if "主演:" in director_actor_line:
            director_part, actor_part = director_actor_line.split("主演:")
            movie["导演"] = director_part.replace("导演:", "").strip()
            movie["主演"] = actor_part.strip()
        else:
            # 有些电影可能没有主演信息
            movie["导演"] = director_part.replace("导演:", "").strip()
            movie["主演"] = "无"
        
        # 4. 年份、国家、类型(从第二行提取)
        info_line = bd_lines[1]
        # 格式:"1994 / 中国大陆 / 剧情",按/分割
        info_parts = [p.strip() for p in info_line.split("/")]
        movie["年份"] = info_parts[0] if len(info_parts)>=1 else "未知"
        movie["国家"] = info_parts[1] if len(info_parts)>=2 else "未知"
        movie["类型"] = info_parts[2] if len(info_parts)>=3 else "未知"
        
        # 5. 评分
        rating = item.find("span", class_="rating_num").text.strip()
        movie["评分"] = float(rating)  # 转成浮点数
        
        # 6. 评价人数(提取数字)
        comment_span = item.find("div", class_="star").find_all("span")[-1]
        comment_text = comment_span.text.strip()
        # 用切片去掉"(人评价)",只留数字
        comment_num = comment_text[1:-4]
        # 处理带逗号的数字(如"123,456")
        movie["评价人数"] = int(comment_num.replace(",", ""))
        
        # 7. 简介(可能为空)
        quote = item.find("span", class_="inq")
        movie["简介"] = quote.text.strip() if quote else "无简介"
        
        movie_list.append(movie)
    
    return movie_list

这里有几个细节需要注意:

  • 标题可能有多个(中文+外文),用if "/" not in t.text过滤出中文标题;
  • 导演和主演可能在同一行,用“主演:”分割,避免提取混乱;
  • 评价人数带逗号(如“123,456”),需要用replace(",", "")处理后再转整数;
  • 部分电影没有简介,用if quote else "无简介"避免报错。

四、数据清洗:让Excel里的数据更规整

爬下来的数据虽然能看,但还有些小问题需要处理,比如:

  • 年份是字符串(如“1994”),可以转成整数;
  • 国家可能有多个(如“中国大陆 / 中国香港”),保持原样即可;
  • 类型可能有多个(如“剧情 / 爱情”),无需拆分;
  • 极少数电影的“评价人数”可能为空(概率极低,加个判断兜底)。

简单写个清洗函数(其实在解析时已经处理了大部分,这里再补充):

def clean_data(movies):
    """清洗电影数据,处理异常值"""
    for movie in movies:
        # 年份转整数(防止有非数字的异常值)
        try:
            movie["年份"] = int(movie["年份"])
        except:
            movie["年份"] = "未知"
    return movies

五、导出Excel:用pandas一行代码搞定

数据处理完后,用pandas导出Excel非常方便。直接创建DataFrame,然后调用to_excel方法:

import pandas as pd

def save_to_excel(movies, filename="豆瓣电影Top250.xlsx"):
    """将电影数据保存到Excel"""
    df = pd.DataFrame(movies)
    # 调整列顺序(按我们想要的顺序排列)
    columns = ["排名", "标题", "年份", "国家", "类型", "导演", "主演", "评分", "评价人数", "简介"]
    df = df[columns]
    # 导出Excel,index=False去掉行索引
    df.to_excel(filename, index=False, engine="openpyxl")
    print(f"数据已保存到{filename},共{len(movies)}条记录")

这里指定engine="openpyxl"是因为pandas导出xlsx格式需要这个引擎(前面已经安装过)。导出后打开Excel,数据会按我们指定的列顺序排列,非常规整。

六、完整代码:组装起来,跑一遍试试

把上面的函数组装成完整程序,加上主逻辑:

import requests
from bs4 import BeautifulSoup
import time
import pandas as pd

# 模拟浏览器请求头
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
}

def get_page_html(page_num):
    """获取第page_num页的HTML内容(page_num从0到9)"""
    start = page_num * 25
    url = f"https://movie.douban.com/top250?start={start}&filter="
    try:
        response = requests.get(url, headers=headers, timeout=10)
        response.raise_for_status()
        response.encoding = "utf-8"
        return response.text
    except Exception as e:
        print(f"第{page_num+1}页请求失败:{e}")
        return None

def parse_html(html):
    """解析单页HTML,返回电影数据列表"""
    soup = BeautifulSoup(html, "html.parser")
    movie_list = []
    items = soup.find_all("li", class_="item")
    
    for item in items:
        movie = {}
        # 排名
        movie["排名"] = int(item.find("em").text.strip())
        # 标题(中文)
        title_tags = item.find_all("span", class_="title")
        chinese_title = [t.text for t in title_tags if "/" not in t.text][0]
        movie["标题"] = chinese_title
        # 导演和主演
        bd_p = item.find("div", class_="bd").find("p").text.strip()
        bd_lines = [line.strip() for line in bd_p.split("\n") if line.strip()]
        director_actor_line = bd_lines[0]
        if "主演:" in director_actor_line:
            director_part, actor_part = director_actor_line.split("主演:")
            movie["导演"] = director_part.replace("导演:", "").strip()
            movie["主演"] = actor_part.strip()
        else:
            movie["导演"] = director_actor_line.replace("导演:", "").strip()
            movie["主演"] = "无"
        # 年份、国家、类型
        info_line = bd_lines[1]
        info_parts = [p.strip() for p in info_line.split("/")]
        movie["年份"] = info_parts[0] if len(info_parts)>=1 else "未知"
        movie["国家"] = info_parts[1] if len(info_parts)>=2 else "未知"
        movie["类型"] = info_parts[2] if len(info_parts)>=3 else "未知"
        # 评分
        movie["评分"] = float(item.find("span", class_="rating_num").text.strip())
        # 评价人数
        comment_span = item.find("div", class_="star").find_all("span")[-1]
        comment_text = comment_span.text.strip()
        movie["评价人数"] = int(comment_text[1:-4].replace(",", ""))
        # 简介
        quote = item.find("span", class_="inq")
        movie["简介"] = quote.text.strip() if quote else "无简介"
        
        movie_list.append(movie)
    return movie_list

def clean_data(movies):
    """清洗数据"""
    for movie in movies:
        try:
            movie["年份"] = int(movie["年份"])
        except:
            movie["年份"] = "未知"
    return movies

def save_to_excel(movies, filename="豆瓣电影Top250.xlsx"):
    """保存到Excel"""
    df = pd.DataFrame(movies)
    columns = ["排名", "标题", "年份", "国家", "类型", "导演", "主演", "评分", "评价人数", "简介"]
    df = df[columns]
    df.to_excel(filename, index=False, engine="openpyxl")
    print(f"数据已保存到{filename},共{len(movies)}条记录")

def main():
    all_movies = []
    # 爬取10页数据(0-9)
    for page in range(10):
        print(f"正在爬取第{page+1}页...")
        html = get_page_html(page)
        if not html:
            continue  # 失败则跳过该页
        movies = parse_html(html)
        all_movies.extend(movies)
        time.sleep(1)  # 间隔1秒,礼貌爬取
    # 清洗并保存
    all_movies = clean_data(all_movies)
    save_to_excel(all_movies)

if __name__ == "__main__":
    main()

运行程序,会看到控制台输出“正在爬取第X页”,最后提示“数据已保存到豆瓣电影Top250.xlsx”。打开Excel,250部电影的信息整齐排列,包括《肖申克的救赎》《霸王别姬》等经典影片的详细信息。

七、避坑总结:这些细节决定爬取成败

  1. User-Agent必须加:豆瓣会检测请求头,没加或用默认头会被403;
  2. 请求间隔别太短:连续快速请求可能被封IP,加time.sleep(1)足够;
  3. 标签提取要精准:比如标题区分中文/外文、评价人数处理逗号,这些细节错了数据就乱了;
  4. 异常处理不能少:用try-except处理请求失败、数据格式异常,避免程序中途崩溃;
  5. 编码指定为utf-8:豆瓣网页用utf-8编码,不指定可能出现中文乱码。

八、扩展思路:让这个爬虫更强大

如果想进一步练习,可以试试这些扩展:

  • 加个IP代理池:如果爬多了被封IP,用代理池切换IP(推荐requests-proxies库);
  • 多线程爬取:用threadingconcurrent.futures提速(注意控制线程数,别给服务器太大压力);
  • 存储到数据库:用pymysql存到MySQL,或sqlite3存本地数据库;
  • 可视化分析:用matplotlib画评分分布直方图,或用wordcloud做电影类型词云。

爬豆瓣Top250看似简单,但能帮你掌握爬虫的核心流程:分析页面→发送请求→解析数据→清洗存储。掌握这些后,再爬其他网站(比如豆瓣图书、IMDb)会更得心应手。如果运行代码时遇到问题,欢迎在评论区留言,我会尽量回复~

Logo

更多推荐