Python爬虫入门:30分钟从零实现豆瓣电影数据抓取与解析

📅 发布时间:2026/8/13 23:48:16
Python爬虫入门:30分钟从零实现豆瓣电影数据抓取与解析
1. 项目缘起为什么从“简单网页抓取”开始如果你刚接触Python或者对“爬虫”这个词既好奇又有点发怵觉得它神秘又复杂那这个项目就是为你准备的。我见过太多新手一上来就想爬淘宝、爬抖音结果被反爬机制、动态加载、验证码搞得晕头转向热情瞬间被浇灭。其实爬虫的核心逻辑远没有那么复杂它本质上就是一个“自动化访问网页并提取信息”的过程。今天我们就从一个最简单的静态网页抓取案例入手目标是让你在30分钟内亲手写出第一个能跑起来的爬虫并且彻底理解它背后的每一步“为什么”。为什么选择“简单网页抓取”作为起点因为它剥离了所有干扰项。我们面对的是一个内容直接写在HTML源码里、没有任何登录验证、没有复杂JavaScript渲染的“老实”网页。这能让我们专注于爬虫最核心的三个环节发送请求、解析内容、保存数据。掌握了这个基础模型就像学会了加减乘除以后面对更复杂的“微积分”问题时你才有能力去拆解和应对。网络上很多“爬虫教程”一上来就堆砌BeautifulSoup、Scrapy等库的复杂用法却很少解释最底层的HTTP请求和HTML结构导致学习者知其然不知其所以然一旦出错就无从下手。这篇内容我们就反其道而行之从最根本的原理讲起手把手带你走通全流程并分享那些教程里不会写的“坑点”。2. 环境准备不仅仅是安装Python和Requests工欲善其事必先利其器。对于这个实战项目我们需要的工具非常简单但每一步的设置都藏着新手容易忽略的细节。2.1 Python安装与验证避开版本陷阱首先你需要一个Python环境。很多教程会直接说“去官网下载安装”但这远远不够。为什么推荐Python 3.7Python 2早已在2020年停止支持所有现代库都不再为其提供更新。Python 3.6也即将结束生命周期。选择3.7或更高版本如3.8、3.9能确保你使用的语法和库都是当前主流且安全的。在安装时请务必勾选“Add Python to PATH”这个选项。这是无数新手遇到的第一个“拦路虎”——不勾选你就无法在命令行中直接使用python命令。安装完成后需要验证。打开你的命令行工具Windows上是CMD或PowerShellMac/Linux上是Terminal输入python --version或者python3 --version你应该能看到类似Python 3.9.13的输出。如果提示“不是内部或外部命令”那大概率就是PATH没配置好需要回头检查安装步骤或手动添加环境变量。2.2 选择你的代码编辑器VSCode的轻量配置写Python代码从简单的记事本到专业的IDE集成开发环境都可以。对于初学者我强烈推荐Visual Studio Code (VSCode)。它轻量、免费、插件生态丰富对新手非常友好。关键插件安装Python扩展 (ms-python.python)这是核心提供了代码高亮、智能提示、调试、运行等所有功能。Pylance (ms-python.vscode-pylance)一个强大的语言服务器能提供更精准的代码补全和类型检查。安装完VSCode和Python插件后通常它会自动检测到你安装的Python解释器。如果没有你可以按F1输入Python: Select Interpreter然后选择你刚安装的Python版本。这一步确保了VSCode知道用哪个Python环境来运行你的代码。2.3 安装必备库使用pip理解虚拟环境我们将使用两个库requests用于发送网络请求beautifulsoup4用于解析HTML。在命令行中输入以下命令进行安装pip install requests beautifulsoup4如果你使用的是Mac或Linux命令可能是pip3。注意这里有一个重要的概念叫“虚拟环境”。简单来说它就像给你的每个项目建立一个独立的“房间”房间里的Python库互不干扰。对于这个简单项目直接在系统环境安装问题不大。但如果你未来要做更多项目强烈建议从一开始就习惯使用虚拟环境如venv或conda这能避免后期库版本冲突的噩梦。你可以通过python -m venv myenv创建然后激活它。安装成功后可以在Python交互环境中验证import requests import bs4 print(requests.__version__)没有报错就说明成功了。3. 目标网站分析与HTTP请求初探在动手写代码之前我们必须先“侦察”目标。爬虫不是蒙着眼睛抓取而是有目的的获取。我们选择一个非常适合练习的网站豆瓣电影Top250的第一页(https://movie.douban.com/top250)。选择它是因为1. 数据公开且结构清晰2. 是静态页面适合新手3. 内容有价值。3.1 使用浏览器开发者工具“解剖”网页这是爬虫工程师最核心的技能之一远比写代码的时间多。打开Chrome或Edge浏览器访问豆瓣Top250页面。第一步查看网页源码静态内容在页面上右键选择“查看网页源代码”。你会看到一堆密密麻麻的HTML代码。我们的目标数据——电影名、评分、引言——都藏在这些标签里。比如你搜索“肖申克的救赎”会发现它被包裹在类似span classtitle肖申克的救赎/span的标签中。这个classtitle就是我们后续定位数据的“钥匙”。第二步使用检查工具Inspect精确定位在电影标题上右键选择“检查”。开发者工具会打开并自动定位到对应的HTML元素。你可以看到清晰的DOM文档对象模型树状结构。多检查几个电影条目你会发现它们的结构是重复的每个电影条目都包裹在一个div classitem里面里面又包含了标题、评分、引言等子元素。这种重复性结构是爬虫最喜欢的因为我们可以用同一套规则提取所有条目。第三步观察网络请求Network Tab按F12打开开发者工具切换到“网络”(Network)选项卡然后刷新页面。你会看到浏览器加载这个页面时发起的所有请求HTML、CSS、图片、JS等。找到第一个文档请求通常是top250点击查看它的“标头”(Headers)。这里有你代码中需要模拟的关键信息Request URL:https://movie.douban.com/top250- 我们的目标地址。Request Method:GET- 请求方法获取数据。User-Agent:一串长字符 - 这是浏览器身份标识。服务器通过它来判断访问者是浏览器还是爬虫。这是第一个反爬虫点也是我们代码中必须模拟的参数。3.2 编写你的第一个请求带上“伪装”理解了以上信息我们就可以用Python的requests库来模拟浏览器发送请求了。import requests # 目标URL url https://movie.douban.com/top250 # 关键步骤设置请求头模拟浏览器访问 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 } # 发送GET请求 response requests.get(url, headersheaders) # 检查请求是否成功HTTP状态码为200表示成功 print(f状态码: {response.status_code}) print(f页面内容的前500个字符:\n{response.text[:500]})代码解读与避坑指南为什么要有headers如果不加User-Agentrequests默认的UA会包含python-requests字样一些敏感的网站如豆瓣会识别并拒绝这种请求返回418或403错误。加上一个常见的浏览器UA是绕过基础反爬的最简单有效的方法。response.status_code务必每次都要检查。200代表成功404代表页面不存在403代表禁止访问500代表服务器错误。根据状态码可以快速判断问题所在。response.text这是服务器返回的HTML源码字符串格式。我们后续的解析工作都基于这个字符串。实操心得你可以尝试把headers注释掉再运行一次对比一下返回的内容。很可能你会得到一个状态码不是200的响应或者返回的text里包含“禁止访问”等提示。这就是反爬机制在起作用。养成好习惯写爬虫第一步永远是先构造合理的请求头。4. 从HTML混沌中提取结构化数据BeautifulSoup解析实战拿到了HTML源码它对我们来说还是一团乱麻。接下来就要用BeautifulSoup这把“手术刀”把我们需要的数据精准地“解剖”出来。4.1 构建Soup对象与基础查找from bs4 import BeautifulSoup # 假设response是上一步成功获取的响应 soup BeautifulSoup(response.text, html.parser) # 使用lxml解析器更快但需要额外安装(pip install lxml)。html.parser是Python内置的无需安装。 # 基础查找找到第一个class为‘title’的span标签 first_title_tag soup.find(span, class_title) print(f第一个电影标题标签: {first_title_tag}) print(f第一个电影标题文本: {first_title_tag.text})解析器选择html.parser够用但速度慢。对于大型项目建议安装lxml(pip install lxml) 并使用BeautifulSoup(response.text, lxml)速度会有显著提升。4.2 定位容器与循环提取应对重复结构我们之前分析过每个电影信息都在一个div classitem里。所以策略是先找到所有的“项目容器”再在每个容器里提取具体信息。# 找到本页所有电影条目所在的div容器 movie_items soup.find_all(div, class_item) print(f本页共找到 {len(movie_items)} 个电影条目。) # 遍历第一个条目看看结构 for item in movie_items[:1]: # 先用第一个做测试 # 电影标题注意中文名在第一个class为title的span里可能有多个title span title_tag item.find(span, class_title) title title_tag.text if title_tag else 未找到标题 # 评分 rating_tag item.find(span, class_rating_num) rating rating_tag.text if rating_tag else 0.0 # 引言/短评 (class为quote的span) quote_tag item.find(span, class_quote) quote quote_tag.text if quote_tag else 暂无引言 print(f标题: {title}) print(f评分: {rating}) print(f引言: {quote}) print(---)为什么用.find()而不是.find_all()在item这个小的容器里我们只需要找第一个标题、第一个评分。.find()只返回找到的第一个结果.find_all()会返回一个列表。这里用.find()更合适。4.3 处理数据提取中的常见陷阱运行上面的代码你可能会发现一些问题这正是实战中必须处理的陷阱一数据缺失或标签不一致不是每部电影都有“引言”quote。如果我们直接用quote_tag.text当quote_tag为None时程序就会崩溃。所以上面代码用了if...else...进行判断这是一种安全的写法。陷阱二多余的空格与换行符提取的文本可能包含很多空格和换行符\n。可以使用字符串的.strip()方法清理title title_tag.text.strip() if title_tag else 未找到标题陷阱三复杂嵌套结构豆瓣的标题有时会包含两部分如“肖申克的救赎 / The Shawshank Redemption”它们都在classtitle的span里。item.find(span, class_title)只找到第一个中文名。如果你想获取纯中文名可能需要更精确的选择或者用item.find_all(span, class_title)[0]来明确指定。陷阱四属性选择器的使用除了class还可以用其他属性定位。例如豆瓣的详情页链接在a标签的href属性里link_tag item.find(a) # 找到第一个a标签 detail_url link_tag[href] if link_tag and link_tag.has_attr(href) else None访问tag[attr]前最好用tag.has_attr(attr)判断一下属性是否存在避免KeyError。5. 数据存储与项目完整代码整合数据提取出来后放在内存里关机就没了。我们需要把它持久化保存。对于这种结构化的列表数据最简单的方式是保存为CSV逗号分隔值文件它可以用Excel直接打开也便于后续分析。5.1 使用csv库保存数据import csv # 在循环提取数据前先准备一个列表来存放所有电影信息 all_movies_info [] for item in movie_items: title_tag item.find(span, class_title) rating_tag item.find(span, class_rating_num) quote_tag item.find(span, class_quote) title title_tag.text.strip() if title_tag else N/A rating rating_tag.text.strip() if rating_tag else N/A quote quote_tag.text.strip() if quote_tag else N/A # 将每部电影的信息作为一个字典存入列表 movie_info { title: title, rating: rating, quote: quote } all_movies_info.append(movie_info) # 将数据写入CSV文件 filename douban_top250_page1.csv with open(filename, w, newline, encodingutf-8-sig) as csvfile: # 定义CSV文件的列名 fieldnames [title, rating, quote] writer csv.DictWriter(csvfile, fieldnamesfieldnames) # 写入表头 writer.writeheader() # 写入所有行数据 writer.writerows(all_movies_info) print(f数据已成功保存到 {filename})关键点解析newline在Windows系统下不指定这个参数写入的行之间可能会多出空行。encodingutf-8-sig指定UTF-8编码并带BOM签名。这样用Excel打开中文CSV文件时不会出现乱码。如果是在Mac/Linux系统或纯代码环境中使用utf-8即可。DictWriter使用字典来写入CSV非常方便字典的键fieldnames自动成为列名字典的值成为对应行的数据。5.2 完整可运行的爬虫脚本将以上所有步骤整合并增加一些健壮性处理如异常捕获、请求延迟我们得到一个完整的、可直接运行的脚本import requests from bs4 import BeautifulSoup import csv import time def scrape_douban_top250(): 抓取豆瓣电影Top250第一页数据并保存为CSV url https://movie.douban.com/top250 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 } all_movies [] try: print(正在发送请求...) response requests.get(url, headersheaders, timeout10) # 设置10秒超时 response.raise_for_status() # 如果状态码不是200主动抛出异常 response.encoding utf-8 # 确保编码正确 print(请求成功) except requests.exceptions.RequestException as e: print(f请求失败: {e}) return # 解析HTML soup BeautifulSoup(response.text, html.parser) movie_items soup.find_all(div, class_item) print(f开始解析共找到 {len(movie_items)} 个条目。) for index, item in enumerate(movie_items, 1): # 提取数据增加健壮性判断 title_elem item.find(span, class_title) rating_elem item.find(span, class_rating_num) quote_elem item.find(span, class_quote) title title_elem.text.strip() if title_elem else N/A rating rating_elem.text.strip() if rating_elem else N/A quote quote_elem.text.strip() if quote_elem else N/A # 打印进度 print(f[{index}/{len(movie_items)}] 标题: {title}, 评分: {rating}) all_movies.append({ 序号: index, 标题: title, 评分: rating, 短评: quote }) # 礼貌性延迟避免请求过快对服务器造成压力也是反反爬的一种措施 time.sleep(0.1) # 保存到CSV filename douban_top250_page1.csv try: with open(filename, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[序号, 标题, 评分, 短评]) writer.writeheader() writer.writerows(all_movies) print(f\n所有数据已成功保存至文件: {filename}) except IOError as e: print(f文件保存失败: {e}) if __name__ __main__: scrape_douban_top250()这个脚本的改进点异常处理 (try...except)网络请求和文件操作都可能失败用异常捕获可以让程序更友好地提示错误而不是直接崩溃。超时设置 (timeout10)防止因为网络问题无限等待。response.raise_for_status()在状态码非200时主动抛出异常便于统一处理错误。进度打印在循环中打印进度让你知道程序正在运行。礼貌延迟 (time.sleep(0.1))在每次处理完一个条目后等待0.1秒。这是一个非常重要的网络礼仪。高频访问会加重服务器负担可能导致你的IP被暂时封禁。对于新手练习这个延迟可以很小但养成这个习惯至关重要。if __name__ __main__:这是一个Python的惯用法确保当你直接运行这个脚本时函数才会被执行如果它被作为模块导入到其他文件则不会自动运行。6. 从“能跑”到“跑得好”常见问题排查与进阶思考你的第一个爬虫已经可以运行并产出数据了。但在真实环境中你会遇到各种各样的问题。下面是一些典型的排查思路和进阶方向。6.1 你可能遇到的错误与解决方案问题现象可能原因解决方案ConnectionError/Timeout网络问题目标服务器不稳定或无法连接。检查网络增加timeout值或使用try...except捕获后重试。HTTPError: 403 Forbidden被网站识别为爬虫禁止访问。1. 检查并完善headers特别是User-Agent。2. 添加Referer等常见请求头。3. 使用IP代理对新手较复杂暂不展开。AttributeError: NoneType object has no attribute text用soup.find()没找到对应标签返回了None。在调用.text或[attr]前务必先判断对象是否为None。KeyError: href试图访问标签不存在的属性。使用tag.has_attr(attr_name)先判断属性是否存在。保存的CSV用Excel打开中文乱码Excel默认可能不是UTF-8编码。写入文件时使用encodingutf-8-sig。爬取的数据不全或错位网页结构发生变化或你的选择器不够精确。重新使用开发者工具分析网页结构调整find中使用的标签名或class。可能需要使用更复杂的选择器如find_all(‘div’, attrs{‘class’: ‘item’})。6.2 如何爬取多页数据豆瓣Top250有10页我们只爬了第一页。观察翻页URL第一页:https://movie.douban.com/top250?start0filter第二页:https://movie.douban.com/top250?start25filter第三页:https://movie.douban.com/top250?start50filter发现了规律start参数以25递增。我们可以用一个循环来构造所有页的URLbase_url https://movie.douban.com/top250?start{}filter for page in range(0, 250, 25): # start从0到225步长25 url base_url.format(page) # 然后对每一页重复上面的请求、解析、保存过程 # 注意每爬完一页最好加一个稍长的延迟比如 time.sleep(1)以示友好6.3 关于反爬虫的初步认识随着你爬取规模的扩大一定会遇到反爬措施。除了我们已经做的设置User-Agent、添加延迟还有更高级的Cookie/Session有些页面需要登录后才能访问你需要用requests.Session()来维持登录状态。JavaScript渲染很多现代网站如小红书、抖音数据是通过JS动态加载的直接拿到的HTML是空的。这时需要用到Selenium或Playwright这类自动化测试工具来模拟浏览器行为或者去分析网站的XHR/Fetch请求找到直接返回数据的API接口这通常是更高效的方式。IP限制如果短时间内请求过多服务器可能会封禁你的IP地址。这时需要使用代理IP池来轮换IP。验证码遇到验证码对于简单图形验证码可以使用OCR库如ddddocr、tesseract识别对于复杂验证码如点选、滑块则可能需要接入打码平台或更复杂的图像处理。重要提醒法律与道德爬虫技术是一把双刃剑。在实践时务必遵守robots.txt协议访问网站域名/robots.txt查看该网站是否允许爬虫抓取哪些目录。尊重网站的规则。速率限制像我们上面做的在请求间加入延迟不要对目标服务器造成DoS攻击式的压力。数据用途爬取的数据应用于个人学习、研究或公益目的。切勿用于商业牟利、侵犯隐私或干扰网站正常运行。版权与隐私不要爬取和传播受版权保护的内容或用户个人隐私信息。7. 项目总结与下一步学习路径通过这个“简单网页抓取”项目你已经完整地走通了一个爬虫工作流环境搭建 - 目标分析 - 发送请求 - 解析数据 - 保存结果。更重要的是你理解了每一步背后的“为什么”比如为什么要加请求头为什么要处理None值为什么要设置延迟。这个项目是爬虫世界的“Hello World”。它虽然简单但包含了最核心的骨架。基于此你可以向多个方向深入解析库的进阶深入学习BeautifulSoup和lxml的复杂选择器如CSS选择器soup.select(‘.title’)它通常更简洁强大。更强大的请求库requests是同步的。学习aiohttp可以实现异步请求极大提升爬取速度适用于IO密集型任务。应对动态页面学习Selenium或Playwright来控制浏览器或者学习如何抓包分析API接口。框架学习当项目变得庞大、需要调度、去重、持久化时学习Scrapy框架。它提供了完整的爬虫项目结构和许多内置的强大功能。数据清洗与存储将数据存入数据库如SQLite, MySQL, MongoDB而不仅仅是CSV并学习使用pandas进行数据清洗和分析。爬虫的路上坑很多但每一个坑踩过去都是实实在在的经验积累。从今天这个能稳定运行的小脚本开始保持好奇多动手试多查看开发者工具多阅读官方文档和社区讨论你很快就能从“小白”成长为能独立解决实际问题的爬虫开发者。记住最有效的学习方式就是找一个你感兴趣的目标网站试着去抓取它的数据在解决问题的过程中所有知识点都会变得具体而深刻。