Playwright实战:从零到一,轻松爬取豆瓣电影TOP10并生成Excel报表
1. 为什么选择Playwright爬取豆瓣电影?
最近几年,网页自动化工具层出不穷,从早期的Selenium到现在的Playwright,技术迭代速度非常快。作为一个长期从事数据采集的开发者,我实测过市面上几乎所有主流工具,最终发现Playwright在易用性和稳定性上表现尤为突出。特别是对于刚入门的新手来说,它提供的同步API和自动生成代码功能,能让你快速上手而不会被复杂的配置劝退。
Playwright最大的优势在于它原生支持Chromium、Firefox和WebKit三大浏览器引擎,这意味着你写的脚本几乎能在所有现代浏览器上运行。还记得我第一次用Selenium时,光是解决浏览器驱动兼容性问题就折腾了大半天。而Playwright通过内置驱动彻底解决了这个痛点,安装完就能直接用,这对新手来说简直太友好了。
另一个让我选择Playwright的原因是它的执行速度。在爬取豆瓣电影这类动态渲染的页面时,传统的requests+BeautifulSoup组合经常拿不到完整数据。Playwright则能完美模拟真实用户操作,等页面完全加载后再提取数据。我做过对比测试,在相同网络环境下,Playwright获取完整DOM树的速度比Selenium快30%左右。
2. 环境准备与基础配置
2.1 安装Playwright
安装Playwright只需要两条命令,但有些细节需要注意。首先建议升级pip到最新版本,这能避免很多依赖冲突问题:
pip install --upgrade pip pip install playwright安装完Python包后,还需要安装浏览器二进制文件。这里有个小技巧:如果你在国内,可能会遇到下载速度慢的问题。可以通过设置环境变量来使用国内镜像源:
PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright playwright install安装过程大概需要2-3分钟,取决于你的网络速度。完成后可以运行playwright --version检查是否安装成功。我建议同时安装chromium、firefox和webkit三个浏览器,虽然本项目只用chromium,但多浏览器支持是Playwright的特色功能:
playwright install chromium firefox webkit2.2 初始化项目结构
创建一个干净的目录来存放项目文件是个好习惯。我的典型项目结构是这样的:
douban_movie_crawler/ ├── main.py # 主程序 ├── utils.py # 工具函数 ├── config.py # 配置文件 └── output/ # 输出目录在main.py中,我们先导入必要的模块。除了playwright自带的sync_api,我们还会用到xlwt来处理Excel文件:
import xlwt from playwright.sync_api import sync_playwright3. 爬取豆瓣电影TOP10实战
3.1 启动浏览器并访问页面
使用Playwright的第一步是创建浏览器实例。这里有个重要参数headless,默认是True(无头模式)。对于调试阶段,我强烈建议设置为False,这样你能看到实际浏览器窗口,方便观察脚本执行情况:
def run(playwright): browser = playwright.chromium.launch( headless=False, slow_mo=1000 # 放慢操作速度,方便观察 )创建context对象相当于打开一个隐私浏览窗口,不同context之间完全隔离。这在需要多账号登录的场景特别有用:
context = browser.new_context( user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' )访问豆瓣电影首页时,建议增加超时设置。我遇到过因为网络波动导致页面加载失败的情况,合理的超时设置能提高脚本稳定性:
page = context.new_page() page.goto("https://movie.douban.com/", timeout=60000)3.2 定位和提取数据
豆瓣电影TOP10榜单实际上是通过AJAX动态加载的,直接访问首页可能看不到。我们需要先点击"排行榜"链接:
page.click("text=排行榜") page.wait_for_selector(".billboard-bd") # 等待榜单加载提取数据时,CSS选择器和XPath是最常用的两种方式。我更喜欢CSS选择器,因为它的语法更简洁:
items = page.query_selector_all("#billboard > div.billboard-bd > table > tbody > tr")不过有时候XPath更灵活,特别是处理没有明确class或id的元素时:
items = page.query_selector_all("//*[@id='billboard']/div[2]/table/tbody/tr")提取到的每个tr元素包含电影名称和链接,我们需要进一步解析:
movie_list = [] for item in items: name = item.query_selector("td:nth-child(1) > a").text_content() link = item.query_selector("td:nth-child(1) > a").get_attribute("href") rating = item.query_selector("td:nth-child(2)").text_content() movie_list.append((name, link, rating))4. 数据存储与Excel导出
4.1 准备Excel文件
使用xlwt创建Excel文件时,编码设置很重要。豆瓣电影可能包含中文和其他特殊字符,utf-8编码能确保不会出现乱码:
workbook = xlwt.Workbook(encoding='utf-8') worksheet = workbook.add_sheet('豆瓣电影TOP10')设置样式可以让表格更美观。我通常会定义几种常用样式:
style_header = xlwt.easyxf( 'font: bold on; align: vert centre, horiz center' ) style_normal = xlwt.easyxf('align: vert centre')4.2 写入数据
表头应该清晰表明每列的内容。除了电影名称和链接,我还增加了评分列:
worksheet.write(0, 0, "豆瓣电影TOP10", style_header) worksheet.write(2, 0, "排名", style_header) worksheet.write(2, 1, "电影名称", style_header) worksheet.write(2, 2, "评分", style_header) worksheet.write(2, 3, "详情链接", style_header)写入数据时要注意行号控制。我习惯从第3行开始写实际数据(0-based索引):
for idx, (name, link, rating) in enumerate(movie_list): row = idx + 3 worksheet.write(row, 0, idx+1, style_normal) worksheet.write(row, 1, name, style_normal) worksheet.write(row, 2, rating, style_normal) worksheet.write(row, 3, link, style_normal)4.3 调整格式并保存
合适的列宽能让Excel更易读。我通常根据内容长度动态设置:
worksheet.col(0).width = 2000 # 排名列 worksheet.col(1).width = 6000 # 名称列 worksheet.col(2).width = 3000 # 评分列 worksheet.col(3).width = 12000 # 链接列最后保存文件时,建议使用绝对路径并处理可能出现的异常:
try: workbook.save('output/douban_top10.xls') except Exception as e: print(f"保存文件失败: {e}")5. 完整代码与优化建议
5.1 完整脚本
将前面的代码片段整合起来,完整的脚本如下:
import xlwt from playwright.sync_api import sync_playwright def get_movie_data(page): page.click("text=排行榜") page.wait_for_selector(".billboard-bd") items = page.query_selector_all("#billboard > div.billboard-bd > table > tbody > tr") movie_list = [] for item in items: name = item.query_selector("td:nth-child(1) > a").text_content() link = item.query_selector("td:nth-child(1) > a").get_attribute("href") rating = item.query_selector("td:nth-child(2)").text_content() movie_list.append((name, link, rating)) return movie_list def save_to_excel(data, filename): workbook = xlwt.Workbook(encoding='utf-8') worksheet = workbook.add_sheet('豆瓣电影TOP10') # 设置样式 style_header = xlwt.easyxf('font: bold on; align: vert centre, horiz center') style_normal = xlwt.easyxf('align: vert centre') # 写入表头 worksheet.write(0, 0, "豆瓣电影TOP10", style_header) worksheet.write(2, 0, "排名", style_header) worksheet.write(2, 1, "电影名称", style_header) worksheet.write(2, 2, "评分", style_header) worksheet.write(2, 3, "详情链接", style_header) # 写入数据 for idx, (name, link, rating) in enumerate(data): row = idx + 3 worksheet.write(row, 0, idx+1, style_normal) worksheet.write(row, 1, name, style_normal) worksheet.write(row, 2, rating, style_normal) worksheet.write(row, 3, link, style_normal) # 调整列宽 worksheet.col(0).width = 2000 worksheet.col(1).width = 6000 worksheet.col(2).width = 3000 worksheet.col(3).width = 12000 workbook.save(filename) def main(): with sync_playwright() as playwright: browser = playwright.chromium.launch(headless=False) context = browser.new_context() page = context.new_page() page.goto("https://movie.douban.com/") movie_data = get_movie_data(page) page.close() context.close() browser.close() save_to_excel(movie_data, "output/douban_top10.xls") if __name__ == "__main__": main()5.2 常见问题排查
在实际运行中可能会遇到几个典型问题:
元素定位失败:豆瓣前端偶尔会改版,导致选择器失效。这时候可以:
- 使用Playwright的
page.pause()方法进入调试模式 - 尝试更宽松的选择器,比如
text="排行榜"改为a:has-text("排行榜")
- 使用Playwright的
反爬机制:如果频繁访问,可能会触发验证码。解决方案包括:
- 增加
slow_mo参数放慢操作速度 - 设置随机延迟:
page.wait_for_timeout(random.randint(1000,3000))
- 增加
数据缺失:确保等待足够长时间让数据加载:
page.wait_for_selector(".billboard-bd", state="attached", timeout=10000)
5.3 扩展思路
这个基础项目可以进一步扩展:
- 添加异常处理,使脚本更健壮
- 使用pandas替代xlwt,支持更复杂的Excel操作
- 爬取更多信息,如导演、主演、短评等
- 设置定时任务,每天自动更新榜单
第一次运行这个脚本时,我因为没加足够的等待时间导致数据获取不全。后来发现Playwright的wait_for_selector比固定sleep可靠得多。建议新手在写自动化脚本时,一定要充分考虑网络延迟和页面加载时间的影响
