尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python爬虫实战:requests+BeautifulSoup提取电影信息

Python爬虫实战:requests+BeautifulSoup提取电影信息 Python爬虫常被用来抓取网页上的公开信息电影网站就是一个很典型的练习场景。很多人一开始会想到“把某个站点的电影列表全部抓下来”但真正需要关注的往往不是能不能抓而是该不该抓、怎么抓、抓到之后做什么。这篇教程会带着你用 requests 和 BeautifulSoup 完成一次完整的电影信息抓取流程请求页面、解析 HTML、提取电影名称、年份、评分和简介最后保存为 JSON 和 CSV 文件。整个示例使用本地 HTML 页面不依赖任何第三方站点也不触碰任何付费内容可以完全复现。学完这套流程你就能理解网络爬虫的请求、解析、存储三个核心环节并且知道在实际项目中如何先做合规判断再写代码。1. 先用合规思路理解 Python 爬虫的边界1.1 Python 爬虫解决什么问题爬虫本质上是一个自动化获取网页数据的程序。它的核心工作是用 HTTP 客户端向服务器发送请求接收响应后按照数据格式提取信息再把信息整理成结构化数据。Python 生态里有非常成熟的库比如 HTTP 请求用 requestsHTML 解析用 BeautifulSoup大规模抓取可以用 Scrapy接口请求可以用 httpx数据清洗还能配合 pandas。对于电影信息这类半结构化网页数据requests 加 BeautifulSoup 是最容易上手、也最容易被理解的组合。在实际项目中爬虫常用于以下场景采集公开的商品价格、库存信息做市场分析。抓取公开的新闻、公告、招聘信息建立内容聚合平台。收集公开的电影、图书、音乐元数据用于个人资料库或推荐系统。监控网站页面变化触发告警或定时更新。为数据分析、机器学习模型准备基础数据集。电影信息非常适合作为爬虫练习对象因为它有明确的列表页、详情页、分页规则数据也容易校验。但电影网站同时也是版权和数据合规的高风险领域练习时最好使用自己创建的页面或公开的 API而不是直接抓取视频资源页面。1.2 合法爬虫必须遵守的边界“能否爬取”不是由技术决定的而是由网站条款、法律法规和 robots 协议共同决定的。编写和运行爬虫前至少要确认以下几点检查项具体说明robots.txt检查目标站点根目录下的 robots.txt确认是否允许爬取指定路径网站服务条款很多站点在注册或使用时会声明不得使用自动化工具批量采集数据版权网页上的文字、图片、视频等可能受版权保护不能随意下载和二次分发登录与付费内容绝对不能通过绕过登录、伪造授权、破解会员等方式获取非公开内容访问频率高频请求会加重服务器负担也可能触发反爬或封 IP数据使用目的学习、个人研究、商业用途面临的要求完全不同如果你的项目目标是抓取“付费电影的播放地址”或“VIP 资源”那么无论代码写得多漂亮都不属于合法爬虫。作为技术学习者应该把注意力放在公开数据的采集和处理上而不是绕过平台的付费机制。非法获取付费内容不仅违反网站条款还可能涉及版权侵权和计算机系统安全的合规风险。1.3 电影数据爬虫的常见合法数据源想练手电影爬虫又不希望触犯红线可以从下面这些数据源里选择自己创建的 HTML 页面最安全完全可控适合学习解析逻辑。公开的开放 API比如很多公开影视库提供的 API需要注册 key但返回 JSON结构清晰。允许抓取的公开榜单页部分平台会公开热映信息但必须确认 robots 和条款。政府或教育机构提供的开放数据集数据稳定没有版权争议。静态导出的数据文件比如 GitHub 上公开的电影评分数据集。这篇文章采用第一种方式自己创建一个电影列表 HTML 文件再用本地 HTTP 服务发布。这样既能练习 requests 和 BeautifulSoup 的完整流程又能跳过网站版权和反爬问题是最适合初学者的实验环境。2. 环境准备与依赖安装2.1 Python 版本与开发环境本文示例基于 Python 3.8 以上版本。不同系统安装 Python 的方式不同但建议统一使用虚拟环境避免全局环境被项目依赖污染。python3 -m venv venv source venv/bin/activateWindows 下的激活命令是venv\Scripts\activate激活后命令行提示符会显示(venv)说明当前已经进入虚拟环境。2.2 安装 requests、beautifulsoup4、lxml需要安装三个库requests发送 HTTP 请求。beautifulsoup4解析 HTML 和 XML 文档。lxml解析器速度比 Python 内置的 html.parser 更快对复杂页面支持更好。安装命令pip install requests beautifulsoup4 lxml如果网络较慢可以更换国内 PyPI 镜像pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests beautifulsoup4 lxml2.3 验证安装是否正确在终端执行python -c import requests, bs4, lxml; print(requests.__version__); print(bs4.__version__)正常会输出类似2.31.0 4.12.2这里没有输出 lxml 版本因为通过lxml.__version__不一定能获取到。可以用下面的方式确认 lxml 已经安装python -c from lxml import etree; print(lxml ok)如果出现ModuleNotFoundError说明对应库没有安装成功需要重新检查 pip 安装过程。注意requests 依赖 urllib3 等底层库安装 requests 时 pip 会自动处理这些依赖不需要手动安装。3. 准备一个可复现的电影信息页3.1 为什么用本地 HTML 页面做示例直接爬取公开网站虽然更真实但会遇到很多额外问题目标网站改版、robots 限制、验证码、IP 封禁、编码混乱、数据接口变化。这些内容对新手容易造成干扰让人无法聚焦在爬虫本身的流程上。用本地 HTML 页面做示例可以预先设计好 HTML 结构然后针对性地写解析代码。这样能保证运行结果可控也方便排查问题。另外本地页面不涉及任何版权问题可以随意修改结构用来测试各种选择器。等把解析逻辑练熟后再迁移到真实网站上思路会更加清晰。3.2 创建 movies.html在项目目录下新建一个文件movies.html内容是一份静态电影列表。这个页面是后面爬虫请求的目标。!DOCTYPE html html langzh-CN head meta charsetUTF-8 title公开电影列表 - 示例页/title /head body div classmovie-list div classmovie-item>python -m http.server 8000启动后在浏览器访问http://localhost:8000/movies.html如果能看到刚才创建的页面说明服务正常。如果 8000 端口被占用可以换一个端口python -m http.server 8001然后在代码中请求http://localhost:8001/movies.html。4. 用爬虫抓取电影列表页4.1 requests 发送 GET 请求第一步是使用 requests 请求页面。创建一个新文件movie_spider.py写入以下代码import requests url http://localhost:8000/movies.html response requests.get(url, timeout10) print(response.status_code) print(response.text[:500])timeout10表示请求超过 10 秒没有响应就抛出异常避免程序长时间卡住。4.2 检查 HTTP 响应状态HTTP 状态码用来判断请求是否成功。常见状态码包括状态码含义常见处理建议200请求成功继续解析301/302重定向可以直接让 requests 跟随403禁止访问可能被反爬或权限限制404页面不存在检查 URL 路径500/502/503服务器异常等待服务器恢复或更换数据源requests 的response.raise_for_status()会在状态码为 4xx/5xx 时抛出异常适合放在正式脚本里import requests url http://localhost:8000/movies.html response requests.get(url, timeout10) response.raise_for_status() print(请求成功页面大小:, len(response.text))4.3 处理请求头和编码有些服务器会根据 User-Agent 判断是否为浏览器访问。requests 默认的 User-Agent 是python-requests/x.x.x容易被识别为自动化脚本。为了减少这种情况可以显式设置浏览器风格的请求头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 } response requests.get(url, headersheaders, timeout10)编码问题也需要关注。HTML 中声明了charsetUTF-8requests 会在大多数情况下自动解析。如果出现乱码可以手动指定编码response.encoding utf-8常见做法是在请求前设置response.encoding response.apparent_encoding让 requests 从内容中猜编码但这种方法在内容量较大时可能不准确。最简单的方案如果页面是 UTF-8就固定response.encoding utf-8。5. 用 BeautifulSoup 解析电影信息5.1 解析 HTML 节点拿到响应文本后交给 BeautifulSoup 构造解析树。from bs4 import BeautifulSoup soup BeautifulSoup(response.text, lxml)lxml是解析器。也可以换成html.parser这是 Python 标准库自带的不需要安装额外库但速度较慢。推荐用 lxml因为它容错性强解析速度更快。5.2 提取电影名称、年份、评分、简介解析核心是定位所有.movie-item节点然后从每个节点中提取子节点信息。items soup.select(.movie-item) movies [] for item in items: title item.select_one(.title).get_text(stripTrue) year item.select_one(.year).get_text(stripTrue) rating item.select_one(.rating).get_text(stripTrue) desc item.select_one(.desc).get_text(stripTrue) movie_id item.get(data-id) movies.append({ id: movie_id, title: title, year: year, rating: rating, desc: desc }) print(movies)select_one返回第一个匹配到的节点select返回所有匹配节点的列表。get_text(stripTrue)会提取节点内所有文本并去掉首尾空白。实际项目中字段可能不在 text 节点里而是在属性中。比如>image_url item.select_one(.poster img).get(src)所以解析时要先观察 HTML 结构再决定用get_text还是get取值。5.3 把结果放入列表和字典上面的代码已经把所有电影信息放到了movies列表里每个电影是一个字典。这样做的目的是统一结构方便后续转换为 JSON 或写入表格。下面把它封装成函数fetch_movies(url)便于复用import requests from bs4 import BeautifulSoup def fetch_movies(url, headersNone): response requests.get(url, headersheaders, timeout10) response.raise_for_status() response.encoding utf-8 soup BeautifulSoup(response.text, lxml) items soup.select(.movie-item) movies [] for item in items: title_node item.select_one(.title) year_node item.select_one(.year) rating_node item.select_one(.rating) desc_node item.select_one(.desc) if not title_node or not year_node: continue movies.append({ id: item.get(data-id), title: title_node.get_text(stripTrue), year: year_node.get_text(stripTrue), rating: rating_node.get_text(stripTrue) if rating_node else , desc: desc_node.get_text(stripTrue) if desc_node else }) return movies这个函数对缺失节点做了保护就算某个.desc不存在也不会导致整个程序崩溃只是把字段设为空字符串。这是爬虫开发中非常必要的容错习惯。6. 保存为 JSON 和 CSV6.1 写入 JSON 文件抓取到的movies列表可以直接序列化为 JSON。JSON 格式结构化强适合后续做接口或数据分析。import json def save_to_json(data, filenamemovies.json): with open(filename, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)ensure_asciiFalse保证中文显示为原始字符而不是\u661f之类的转义序列。indent2让输出文件有缩进方便阅读。生成的movies.json内容大致是[ { id: 1, title: 星际穿越, year: 2014, rating: 9.4, desc: 近未来地球濒临毁灭人类通过虫洞寻找新家园。 }, { id: 2, title: 盗梦空间, year: 2010, rating: 9.3, desc: 进入他人梦境植入想法完成一次不可能的任务。 } ]6.2 写入 CSV 文件CSV 适合用 Excel 打开也方便表格类数据处理。使用 Python 内置的 csv 模块import csv def save_to_csv(data, filenamemovies.csv): with open(filename, w, encodingutf-8-sig, newline) as f: fieldnames [id, title, year, rating, desc] writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() writer.writerows(data)这里用了utf-8-sig编码而不是普通的utf-8。原因是 Excel 直接打开 UTF-8 编码的 CSV 文件时中文容易乱码加上 BOM 头后 Excel 能正确识别。newline是为了避免在 Windows 下写入 CSV 时出现多余空行。生成的movies.csv内容id,title,year,rating,desc 1,星际穿越,2014,9.4,近未来地球濒临毁灭人类通过虫洞寻找新家园。 2,盗梦空间,2010,9.3,进入他人梦境植入想法完成一次不可能的任务。6.3 定义 main 函数组合流程把请求、解析、保存串起来形成可执行脚本def main(): url http://localhost:8000/movies.html 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 } movies fetch_movies(url, headersheaders) save_to_json(movies) save_to_csv(movies) print(f共抓取 {len(movies)} 条电影信息) print(movies) if __name__ __main__: main()入口使用if __name__ __main__是 Python 项目的基本规范避免模块被导入时直接执行。7. 运行验证与结果分析7.1 完整脚本把前面的代码整合到一个文件中最终脚本结构如下import requests import json import csv from bs4 import BeautifulSoup def fetch_movies(url, headersNone): response requests.get(url, headersheaders, timeout10) response.raise_for_status() response.encoding utf-8 soup BeautifulSoup(response.text, lxml) items soup.select(.movie-item) movies [] for item in items: title_node item.select_one(.title) year_node item.select_one(.year) if not title_node or not year_node: continue movies.append({ id: item.get(data-id), title: title_node.get_text(stripTrue), year: year_node.get_text(stripTrue), rating: item.select_one(.rating).get_text(stripTrue) if item.select_one(.rating) else , desc: item.select_one(.desc).get_text(stripTrue) if item.select_one(.desc) else }) return movies def save_to_json(data, filenamemovies.json): with open(filename, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) def save_to_csv(data, filenamemovies.csv): with open(filename, w, encodingutf-8-sig, newline) as f: fieldnames [id, title, year, rating, desc] writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() writer.writerows(data) def main(): url http://localhost:8000/movies.html 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 } movies fetch_movies(url, headersheaders) save_to_json(movies) save_to_csv(movies) print(f共抓取 {len(movies)} 条电影信息) for movie in movies: print(movie[title], movie[year], movie[rating]) if __name__ __main__: main()实际项目里会把这几个函数拆到不同模块这里合并到一个文件是为了降低学习门槛。7.2 预期输出先启动本地服务python -m http.server 8000再在另一个终端运行脚本python movie_spider.py预期输出共抓取 5 条电影信息 星际穿越 2014 9.4 盗梦空间 2010 9.3 千与千寻 2001 9.4 控方证人 1957 9.5 当幸福来敲门 2006 9.2同时当前目录下会出现movies.json和movies.csv两个文件。打开 CSV 文件后可以用 Excel 看到完整的表格列。7.3 异常情况与日志如果运行脚本时本地服务没有启动会看到类似这样的异常ConnectionError: HTTPConnectionPool(hostlocalhost, port8000): Max retries exceeded with url: /movies.html (Caused by NewConnectionError(urllib3.connection.HTTPConnection object: Failed to establish a new connection: [Errno 111] Connection refused))这说明请求被拒绝需要确认服务是否正常运行。如果页面结构变化比如.movie-item被改成.movie-carditems就会是空列表程序输出“共抓取 0 条电影信息”但不会报错。这是爬虫的一个典型问题结构改变导致数据静默丢失。所以在实际项目中解析完最好校验数据条数和关键字段比如if not movies: raise ValueError(没有解析到任何电影请检查 HTML 结构或选择器)8. 常见问题排查8.1 请求失败或超时问题现象可能原因检查方式解决建议ConnectionError本地服务未启动或端口错误浏览器访问 URL 确认启动服务或修改端口Timeout网络不通或服务器响应慢尝试 curl 命令增加 timeout 时间或使用重试机制SSL 错误HTTPS 证书问题查看异常信息验证目标域名不建议直接关闭证书校验如果使用timeout10仍然超时可能目标服务器响应非常慢。此时可以结合retry机制处理from requests.adapters import HTTPAdapter session requests.Session() adapter HTTPAdapter(max_retries2) session.mount(http://, adapter) session.mount(https://, adapter)8.2 编码乱码如果抓取下来的中文变成乱码通常是编码设置不对。定位方法print(response.encoding) print(response.apparent_encoding)如果response.encoding不是utf-8可以手动设置response.encoding utf-8如果页面是 GBK 编码要设置成response.encoding gbk最保险的方式是优先读取响应头里的charset再配合 HTML 中的 meta 声明。对于常见页面手动设置通常足够。8.3 选择器没有匹配到节点BeautifulSoup 的select返回空列表可能原因有HTML 结构真的没有对应 class。页面内容是 JavaScript 动态渲染的初始 HTML 里没有这些节点。class 名称写错比如多了空格或大小写不一致。检查方式print(soup.prettify()[:2000])把页面内容打印出来人工确认目标节点是否存在。如果页面不是静态 HTML需要使用 Playwright 或 Selenium 等浏览器自动化工具但这也意味着目标站点的数据获取成本更高、合规风险更大。8.4 数据源加了反爬限制真实网站经常会对爬虫做访问控制。常见现象返回 403 Forbidden。返回验证码页面。返回空页面但状态码是 200。访问几次后被临时封禁 IP。这些情况不能简单地通过修改 User-Agent 解决。正确的做法是阅读 robots.txt确认是否允许该路径。降低访问频率添加随机延时。使用正规的 API 服务。如果网站明确禁止爬虫就不要强行绕过。import time import random time.sleep(random.uniform(1, 3))随机延时只能降低压力不能回避网站条款。真正合规的爬虫项目应该在正式开发前完成授权评估。9. 最佳实践与扩展方向9.1 真实项目中要做的合规检查清单下面这份清单适用于任何想在项目里使用爬虫的开发者建议在写代码之前逐项确认[ ] 目标网站的 robots.txt 是否允许爬取该路径[ ] 目标网站的服务条款是否禁止自动化访问[ ] 数据是否包含版权内容或个人信息[ ] 是否计划大规模并发请求[ ] 是否设置合理的请求频率和延时[ ] 是否保留数据来源和抓取时间便于追溯[ ] 是否只保存必要字段不做超范围采集[ ] 是否具有数据删除和纠错机制[ ] 是否将结果用于商业用途有无授权依据学习环境里用本地 HTML 页面练习可以完全忽略这些条款问题。但一旦接入真实网站任何一条不满足都可能带来法律或封禁风险。不要抱着“先抓了再说”的心态先确认合规再写代码。9.2 性能提升与异步抓取本文示例只抓取了一个页面。真实项目中可能要抓取几十甚至几百个列表页。直接使用for循环逐个请求会非常慢因为网络 I/O 浪费时间。可以改用requests.Session复用连接session requests.Session() for page in range(1, 6): resp session.get(fhttp://localhost:8000/movies.html?page{page}, timeout10)也可以使用concurrent.futures.ThreadPoolExecutor进行多线程抓取from concurrent.futures import ThreadPoolExecutor def fetch_one(url): resp requests.get(url, timeout10) return resp.text with ThreadPoolExecutor(max_workers4) as pool: results list(pool.map(fetch_one, url_list))多线程能显著提升吞吐量但也会更快地触发目标站点反爬。生产环境必须做好频率控制、失败重试、日志记录和监控。9.3 新手学习的扩展方向爬完一个静态列表页后可以沿着下面几个方向继续深入增加分页功能解析下一页按钮的链接循环抓取多页。进入详情页从列表页提取详情页 URL再请求详情页解析更多字段。处理动态页面使用 Playwright 加载 JavaScript 渲染的页面。使用 Scrapy 框架它内置了请求队列、下载中间件、Item Pipeline 等功能适合中大型项目。数据清洗与可视化把抓取到的 CSV 交给 pandas 做分析用 matplotlib 展示电影年份和评分分布。定时调度用 APScheduler 或 cron 定期运行爬虫更新本地数据集。这些方向的核心从没变过请求、解析、提取、存储。唯一变化是应对不同网站结构的技巧以及合规成本越来越高。建议初学者先在本地页面把核心循环练熟再逐步扩展。这样遇到真实网页时你会知道问题出在请求层、解析层还是存储层而不是把所有问题都揉在一起既不好排查也容易写出风险极高的代码。
返回列表