ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Python爬虫实战:豆瓣电影与王者荣耀数据抓取全解析

Python爬虫实战:豆瓣电影与王者荣耀数据抓取全解析 1. 项目缘起一次数据获取的实战演练最近在整理一些关于流行文化的数据集想看看豆瓣上的电影评分和《王者荣耀》的英雄信息之间有没有什么有趣的关联。比如是不是某个类型的电影观众会更偏爱特定类型的英雄这个想法听起来有点跨界但数据本身是相通的。要实现这个想法第一步也是最关键的一步就是获取数据。豆瓣的电影信息和《王者荣耀》的英雄资料都是典型的结构化数据散落在网页上手动收集效率太低也不现实。所以这次的任务很明确写两个爬虫分别从豆瓣电影和《王者荣耀》官网或相关数据站点把我们需要的信息“拿”下来。这不仅仅是一个简单的“复制粘贴”练习。它涉及到对两个完全不同网站结构的分析、对反爬机制的应对、对数据清洗和存储的规划。豆瓣作为一个成熟的社区对爬虫有一定的限制而游戏官网的数据展示方式也可能随时调整。整个过程就像一次小型的数据工程实战从需求分析、工具选型、编码实现到错误处理每一步都需要仔细考量。接下来我就把这次“实验”的完整过程、踩过的坑以及最终沉淀下来的代码和经验详细地分享出来。无论你是想学习基础的网络爬虫还是对这两个特定网站的数据获取感兴趣相信都能从中找到有用的参考。2. 战场侦察目标网站结构与反爬策略分析动手写代码之前花时间仔细分析目标网站是事半功倍的关键。盲目请求很容易触发反爬机制导致IP被封或者拿不到数据。我们需要像侦察兵一样先摸清“敌情”。2.1 豆瓣电影Top250页面解析我们的目标是豆瓣电影Top250榜单。打开页面通过浏览器的开发者工具F12查看网络请求是标准操作。首先看URL规律。翻页时URL从https://movie.douban.com/top250?start0变为start25,start50以此类推。这是一个非常友好的信号意味着我们可以通过循环改变start参数来遍历所有页面。其次查看页面源代码。豆瓣Top250页面的数据是直接渲染在HTML中的没有通过复杂的JavaScript动态加载。这意味着我们可以直接用requests库获取HTML然后用BeautifulSoup或lxml进行解析无需动用Selenium这类浏览器自动化工具大大降低了复杂度。然后是最重要的反爬机制。豆瓣对未登录用户和频繁访问有比较明显的限制。User-Agent这是最基本的标识。如果不设置一个常见的浏览器UA直接使用requests的默认UA类似python-requests/2.28.1很大概率第一次请求就会收到一个简单的、不含电影列表的页面或者直接返回403错误。请求频率即使设置了UA如果以极快的速度比如0.1秒一次连续请求10页也很可能被暂时封禁。因此必须在请求之间加入随机延时。Cookies对于更复杂的数据如详细的影评可能需要登录后的cookies。但针对Top250的基础信息片名、评分、引言等无需登录即可获取。IP限制长时间、高频次从同一个IP爬取IP有被禁的风险。对于个人小规模爬取如只爬250条在控制好频率的前提下通常问题不大。如果数据量巨大则需要考虑使用代理IP池。注意务必尊重网站的robots.txt协议。豆瓣的robots.txt对爬虫有一定限制我们的爬取应仅限于个人学习、小批量数据获取并确保不对网站服务器造成压力。2.2 《王者荣耀》英雄资料页面解析《王者荣耀》的官方资料站如官网的英雄资料库通常页面精美但数据可能通过API接口异步加载直接解析HTML会比较困难。更常见的做法是寻找第三方整理好的数据接口或静态页面。经过一番搜索和尝试我发现一个常用的数据来源是像“王者荣耀官网-英雄资料”这类页面但其英雄列表和详情往往是动态渲染的。更稳定和简单的方法是使用一些游戏社区或数据平台提供的结构化接口。例如某些网站提供了包含所有英雄基本信息名称、称号、定位、技能描述等的JSON数据接口。本次实验为了模拟更通用的爬虫场景我选择了一个结构相对清晰的第三方资料站作为目标。其英雄列表页以静态HTML形式呈现每个英雄都有一个独立的详情页链接。分析要点如下列表页包含所有英雄的头像、名称和链接。我们需要从这里提取每个英雄详情页的URL。详情页包含该英雄的详细资料如英雄故事、技能介绍、技能图标、基础属性等。这些信息通常也直接写在HTML中。反爬策略这类游戏资料站的反爬相对宽松但基本的礼貌性原则仍需遵守。同样需要设置合理的User-Agent和请求间隔。有些站点可能会检查Referer头确保请求是从站内页面跳转而来在代码中也需要模拟这一点。明确了两个目标站点的基本情况后我们就可以开始搭建爬虫环境并设计数据存储方案了。3. 装备整理环境搭建与核心工具选型工欲善其事必先利其器。选择合适的工具库能让爬虫开发更高效、更健壮。下面是我为这次项目选择的“装备库”及其理由。1. 网络请求库Requests这是Python生态中公认的、用于HTTP请求的瑞士军刀。它比Python内置的urllib更简洁、更人性化。我们需要用它来发送GET请求获取网页的HTML内容。它的会话Session对象可以自动管理cookies在需要模拟登录状态时非常有用。2. HTML解析库BeautifulSoup4 lxmlBeautifulSoup简称bs4提供了非常Pythonic的方式来遍历和搜索HTML/XML文档树。它本身不解析文档需要指定一个解析器。lxml是一个速度快、功能强的解析器是处理现代网页的绝佳选择。bs4 lxml的组合在易用性和性能之间取得了很好的平衡。我们用它来从杂乱的HTML中精准定位并提取出我们需要的数据字段。3. 数据存储Pandas CSV/JSON爬取到的数据最终需要持久化。Pandas是数据处理和分析的核心库它的DataFrame结构非常适合存储和操作表格型数据。我们可以先将每一条电影或英雄信息存储为字典然后追加到一个列表中最后用Pandas一次性转换为DataFrame并轻松导出为CSV或JSON文件。CSV文件通用性强可用Excel直接打开查看JSON则更适合嵌套结构的数据。4. 辅助工具Time Randomtime库用于在请求间添加延时time.sleep这是规避反爬、体现“礼貌”的关键。random库可以用来生成随机的延时秒数如random.uniform(1, 3)让请求模式更接近人类行为进一步降低被识别为爬虫的风险。环境安装命令如下pip install requests beautifulsoup4 lxml pandas如果使用Anaconda这些库通常已经预装。确保你的Python版本在3.6以上即可。选型理由总结这套组合拳Requests获取、BeautifulSoup解析、Pandas存储是Python爬虫领域的经典入门乃至中级套餐。它避免了过度设计比如一开始就用Scrapy框架让我们能更聚焦于爬虫逻辑本身对于本次两个目标明确、规模不大的任务来说完全够用且高效。4. 实战一豆瓣电影Top250爬虫实现与细节剖析现在我们进入第一个实战环节。目标是爬取豆瓣电影Top250榜单中每一部电影的排名、电影名称、评分、评价人数、经典台词引言、详情页链接。4.1 基础爬取框架搭建首先我们构建一个稳健的爬取循环。核心思路是根据URL规律循环10次250/25每次请求一页解析该页的25部电影信息。import requests from bs4 import BeautifulSoup import pandas as pd import time import random def crawl_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 start in range(0, 250, 25): # start: 0, 25, 50, ... 225 url f{base_url}?start{start} print(f正在爬取: {url}) try: # 发送请求 response requests.get(url, headersheaders, timeout10) response.raise_for_status() # 如果状态码不是200抛出异常 response.encoding utf-8 # 设置编码 # 解析HTML soup BeautifulSoup(response.text, lxml) # 找到当前页所有电影项。通过观察HTML每个电影都在一个 classitem 的 div 中 movie_items soup.find_all(div, class_item) for item in movie_items: movie_info parse_movie_item(item) # 解析单个电影块的函数 if movie_info: all_movies.append(movie_info) # 礼貌性延时随机等待1-3秒 time.sleep(random.uniform(1, 3)) except requests.RequestException as e: print(f请求 {url} 时出错: {e}) break # 或者 continue根据需求决定 except Exception as e: print(f解析 {url} 时出现未知错误: {e}) continue # 所有页面爬取完成后保存数据 save_to_csv(all_movies, douban_top250.csv) print(豆瓣Top250数据爬取完成)这个框架包含了错误处理try-except、请求头设置、延时控制等基本要素是一个比较健壮的起点。4.2 关键数据解析函数parse_movie_item这是爬虫的核心需要仔细分析HTML结构。我们打开豆瓣Top250页面右键“检查”一个电影条目。def parse_movie_item(item): 解析单个电影条目提取所需信息。 info {} try: # 1. 排名 (位于 classpic 的 div 中的 em 标签) rank_tag item.find(div, class_pic).find(em) info[rank] rank_tag.get_text() if rank_tag else N/A # 2. 电影名称和链接 (位于 classhd 的 div 中) hd_div item.find(div, class_hd) if hd_div: # 第一个 a 标签的文本需要清理空格和换行 title_tag hd_div.find(a) if title_tag: # 电影名可能包含多语言我们取第一个 span通常是中文名 title_spans title_tag.find_all(span, class_title) info[title_cn] title_spans[0].get_text().strip() if title_spans else N/A info[url] title_tag.get(href, N/A) # 3. 评分和评价人数 (位于 classstar 的 div 中) star_div item.find(div, class_star) if star_div: rating_span star_div.find(span, class_rating_num) info[rating] rating_span.get_text().strip() if rating_span else N/A # 评价人数在最后一个 span 中 rating_people_spans star_div.find_all(span) if len(rating_people_spans) 3: # 通常有4个span people_text rating_people_spans[-1].get_text() # 提取数字例如 150000人评价 - 150000 import re num_match re.search(r(\d), people_text.replace(,, )) info[rating_people] int(num_match.group(1)) if num_match else 0 else: info[rating_people] 0 # 4. 经典台词/引言 (位于 classquote 的 span 中) quote_span item.find(span, class_quote) info[quote] quote_span.get_text().strip() if quote_span else # 5. 其他信息如导演、主演、年份等 (位于 classbd 的 div 中) bd_div item.find(div, class_bd) if bd_div: bd_text bd_div.get_text(stripTrue, separator\n) # 这里可以进一步用正则或字符串分割提取导演、年份等信息 # 作为示例我们只保存原始文本 info[info_raw] bd_text[:200] # 只取前200字符避免过长 except AttributeError as e: # 如果某个标签找不到AttributeError很常见 print(f解析电影条目时发生属性错误: {e}跳过此项) return None except Exception as e: print(f解析电影条目时发生未知错误: {e}) return None return info这个解析函数使用了find和find_all方法通过标签名和CSS类名进行定位。这里有几个关键点容错处理每一步都判断标签是否存在if tag:避免因个别条目结构差异导致整个程序崩溃。数据清洗使用.strip()去除字符串两端的空白字符。对于评价人数使用了正则表达式re.search来提取纯数字。结构观察豆瓣页面的HTML结构清晰类名如item,hd,star,quote具有很好的标识性这是我们能顺利解析的前提。4.3 数据存储与导出爬取和解析完成后我们将数据保存下来。def save_to_csv(data_list, filename): 将数据列表保存为CSV文件。 if not data_list: print(数据为空不保存文件。) return df pd.DataFrame(data_list) # 选择要保存的列并排序 columns_to_save [rank, title_cn, rating, rating_people, quote, url, info_raw] # 确保列存在 existing_columns [col for col in columns_to_save if col in df.columns] df df[existing_columns] df.to_csv(filename, indexFalse, encodingutf-8-sig) # utf-8-sig 确保Excel打开不乱码 print(f数据已保存至 {filename}共 {len(df)} 条记录。)调用crawl_douban_top250()函数稍等片刻你就能在本地得到一个名为douban_top250.csv的文件用Excel或文本编辑器打开250部电影的核心信息尽在其中。踩坑提醒在实际运行中即使设置了UA和延时偶尔也可能遇到某一页请求失败返回非200状态码。我的策略是在try-except中捕获requests.RequestException并打印错误信息。这里我选择了break即一旦出错就停止整个爬取这是为了在调试阶段快速发现问题。在生产环境中你可能需要更复杂的重试机制如重试3次或记录失败页稍后补爬。5. 实战二《王者荣耀》英雄资料爬虫实现与结构适配第二个目标爬取《王者荣耀》的英雄资料。如前所述我选择了一个结构清晰的第三方资料站。假设其英雄列表页URL为https://example.com/herolist此为示例实际请替换为真实可用的地址每个英雄的详情页链接格式类似https://example.com/hero/1。5.1 获取英雄列表与链接第一步是从列表页获取所有英雄的基本信息和其详情页链接。def crawl_wzry_hero_list(): list_url https://example.com/herolist # 请替换为实际URL 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, Referer: https://example.com/ # 模拟从首页跳转 } hero_list [] try: resp requests.get(list_url, headersheaders, timeout10) resp.raise_for_status() resp.encoding utf-8 soup BeautifulSoup(resp.text, lxml) # 关键找到英雄列表的容器。需要实际分析网站HTML结构。 # 假设每个英雄条目在一个 classhero-item 的 li 标签中 hero_items soup.find_all(li, class_hero-item) for item in hero_items: hero_basic {} # 提取英雄名和链接 link_tag item.find(a) if link_tag: hero_basic[name] link_tag.get(title, ).strip() or link_tag.get_text().strip() hero_basic[detail_url] link_tag.get(href) # 注意href可能是相对路径需要补全为绝对URL if hero_basic[detail_url] and not hero_basic[detail_url].startswith(http): from urllib.parse import urljoin hero_basic[detail_url] urljoin(list_url, hero_basic[detail_url]) # 可能还有其他基础信息如头像、定位 img_tag item.find(img) if img_tag: hero_basic[avatar] img_tag.get(src, ) hero_list.append(hero_basic) print(f从列表页获取到 {len(hero_list)} 个英雄。) time.sleep(random.uniform(1, 2)) except Exception as e: print(f获取英雄列表失败: {e}) return [] return hero_list这个函数的核心是soup.find_all(li, class_hero-item)这一行。这里的hero-item只是一个示例你必须根据目标网站的实际HTML结构来修改。正确找到包裹每个英雄信息的HTML标签和类名是这一步成功的关键。5.2 遍历详情页获取完整信息拿到英雄列表和详情页链接后我们逐个访问这些链接获取详细信息。def crawl_wzry_hero_details(hero_list): 根据英雄列表中的详情页链接爬取每个英雄的详细信息。 all_hero_details [] 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 } for i, hero in enumerate(hero_list): detail_url hero.get(detail_url) if not detail_url: print(f英雄 {hero.get(name)} 无详情链接跳过。) continue print(f正在爬取英雄详情 ({i1}/{len(hero_list)}): {hero.get(name)}) try: resp requests.get(detail_url, headersheaders, timeout15) resp.raise_for_status() # 编码可能需要根据网站调整有的可能是 gbk resp.encoding utf-8 detail_soup BeautifulSoup(resp.text, lxml) # 解析详情页提取所需字段 hero_detail parse_hero_detail_page(detail_soup) # 将基础信息合并进来 hero_detail.update({k: v for k, v in hero.items() if k ! detail_url}) all_hero_details.append(hero_detail) # 详情页请求间隔可以稍长 time.sleep(random.uniform(2, 4)) except requests.RequestException as e: print(f请求英雄详情页 {detail_url} 失败: {e}) continue except Exception as e: print(f解析英雄 {hero.get(name)} 详情时出错: {e}) continue return all_hero_details5.3 解析英雄详情页parse_hero_detail_page这是最需要定制化的部分因为每个网站的英雄详情页布局都不同。你需要像侦探一样仔细分析目标页面的HTML。def parse_hero_detail_page(soup): 解析单个英雄详情页提取详细信息。 这是一个示例函数你需要根据实际网站结构重写。 detail {} try: # 示例1假设英雄标题在 h1 classhero-title 里 title_tag soup.find(h1, class_hero-title) detail[full_name] title_tag.get_text().strip() if title_tag else N/A # 示例2假设英雄故事在 div classhero-story 的 p 标签里 story_div soup.find(div, class_hero-story) if story_div: story_para story_div.find(p) detail[story] story_para.get_text().strip() if story_para else # 示例3假设技能列表在 ul classskill-list 里 skill_ul soup.find(ul, class_skill-list) skills [] if skill_ul: for li in skill_ul.find_all(li): skill_name_tag li.find(span, class_skill-name) skill_desc_tag li.find(p, class_skill-desc) skill { name: skill_name_tag.get_text().strip() if skill_name_tag else N/A, description: skill_desc_tag.get_text().strip() if skill_desc_tag else } skills.append(skill) detail[skills] skills # 这是一个列表存储多个技能字典 # 示例4假设基础属性在一个表格里 table classhero-stats stats_table soup.find(table, class_hero-stats) if stats_table: # 遍历表格行提取键值对 for row in stats_table.find_all(tr): cols row.find_all(td) if len(cols) 2: key cols[0].get_text().strip() value cols[1].get_text().strip() detail[key] value # 例如 detail[最大生命] 3000 except Exception as e: print(f解析详情页时发生错误: {e}) return detail这个函数是高度定制化的。你必须打开目标英雄详情页使用浏览器的“检查元素”功能找到英雄名称、故事、技能、属性等数据所在的HTML标签及其类名或ID然后替换掉上面示例中的find参数。5.4 整合与存储最后将列表爬取和详情爬取整合起来并保存数据。def crawl_all_wzry_heroes(): print(开始爬取《王者荣耀》英雄资料...) # 1. 获取英雄列表 hero_list crawl_wzry_hero_list() if not hero_list: print(未获取到英雄列表程序终止。) return # 2. 遍历列表爬取每个英雄的详情 all_heroes crawl_wzry_hero_details(hero_list) # 3. 保存数据 if all_heroes: # 由于数据可能包含嵌套结构如skills列表保存为JSON更合适 import json with open(wzry_heroes.json, w, encodingutf-8) as f: # 使用 indent 参数美化输出方便阅读 json.dump(all_heroes, f, ensure_asciiFalse, indent2) print(f英雄资料已保存至 wzry_heroes.json共 {len(all_heroes)} 个英雄。) # 也可以尝试将扁平化的数据转为CSV如果结构简单 # 例如只保存英雄名、故事等非嵌套字段 flat_data [] for hero in all_heroes: flat_hero {k: v for k, v in hero.items() if not isinstance(v, (list, dict))} # 将技能列表合并为一个字符串 if skills in hero: flat_hero[skills_summary] ; .join([s.get(name, ) for s in hero[skills]]) flat_data.append(flat_hero) df_flat pd.DataFrame(flat_data) df_flat.to_csv(wzry_heroes_flat.csv, indexFalse, encodingutf-8-sig) print(扁平化数据已保存至 wzry_heroes_flat.csv) else: print(未爬取到任何英雄详情数据。)运行crawl_all_wzry_heroes()程序就会自动完成从列表到详情的全流程爬取。最终你会得到一份结构化的JSON文件包含了所有英雄的详细信息。6. 常见问题排查与代码优化心得在实际爬取过程中你几乎一定会遇到各种问题。下面是我在本次及以往项目中总结的一些常见“坑”和应对策略。问题一请求被拒绝返回403 Forbidden或简短的反爬页面。原因最可能的原因是User-Agent被识别为爬虫。解决确保Headers中设置了常见的浏览器UA如上面代码所示。可以准备一个UA列表每次请求随机选择一个增加多样性。检查是否需要其他Headers如Accept,Accept-Language,Referer。Referer通常设置为目标网站的上一级页面或首页模拟正常浏览行为。问题二能收到HTML但用BeautifulSoup找不到预期的标签。原因A网页是动态加载的。有些网站的数据是通过JavaScript在浏览器端渲染的直接拿到的初始HTML是空的或只有框架。排查在浏览器中右键“查看网页源代码”与开发者工具中“Elements”面板的HTML进行对比。如果源代码里没有数据但Elements里有就是动态加载。解决方案1推荐针对有公开API的在开发者工具的“Network”面板中筛选XHR/Fetch请求寻找返回JSON数据的API接口。直接请求这个接口效率更高且数据更干净。这是爬虫的“高级玩法”。方案2使用Selenium或Playwright这类工具模拟浏览器等待JavaScript执行完毕再获取HTML。但这会大大增加资源消耗和复杂度。原因B解析器或查找语法错误。标签名或类名写错了或者HTML结构有嵌套变化。排查打印出soup.prettify()的一小段仔细核对目标数据周围的HTML结构。确认你使用的find或find_all参数是否正确。有时类名可能有空格需要用class_re.compile(partial-class-name)进行模糊匹配。问题三爬取中途中断或数据缺失。原因网络波动、网站临时不可用、触发反爬被临时封禁、个别页面结构特殊导致解析失败。解决增强异常处理像我们代码中那样用try-except包裹核心请求和解析逻辑捕获特定异常并记录错误如打印出错的URL然后continue跳过当前项而不是让整个程序崩溃。添加重试机制对于请求失败可以实现一个重试函数。def request_with_retry(url, headers, max_retries3): for attempt in range(max_retries): try: resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() return resp except requests.RequestException as e: print(f请求失败 (尝试 {attempt1}/{max_retries}): {e}) if attempt max_retries - 1: time.sleep(2 ** attempt) # 指数退避延时 else: raise e # 重试多次后仍失败抛出异常 return None保存进度对于大量数据的爬取可以将已成功爬取的英雄名或URL保存到一个文件或列表中。程序重启时先加载这个列表跳过已爬取的内容实现“断点续爬”。问题四数据保存到CSV后用Excel打开中文乱码。原因Excel默认可能不识别UTF-8编码的CSV文件。解决使用encodingutf-8-sig参数保存CSV文件。utf-8-sig会在文件开头添加一个BOM字节顺序标记帮助Excel正确识别编码。关于代码优化会话Session对象如果爬取需要维持登录状态或cookies使用requests.Session()会更高效它可以自动在多次请求间保持cookies。并发爬取对于成百上千个独立页面如英雄详情页顺序爬取非常慢。可以考虑使用concurrent.futures.ThreadPoolExecutor进行多线程爬取但要格外注意控制并发数如5-10个线程和请求频率避免对目标网站造成过大压力这既是道德要求也能防止IP被快速封禁。数据去重与清洗爬取下来的原始数据往往包含多余的空格、换行符或HTML实体如nbsp;。在保存前进行统一的清洗处理是很好的习惯。最后也是最重要的心得爬虫的本质是模拟浏览器行为。你的代码越像真人浏览成功率就越高。这包括合理的延时、随机的操作间隔、完整的HTTP头信息等。同时务必遵守法律法规和网站的robots.txt协议将爬取速度控制在合理范围仅将数据用于个人学习与研究。
返回列表