Python爬虫实战:Selenium+Requests抓取动态网页图片
1. 项目缘起从手动保存到自动化抓取作为一个游戏爱好者同时也是个喜欢折腾的程序员我经常遇到一个不大不小的痛点想收集一些游戏的高清壁纸但官网的下载方式往往很繁琐要么需要一张张点击要么有各种限制。最近想整理一批《王者荣耀》的官方壁纸发现其官网的壁纸页面设计得还算规整但手动操作起来依然费时费力。这让我萌生了一个想法能不能用Python写个脚本自动把这些壁纸都“搬”下来这个需求听起来很简单不就是个爬虫嘛。但实际操作起来你会发现从pvp.qq.com这个域名下的壁纸页面抓取图片远不像爬取一个静态图片链接那么简单。页面是动态加载的图片链接可能藏在JavaScript渲染后的DOM里直接requests上去很可能只拿到一个空壳。这正是我们今天要解决的核心问题如何绕过前端渲染稳定、高效地获取到《王者荣耀》官网壁纸的真实下载地址并完成批量下载。这个项目适合所有对Python爬虫感兴趣的朋友无论你是刚入门想找个有成就感的实战项目还是已经有一定经验想了解如何处理动态加载页面的开发者。整个过程会涉及到requests的基础请求、Selenium模拟浏览器操作、以及如何优雅地处理可能遇到的反爬策略。我会把每一步的原理、踩过的坑和优化思路都讲清楚让你不仅能跑通代码更能理解背后的逻辑。2. 目标分析与技术选型为什么不用单纯的Requests在动手之前我们先得搞清楚目标网站https://pvp.qq.com/web201605/wallpaper.shtml是怎么工作的。用浏览器打开这个页面你会看到很多壁纸的缩略图点击后可以查看大图并下载。如果我们直接右键查看网页源代码View Page Source然后搜索.jpg或.png可能会发现找不到那些高清大图的直接链接。这是因为这些图片数据很可能是通过JavaScript动态加载的。2.1 静态分析与动态渲染的差异静态页面服务器返回的HTML文档中已经包含了所有内容包括图片的img src...标签。对于这种页面使用requests库获取HTML再用BeautifulSoup或lxml解析就能直接拿到资源链接。动态页面服务器返回的HTML只是一个“骨架”大量的内容如图片列表、用户评论等是通过后续的JavaScript代码执行后再向服务器发起Ajax请求获取数据并动态插入到页面中的。直接查看网页源代码看到的是“骨架”而我们在浏览器里右键“检查”Inspect元素看到的是经过JavaScript渲染后的“完整形态”。对于《王者荣耀》这个壁纸页面经过简单测试比如禁用浏览器JavaScript后刷新页面图片可能无法显示基本可以判断它是一个动态页面。图片列表是通过JS加载的。2.2 技术方案对比与决策面对动态页面我们主要有两种思路逆向Ajax接口通过浏览器的开发者工具F12 - Network - XHR/Fetch监控页面加载过程中发出的网络请求找到那个真正返回图片列表数据的Ajax请求。然后我们用requests直接模拟这个请求就能拿到结构化的数据通常是JSON格式从中解析出图片链接。这种方法效率最高对服务器压力小是首选方案。模拟浏览器渲染使用Selenium、Playwright或Puppeteer等工具自动化控制一个真实的浏览器如Chrome去访问页面等待JavaScript执行完毕页面完全渲染后再从浏览器的DOM树中提取元素和链接。这种方法更接近真实用户能应对复杂的交互和加密但速度慢资源占用高。我们的决策流程应该是优先尝试方案一如果接口复杂、有加密或难以模拟再启用方案二。我首先对目标页面进行了网络抓包分析。打开开发者工具清空记录然后刷新壁纸页面。在Network标签页中我仔细筛选了XHR和Fetch请求。遗憾的是我没有找到一个直接返回所有壁纸列表的、清晰的JSON接口。页面加载后有一些请求但数据可能被拆分或夹杂在其他资源中逆向起来比较麻烦。考虑到这是一个展示型页面结构可能并不复杂为了更通用、更稳定地获取渲染后的DOM内容我决定采用方案二使用Selenium模拟浏览器来作为本次爬取的核心手段。requests库则用于后续对图片链接发起高效的下载请求。技术栈确定Selenium用于启动和控制浏览器获取完整渲染后的网页HTML。ChromeDriverSelenium控制Chrome浏览器的桥梁需与本地Chrome版本匹配。Requests用于从获取到的图片URL下载图片数据比用Selenium点击下载要高效得多。BeautifulSoup可选但推荐用于解析Selenium获取到的完整HTML提取图片链接。虽然Selenium自身也有元素查找方法但BeautifulSoup的解析语法更灵活简洁。Python内置库如os创建文件夹time进行延时防止请求过快。3. 环境搭建与核心工具详解工欲善其事必先利其器。这一步看似简单却是新手最容易卡住的地方尤其是Selenium和ChromeDriver的配置。3.1 Python与基础库安装确保你已安装Python3.6及以上版本。使用pip安装必要的库pip install selenium requests beautifulsoup4selenium 核心自动化库。requests HTTP请求库用于下载图片。beautifulsoup4 HTML解析库。3.2 ChromeDriver的配置版本匹配是关键这是Selenium项目最常见的“拦路虎”。ChromeDriver是一个独立的可执行文件Selenium通过它向Chrome浏览器发送指令。你必须确保ChromeDriver的版本与你电脑上安装的Chrome浏览器主版本号一致。注意是主版本号一致例如Chrome 121.x.x.x就需要ChromeDriver 121.x.x.x而不是“差不多就行”不匹配会导致Selenium报错无法启动。查看Chrome版本打开Chrome浏览器。点击右上角三个点 - 帮助 - 关于Google Chrome。弹出的窗口会显示版本号例如版本 121.0.6167.185正式版本。记住主版本号121。下载对应版本的ChromeDriver访问ChromeDriver官方下载站或国内镜像站。官方地址通常更新最快。找到与你Chrome主版本号匹配的驱动版本。如果官网没有完全一致的版本例如只有121.0.6167.x选择版本号最接近的通常也是121开头的即可。根据你的操作系统Windows, macOS, Linux下载对应的压缩包。配置ChromeDriver 下载后是一个可执行文件如chromedriver.exe(Windows)或chromedriver(macOS/Linux)。你有两种方式让Python找到它方法一推荐方便管理将chromedriver文件放在一个固定的目录例如C:\WebDriver\或/usr/local/bin/然后将该目录添加到系统的环境变量PATH中。这样Selenium就能自动找到它。方法二简单直接将chromedriver文件放在你的Python项目目录下然后在代码中指定它的绝对路径。我个人偏好方法一一次配置多个项目受益。如果你在Windows上临时使用也可以把chromedriver.exe放在和你的Python脚本同一个文件夹下并在代码中指定executable_path./chromedriver.exe旧版Selenium写法或通过Service对象指定新版推荐写法。3.3 验证环境创建一个简单的测试脚本test_env.py来验证一切是否就绪from selenium import webdriver from selenium.webdriver.chrome.service import Service import time # 指定你的chromedriver路径如果已加入PATH则不需要 # service Service(executable_path你的chromedriver路径) # driver webdriver.Chrome(serviceservice) # 如果chromedriver已在PATH中可以直接创建 driver webdriver.Chrome() try: driver.get(https://www.baidu.com) print(浏览器成功打开标题是, driver.title) time.sleep(2) # 等待2秒看看页面 except Exception as e: print(出错了, e) finally: driver.quit() # 一定要关闭浏览器释放资源运行这个脚本如果它能自动打开Chrome浏览器并访问百度然后打印出标题说明Selenium和ChromeDriver配置成功。你会看到一个弹窗提示“Chrome正受到自动测试软件的控制”这是正常的。4. 爬虫核心逻辑设计与实现环境准备好了现在我们来设计爬虫的主要步骤。核心思路是用Selenium获取完整页面 - 解析HTML提取图片链接 - 用Requests下载图片。4.1 第一步使用Selenium加载并等待页面渲染我们的目标是https://pvp.qq.com/web201605/wallpaper.shtml。直接让Selenium去访问然后需要给页面足够的时间让JavaScript加载出图片列表。from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time # 初始化浏览器驱动 driver webdriver.Chrome() # 确保chromedriver在PATH中 try: # 访问目标页面 driver.get(https://pvp.qq.com/web201605/wallpaper.shtml) print(已访问页面, driver.title) # **关键步骤等待目标内容加载** # 我们需要等待图片列表区域出现。通过观察页面发现壁纸列表可能在一个特定的容器里。 # 假设我们通过检查元素发现图片的父容器有一个id或class比如是div classpic-list # 这里我们使用更通用的方式等待页面出现某个代表性的图片元素。 # 通过F12查看发现壁纸缩略图的img标签可能具有特定的class例如pic-item或screenshot。 # 我们使用显式等待最多等10秒直到至少有一个图片元素被加载出来。 wait WebDriverWait(driver, 10) # 这里需要你实际查看页面元素来确定选择器。假设图片的img标签class是wallpaper-img # 你可以尝试By.CLASS_NAME, By.TAG_NAME, By.CSS_SELECTOR # 例如等待第一个img标签出现可能不够精确 # first_image wait.until(EC.presence_of_element_located((By.TAG_NAME, img))) # 更稳健的做法等待页面主体内容加载完成可以通过判断某个特定文本或元素出现 # 例如等待页面中“壁纸”这个标题出现 # wait.until(EC.presence_of_element_located((By.XPATH, //*[contains(text(), 壁纸)]))) # 为了简单演示我们使用隐式等待结合显式等待并直接等待一段时间确保JS执行完毕。 driver.implicitly_wait(5) # 隐式等待查找元素时最多等5秒 time.sleep(3) # 强制等待3秒确保动态内容加载。这是一个保守策略在实际中可以优化。 # 此时页面应该已经渲染完成我们可以获取完整的页面源代码 page_source driver.page_source print(页面源代码长度, len(page_source)) except Exception as e: print(页面加载或等待过程中出错, e) finally: driver.quit()实操心得time.sleep()是一种简单粗暴的等待方式不够高效。最佳实践是使用WebDriverWait配合expected_conditions等待某个特定条件成立如元素可见、可点击、数量大于N等。这需要你仔细分析目标页面的结构找到一个可靠的“标志性元素”。如果页面加载速度不稳定可以结合使用WebDriverWait和少量的time.sleep。4.2 第二步解析页面提取高清图片链接现在我们拿到了完整的page_source这是一个字符串包含了渲染后的所有HTML。接下来用BeautifulSoup来解析它找到所有壁纸图片的链接。这里有一个至关重要的技巧我们需要的不是缩略图通常较小可能带有thumb、small等字样而是高清原图。我们需要分析点击“查看大图”或“下载”后真正的高清图地址是什么。分析方法在浏览器中打开壁纸页面F12进入开发者工具。点击一张壁纸通常会弹出模态框显示大图。在Elements面板中找到这个大图对应的img标签查看它的src属性。或者在Network面板中筛选Img类型查看加载的高清大图请求的URL。观察这个高清图URL的规律。例如它可能类似于https://ossweb-img.qq.com/upload/adw/image/2024/xxxxxx.jpg。假设我们通过分析发现所有高清壁纸的img标签都有一个共同的class比如hd-img并且其src属性就是直接的jpg/png链接。from bs4 import BeautifulSoup import re # 假设page_source是上一步从driver.page_source获取的 soup BeautifulSoup(page_source, html.parser) # 方案一通过特定的class查找需要你实际查看页面确定 # image_elements soup.find_all(img, class_hd-img) # 假设高清图class是hd-img # 方案二通过图片URL的模式来查找更通用但需要知道规律 # 观察到的规律高清图链接可能包含‘upload’、‘wallpaper’等路径并且以.jpg或.png结尾 image_urls set() # 用集合避免重复 all_img_tags soup.find_all(img) for img in all_img_tags: src img.get(src) if src: # 这里需要根据实际情况编写过滤条件 # 例如只收集来自腾讯oss域名且是高清图的链接 if ossweb-img.qq.com in src and (jpg in src.lower() or png in src.lower()): # 确保是完整的URL if src.startswith(http): full_url src else: # 如果是相对路径需要拼接基础URL base_url https://pvp.qq.com full_url base_url src if src.startswith(/) else base_url / src image_urls.add(full_url) print(f发现图片链接{full_url}) print(f共发现 {len(image_urls)} 个可能的图片链接。) # 如果通过img标签找不到可能需要分析其他元素比如div的data-url属性或者分析页面中的JavaScript变量。 # 动态页面的数据有时会藏在script标签的JSON里这需要更复杂的解析可能用到json模块和正则表达式。4.3 第三步使用Requests下载并保存图片获取到图片URL集合后下载就很简单了。使用requests.get()获取图片二进制内容然后写入本地文件。import requests import os # 创建保存图片的目录 save_dir 王者荣耀壁纸 if not os.path.exists(save_dir): os.makedirs(save_dir) headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 } for i, url in enumerate(image_urls): try: print(f正在下载第{i1}张图片: {url}) # 设置streamTrue以流式下载大文件 response requests.get(url, headersheaders, streamTrue, timeout10) response.raise_for_status() # 检查请求是否成功 # 从URL中提取文件名如果没有则用索引 filename os.path.join(save_dir, url.split(/)[-1]) # 如果文件名不包含扩展名或太乱可以自定义 if not filename.lower().endswith((.jpg, .jpeg, .png, .gif)): filename os.path.join(save_dir, fwallpaper_{i1}.jpg) # 写入文件 with open(filename, wb) as f: for chunk in response.iter_content(chunk_size8192): f.write(chunk) print(f 已保存为{filename}) # 礼貌性延时避免请求过快 time.sleep(0.5) except requests.exceptions.RequestException as e: print(f 下载失败{e}) except Exception as e: print(f 保存文件时出错{e})4.4 完整代码整合将以上步骤整合并加入一些错误处理和优化就得到了一个可运行的爬虫脚本。请注意下面的选择器如img[src*ossweb-img]是示例你需要根据实际页面结构进行调整。import os import time import requests from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from bs4 import BeautifulSoup def main(): # 1. 初始化Selenium WebDriver driver webdriver.Chrome() # 请确保ChromeDriver配置正确 driver.implicitly_wait(5) try: # 2. 访问目标页面 url https://pvp.qq.com/web201605/wallpaper.shtml driver.get(url) print(f开始抓取: {driver.title}) # 3. 等待页面动态内容加载 # 尝试等待一个特定的元素出现例如包含壁纸的容器 # 这里使用CSS选择器示例你需要根据实际页面修改 try: wait WebDriverWait(driver, 15) # 假设壁纸列表在一个class为‘wallpaper-list’的div里 wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, .wallpaper-list))) except: print(未找到特定元素使用固定等待。) time.sleep(5) # 保底等待 # 4. 获取渲染后的页面源码并用BeautifulSoup解析 soup BeautifulSoup(driver.page_source, html.parser) # 5. 提取图片链接 (这是最关键且需要你自定义的部分) image_urls set() # 方式A: 通过img标签的特定属性筛选 for img in soup.find_all(img, srcTrue): src img[src] # 根据观察到的规律过滤例如链接包含特定域名和关键词 if ossweb-img.qq.com in src and /upload/ in src: if src.startswith(//): # 处理以//开头的协议相对URL src https: src elif not src.startswith(http): src https://pvp.qq.com src if src.startswith(/) else https://pvp.qq.com/ src image_urls.add(src) # 方式B: 如果方式A找不到可能需要查找其他元素比如a标签的href或者div的data-src # for a in soup.find_all(a, hrefTrue): # href a[href] # if href.endswith(.jpg) or href.endswith(.png): # # ... 拼接完整URL并加入集合 print(f解析到 {len(image_urls)} 个图片链接。) if not image_urls: print(未找到图片链接请检查CSS选择器或页面结构是否已变化。) return # 6. 创建本地保存目录 save_dir 王者荣耀壁纸 os.makedirs(save_dir, exist_okTrue) # 7. 下载图片 headers {User-Agent: Mozilla/5.0 ...} # 使用完整的User-Agent for idx, img_url in enumerate(image_urls): try: print(f[{idx1}/{len(image_urls)}] 下载: {img_url[:80]}...) resp requests.get(img_url, headersheaders, streamTrue, timeout15) resp.raise_for_status() # 生成文件名 filename os.path.join(save_dir, f{idx1:03d}_{img_url.split(/)[-1].split(?)[0]}) with open(filename, wb) as f: for chunk in resp.iter_content(chunk_size8192): f.write(chunk) print(f 保存成功: {filename}) time.sleep(0.3) # 请求间隔避免触发反爬 except Exception as e: print(f 下载失败: {e}) finally: # 8. 关闭浏览器 driver.quit() print(浏览器已关闭。) if __name__ __main__: main()5. 实战中的问题排查与进阶优化直接运行上面的脚本你可能不会一帆风顺。爬虫开发就是一个不断遇到问题、分析问题、解决问题的过程。下面我分享几个常见的坑和优化思路。5.1 问题一Selenium找不到元素或页面加载不全现象WebDriverWait超时或者获取到的page_source里没有我们想要的图片标签。排查检查选择器你用的CSS选择器或XPath可能不对。在浏览器的开发者工具里使用$(.your-selector)或$x(your-xpath)测试一下看能否选中元素。检查等待条件页面结构可能比你想象的复杂。尝试等待更长时间或者换一个更稳定、更早出现的元素作为“标志”。例如等待页面标题title包含“壁纸”二字。检查iframe目标内容是否被包裹在iframe里如果是你需要用driver.switch_to.frame()切换到对应的iframe后才能找到里面的元素。检查页面是否懒加载壁纸列表可能是滚动到下方时才加载更多。这时需要Selenium模拟滚动操作driver.execute_script(window.scrollTo(0, document.body.scrollHeight);)然后等待新内容加载。解决方案增加更健壮的等待逻辑并考虑模拟滚动。# 模拟滚动以触发懒加载 last_height driver.execute_script(return document.body.scrollHeight) while True: driver.execute_script(window.scrollTo(0, document.body.scrollHeight);) time.sleep(2) # 等待新内容加载 new_height driver.execute_script(return document.body.scrollHeight) if new_height last_height: break # 高度不再变化说明已滚动到底部或加载完毕 last_height new_height5.2 问题二Requests下载图片返回403或429错误现象requests.get()抛出异常状态码是403禁止访问或429请求过多。原因403网站识别出你是爬虫拒绝了请求。可能缺少必要的请求头如User-Agent、Referer或者对资源链接有防盗链。429你的请求频率太高触发了服务器的限流机制。解决方案完善请求头尽量模拟浏览器。除了User-Agent有时还需要加上Referer来源页面通常是壁纸页面的URL和Accept等。headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36, Referer: https://pvp.qq.com/web201605/wallpaper.shtml, # 很重要 Accept: image/webp,image/apng,image/*,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9, }添加请求间隔在下载每个文件之间使用time.sleep()比如time.sleep(1)降低请求频率。使用Sessionrequests.Session()可以保持一些会话信息有时更接近浏览器行为。处理防盗链如果加了Referer还不行有些网站可能需要校验其他头信息如Origin。这需要更深入的分析。5.3 问题三下载的图片损坏或不是原图现象文件能下载但打开是黑的、绿的或者图片尺寸很小缩略图。排查链接是否正确打印出你提取的链接在浏览器中直接打开看看是不是一张真正的高清大图。是否下载了正确的内容有些链接可能返回的不是图片而是一个HTML页面比如跳转到了登录页。检查response.headers[Content-Type]是否包含image。解决方案在下载前对链接和响应进行验证。resp requests.get(img_url, headersheaders, streamTrue, timeout15) content_type resp.headers.get(Content-Type, ) if image not in content_type: print(f 警告{img_url} 返回的内容类型不是图片: {content_type}) continue # 跳过这个链接5.4 进阶优化提升效率和稳定性并发下载如果图片很多单线程下载太慢。可以使用concurrent.futures模块的ThreadPoolExecutor进行多线程下载但要注意控制并发数避免触发429错误。from concurrent.futures import ThreadPoolExecutor, as_completed def download_one(img_url, idx, total, save_dir, headers): # ... 单张图片的下载逻辑 ... pass with ThreadPoolExecutor(max_workers3) as executor: # 控制为3个线程 future_to_url {executor.submit(download_one, url, i, len(urls), save_dir, headers): url for i, url in enumerate(urls)} for future in as_completed(future_to_url): url future_to_url[future] try: future.result() except Exception as exc: print(f{url} 下载时产生异常: {exc})断点续传/状态保存如果下载中途中断重新运行脚本会从头开始。可以将已成功下载的图片URL记录到一个文件里下次运行时先读取这个文件跳过已下载的。更智能的等待与重试使用retrying库或自己实现重试逻辑对失败的请求进行有限次数的重试。Headless模式如果不需要看到浏览器界面可以启用无头模式节省资源。from selenium.webdriver.chrome.options import Options chrome_options Options() chrome_options.add_argument(--headless) # 启用无头模式 chrome_options.add_argument(--disable-gpu) driver webdriver.Chrome(optionschrome_options)6. 项目总结与扩展思考通过这个项目我们完成了一个从动态网页中抓取图片的完整爬虫。核心在于Selenium获取渲染后内容与Requests高效下载资源的结合。整个过程涉及了环境配置、页面分析、元素定位、数据解析、文件操作和基本的反爬应对。回过头看有几个点值得再次强调动态内容定位对于现代网站学会使用浏览器的开发者工具分析网络请求和元素结构是必备技能。优先尝试寻找Ajax接口如果不行再考虑Selenium。等待的艺术Selenium爬虫的稳定性很大程度上取决于等待策略。显式等待WebDriverWait优于隐式等待和固定等待。请求的礼貌性在下载资源时添加合理的请求头尤其是User-Agent和Referer和控制请求频率是对目标网站的基本尊重也能让你的爬虫运行得更久。代码的健壮性网络请求、文件IO都可能出错使用try...except进行适当的异常处理能让脚本在遇到部分错误时继续运行而不是整体崩溃。这个脚本还可以进一步扩展。例如可以增加对分页的处理如果壁纸有很多页或者将图片信息如英雄名称、皮肤名称如果页面有提供也爬取下来并以此命名文件而不仅仅是数字序号。你也可以尝试将爬虫打包成一个带有简单图形界面用Tkinter或PyQt的小工具方便非技术朋友使用。爬虫技术是一把双刃剑在学习和实践的过程中请务必遵守网站的robots.txt协议尊重版权不要对目标服务器造成过大压力。将技术用于正途才是长久之道。希望这个详细的爬取《王者荣耀》官网壁纸的案例能为你打开Python爬虫世界的一扇大门。