2026/8/4 7:10:40

Python爬虫实战:Requests+BeautifulSoup+正则批量提取视频选集信息

Python爬虫实战:Requests+BeautifulSoup+正则批量提取视频选集信息 1. 项目概述从视频网站精准“收割”选集信息最近在整理一些在线课程或者追剧的时候有没有遇到过这样的烦恼一个几十集甚至上百集的系列视频你想把每一集的名字都整理出来做成一个目录或者导入到自己的笔记软件里结果发现网站根本不提供批量导出功能。手动一集一集地复制粘贴那简直是“人狗大作战”级别的体力活枯燥且容易出错。作为一个经常和数据打交道的Pythoner我本能地想到这事儿应该让代码来干。这个项目的核心就是利用Python来自动化地从一个视频播放页面比如某个课程的系列页、某个剧集的详情页中提取出所有分集选集的标题。听起来很简单不就是“爬虫”嘛。但实际操作起来你会发现从不同的网站结构里稳定、准确地提取出我们想要的信息里面有不少门道。它涉及到如何模拟浏览器访问requests、如何解析复杂的HTML文档BeautifulSoup、以及如何从一堆杂乱的文本中精准匹配出目标正则表达式。今天我就把自己在多个项目中总结出来的这套方法从思路到代码从工具选型到避坑指南完整地分享给你。无论你是想批量下载教程、整理观影清单还是进行简单的数据分析这套技能都能让你事半功倍。2. 核心思路与工具选型解析2.1 为什么是Requests BeautifulSoup 正则当我们决定用Python来获取网页数据时面临第一个选择用什么工具组合市面上库很多但“Requests用于获取BeautifulSoup用于解析正则用于精炼”这个铁三角对于这类结构化信息提取任务依然是平衡了学习成本、灵活性和稳定性的最佳选择。Requests库它的职责单一而强大——发送HTTP请求获取网页的原始HTML代码。相比于Python内置的urllibrequests的API设计极其人性化几行代码就能处理cookies、headers、session等复杂情况。在爬虫中我们最常遇到的就是403 Forbidden禁止访问或429 Too Many Requests请求过多这类反爬虫状态码。requests可以让我们方便地添加请求头如User-Agent模拟浏览器、设置访问间隔来应对这些挑战。BeautifulSoup库拿到一堆HTML字符串后我们需要把它转换成一颗结构化的“树”然后像在文件管理器中找文件一样通过标签名、class、id等属性来定位元素。BeautifulSoup就是干这个的解析器。它支持多种解析后端如lxml,html.parser能容忍一些不太规范的HTML并且提供了非常直观的find()、find_all()、select()等方法。对于网页上结构清晰的选集列表通常是用ulli或者div包裹的用BeautifulSoup可以非常优雅地提取。正则表达式有时候我们需要的信息并非规整地放在某个标签的属性里而是混杂在脚本script变量、复杂的onclick事件或者一段特定的文本模式中。这时BeautifulSoup可能就力有不逮了。正则表达式re库是处理文本模式的利器。例如选集标题可能被放在一个JavaScript数组里像var episodes [“第一集 xxx”, “第二集 yyy”];用正则就能快速把这个数组内容提取出来。它的核心理念是“匹配模式”功能强大但学习曲线较陡需要谨慎使用否则容易写出难以维护的“天书”。注意这个组合并非万能。对于大量动态渲染即数据由JavaScript异步加载的现代网站如单页应用直接拿到的HTML里可能没有我们想要的数据。这时可能需要用到Selenium或Playwright这类能控制真实浏览器的工具或者去分析网站的XHR/Fetch请求直接请求数据接口API。这属于更进阶的爬虫技巧本文主要聚焦于静态或服务端渲染页面的处理。2.2 项目流程总览在动手写代码之前我们先在脑子里把整个流程过一遍这能帮你避开很多后期的坑。目标分析手动打开目标视频系列页面。按下F12打开开发者工具切换到“元素”Elements面板。找到显示选集列表的那个区域观察它的HTML结构。它是用一个div classepisode-list包裹的吗每个集名是在a标签里还是在span里它们的class或id有什么共同特征这一步是后续所有操作的基础务必做细。环境准备确保你的Python环境无论是用python安装教程新搭的还是在vscode python环境配置好的里已经安装了必要的库。通常只需要pip install requests beautifulsoup4。如果网站结构复杂可能还需要lxml解析器pip install lxml它比内置的html.parser更快、容错性更好。数据获取使用requests.get()发起请求。关键一步是设置请求头特别是User-Agent把自己伪装成一个普通的浏览器这是绕过最简单反爬的基础。如果页面需要登录才能看可能还需要处理cookies或session。内容解析将requests返回的HTML文本喂给BeautifulSoup进行解析。然后利用上一步分析出的HTML结构特征使用soup.find_all()或soup.select()定位到所有包含集名的元素。信息提取与清洗从定位到的元素中提取出纯文本的集名。这里常常会夹杂着多余的空白符、换行符或者一些你不想要的符号如“播放”、“HD”图标对应的文字。需要使用字符串方法.strip(),.replace()或简单的正则进行清洗。数据存储与输出将清洗好的集名列表按你喜欢的格式保存下来。可以简单打印到控制台写入到episodes.txt文本文件或者保存为episodes.csv方便用Excel打开。异常处理与优化网络请求可能会失败超时、404网站结构可能会变化。你的代码需要能优雅地处理这些异常比如记录错误日志、重试机制。对于大量页面还需要考虑设置请求延迟避免触发429 Too Many Requests限制。3. 实战演练分步拆解与代码实现光说不练假把式。我们假设一个常见的场景从一个在线教育网站结构相对规整的课程页面抓取所有章节/视频的名称。下面我将结合代码一步步带你走完整个流程。3.1 步骤一环境搭建与目标页面分析首先安装库。打开你的终端或命令提示符执行pip install requests beautifulsoup4 lxmllxml是一个高性能的解析器推荐安装。假设我们的目标URL是https://www.example-course.com/course/101这是一个示例请替换为真实地址。用浏览器打开它找到选集列表。假设通过检查元素我们发现结构如下div classchapter-list div classchapter-item>import requests from bs4 import BeautifulSoup import re import time # 目标URL - 请替换成真实的地址 url https://www.example-course.com/course/101 # 1. 设置请求头模拟浏览器访问 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 } try: # 2. 发送GET请求 response requests.get(url, headersheaders, timeout10) # 设置10秒超时 # 检查请求是否成功 response.raise_for_status() # 如果状态码不是200会抛出HTTPError异常 # 设置正确的编码避免中文乱码 response.encoding response.apparent_encoding or utf-8 except requests.exceptions.RequestException as e: print(f网络请求失败: {e}) exit(1) # 3. 使用BeautifulSoup解析HTML指定lxml解析器 soup BeautifulSoup(response.text, lxml) # 4. 根据之前分析的HTML结构定位选集元素 # 方法A使用find_all查找所有符合条件的a标签 # 假设所有视频链接都在 classvideo-list 下的 a 标签里 episode_links soup.select(.video-list a) # CSS选择器更简洁 # 或者用 find_all: episode_links soup.find_all(a, class_re.compile(video-link)) # 如果class名不固定 # 初始化一个列表来存储提取的集名 episode_titles [] for link in episode_links: # 获取标签内的纯文本 title link.get_text(stripTrue) # stripTrue 可以去除首尾空白字符 # 简单的清洗这里可以加入更多自定义清洗逻辑 # 例如如果标题末尾有“高清”字样可以去掉 # title re.sub(r\s*高清\s*$, , title) if title: # 确保不是空字符串 episode_titles.append(title) # 也可以顺便把链接提取出来 # episode_url link.get(href) # print(f标题: {title}, 链接: {episode_url}) # 5. 打印结果 print(f共找到 {len(episode_titles)} 个选集) for idx, title in enumerate(episode_titles, start1): print(f{idx:03d}. {title}) # 6. 可选保存到文件 with open(episode_titles.txt, w, encodingutf-8) as f: for title in episode_titles: f.write(title \n) print(选集名称已保存到 episode_titles.txt)这段代码已经是一个可用的基础版本。它完成了从发送请求到解析、提取、保存的全过程。3.3 步骤三应对复杂场景与正则表达式介入上面的代码适用于结构清晰的页面。但如果页面结构混乱或者信息藏在script标签里呢比如你发现选集数据是以JavaScript变量的形式存在的script typetext/javascript var courseData { episodes: [ {id: 1, title: 第一讲课程介绍, url: /play/001}, {id: 2, title: 第二讲环境搭建, url: /play/002}, // ... 更多数据 ] }; /script这时BeautifulSoup很难直接解析JavaScript变量。正则表达式就该上场了。# 接续上面的代码在解析完soup之后... # 查找所有的script标签 script_tags soup.find_all(script) for script in script_tags: # 如果script标签里有内容并且包含我们感兴趣的关键字比如episodes if script.string and episodes in script.string: script_content script.string # 使用正则表达式匹配 episodes: [...] 这部分JSON数组 # 这个正则模式尝试匹配 episodes: 后面跟着的由 [ 开始到匹配的 ] 结束的整个数组字符串。 # re.DOTALL 让 . 可以匹配换行符。 pattern repisodes\s*:\s*(\[.*?\]) match re.search(pattern, script_content, re.DOTALL) if match: json_str match.group(1) print(找到疑似选集数据JSON字符串:, json_str[:100]) # 打印前100字符看看 # 注意这里找到的 json_str 可能不是完全标准的JSON可能末尾有分号或者键名没有引号。 # 我们需要进行一些预处理才能用 json.loads 解析。 # 例如确保键名被双引号包围一个简单但可能不完美的修复 # json_str_fixed re.sub(r(\w):, r\1:, json_str) # 更稳健的做法是使用专门的工具如 json5 库或者继续用正则提取每个标题。 # 方法直接用正则从数组字符串里提取所有title的值 title_pattern rtitle\s*:\s*([^]) titles_from_js re.findall(title_pattern, json_str) if titles_from_js: print(\n从JavaScript中提取到选集标题) episode_titles.extend(titles_from_js) # 合并到总列表 # 注意去重 episode_titles list(dict.fromkeys(episode_titles)) break # 找到一个就跳出循环正则表达式rtitle\s*:\s*([^])解读\title\匹配键名title。\s*匹配0个或多个空白字符空格、换行等。:匹配冒号。\s*再次匹配可能的空白。\匹配开头的双引号。([^\])捕获组匹配一个或多个不是双引号的字符这就是我们想要的标题内容。\匹配结尾的双引号。re.findall()会返回所有匹配的捕获组内容即所有标题字符串的列表。实操心得正则表达式虽然强大但极易写错且难以调试。我的建议是优先使用BeautifulSoup等结构化解析方法只有当结构化方法完全无效时才考虑正则。写正则时可以先用在线测试工具如 regex101.com验证你的模式是否能准确匹配目标文本。对于从JavaScript中提取数据如果网站提供了公开的API接口直接请求接口获取干净的JSON数据是更优解。4. 高级技巧与反爬虫策略应对真实的网站不会让你这么轻松地抓取数据。下面分享几个我爬虫实践中总结的高级技巧和应对策略。4.1 处理动态加载与请求参数很多网站的选集列表是滚动加载或点击“加载更多”按钮动态获取的。这种情况下初始HTML里只有前几集。你需要分析网络请求在开发者工具的“网络”Network面板中筛选XHR或Fetch请求当你滚动页面或点击按钮时观察浏览器向哪个地址发送了请求请求参数是什么通常在Payload或Query String Parameters标签页里。模拟请求用requests库去模拟这个请求。它通常是一个GET或POST请求携带一些参数如page2,size20,course_id101甚至可能有一个加密的token或signature。拼接数据循环请求所有页面直到没有新数据为止然后将结果合并。import requests import json base_api_url https://www.example-course.com/api/episodes headers {User-Agent: 你的User-Agent} all_episodes [] page 1 while True: params {course_id: 101, page: page, page_size: 50} # 有时是POST请求数据在data里 # response requests.post(base_api_url, headersheaders, jsonparams) response requests.get(base_api_url, headersheaders, paramsparams) if response.status_code ! 200: print(f请求第{page}页失败状态码{response.status_code}) break data response.json() # 假设返回的是JSON episodes data.get(data, []) # 根据实际JSON结构调整 if not episodes: print(f第{page}页无数据爬取结束。) break for ep in episodes: all_episodes.append(ep.get(title)) print(f已获取第{page}页共{len(episodes)}条。) page 1 time.sleep(1) # 礼貌性延迟避免请求过快 print(f总共获取到 {len(all_episodes)} 个选集。)4.2 伪装与延迟应对基础反爬网站通过检查请求头、访问频率等来识别爬虫。完善请求头除了User-Agent有时还需要带上Referer来源页、Accept-Language等。可以复制浏览器正常访问时的完整请求头。使用Sessionrequests.Session()可以保持cookies和headersacross requests模拟一个会话对于需要登录或有多步交互的网站很有用。设置随机延迟在循环请求间使用time.sleep(random.uniform(1, 3))让请求间隔时间随机化模拟人类操作。处理Cookies如果网站通过Cookie识别会话你需要从浏览器中复制Cookie字符串或者在代码中先访问一次首页获取Cookie。代理IP如果单个IP被封锁返回429状态码就需要使用代理IP池。但这涉及付费服务或自建代理复杂度较高对于一般个人项目优先通过降低频率和完善请求头来避免被封。4.3 数据清洗与存储优化提取到的原始文本往往不干净。去除空白与特殊字符使用.strip()、re.sub(r\s, , text)将多个空白合并为一个空格。统一格式比如将所有全角字符转为半角或者统一去除标题中的“【】”、“()”等。结构化存储除了存文本文件存成CSV或JSON更有用。import csv import json # 存为CSV with open(episodes.csv, w, newline, encodingutf-8-sig) as f: # utf-8-sig 让Excel正确识别编码 writer csv.writer(f) writer.writerow([序号, 选集标题]) # 写入表头 for idx, title in enumerate(episode_titles, start1): writer.writerow([idx, title]) # 存为JSON episodes_data [{id: idx, title: title} for idx, title in enumerate(episode_titles, start1)] with open(episodes.json, w, encodingutf-8) as f: json.dump(episodes_data, f, ensure_asciiFalse, indent2) # ensure_asciiFalse 保证中文正常显示5. 常见问题排查与调试心得爬虫代码跑不起来或者结果不对是常态。这里有一份我总结的“排错清单”问题现象可能原因排查步骤与解决方案返回状态码403/4041. 请求头特别是User-Agent被识别为爬虫。2. 目标页面需要登录或已失效。3. 网站有IP访问频率限制。1. 检查并完善headers使用浏览器的真实UA。2. 手动在浏览器中打开目标URL确认可访问。如需登录考虑使用session并携带登录后的cookie。3. 增加请求延迟time.sleep()或添加Referer等头信息。获取到的HTML是空或乱码1. 请求被重定向或拦截如跳转到验证页面。2. 编码问题。1. 打印response.url和response.history查看是否被重定向。可能需要处理cookies或session。2. 打印response.encoding和response.apparent_encoding手动设置正确的编码如response.encoding utf-8。BeautifulSoup找不到元素1. HTML结构分析错误标签名、class不对。2. 数据是JavaScript动态加载的初始HTML中没有。3. 使用了错误的解析器。1.再次仔细检查元素确认标签和属性名。尝试使用更通用的CSS选择器如soup.select(div[class*list] a)。2. 在“网络”面板中查找XHR请求尝试直接请求数据接口。3. 尝试换用lxml解析器BeautifulSoup(html, lxml)。提取的文本包含多余字符目标元素内嵌套了其他标签如span,i。使用.get_text(stripTrue)会获取该元素下所有子孙的文本。如果只想获取直接子文本可能需要更精确的定位或使用.find(textTrue, recursiveFalse)等方法。正则匹配不到或匹配过多正则表达式模式写得不精确。1. 将待匹配的源文本片段打印出来仔细核对。2. 使用在线正则测试工具调试你的模式。3. 考虑使用非贪婪匹配.*?或更精确的字符集[^]。程序运行一段时间后报错停止1. 网络不稳定或超时。2. 网站结构在后续页面发生变化。3. 触发了反爬机制如弹出验证码。1. 增加try...except块捕获requests.exceptions.Timeout等异常并加入重试逻辑。2. 在解析循环中加入健壮性判断如果关键元素缺失记录日志并跳过而不是直接崩溃。3. 如果遇到验证码对于免费公开数据建议停止爬取或大幅降低频率。商业爬虫需要更复杂的解决方案。调试心得打印中间结果这是最有效的调试方法。在关键步骤后打印response.status_code、response.text[:500]看前500字符、soup.prettify()格式化后的HTML片段或你定位到的元素列表长度。使用浏览器开发者工具不仅仅是“元素”面板“网络”面板是你分析动态请求的利器“控制台”Console可以执行JavaScript来测试你的选择器是否有效如document.querySelectorAll(.video-list a)。从小处着手先写代码提取一个元素成功了再扩展到循环提取所有元素。先处理静态页面再挑战动态加载。尊重robots.txt在爬取前访问目标网站/robots.txt了解网站是否允许爬虫以及爬取频率限制。遵守规则是良好的网络公民行为。最后我想说爬虫技术是一把双刃剑。在实践过程中请务必遵守相关法律法规和网站的服务条款只爬取公开且允许爬取的数据并将爬取频率控制在合理范围避免对目标网站服务器造成负担。把技术用在正道上比如高效地整理自己的学习资料才是它最大的价值。希望这篇长文能帮你打开自动化信息收集的大门如果你在实操中遇到具体问题欢迎带着你的代码和错误信息继续探讨。