1. 项目概述从零构建你的第一个Python网页抓取器如果你和我一样对机器学习、人工智能或者数据分析充满兴趣并且选择了Python作为你的主要工具那么你迟早会遇到一个核心问题数据从哪里来教科书和公开数据集固然好用但真实世界的问题往往需要更定制化、更鲜活的数据。当你在Kaggle上找不到心仪的数据集或者API调用次数受限、费用高昂时网页抓取Web Scraping就成了一项必须掌握的生存技能。这不仅仅是获取数据更是理解网络信息结构、锻炼编程逻辑的绝佳项目。我自学编程时就是从构建一个电影信息抓取器开始的这个过程充满了“原来如此”的顿悟时刻也踩遍了新手能遇到的所有坑。今天我就把这个从零到一的完整过程连同我趟过的雷、总结的技巧毫无保留地分享给你。我们将以IMDb Top 1000电影榜单的前50页为例手把手教你打造一个能稳定运行、数据整洁的Python网页抓取器。2. 核心思路与工具选型为什么是它们在动手写代码之前理清思路和选对工具是成功的一半。网页抓取的本质是模拟浏览器访问网页然后从返回的HTML“源代码”中提取结构化信息。这个过程听起来简单但涉及到网络请求、HTML解析、数据清洗等多个环节。2.1 为什么选择Requests和BeautifulSoup对于初学者乃至大多数常规抓取任务RequestsBeautifulSoup的组合是黄金标准。Requests库负责与网络服务器对话用几行代码就能模拟浏览器发送请求并获取网页内容它比Python自带的urllib库更简洁、更人性化。而BeautifulSoup则是一个HTML/XML解析器它能把Requests抓回来的、杂乱无章的HTML字符串转换成一棵结构清晰的“树”。你可以像在文件管理器中导航文件夹一样在这棵树上通过标签名、属性等轻松定位到你想要的任何数据节点。这个组合的学习曲线平缓文档丰富社区支持强大非常适合作为入门之选。注意有些网站内容由JavaScript动态加载即“所见”并非“所得”初始HTML中不包含数据。RequestsBeautifulSoup抓取的是服务器最初返回的静态HTML。对于动态加载的网站可能需要用到Selenium或Playwright这类能控制真实浏览器的工具。幸运的是我们目标网站IMDb的榜单页面是静态的用我们的组合正合适。2.2 数据处理搭档Pandas与NumPy抓取数据只是第一步让数据变得可用才是目的。Pandas是Python数据分析的基石它提供的DataFrame数据结构你可以理解为增强版的Excel表格是存储、清洗、分析表格数据的利器。我们将把抓取到的零散列表电影名、年份等组合成一个DataFrame后续的清洗操作如去除符号、转换类型在Pandas中都能用一行优雅的代码完成。NumPy则为Pandas提供了底层的高性能数学运算支持虽然在这个项目中我们直接使用不多但作为科学计算生态的核心一并导入是好习惯。2.3 环境准备告别配置烦恼为了避免复杂的本地环境配置我强烈建议初学者使用在线编程环境如Replit来跟随本教程。它开箱即用预装了Python和常用库你只需要一个浏览器就能开始编码。当然如果你本地已经有Jupyter Notebook或VS Code等IDE也完全没问题。确保你的Python版本在3.6以上然后用pip安装必要的库pip install requests beautifulsoup4 pandas numpy3. 实战解析一步步拆解IMDb电影数据抓取理论说再多不如一行代码。让我们直接进入实战我会详细解释每一段代码的意图和可能遇到的陷阱。3.1 发起请求与解析HTML首先我们需要告诉程序去哪里获取数据并确保服务器愿意把数据给我们。import requests from bs4 import BeautifulSoup import pandas as pd import numpy as np # 设置请求头模拟真实浏览器访问这是规避基础反爬机制的关键一步 headers { 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, Accept-Language: en-US,en;q0.9, # 明确要求英文内容避免拿到其他语言标题 } # 目标URLIMDb Top 1000电影列表的第一页 url https://www.imdb.com/search/title/?groupstop_1000sortuser_rating,desccount50 # 发送GET请求 response requests.get(url, headersheaders) # 检查请求是否成功状态码200表示成功 if response.status_code 200: print(网页请求成功) # 使用BeautifulSoup解析返回的HTML内容指定解析器为lxml需安装或html.parser内置 soup BeautifulSoup(response.content, html.parser) else: print(f请求失败状态码{response.status_code})关键点解析User-Agent这是你的网络请求的“身份证”。如果不设置默认的Python-Requests标识可能会被网站识别为爬虫并拒绝服务。这里我们伪装成Chrome浏览器。Accept-Language确保我们拿到的是英文电影标题而不是其他语言版本。response.status_code始终检查状态码是一个好习惯。除了200常见的还有404页面未找到、403禁止访问、429请求过多等针对不同状态码需要设计不同的处理逻辑。解析器选择html.parser是Python内置的无需安装lxml解析速度更快、容错能力更强。如果你安装了lxmlpip install lxml推荐使用它。3.2 定位数据容器学会使用开发者工具这是网页抓取中最核心的技能——找到目标数据在HTML中的唯一“地址”。以Chrome浏览器为例在目标网页上右键点击选择“检查”Inspect打开开发者工具。我们的目标是抓取每一部电影的信息。在页面上右键点击任意一部电影的区域选择“检查”你会发现高亮显示的HTML代码通常是一个div标签并且带有一个特定的class属性比如classlister-item mode-advanced。这个div就像是一个集装箱里面装着一部电影的所有信息标题、年份、评分等。在开发者工具中你可以按CtrlF搜索lister-item mode-advanced会发现正好有50个匹配项对应页面上的50部电影。这就确定了我们抓取的循环单元。# 找到所有包含单部电影信息的容器div movie_containers soup.find_all(div, class_lister-item mode-advanced) print(f本页共找到 {len(movie_containers)} 个电影容器。)3.3 数据提取遍历容器与精准抓取现在我们需要从一个“集装箱”里把我们需要的小件货物数据一件件拿出来。我们将创建几个空列表用于存储从50个容器里提取出的同类数据。# 初始化空列表用于存储抓取的数据 titles [] years [] runtimes [] imdb_ratings [] metascores [] votes [] grosses []接下来我们遍历每一个电影容器并从中提取具体信息。这里会用到BeautifulSoup的各种查找方法。提取电影标题 标题通常在一个h3标签内的a标签里。因为结构清晰我们可以使用“点号”链式访问。for container in movie_containers: # 提取标题 title container.h3.a.text titles.append(title)提取上映年份 年份信息在一个span标签里但页面上有多个span。我们需要用更精确的属性来定位它比如它的class是lister-item-year。这里使用find()方法它只返回第一个匹配的结果。# 提取年份并处理可能的缺失值 year_element container.find(span, class_lister-item-year) year year_element.text.strip(() ) if year_element else N/A # 去除括号和空格若无则标记为N/A years.append(year)提取时长、评分等信息 原理类似都是通过标签和属性定位。关键在于观察HTML结构找到目标数据独一无二的标识。# 提取时长 runtime_element container.find(span, class_runtime) runtime runtime_element.text.replace( min, ) if runtime_element else N/A runtimes.append(runtime) # 提取IMDb评分在strong标签内相对容易 imdb_element container.find(strong) imdb float(imdb_element.text) if imdb_element else N/A imdb_ratings.append(imdb) # 提取Metascore注意其class可能是metascore favorable或metascore mixed只取共同部分metascore metascore_element container.find(span, class_metascore) metascore int(metascore_element.text) if metascore_element else N/A metascores.append(metascore)最棘手的部分投票数与票房收入 观察HTML你会发现投票数Votes和票房Gross都在属性为namenv的span标签里且投票数在前票房在后。但问题来了不是每部电影都有票房数据比如新上映或特定地区的电影。我们必须编写能处理这种数据缺失情况的健壮代码。# 提取所有属性为namenv的span标签这通常包含votes和gross数据 nv_elements container.find_all(span, attrs{name: nv}) # 提取投票数通常是第一个nv元素 vote nv_elements[0].text.replace(,, ) if len(nv_elements) 0 else N/A votes.append(vote) # 提取票房通常是第二个nv元素但可能不存在 if len(nv_elements) 1: gross nv_elements[1].text else: gross N/A grosses.append(gross)循环结束至此for循环内的代码完成了一部电影所有信息的提取并追加到了各自的列表中。循环会重复50次直到处理完本页所有电影。3.4 数据清洗与格式化从原始文本到可用数据现在我们有了6个列表但里面的数据是夹杂着各种符号如$、,、min、()的字符串。为了进行数值分析我们必须清洗它们。# 创建DataFrame movies_df pd.DataFrame({ title: titles, year: years, runtime_min: runtimes, imdb_rating: imdb_ratings, metascore: metascores, votes: votes, us_gross_millions: grosses }) print(原始数据预览) print(movies_df.head()) print(\n原始数据类型) print(movies_df.dtypes)查看输出你会发现year、runtime_min等列是object类型即字符串而不是我们期望的整数或浮点数。清洗年份去除括号转换为整数。# 使用正则表达式提取数字部分并转换类型 movies_df[year] movies_df[year].str.extract((\d)).astype(float).astype(Int64) # 使用Int64支持NaN整数清洗时长去除“min”字样转换为整数。movies_df[runtime_min] pd.to_numeric(movies_df[runtime_min], errorscoerce).astype(Int64)清洗Metascore已经是数字直接转换注意处理缺失值。movies_df[metascore] pd.to_numeric(movies_df[metascore], errorscoerce).astype(Int64)清洗投票数去除千分位逗号转换为整数。movies_df[votes] movies_df[votes].str.replace(,, , regexFalse) movies_df[votes] pd.to_numeric(movies_df[votes], errorscoerce).astype(Int64)清洗票房这是最复杂的一步需要去除美元符号$和“M”代表百万并转换为浮点数。同时缺失值‘N/A’需要被正确处理。# 首先将非数字字符串如N/A替换为NaN movies_df[us_gross_millions] movies_df[us_gross_millions].replace(N/A, np.nan) # 然后对剩余字符串进行处理去除$和M并转换为浮点数 # 使用lambda函数逐元素处理 movies_df[us_gross_millions] movies_df[us_gross_millions].apply( lambda x: float(str(x).lstrip($).rstrip(M)) if pd.notna(x) and x ! N/A else np.nan )最终检查print(\n清洗后数据预览) print(movies_df.head()) print(\n清洗后数据类型) print(movies_df.dtypes) print(f\n数据形状{movies_df.shape})3.5 数据持久化保存成果清洗好的数据如果不保存程序关闭后就消失了。保存为CSV文件是最通用的选择。# 保存到CSV文件 movies_df.to_csv(imdb_top_50_movies.csv, indexFalse, encodingutf-8-sig) print(数据已成功保存至 imdb_top_50_movies.csv)indexFalse不将DataFrame的索引0,1,2...保存到文件。encodingutf-8-sig使用带BOM的UTF-8编码确保在Excel等软件中打开时中文或其他字符不会乱码。4. 避坑指南与进阶技巧按照上面的步骤你应该已经成功运行了一次抓取。但真实世界的抓取任务远非一帆风顺。下面是我在实践中总结的常见问题和进阶思路。4.1 请求被拒与反爬虫策略如果你在运行requests.get()时收到403 Forbidden或429 Too Many Requests错误说明网站采取了反爬措施。策略一完善请求头。除了User-Agent有时还需要添加Referer来源页、Accept-Encoding等。用浏览器开发者工具的“Network”面板查看一次正常访问的请求头并复制。策略二添加延迟。在循环请求页面时使用time.sleep()在请求间随机等待几秒模拟人类浏览速度。import time import random time.sleep(random.uniform(1, 3)) # 随机等待1到3秒策略三使用代理IP。当单一IP请求过于频繁时轮换使用不同的IP地址是有效方法。但这涉及付费服务或自建代理池复杂度较高初学者可先了解。最重要原则遵守robots.txt。在网站根目录下如https://www.imdb.com/robots.txt查看其爬虫协议。尊重Disallow规则是对网站运营者的基本尊重也能避免法律风险。4.2 数据提取失败与代码健壮性你的代码可能在99部电影上运行良好却在第100部崩溃因为它的HTML结构稍有不同。始终假设数据可能缺失就像我们处理票房数据一样对每个find()操作都使用if...else进行判断或利用try...except捕获异常并为缺失数据设置默认值如np.nan或‘N/A’。使用更灵活的查找方法除了class还可以用id、name属性甚至CSS选择器soup.select()。find()和find_all()也支持正则表达式匹配应对微小的属性值变动。打印中间结果调试当提取不到数据时将soup或当前container的内容漂亮地打印出来print(container.prettify())仔细对比与预期结构的差异。4.3 抓取多页数据我们目前只抓了一页50部电影。要抓取Top 1000需要分析翻页逻辑。观察IMDb榜单URLhttps://www.imdb.com/search/title/?groupstop_1000sortuser_rating,desccount50start51ref_adv_nxt关键参数是start51表示从第51部电影开始。下一页是start101以此类推。我们可以用一个循环来生成所有页面的URLbase_url https://www.imdb.com/search/title/?groupstop_1000sortuser_rating,desccount50start{} all_movies_data [] # 用于存放所有页的数据 for start in range(1, 1001, 50): # start从1开始每次增加50直到1000 url base_url.format(start) print(f正在抓取: {url}) # 这里插入之前单页抓取的全部代码但将数据追加到all_movies_data # ... # 抓取完一页后务必添加延迟 time.sleep(random.uniform(2, 5)) # 循环结束后将all_movies_data列表合并成一个大的DataFrame4.4 数据清洗的更多可能性我们进行了基础的类型转换和字符清理。根据分析目的还可以统一格式检查year列中是否混有(I),(II)等罗马数字标识并决定是保留还是剔除。处理异常值检查runtime_min或imdb_rating是否有明显不合理的值如时长5000分钟评分11分。衍生新特征例如计算imdb_rating和metascore的差值作为“影评人与大众口碑分歧度”的指标。5. 项目总结与扩展方向至此一个功能完整、具备一定健壮性的单页网页抓取器就构建完成了。回顾整个过程你不仅学会了如何使用Requests和BeautifulSoup抓取数据更掌握了数据分析的前置关键步骤——数据清洗与格式化。这个项目麻雀虽小五脏俱全涵盖了从环境搭建、网络请求、HTML解析、数据提取、异常处理到数据保存的完整数据获取流水线。这个项目可以轻松地扩展为你的个人数据工具箱定时抓取监控结合计划任务如cron定期抓取特定商品价格、股票指数、新闻头条制作自己的监控面板。竞品分析抓取电商平台的商品信息、评论进行价格和口碑分析。知识聚合抓取技术博客、论坛的优质文章构建自己的知识库。深入IMDb尝试抓取每部电影的详细页面获取导演、演员、剧情简介、类型标签等信息构建更丰富的电影数据集。记住网页抓取是一项实践性极强的技能。最好的学习方式就是找到你感兴趣的数据源不断地尝试、出错、调试、再尝试。每一次解决“为什么抓不到数据”的问题都会让你对网络和代码的理解更深一层。
Python网页抓取入门:从零构建IMDb电影数据采集器
1. 项目概述从零构建你的第一个Python网页抓取器如果你和我一样对机器学习、人工智能或者数据分析充满兴趣并且选择了Python作为你的主要工具那么你迟早会遇到一个核心问题数据从哪里来教科书和公开数据集固然好用但真实世界的问题往往需要更定制化、更鲜活的数据。当你在Kaggle上找不到心仪的数据集或者API调用次数受限、费用高昂时网页抓取Web Scraping就成了一项必须掌握的生存技能。这不仅仅是获取数据更是理解网络信息结构、锻炼编程逻辑的绝佳项目。我自学编程时就是从构建一个电影信息抓取器开始的这个过程充满了“原来如此”的顿悟时刻也踩遍了新手能遇到的所有坑。今天我就把这个从零到一的完整过程连同我趟过的雷、总结的技巧毫无保留地分享给你。我们将以IMDb Top 1000电影榜单的前50页为例手把手教你打造一个能稳定运行、数据整洁的Python网页抓取器。2. 核心思路与工具选型为什么是它们在动手写代码之前理清思路和选对工具是成功的一半。网页抓取的本质是模拟浏览器访问网页然后从返回的HTML“源代码”中提取结构化信息。这个过程听起来简单但涉及到网络请求、HTML解析、数据清洗等多个环节。2.1 为什么选择Requests和BeautifulSoup对于初学者乃至大多数常规抓取任务RequestsBeautifulSoup的组合是黄金标准。Requests库负责与网络服务器对话用几行代码就能模拟浏览器发送请求并获取网页内容它比Python自带的urllib库更简洁、更人性化。而BeautifulSoup则是一个HTML/XML解析器它能把Requests抓回来的、杂乱无章的HTML字符串转换成一棵结构清晰的“树”。你可以像在文件管理器中导航文件夹一样在这棵树上通过标签名、属性等轻松定位到你想要的任何数据节点。这个组合的学习曲线平缓文档丰富社区支持强大非常适合作为入门之选。注意有些网站内容由JavaScript动态加载即“所见”并非“所得”初始HTML中不包含数据。RequestsBeautifulSoup抓取的是服务器最初返回的静态HTML。对于动态加载的网站可能需要用到Selenium或Playwright这类能控制真实浏览器的工具。幸运的是我们目标网站IMDb的榜单页面是静态的用我们的组合正合适。2.2 数据处理搭档Pandas与NumPy抓取数据只是第一步让数据变得可用才是目的。Pandas是Python数据分析的基石它提供的DataFrame数据结构你可以理解为增强版的Excel表格是存储、清洗、分析表格数据的利器。我们将把抓取到的零散列表电影名、年份等组合成一个DataFrame后续的清洗操作如去除符号、转换类型在Pandas中都能用一行优雅的代码完成。NumPy则为Pandas提供了底层的高性能数学运算支持虽然在这个项目中我们直接使用不多但作为科学计算生态的核心一并导入是好习惯。2.3 环境准备告别配置烦恼为了避免复杂的本地环境配置我强烈建议初学者使用在线编程环境如Replit来跟随本教程。它开箱即用预装了Python和常用库你只需要一个浏览器就能开始编码。当然如果你本地已经有Jupyter Notebook或VS Code等IDE也完全没问题。确保你的Python版本在3.6以上然后用pip安装必要的库pip install requests beautifulsoup4 pandas numpy3. 实战解析一步步拆解IMDb电影数据抓取理论说再多不如一行代码。让我们直接进入实战我会详细解释每一段代码的意图和可能遇到的陷阱。3.1 发起请求与解析HTML首先我们需要告诉程序去哪里获取数据并确保服务器愿意把数据给我们。import requests from bs4 import BeautifulSoup import pandas as pd import numpy as np # 设置请求头模拟真实浏览器访问这是规避基础反爬机制的关键一步 headers { 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, Accept-Language: en-US,en;q0.9, # 明确要求英文内容避免拿到其他语言标题 } # 目标URLIMDb Top 1000电影列表的第一页 url https://www.imdb.com/search/title/?groupstop_1000sortuser_rating,desccount50 # 发送GET请求 response requests.get(url, headersheaders) # 检查请求是否成功状态码200表示成功 if response.status_code 200: print(网页请求成功) # 使用BeautifulSoup解析返回的HTML内容指定解析器为lxml需安装或html.parser内置 soup BeautifulSoup(response.content, html.parser) else: print(f请求失败状态码{response.status_code})关键点解析User-Agent这是你的网络请求的“身份证”。如果不设置默认的Python-Requests标识可能会被网站识别为爬虫并拒绝服务。这里我们伪装成Chrome浏览器。Accept-Language确保我们拿到的是英文电影标题而不是其他语言版本。response.status_code始终检查状态码是一个好习惯。除了200常见的还有404页面未找到、403禁止访问、429请求过多等针对不同状态码需要设计不同的处理逻辑。解析器选择html.parser是Python内置的无需安装lxml解析速度更快、容错能力更强。如果你安装了lxmlpip install lxml推荐使用它。3.2 定位数据容器学会使用开发者工具这是网页抓取中最核心的技能——找到目标数据在HTML中的唯一“地址”。以Chrome浏览器为例在目标网页上右键点击选择“检查”Inspect打开开发者工具。我们的目标是抓取每一部电影的信息。在页面上右键点击任意一部电影的区域选择“检查”你会发现高亮显示的HTML代码通常是一个div标签并且带有一个特定的class属性比如classlister-item mode-advanced。这个div就像是一个集装箱里面装着一部电影的所有信息标题、年份、评分等。在开发者工具中你可以按CtrlF搜索lister-item mode-advanced会发现正好有50个匹配项对应页面上的50部电影。这就确定了我们抓取的循环单元。# 找到所有包含单部电影信息的容器div movie_containers soup.find_all(div, class_lister-item mode-advanced) print(f本页共找到 {len(movie_containers)} 个电影容器。)3.3 数据提取遍历容器与精准抓取现在我们需要从一个“集装箱”里把我们需要的小件货物数据一件件拿出来。我们将创建几个空列表用于存储从50个容器里提取出的同类数据。# 初始化空列表用于存储抓取的数据 titles [] years [] runtimes [] imdb_ratings [] metascores [] votes [] grosses []接下来我们遍历每一个电影容器并从中提取具体信息。这里会用到BeautifulSoup的各种查找方法。提取电影标题 标题通常在一个h3标签内的a标签里。因为结构清晰我们可以使用“点号”链式访问。for container in movie_containers: # 提取标题 title container.h3.a.text titles.append(title)提取上映年份 年份信息在一个span标签里但页面上有多个span。我们需要用更精确的属性来定位它比如它的class是lister-item-year。这里使用find()方法它只返回第一个匹配的结果。# 提取年份并处理可能的缺失值 year_element container.find(span, class_lister-item-year) year year_element.text.strip(() ) if year_element else N/A # 去除括号和空格若无则标记为N/A years.append(year)提取时长、评分等信息 原理类似都是通过标签和属性定位。关键在于观察HTML结构找到目标数据独一无二的标识。# 提取时长 runtime_element container.find(span, class_runtime) runtime runtime_element.text.replace( min, ) if runtime_element else N/A runtimes.append(runtime) # 提取IMDb评分在strong标签内相对容易 imdb_element container.find(strong) imdb float(imdb_element.text) if imdb_element else N/A imdb_ratings.append(imdb) # 提取Metascore注意其class可能是metascore favorable或metascore mixed只取共同部分metascore metascore_element container.find(span, class_metascore) metascore int(metascore_element.text) if metascore_element else N/A metascores.append(metascore)最棘手的部分投票数与票房收入 观察HTML你会发现投票数Votes和票房Gross都在属性为namenv的span标签里且投票数在前票房在后。但问题来了不是每部电影都有票房数据比如新上映或特定地区的电影。我们必须编写能处理这种数据缺失情况的健壮代码。# 提取所有属性为namenv的span标签这通常包含votes和gross数据 nv_elements container.find_all(span, attrs{name: nv}) # 提取投票数通常是第一个nv元素 vote nv_elements[0].text.replace(,, ) if len(nv_elements) 0 else N/A votes.append(vote) # 提取票房通常是第二个nv元素但可能不存在 if len(nv_elements) 1: gross nv_elements[1].text else: gross N/A grosses.append(gross)循环结束至此for循环内的代码完成了一部电影所有信息的提取并追加到了各自的列表中。循环会重复50次直到处理完本页所有电影。3.4 数据清洗与格式化从原始文本到可用数据现在我们有了6个列表但里面的数据是夹杂着各种符号如$、,、min、()的字符串。为了进行数值分析我们必须清洗它们。# 创建DataFrame movies_df pd.DataFrame({ title: titles, year: years, runtime_min: runtimes, imdb_rating: imdb_ratings, metascore: metascores, votes: votes, us_gross_millions: grosses }) print(原始数据预览) print(movies_df.head()) print(\n原始数据类型) print(movies_df.dtypes)查看输出你会发现year、runtime_min等列是object类型即字符串而不是我们期望的整数或浮点数。清洗年份去除括号转换为整数。# 使用正则表达式提取数字部分并转换类型 movies_df[year] movies_df[year].str.extract((\d)).astype(float).astype(Int64) # 使用Int64支持NaN整数清洗时长去除“min”字样转换为整数。movies_df[runtime_min] pd.to_numeric(movies_df[runtime_min], errorscoerce).astype(Int64)清洗Metascore已经是数字直接转换注意处理缺失值。movies_df[metascore] pd.to_numeric(movies_df[metascore], errorscoerce).astype(Int64)清洗投票数去除千分位逗号转换为整数。movies_df[votes] movies_df[votes].str.replace(,, , regexFalse) movies_df[votes] pd.to_numeric(movies_df[votes], errorscoerce).astype(Int64)清洗票房这是最复杂的一步需要去除美元符号$和“M”代表百万并转换为浮点数。同时缺失值‘N/A’需要被正确处理。# 首先将非数字字符串如N/A替换为NaN movies_df[us_gross_millions] movies_df[us_gross_millions].replace(N/A, np.nan) # 然后对剩余字符串进行处理去除$和M并转换为浮点数 # 使用lambda函数逐元素处理 movies_df[us_gross_millions] movies_df[us_gross_millions].apply( lambda x: float(str(x).lstrip($).rstrip(M)) if pd.notna(x) and x ! N/A else np.nan )最终检查print(\n清洗后数据预览) print(movies_df.head()) print(\n清洗后数据类型) print(movies_df.dtypes) print(f\n数据形状{movies_df.shape})3.5 数据持久化保存成果清洗好的数据如果不保存程序关闭后就消失了。保存为CSV文件是最通用的选择。# 保存到CSV文件 movies_df.to_csv(imdb_top_50_movies.csv, indexFalse, encodingutf-8-sig) print(数据已成功保存至 imdb_top_50_movies.csv)indexFalse不将DataFrame的索引0,1,2...保存到文件。encodingutf-8-sig使用带BOM的UTF-8编码确保在Excel等软件中打开时中文或其他字符不会乱码。4. 避坑指南与进阶技巧按照上面的步骤你应该已经成功运行了一次抓取。但真实世界的抓取任务远非一帆风顺。下面是我在实践中总结的常见问题和进阶思路。4.1 请求被拒与反爬虫策略如果你在运行requests.get()时收到403 Forbidden或429 Too Many Requests错误说明网站采取了反爬措施。策略一完善请求头。除了User-Agent有时还需要添加Referer来源页、Accept-Encoding等。用浏览器开发者工具的“Network”面板查看一次正常访问的请求头并复制。策略二添加延迟。在循环请求页面时使用time.sleep()在请求间随机等待几秒模拟人类浏览速度。import time import random time.sleep(random.uniform(1, 3)) # 随机等待1到3秒策略三使用代理IP。当单一IP请求过于频繁时轮换使用不同的IP地址是有效方法。但这涉及付费服务或自建代理池复杂度较高初学者可先了解。最重要原则遵守robots.txt。在网站根目录下如https://www.imdb.com/robots.txt查看其爬虫协议。尊重Disallow规则是对网站运营者的基本尊重也能避免法律风险。4.2 数据提取失败与代码健壮性你的代码可能在99部电影上运行良好却在第100部崩溃因为它的HTML结构稍有不同。始终假设数据可能缺失就像我们处理票房数据一样对每个find()操作都使用if...else进行判断或利用try...except捕获异常并为缺失数据设置默认值如np.nan或‘N/A’。使用更灵活的查找方法除了class还可以用id、name属性甚至CSS选择器soup.select()。find()和find_all()也支持正则表达式匹配应对微小的属性值变动。打印中间结果调试当提取不到数据时将soup或当前container的内容漂亮地打印出来print(container.prettify())仔细对比与预期结构的差异。4.3 抓取多页数据我们目前只抓了一页50部电影。要抓取Top 1000需要分析翻页逻辑。观察IMDb榜单URLhttps://www.imdb.com/search/title/?groupstop_1000sortuser_rating,desccount50start51ref_adv_nxt关键参数是start51表示从第51部电影开始。下一页是start101以此类推。我们可以用一个循环来生成所有页面的URLbase_url https://www.imdb.com/search/title/?groupstop_1000sortuser_rating,desccount50start{} all_movies_data [] # 用于存放所有页的数据 for start in range(1, 1001, 50): # start从1开始每次增加50直到1000 url base_url.format(start) print(f正在抓取: {url}) # 这里插入之前单页抓取的全部代码但将数据追加到all_movies_data # ... # 抓取完一页后务必添加延迟 time.sleep(random.uniform(2, 5)) # 循环结束后将all_movies_data列表合并成一个大的DataFrame4.4 数据清洗的更多可能性我们进行了基础的类型转换和字符清理。根据分析目的还可以统一格式检查year列中是否混有(I),(II)等罗马数字标识并决定是保留还是剔除。处理异常值检查runtime_min或imdb_rating是否有明显不合理的值如时长5000分钟评分11分。衍生新特征例如计算imdb_rating和metascore的差值作为“影评人与大众口碑分歧度”的指标。5. 项目总结与扩展方向至此一个功能完整、具备一定健壮性的单页网页抓取器就构建完成了。回顾整个过程你不仅学会了如何使用Requests和BeautifulSoup抓取数据更掌握了数据分析的前置关键步骤——数据清洗与格式化。这个项目麻雀虽小五脏俱全涵盖了从环境搭建、网络请求、HTML解析、数据提取、异常处理到数据保存的完整数据获取流水线。这个项目可以轻松地扩展为你的个人数据工具箱定时抓取监控结合计划任务如cron定期抓取特定商品价格、股票指数、新闻头条制作自己的监控面板。竞品分析抓取电商平台的商品信息、评论进行价格和口碑分析。知识聚合抓取技术博客、论坛的优质文章构建自己的知识库。深入IMDb尝试抓取每部电影的详细页面获取导演、演员、剧情简介、类型标签等信息构建更丰富的电影数据集。记住网页抓取是一项实践性极强的技能。最好的学习方式就是找到你感兴趣的数据源不断地尝试、出错、调试、再尝试。每一次解决“为什么抓不到数据”的问题都会让你对网络和代码的理解更深一层。