ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python爬虫实战:从豆瓣电影到王者荣耀的数据抓取与清洗

Python爬虫实战:从豆瓣电影到王者荣耀的数据抓取与清洗 1. 项目概述一次从数据采集到结构化的实战演练最近在整理技术学习笔记翻到了一个挺有意思的旧项目当时为了练手Python爬虫和数据清洗同时抓取了豆瓣电影Top250和王者荣耀全英雄的数据。这听起来像是两个风马牛不相及的领域但背后的技术逻辑和数据处理的思路其实是相通的。今天就把这个“实验报告”重新梳理一遍分享其中的核心步骤、踩过的坑以及一些通用的数据抓取心法。无论你是想学习爬虫基础还是对数据处理感兴趣这个案例都能提供一条从零到一的清晰路径。这个项目的核心目标很明确一是从豆瓣电影Top250页面获取电影的名称、评分、评价人数、经典台词等信息二是从王者荣耀官方资料站获取所有英雄的详细信息包括英雄名称、定位、技能描述、皮肤等。最终我们需要将这两类来源、结构各异的数据经过清洗和整理存储到结构化的格式如CSV或数据库中以便后续分析或展示。整个过程涉及HTTP请求、HTML解析、反爬策略应对、数据清洗以及数据持久化等多个环节是一个综合性很强的练手项目。2. 技术选型与环境搭建思路工欲善其事必先利其器。在开始写代码之前选择合适的工具链至关重要。这个项目主要面向网页数据的抓取与解析因此我们的技术栈将围绕Python生态中成熟稳定的库来构建。2.1 核心库的选择与考量对于发送网络请求requests库是当之无愧的首选。它语法简洁直观封装了HTTP连接的复杂性让我们能像调用普通函数一样获取网页内容。相比于Python内置的urllibrequests在易用性和功能完整性上优势明显例如会话保持、连接池管理、自动解压等特性对于需要处理多个页面的爬虫任务来说非常友好。拿到网页的HTML源代码后下一步就是从中提取我们需要的数据。这里有两个主流选择BeautifulSoup和lxml。BeautifulSoup以其“傻瓜式”的解析方式闻名即使HTML结构混乱它也能很好地处理并且提供了非常人性化的查找方法如find()和find_all()学习曲线平缓。而lxml则是一个基于C语言的高性能解析库速度极快支持XPath语法。XPath是一种在XML和HTML文档中查找信息的语言路径表达式非常精准和强大。在这个项目中我选择使用lxml结合XPath主要是因为王者荣耀官网的页面结构清晰用XPath可以写出非常简洁且高效的定位代码同时也能为应对更复杂的页面结构做好准备。数据存储方面为了便于查看和后续用Excel或Pandas处理csv模块是轻量级的不二之选。如果数据量更大或关系更复杂可以考虑SQLite或MySQL。2.2 开发环境快速搭建环境搭建力求简洁。首先确保你安装了Python建议3.7及以上版本。然后通过pip一键安装所需的库pip install requests lxml如果网络环境导致安装缓慢可以使用国内的镜像源加速例如清华源pip install requests lxml -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后可以在Python交互环境中导入一下确认没有报错import requests from lxml import etree import csv这就完成了最基本的准备工作。接下来我们将分别进入两个站点的实战环节。我会先以结构相对标准的豆瓣电影为例讲解基础流程然后再处理动态内容更多的王者荣耀官网。3. 豆瓣电影Top250数据抓取实战豆瓣电影Top250页面https://movie.douban.com/top250是一个经典的爬虫练习场。它的页面结构规整数据以列表形式呈现非常适合初学者理解爬虫的基本工作流请求、解析、提取、翻页。3.1 分析页面结构与制定抓取策略首先用浏览器打开豆瓣Top250页面按下F12打开开发者工具。我们需要找到目标数据在HTML中的“坐标”。观察发现每一部电影的信息都包裹在一个div class”item”…/div的标签内。在这个div中电影标题位于span class”title”中注意第一个是中文名可能后面跟一个span class”title”包含英文名。评分位于span class”rating_num”中。评价人数位于div class”star”下的最后一个span内。经典台词引言位于span class”inq”中但这个标签可能不存在有些电影没有。翻页逻辑也很清晰页面底部有分页导航共10页每页25部电影。URL规律是https://movie.douban.com/top250?start{offset}其中offset从0开始以25递增。3.2 编写爬虫代码与处理反爬基于以上分析我们可以开始编写代码。第一步是模拟浏览器发送请求。直接使用requests.get()可能会被网站识别为爬虫而拒绝服务因此我们需要设置请求头Headers特别是User-Agent字段来让自己看起来像一个普通的浏览器访问。import requests from lxml import etree import csv import time def fetch_douban_top250(): base_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 [] for page in range(0, 10): # 0到9页 start page * 25 url f”{base_url}?start{start}” print(f”正在抓取: {url}”) try: # 发送请求 response requests.get(url, headersheaders) response.raise_for_status() # 检查请求是否成功 response.encoding ‘utf-8’ # 解析HTML html etree.HTML(response.text) # 使用XPath定位所有电影项目 movie_items html.xpath(‘//div[class”item”]’) for item in movie_items: # 提取具体信息使用.//表示在当前节点下相对查找 title item.xpath(‘.//span[class”title”][1]/text()’) title title[0] if title else ‘N/A’ rating item.xpath(‘.//span[class”rating_num”]/text()’) rating rating[0] if rating else ‘N/A’ # 评价人数在star类div下的最后一个span rating_people item.xpath(‘.//div[class”star”]/span[last()]/text()’) rating_people rating_people[0].replace(‘人评价’, ‘’) if rating_people else ‘0’ quote item.xpath(‘.//span[class”inq”]/text()’) quote quote[0] if quote else ‘’ all_movies.append({ ‘标题’: title, ‘评分’: rating, ‘评价人数’: rating_people, ‘经典台词’: quote }) # 礼貌性延迟避免请求过快 time.sleep(2) except requests.RequestException as e: print(f”请求出错: {url}, 错误: {e}”) break return all_movies注意豆瓣网对爬虫有一定限制。如果频繁、高速访问IP可能会被暂时封禁。代码中的time.sleep(2)是必要的礼貌延迟。在实际操作中如果数据量更大可能需要使用代理IP池或更复杂的策略。3.3 数据存储与初步清洗数据抓取完成后我们将其保存到CSV文件中。CSV格式通用便于用Excel打开或导入数据库。def save_to_csv(data, filename’douban_top250.csv’): if not data: print(“没有数据可保存”) return # 获取字典的键作为CSV的表头 headers data[0].keys() with open(filename, ‘w’, newline‘’, encoding‘utf-8-sig’) as f: # ‘utf-8-sig’解决Excel打开中文乱码 writer csv.DictWriter(f, fieldnamesheaders) writer.writeheader() writer.writerows(data) print(f”数据已保存至 {filename} 共 {len(data)} 条记录。”) if __name__ ‘__main__’: movies fetch_douban_top250() save_to_csv(movies)运行这段代码稍等片刻你就能在当前目录下得到一个名为douban_top250.csv的文件里面整齐地排列着250部电影的信息。至此一个基础的、针对静态页面的爬虫就完成了。接下来我们将挑战一个稍微复杂一些的目标。4. 王者荣耀全英雄信息抓取进阶王者荣耀的英雄资料页面以官网为例相比豆瓣情况要复杂一些。虽然核心流程依然是请求-解析-提取但可能会遇到数据动态加载、页面结构嵌套更深等问题。4.1 应对动态内容与接口分析很多现代网站为了性能和用户体验会采用前后端分离的架构。这意味着你直接在浏览器看到的页面其数据可能是通过JavaScript异步请求接口API获取并渲染的。直接爬取初始HTML可能找不到英雄列表。解决方法就是“抓包”。打开浏览器开发者工具切换到Network网络选项卡然后刷新或打开王者荣耀英雄列表页面。在所有的网络请求中寻找类型为XHR或Fetch的请求这些通常是数据接口。仔细查看这些请求的Response响应看看是否包含了结构化的英雄数据通常是JSON格式。如果找到了返回JSON数据的接口那么爬虫工作将变得异常简单和高效。我们可以直接模拟请求这个接口拿到现成的结构化数据省去解析HTML的麻烦。这是爬虫中更优的解决方案。假设我们通过分析找到了一个返回英雄基础列表的接口例如https://pvp.qq.com/web201605/js/herolist.json这是一个示例实际接口可能变化。那么抓取代码会变得非常简洁def fetch_heros_via_api(): api_url “https://pvp.qq.com/web201605/js/herolist.json” # 示例接口需实际分析 headers {‘User-Agent’: ‘Mozilla/5.0…’} # 同上需设置Headers try: response requests.get(api_url, headersheaders) response.encoding ‘gbk’ # 注意有些老接口可能是gbk编码 hero_list response.json() # 直接解析JSON all_heros [] for hero in hero_list: # 假设接口返回的JSON中英雄名字字段是’cname’英雄ID是’ename’ hero_id hero.get(‘ename’) hero_name hero.get(‘cname’) # 有了英雄ID我们可以进一步构造每个英雄的详情页URL detail_url f”https://pvp.qq.com/web201605/herodetail/{hero_id}.shtml” # 再去抓取详情页获取技能、皮肤等更细的信息 hero_detail fetch_hero_detail(detail_url, headers) all_heros.append({‘id’: hero_id, ‘name’: hero_name, **hero_detail}) return all_heros except Exception as e: print(f”通过API抓取出错: {e}”) return []4.2 详情页信息深度提取通过列表接口拿到英雄ID和基础信息后我们需要进入每个英雄的详情页抓取技能描述、皮肤名称等更丰富的信息。详情页通常是静态或半静态的HTML我们回到使用lxml和XPath进行解析。def fetch_hero_detail(url, headers): “””抓取单个英雄的详细信息””” detail_info {‘技能’: [], ‘皮肤’: []} try: resp requests.get(url, headersheaders, timeout10) resp.encoding ‘gbk’ # 官网常用编码 html etree.HTML(resp.text) # 示例XPath实际需要根据页面结构调整 # 假设技能描述在某个特定的div中 skills html.xpath(‘//div[class”skill-show”]//p/text()’) detail_info[‘技能’] [s.strip() for s in skills if s.strip()] # 假设皮肤信息在另一个区域 skins html.xpath(‘//ul[class”skin-list”]/li/p/text()’) detail_info[‘皮肤’] [skin.strip() for skin in skins] # 可以继续提取定位、生存能力等雷达图数据如果有的话 # … except Exception as e: print(f”抓取详情页 {url} 失败: {e}”) return detail_info4.3 数据整合与规范化存储将列表数据和详情数据整合后我们需要设计一个合理的存储格式。英雄数据比电影数据更复杂可能包含列表多个技能、多个皮肤。存储到CSV时一个简单的办法是将列表用特定的分隔符如分号连接成一个字符串。def save_heros_to_csv(hero_data, filename’wangzhe_heros.csv’): # 为了存储将列表字段合并成字符串 for hero in hero_data: hero[‘技能’] ‘’.join(hero.get(‘技能’, [])) hero[‘皮肤’] ‘’.join(hero.get(‘皮肤’, [])) headers [‘id’, ‘name’, ‘技能’, ‘皮肤’] # 根据实际字段调整 with open(filename, ‘w’, newline‘’, encoding‘utf-8-sig’) as f: writer csv.DictWriter(f, fieldnamesheaders, extrasaction‘ignore’) writer.writeheader() writer.writerows(hero_data) print(f”英雄数据已保存至 {filename}”)如果数据关系非常复杂例如还想记录每个技能的具体效果、皮肤海报链接等那么使用关系型数据库如SQLite或文档型数据库如MongoDB会是更专业的选择。这取决于你的项目需求。5. 爬虫实践中的核心问题与通用解决方案在实际爬取过程中你几乎一定会遇到各种问题。下面总结几个最常见的情况及其应对策略。5.1 请求被拒绝与反爬虫策略应对网站为了防止资源被过度抓取会设置反爬机制。常见的表现有返回403 Forbidden错误、返回验证页面、或者需要登录才能访问。完善请求头Headers这是最基本也是最重要的一步。除了User-Agent有时还需要带上Referer来源页、Accept-Language接受语言等让请求看起来更“自然”。你可以直接从浏览器开发者工具中复制一次真实请求的Headers。使用会话Sessionrequests.Session()可以自动保持Cookies模拟用户连续访问的行为对于需要保持登录状态或应对某些基于会话的反爬很有用。设置访问延迟Delay在循环请求页面时使用time.sleep(random.uniform(1, 3))随机休眠避免请求过于密集。这是对目标网站最基本的尊重。处理验证码如果遇到简单的图形验证码可以考虑使用OCR库如ddddocr、tesseract识别。复杂的滑动或点选验证码则需要更高级的方案如机器学习识别或接入打码平台这通常意味着爬取成本急剧上升。使用代理IP当单个IP被封锁后代理IP池是继续工作的关键。可以从一些代理服务商获取但需要注意代理的稳定性、匿名性和速度。代码中可以通过为requests.get()设置proxies参数来使用代理。5.2 数据解析失败与XPath调试技巧写好的XPath突然匹配不到数据了这是家常便饭。原因可能是页面结构变了或者一开始分析得就不够精准。使用浏览器控制台测试XPath在开发者工具的Elements面板选中一个元素然后在Console面板输入$x(‘你的XPath表达式’)回车后可以看到匹配到的元素列表。这是最快速的调试方法。采用更稳健的定位方式尽量避免使用绝对路径如/html/body/div[3]/div[1]/div因为它极度脆弱。优先使用具有唯一性的id、class属性或者相对路径结合层级关系。例如//div[class”hero-info”]//p[class”skill-desc”]就比一长串的div[index]要稳健得多。处理动态生成的Class有些网站的class名可能包含随机字符串如class”js-abc123″不要用这种会变化的部分做定位。寻找其父级或子级稳定的元素。备用方案与异常处理在代码中对于关键数据的提取要有备用的XPath或正则表达式方案并用try…except包裹避免因为一个字段提取失败导致整个程序崩溃。5.3 数据清洗与存储的优化建议抓取下来的原始数据往往是“脏”的包含多余的空格、换行符、不可见字符或者格式不统一。字符串清洗使用字符串的.strip()、.replace()方法或正则表达式re.sub()来清理数据。例如去除首尾空白将多个空格替换为一个。处理缺失值与异常值对于可能不存在的字段如豆瓣电影的“经典台词”要像示例代码中那样使用条件判断赋予默认值如空字符串或’N/A’。数据去重在保存前检查是否有重复条目。可以根据唯一标识如电影ID、英雄ID进行去重。选择合适的数据格式对于简单的二维表数据CSV足矣。对于嵌套的、结构复杂的数据如英雄包含多个技能每个技能又有名称、描述、冷却时间等多个属性JSON格式或数据库更适合。Python的json模块可以很方便地将字典列表保存为JSON文件。增量抓取如果数据需要定期更新设计爬虫时应该考虑增量抓取。可以记录已抓取条目的ID或更新时间下次只抓取新的或更新的内容而不是全部重新抓取这能极大节省资源和时间。6. 从实验到项目安全、合规与扩展思考完成基础的数据抓取后这个“实验”可以进一步深化为一个更严谨的项目。这里有几个重要的方向值得思考。6.1 遵守Robots协议与法律伦理边界在爬取任何网站数据前第一件事是查看其robots.txt文件通常在网站根目录如https://www.douban.com/robots.txt。这个文件规定了哪些页面允许爬虫抓取哪些不允许。虽然从技术上讲可以绕过但遵守robots.txt是网络爬虫的基本礼仪和法律风险规避的重要一环。此外必须明确数据的用途。将抓取的数据用于个人学习、研究或公益目的风险相对较低。但如果用于商业盈利、大量公开传播或对目标网站造成明显负担如DDoS攻击效果则可能涉及侵犯著作权、不正当竞争甚至触犯相关法律法规。在项目中应控制请求频率避免对目标服务器造成压力。6.2 项目结构的优化与工程化一个可维护的爬虫项目不应该把所有代码都写在一个文件里。良好的项目结构大致如下wangzhe_spider/ ├── spiders/ # 爬虫核心模块 │ ├── douban_spider.py │ └── wzry_spider.py ├── utils/ # 工具函数 │ ├── request_handler.py # 处理请求、代理、Headers │ └── data_cleaner.py # 数据清洗函数 ├── config.py # 配置文件URL、Headers、数据库连接等 ├── pipelines.py # 数据存储管道CSV、数据库等 ├── main.py # 主程序入口 └── requirements.txt # 项目依赖这样分层之后代码逻辑更清晰也便于复用和扩展。例如request_handler.py可以集中管理所有与网络请求相关的逻辑包括错误重试、代理切换等。6.3 拓展应用场景与技能延伸掌握了基础爬虫技能后你可以尝试更有挑战性的项目这同时也是技能提升的过程应对更复杂的动态页面对于大量依赖JavaScript渲染的页面如单页应用SPArequestslxml的组合就力不从心了。这时需要用到Selenium或Playwright这类浏览器自动化工具它们可以模拟真实用户操作等待页面完全加载后再获取数据。分布式爬虫与效率提升当需要抓取的数据量极大时单机单线程的速度会成为瓶颈。可以学习使用Scrapy框架它内置了异步处理、请求调度、中间件等强大功能并且天然支持分布式扩展。数据分析与可视化爬虫的终点不是数据存储而是数据价值的挖掘。将抓取到的豆瓣电影数据用Pandas进行分析比如评分分布、导演统计用Matplotlib或Seaborn画图分析王者荣耀英雄的属性平衡、定位分布等。这才是让数据“活”起来的关键一步。定时任务与自动化使用APScheduler或操作系统的crontabLinux或计划任务Windows可以让爬虫定时自动运行实现数据的自动更新。回过头看这个“爬取豆瓣以及王者所有英雄信息”的实验其价值远不止于得到两个数据文件。它是一条完整的实践链路串联起了网络请求、数据解析、异常处理、数据存储等多个知识点。更重要的是在解决问题的过程中你学会了如何分析网页结构、如何应对反爬策略、如何设计健壮的代码。这些能力是比任何具体的数据都更宝贵的财富。在实际操作中耐心和细心往往比技术本身更重要多观察、多测试、多思考大部分问题都能找到解决路径。
返回列表