ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Python爬虫实战:合规抓取公开电影信息全流程

Python爬虫实战:合规抓取公开电影信息全流程 各位读者朋友大家好。平时逛技术社区的时候经常能看到类似“用 Python 白嫖全网 VIP 视频”“永久免费看付费电影”这样标题的文章点进去往往就是一两个 requests 请求加一个解析接口声称能破解各大视频网站的会员限制。作为一个常年写爬虫的开发者我必须先泼一盆冷水这类内容大多数是灰色地带的产物有的接口早已失效有的甚至捆绑了恶意代码轻则浪费你一下午时间重则泄露个人信息、牵连账号安全。本文不做任何绕过会员、破解版权鉴权、抓取盗版资源的内容。但如果你对 Python 爬虫本身感兴趣想系统学会“如何从网页里提取公开数据”那这篇文章会给你一套安全、合规、可落地的实战方案。我们会从爬虫的法律边界讲起再实现一个能够稳定运行的“电影信息抓取”案例——抓取公开的、允许访问的电影列表、评分、简介等数据并把结果保存成结构化文件。学完本文你将掌握爬虫的合法边界与职业道德判断标准知道什么能爬、什么不能碰。requests 库的完整用法包括请求头设置、超时控制、异常处理。XPath 和 CSS 选择器解析 HTML 的方法。一个完整的“公开电影信息采集”项目源码从零到保存 CSV 文件。常见反爬策略的合规应对思路。把脚本打包成可执行文件的流程。文章涉及的代码全部在本地运行数据源使用公开接口和公开页面不涉及任何付费内容解密与盗版传播。下面直接进入正文。1. 先搞清楚什么样的爬虫是合法的1.1 爬虫的“三不碰”原则很多初学者对爬虫有一个误解只要代码能跑通数据就能随便抓。实际上爬虫的合法性边界非常明确大致可以归结为三条底线。不碰付费内容与版权保护内容。绕过 VIP 鉴权、抓取付费课程、采集付费文档、破解授权加密这些行为在法律上属于破坏技术保护措施即使代码写得再漂亮也属于违法侵权。不碰个人隐私数据。手机号、身份证号、家庭住址、社交关系等个人敏感信息未授权采集触碰个人信息保护法。不碰对方明确禁止的访问行为。robots.txt 协议网站根目录下的一个文本文件用来说明哪些路径允许爬虫访问、网站用户协议、页面中明确标注“禁止采集”的内容都应该尊重。1.2 什么内容可以放心爬合法抓取的数据源通常具备以下特征数据本身是公开的比如政府公开数据、公开 API、影视网站公开的影片信息。数据源提供了开放接口允许开发者调用很多视频网站都有公开的影视资料接口。页面没有明确禁止爬虫robots.txt 允许访问。采集频率很低不会对对方服务器造成压力。采集后的数据仅用于个人学习研究不分发、不售卖、不批量导出敏感信息。本文采用的示例数据源是一个公开的电影信息接口返回的是影片名称、海报链接、简介等基础数据。这类数据在法律上是公开信息抓取行为和人工浏览页面没有本质区别只用于技术学习完全合规。1.3 如果看到“白嫖 VIP”类的文章建议直接关掉这里多说一句网上那些“一键解析全网 VIP 视频”的代码往往是抓取他人的盗版解析接口这类接口随时可能失效而且因为著作权问题传播者和使用者都有法律风险。更危险的是部分此类开源项目会在代码中夹带广告 SDK、挖矿脚本、甚至窃取 Cookie 的恶意代码。为了看一部电影去运行来路不明的脚本风险收益完全不成比例。2. 环境准备与版本说明2.1 运行环境本文代码在以下环境测试通过但并非强制要求原理与代码均可复用操作系统Windows 10 / Windows 11macOS 与 Linux 同样支持。Python 版本3.8 及以上。建议使用 Python 3.10 或 3.11目前稳定性和生态兼容性都比较好。IDEVS Code 或 PyCharm 均可本文示例以 VS Code 为主。包管理工具pip。版本需要根据你的实际情况调整如果你电脑里已经装了 Python 3.8不需要特意升级。本文重点演示配置思路代码层面的兼容性很好2.x 时代的 Python 已经不再维护建议至少使用 3.8 以上版本。2.2 安装第三方依赖我们需要安装三个库requests发起 HTTP 请求获取网页或接口数据。lxml高性能 HTML/XML 解析库配合 XPath 使用。beautifulsoup4HTML 解析库配合 CSS 选择器使用。打开终端Windows 为 CMD 或 PowerShellmacOS 为 Terminal执行以下命令pip install requests lxml beautifulsoup4如果你的环境默认使用 Python 3但命令是python3可以这样写python3 -m pip install requests lxml beautifulsoup4安装完成后可以检查一下版本python -c import requests; print(requests.__version__)这一步能确认依赖库是否安装成功。2.3 用虚拟环境隔离依赖实际项目中更推荐创建虚拟环境避免多个项目之间互相污染依赖。创建方式如下python -m venv venvWindows 下激活虚拟环境venv\Scripts\activatemacOS / Linux 下激活虚拟环境source venv/bin/activate激活后终端前面会出现(venv)字样后续安装的库都会装进这个独立环境里。3. 爬虫核心知识点拆解在写完整代码之前先掌握三个核心知识点requests 请求库、XPath 解析、CSS 选择器解析。这是绝大多数 Python 爬虫的基础。3.1 requests 库的基本用法requests 是 Python 生态里最常用的 HTTP 请求库底层基于 urllib但 API 设计更人性化。最简单的 GET 请求只需要一行import requests url https://example.com response requests.get(url) print(response.status_code) # 200 表示请求成功 print(response.text) # 网页源码但实际开发中我们不能裸奔式地发请求。很多网站的风控系统会检查请求头中的 User-Agent用户代理用来标识客户端类型如果它检测到请求来自爬虫脚本可能会直接拒绝访问或返回验证码页面。因此我们需要伪装成浏览器访问。下面是一个带请求头的完整示例import requests headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 } url https://example.com try: response requests.get(url, headersheaders, timeout10) response.raise_for_status() print(response.text) except requests.exceptions.Timeout: print(请求超时) except requests.exceptions.RequestException as e: print(f请求发生异常: {e})这里有几个关键点需要解释参数作用推荐设置headers设置请求头模拟浏览器访问至少设置 User-Agenttimeout请求超时时间秒5 到 10 秒避免长时间卡住raise_for_status()状态码不是 200 时抛出异常必须捕获处理3.2 XPath 解析 HTML拿到网页源码之后就需要把 HTML 结构中的目标数据提取出来。XPath 是一种在 XML/HTML 文档中查找信息的语言语法简洁强烈推荐入门时优先学习。先看一个简单的 HTML 片段div classmovie-item a classtitle href/movie/1流浪地球/a span classrating8.4/span p classdesc太阳即将毁灭人类启动流浪地球计划.../p /div使用 lxml 库可以这样解析from lxml import etree html div classmovie-item a classtitle href/movie/1流浪地球/a span classrating8.4/span p classdesc太阳即将毁灭人类启动流浪地球计划.../p /div root etree.HTML(html) # 提取标题 title root.xpath(//a[classtitle]/text())[0] print(title) # 流浪地球 # 提取评分 rating root.xpath(//span[classrating]/text())[0] print(rating) # 8.4 # 提取描述 desc root.xpath(//p[classdesc]/text())[0] print(desc)XPath 语法并不复杂最常用的几个写法//在任意层级查找节点。/在当前节点下直接查找子节点。class匹配属性值。text()取节点文本。[n]取第 n 个匹配项下标从 1 开始。contains()模糊匹配属性值例如//div[contains(class, movie)]。3.3 CSS 选择器解析 HTML除了 XPathBeautifulSoup 的 CSS 选择器也非常好用。同一个例子用 BeautifulSoup 写from bs4 import BeautifulSoup html div classmovie-item a classtitle href/movie/1流浪地球/a span classrating8.4/span p classdesc太阳即将毁灭人类启动流浪地球计划.../p /div soup BeautifulSoup(html, html.parser) title soup.select_one(.title).get_text() rating soup.select_one(.rating).get_text() desc soup.select_one(.desc).get_text() print(title, rating, desc)CSS 选择器的优势是写法直观和前端开发的选择器语法一致soup.select_one(.title)选择第一个 class 为 title 的元素。soup.select(.item)选择所有 class 为 item 的元素返回列表。soup.select_one(#main)选择 id 为 main 的元素。soup.select_one(div a)选择 div 下所有 a 标签。两种解析方式没有绝对的优劣XPath 功能更强CSS 选择器更简单。实际项目中可以混用根据页面结构选择最顺手的方式。4. 完整实战爬取公开电影信息列表这一节我们写一个完整的爬虫项目目标是从一个公开的、免费的电影信息接口中抓取热门电影名称、类型、简介等公开数据保存为 CSV 文件方便后续做数据分析和个人学习使用。数据源选用豆瓣公开的“正在热映”页面吗这里我要谨慎说明一下豆瓣的反爬比较严格未授权抓取容易触发封 IP。为了演示效果更稳定我们直接使用 TMDBThe Movie Database的公开 API。TMDB 是一个开放的电影数据库提供免费的开发者 API很多正规 App 都在使用。你只需要去 TMDB 官网注册一个账号然后在设置页面申请 API Key开发者密钥申请免费且完全合法。如果你不想注册也可以把代码中的接口换成任何你已知的公开数据源只要确认该数据源允许程序访问即可。4.1 创建项目结构先创建一个项目文件夹名字随意结构如下movie_spider/ ├── main.py # 爬虫主程序 ├── requirements.txt # 依赖列表 └── data/ # 保存抓取结果 └── movies.csv创建目录的命令mkdir movie_spider cd movie_spider mkdir data4.2 配置依赖在requirements.txt中写入以下内容requests2.31.0 lxml5.1.0 beautifulsoup44.12.3这里把版本写死是为了保证环境一致实际使用时也可以不指定版本直接pip install -r requirements.txt安装最新版pip install -r requirements.txt4.3 编写核心代码下面编写main.py。整个程序分为四个模块请求模块负责调用 TMDB API 获取热门电影数据。解析模块从返回的 JSON 中提取需要字段。存储模块把数据写入 CSV 文件。主函数串联整个流程。先看完整代码# 文件路径movie_spider/main.py import csv import os import time import requests # TMDB API 配置 API_KEY 你的TMDB_API_KEY # 在这里填入你自己的 API Key BASE_URL https://api.themoviedb.org/3/movie/popular REGION CN # 地区设置为中国获取中文区热门影片 LANGUAGE zh-CN # 语言设置为简体中文 PAGE_NUM 3 # 抓取页数每页 20 条3 页共 60 条 def fetch_movies(page: int 1) - list: 请求 TMDB 热门电影接口返回电影列表 params { api_key: API_KEY, language: LANGUAGE, region: REGION, page: page } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 } try: response requests.get(BASE_URL, paramsparams, headersheaders, timeout10) response.raise_for_status() data response.json() return data.get(results, []) except requests.exceptions.Timeout: print(f第 {page} 页请求超时) return [] except requests.exceptions.RequestException as e: print(f第 {page} 页请求失败: {e}) return [] def parse_movies(raw_movies: list) - list: 解析接口返回的数据只保留需要的字段 parsed [] for item in raw_movies: movie { id: item.get(id), 标题: item.get(title), 原始标题: item.get(original_title), 简介: item.get(overview, ), 上映日期: item.get(release_date, ), 评分: item.get(vote_average, 0), 海报路径: item.get(poster_path, ), } # 只保留有标题的影片数据 if movie[标题]: parsed.append(movie) return parsed def save_to_csv(movies: list, filepath: str data/movies.csv) - None: 把电影数据写入 CSV 文件 # 确保目录存在 os.makedirs(os.path.dirname(filepath), exist_okTrue) # 字段名 fieldnames [id, 标题, 原始标题, 简介, 上映日期, 评分, 海报路径] with open(filepath, modew, encodingutf-8-sig, newline) as f: writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() writer.writerows(movies) print(f数据已保存到 {filepath}共 {len(movies)} 条记录) def main(): all_movies [] for page in range(1, PAGE_NUM 1): print(f正在抓取第 {page} 页...) raw_list fetch_movies(page) if raw_list: parsed_list parse_movies(raw_list) all_movies.extend(parsed_list) # 访问间隔避免请求过于频繁 time.sleep(1) if all_movies: save_to_csv(all_movies) else: print(未获取到任何数据请检查网络和 API Key 是否配置正确) if __name__ __main__: main()4.4 代码逐段讲解上面的代码不是一个简短的速成脚本而是按照工程化思维拆分的完整项目。下面解释每个模块的设计思路。首先看fetch_movies函数。这里使用params参数传递 API 参数而不是直接拼 URL这样更清晰requests 会自动处理 URL 编码。headers里设置了 User-Agent模拟真实浏览器请求。同时用try...except捕获了超时和一般请求异常避免单页失败导致整个程序崩溃。然后是parse_movies函数。TMDB API 返回的是 JSON 数据里面包含二十多个字段比如视频信息、成人标记、语言类型等。我们不需要全部保留只提取自己关心的字段并组成一个字典。这样后面的存储代码会很简洁。接着是save_to_csv函数。写入 CSV 时使用了encodingutf-8-sig这是一个非常容易踩坑的细节。如果只写成utf-8用 Excel 打开 CSV 文件时中文会出现乱码而utf-8-sig会在文件开头加上 BOM 头字节顺序标记Excel 就能正确识别中文。newline则避免在 Windows 下写入时出现多余的空行。最后是main函数。通过循环抓取多页数据每次请求之间用time.sleep(1)暂停 1 秒这既是礼貌也是避免触发网站风控的常用手段。4.5 运行与验证把API_KEY换成你自己的 TMDB Key 之后在项目目录下运行python main.py预期输出正在抓取第 1 页... 正在抓取第 2 页... 正在抓取第 3 页... 数据已保存到 data/movies.csv共 60 条记录打开data/movies.csv内容大致如下id,标题,原始标题,简介,上映日期,评分,海报路径 693134,沙丘2,Dune: Part Two,保罗·厄崔迪与契妮和弗雷曼人联合...,2024-02-27,8.2,/1FO4kPzJ... 872585,哥斯拉大战金刚2,Godzilla x Kong: The New Empire,一场前所未有的战斗...,2024-03-27,7.5,/5woHhR...到这里一个最简单的“公开电影信息采集”项目就完成了。它虽然没有“白嫖 VIP”那么刺激但它是干净的、稳定的、可以放心运行的爬虫脚本。4.6 进阶下载海报图片很多读者对图片爬取感兴趣这里补充一个扩展功能把电影海报下载到本地。TMDB 的图片基础路径是https://image.tmdb.org/t/p/original拼接海报路径即可得到完整图片地址。在main.py中新增一个函数def download_poster(movie): 下载电影海报图片到 data/posters/ 目录 if not movie[海报路径]: return base_img_url https://image.tmdb.org/t/p/w500 img_url base_img_url movie[海报路径] poster_dir data/posters os.makedirs(poster_dir, exist_okTrue) # 文件名使用电影 id避免重名 filename f{movie[id]}.jpg filepath os.path.join(poster_dir, filename) try: resp requests.get(img_url, timeout10) resp.raise_for_status() with open(filepath, wb) as f: f.write(resp.content) print(f海报已下载: {filepath}) except requests.exceptions.RequestException as e: print(f海报下载失败 {img_url}: {e})下载图片时注意两点一是以二进制模式写入文件即wb二是文件名尽量使用唯一字段避免重复覆盖。5. 常见问题与排查思路爬虫实战中报错是家常便饭。这里整理几个高频问题覆盖大部分初学者的踩坑场景。问题现象常见原因解决思路ModuleNotFoundError: No module named requests没有安装 requests或安装到了其他 Python 环境执行pip install requests用pip list检查已装包请求报 403 错误请求头被目标服务器识别为爬虫添加 User-Agent、Referer 等请求头模拟真实浏览器请求报 429 错误请求频率过高触发了接口限流降低请求频率在循环中增加time.sleep()中文乱码写入 CSV 时未使用utf-8-sig编码把encodingutf-8改为encodingutf-8-sig解析结果为空页面结构是动态渲染的requests 获取不到改用 requests 直接请求数据接口或使用 Selenium 渲染后解析接口超时网络不稳定或目标服务器响应慢设置合理的timeout参数增加重试机制5.1 从 403 错误说起请求头伪装新手最容易遇到的错误就是 403 Forbidden表示服务器拒绝访问。最常见的原因是服务端检查了 User-Agent发现不是浏览器的请求。解决方法是把浏览器里的请求头完整复制过来尤其是 User-Agent 和 Referer。Referer 表示请求来源页面部分接口会校验它。headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://www.themoviedb.org/, Accept: application/json, text/plain, */*, Accept-Language: zh-CN,zh;q0.9,en;q0.8, }如何获取这些字段很简单打开浏览器开发者工具切换到 Network网络选项卡访问目标页面点击任意一个 XHR 请求在 Headers 里就能看到完整的请求头。5.2 动态加载页面怎么处理如果目标页面是一个纯前端渲染的 SPA 应用单页应用HTML 源码里没有你要的数据requests 直接获取页面只能拿到一个空壳。这时候有两个思路寻找接口打开开发者工具的 Network 面板刷新页面找到返回 JSON 数据的 XHR 请求直接请求这个接口。这是效率最高、最稳定的方式。使用渲染工具比如 Selenium 或 Playwright它们会模拟真实浏览器执行 JavaScript渲染完成后获取 HTML。缺点是资源占用大速度慢而且更容易触发反爬机制。实际项目中优先寻找背后的接口实在找不到再去考虑浏览器渲染。5.3 API Key 失效或额度不够使用第三方 API 时还需要考虑 Key 的额度和失效问题。TMDB 的免费 API 有速率限制注册信息里会有明确说明。如果请求频率太高可能被临时封禁。好的做法是把 API Key 配置在环境变量或配置文件中而不是硬编码在代码里方便后期替换和维护。6. 反爬策略的合规应对思路爬虫写多了一定会遇到反爬机制。这里强调一下反爬不是用来帮你破解付费内容的而是提醒你“对方不希望被程序采集”。合规的思路不是对抗而是尊重并调整策略。6.1 常见反爬手段反爬手段表现特征合规应对方式User-Agent 检测无 UA 或异常 UA 返回 403设置浏览器 UA频率限制短时间大量请求触发验证码降低频率随机延时IP 封禁单位时间超限封 IP放慢速度使用合法代理需授权登录验证部分数据需要登录后可见只采集公开数据不碰登录态动态渲染页面数据由 JS 加载寻找公开的 JSON 接口6.2 合规爬虫的工程建议这里给出几条经过验证的工程建议每个请求之间至少间隔 1 到 3 秒不要并发打满。对服务器资源的尊重也是对自己 IP 的保护。设置重试机制但最多重试 3 次重试次数过多会加剧服务器压力。抓取到的数据只用于个人学习和研究不在公开渠道发布或售卖。如果网站提供了 API优先使用官方 API而不是爬取网页。官方 API 更稳定也更合法。在代码中明确注释数据来源、用途、采集时间方便日后审查。6.3 遇到验证码怎么办验证码的出现通常意味着请求频率已经越过了红线。合规的做法是停止程序检查自己的采集频率调低到合理范围。不要尝试破解验证码那不是爬虫技术而是攻击行为。7. 把脚本打包成 exe 文件文章标题里提到了“一键操作”很多读者会把写好的爬虫脚本分享给不懂代码的朋友使用。这时候把 Python 脚本打包成 exe 可执行文件就很有用了。推荐使用 PyInstallerpip install pyinstaller打包命令pyinstaller -F -w movie_spider.py参数解释-F打包成单个 exe 文件便于分享。-w不显示命令行窗口适合图形界面程序。如果脚本需要打印日志可以去掉-w。打包完成后在dist目录下找到movie_spider.exe双击即可运行。不过这里要注意打包后的 exe 文件运行时如果脚本里写了相对路径data/movies.csv程序会在当前目录下创建data文件夹并保存结果分享给朋友之前最好在干净环境里完整测试一遍。8. 最佳实践与扩展方向8.1 代码层面的规范爬虫脚本虽然看起来简单但工程化之后还是有不少细节。第一配置与代码分离。API Key、URL、页面数这类变量不要散落在代码各个角落统一放到配置区或环境变量中方便修改。上面的示例代码里已经把配置集中在文件开头这是最基本的做法。生产环境建议用.env文件或配置文件管理敏感信息。第二异常处理要完整。网络请求是最不可控的部分超时、断连、状态码异常都可能发生。每个请求函数都应该有try...except避免一个异常让整个程序终止。第三日志要留痕。任务少的时候用print够了但如果你写的是定时任务建议使用 Python 的logging模块把运行日志输出到文件方便排查问题。下面给出一个 logging 配置示例import logging logging.basicConfig( levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(spider.log, encodingutf-8), logging.StreamHandler() ] ) logger logging.getLogger(__name__) # 使用示例 logger.info(开始抓取第 1 页数据) logger.error(请求失败: 超时)8.2 数据存储到数据库数据量小的时候 CSV 很够用但如果你计划长期采集把数据存进 SQLite 或 MySQL 会更方便查询。以 SQLite 为例Python 内置了sqlite3模块不需要额外安装依赖。import sqlite3 conn sqlite3.connect(movies.db) cursor conn.cursor() # 创建表 cursor.execute( CREATE TABLE IF NOT EXISTS movies ( id INTEGER PRIMARY KEY, title TEXT, original_title TEXT, overview TEXT, release_date TEXT, rating REAL, poster_path TEXT ) ) # 插入数据 for movie in all_movies: cursor.execute( INSERT OR REPLACE INTO movies (id, title, original_title, overview, release_date, rating, poster_path) VALUES (?, ?, ?, ?, ?, ?, ?) , ( movie[id], movie[标题], movie[原始标题], movie[简介], movie[上映日期], movie[评分], movie[海报路径] )) conn.commit() conn.close()使用INSERT OR REPLACE可以避免重复插入时主键冲突配合id字段非常合适。8.3 定时抓取与增量更新电影热门榜单是动态变化的如果你想定期更新数据可以把脚本加入系统计划任务Windows使用“任务计划程序”创建基本任务定时执行python main.py。macOS / Linux使用 crontab。示例 cron 表达式每天凌晨 2 点执行一次0 2 * * * cd /path/to/movie_spider /usr/bin/python3 main.py spider.log 21增量更新的思路是每次抓取时对比数据库中的id字段只更新新增的影片。这样既节省资源也方便追踪数据变化。8.4 后续学习路线如果这篇文章的内容你已经完全吃透下一步可以往下走学习 Scrapy 框架它能更高效地管理爬虫任务、中间件和管道适合中大型项目。学习数据清洗与可视化把抓取到的电影数据做成图表比如评分分布、发行年份趋势等。学习异步爬虫使用httpx或aiohttp提升并发抓取效率但前提是目标站点允许并发访问。切记技术本身是中性的关键是用途。爬虫学得好可以做数据分析、舆情监控、价格监测、知识库构建这些都是高价值应用方向。唯独不要把它用在破解付费、盗版传播上。最后留一个小作业把本文的示例代码跑通之后试着改造成抓取其他公开数据源的结构比如公开的天气接口、公开的图书信息接口体会接口请求与数据解析的通感。代码会报错没关系报错本身也是学习过程的一部分。希望这篇教程对你有帮助有问题欢迎在评论区交流。
返回列表