Pyppeteer 爬取实战

爬取目标

 https://spa2.scrape.center/

本节工作

遍历每页列表,获取每部电影详情页的 URL

爬取每部电影的详情页, 提取电影名称, 评分,类别,封面,简介等信息

将爬取的数据保存为 JSON数据

准备工作

安装好 python  (最低 3.6)

安装好 Pyppeteer 并能成功运行实例

爬取列表页

import logginglogging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s: %(message)s')INDEX_URL = 'https://spa2.scrape.center/page/{page}'
TIMEOUT = 10 
TOTAL_PAGE = 10
WINDOW_WIDTH, WINDOW_HEIGHT = 1366, 768
HEADLESS = False

这里导入了必要的包,定义了日志的配置和几个变量

INDEX_URL:  列表页的 URL  后面的page 是动态的,翻页用

TIMEOUT :加载超时的最大时间  秒

TOTAL_PAGE : 爬取的总页数

WINDOW_WIDTH,WINDOW_HEIGHT: 浏览器的大小

HEADLESS ; 浏览器是否为无头模式,默认是 True, 这里是 False, 

初始化 Pyppeteer, 设置窗口大小

from pyppeteer import launchbrowser, tab = None, Noneasync def init():global browser, tabbrowser = await launch(headless=HEADLESS, args=['--disable-infobars', f'--window-size={WINDOW_WIDTH},{WINDOW_HEIGHT}'])tab = await browser.newPage()await tab.setViewport({'width': WINDOW_WIDTH, 'height': WINDOW_HEIGHT})

这里先声明了 browser 变量和 tab 变量, 前者代表 Pyppeteer 所用的浏览器对象,后者代表新建的页面选项卡。 这两项都被设置为了全局变量,能够方便其他方法调用

然后定义了一个 init 方法,该方法调用了 Pyppeteer 的 launch 方法, 并且给 headless 参数传入了 HEADLESS , 将 Pyppeteer 设置为非无头模式还通过 args 参数指定了隐藏提示条河设置浏览器窗口宽高

接下来定义一个通用的爬取方法

  

from pyppeteer.errors import TimeoutErrorasync def scrape_page(url, selector):logging.info('scraping %s', url)try:await tab.goto(url)await tab.waitForSelector(selector, options={'timeout': TIMEOUT * 1000})except TimeoutError:logging.error('error occurred while scraping %s', url, exc_info=True)

这里定义了 scrape_page 方法,它接收两个参数, 一个是 url , 代表爬取页面的 url ,使用 goto 方法调用此URL 即可访问对应页面, 另一个是 selector ,即等待渲染出的节点对应的 CSS 选择器,此外,我们调用了 waitForSelector 方法, 传入 selector , 并通过 options 指定了最长等待时间

运行时,会首先访问传入的 URL 对应的页面,然后等待某个和选择器匹配的节点加载出来,最长等待 10 秒,如果10秒内加载出来, 就接着往下执行,否则抛出异常,并输出错误日志

下面实现爬取列表页的方法

async def scrape_index(page):url = INDEX_URL.format(page=page)await scrape_page(url, '.item .name')

这里定义了一个 scrape_index 方法,它接收参数 page ,代表爬取的页面的页码,方法中我们先通过  INDEX_URL 构造出了列表页的 URL ,然后调用 scrape_page 方法并将构造出 URL 传入其中,同时传入选择器

我们传入的选择器是 .item .name 是列表页的电影名称, 意味着电影名称加载出来就代表页面加载成功了

我们再定义一个分析列表页的方法,用来提取详情页的 URL 

async def parse_index():return await tab.querySelectorAllEval('.item .name', 'nodes => nodes.map(node => node.href)')

这里我们调用了 querySlectorAllEval 方法, 它接收两个参数, 一个是  selector , 代表选择器, 另一个是 pageFunction , 代表要执行的 JavaScript 方法。 这个方法的作用是找出和选择器匹配的节点,然后根据 pageFunction 定义的逻辑从这些节点中抽取中对应的结果并返回。

我们给参数 selector 传入了电影名称,由于和选择器相匹配的节点有多个,所以给 pageFunction 参数输入的 JavaScript 方法就是 nodes , 其返回值是调用 map 方法得到的 node ,然后调用 node 的 href 属性得到超链接。 这样 querySelectorAllEval 的返回结果就是当前列表页所有电影的详情页 URL组成的列表

接下来我们串联刚刚实现的方法

import asyncioasync def main():await init()try:for page in range(1, TOTAL_PAGE + 1 ):await scrape_index(page)detail_urls = await parse_index()logging.info('detail_urls %s', detail_urls)finally:await browser.close()
if __name__ == '__main__':asyncio.run(main())

部分输出结果 

2024-07-29 11:31:52,966 - INFO: scraping https://spa2.scrape.center/page/8
2024-07-29 11:31:53,634 - INFO: detail_urls ['https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI3MQ==', 'https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI3Mg==', 'https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI3Mw==', 'https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI3NA==', 'https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI3NQ==', 'https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI3Ng==', 'https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI3Nw==', 'https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI3OA==', 

这里定义了 main 方法,其中首先调用了 init 方法, 然后遍历所有页码,调用 scrape_index 方法爬取了每一页列表页, 接着调用 parse_index 方法,从列表页提取每个详情页的 URL  最后输出

爬取详情页

先定义一个爬取详情页的方法

async def scrape_detail(url):await scrape_page(url, 'h2')

这里很简单,直接调用 scrape_page 方法,传入详情页的 URL 和选择器即可, 这里的选择器我们传入了 h2 , 代表电影名称, 运行的话, Pyppeteer 已经成功加载出详情页了

下一步就是提取详情页的信息了

async def parse_detail():url = tab.urlname = await tab.querySelectorEval('h2', 'node => node.innerText')categories = await tab.querySelectorAllEval('.categories button span', 'nodes => nodes.map(node => node.innerText)')cover = await tab.querySelectorEval('.cover', 'node => node.src')score = await tab.querySelectorEval('.score', 'node => node.innerText')drama = await tab.querySelectorEval('.drama p', 'node => node.innerText')return {'url': url,'name': name,'categories': categories,'cover': cover,'score': score,'drama': drama}

这里我们定义了 parse_detail 方法, 提取了 URL ,名称,类别,封面, 分数,简介等内容。

最后将提取结果汇总成一个字典并返回

接下来,在 main 方法中添加对 scrape_detail 方法和 parse_detail 方法的调用

import asyncioasync def main():await init()try:for page in range(1, TOTAL_PAGE + 1 ):await scrape_index(page)detail_urls = await parse_index()for detail_url in detail_urls:await scrape_detail(detail_url)detail_data = await parse_detail()logging.info('data %s', detail_data)finally:await browser.close()
if __name__ == '__main__':asyncio.run(main())

2024-07-29 11:50:49,704 - INFO: scraping https://spa2.scrape.center/page/1
2024-07-29 11:50:51,151 - INFO: scraping https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx
2024-07-29 11:50:53,454 - INFO: data {'url': 'https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx', 'name': '霸王别姬 - Farewell My Concubine', 'categories': ['剧情', '爱情'], 'cover': 'https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@464w_644h_1e_1c', 'score': '9.5', 'drama': '影片借一出《霸王别姬》的京戏,牵扯出三个人之间一段随时代风云变幻的爱恨情仇。段小楼(张丰毅 饰)与程蝶衣(张国荣 饰)是一对打小一起长大的师兄弟,两人一个演生,一个饰旦,一向配合天衣无缝,尤其一出《霸王别姬》,更是誉满京城,为此,两人约定合演一辈子《霸王别姬》。但两人对戏剧与人生关系的理解有本质不同,段小楼深知戏非人生,程蝶衣则是人戏不分。段小楼在认为该成家立业之时迎娶了名妓菊仙(巩俐 饰),致使程蝶衣认定菊仙是可耻的第三者,使段小楼做了叛徒,自此,三人围绕一出《霸王别姬》生出的爱恨情仇战开始随着时代风云的变迁不断升级,终酿成悲剧。'}
 

这里看到,我们已经提取了想要的信息

数据存储

数据存储格式为 JSON 文件

import json
from os import makedirs
from os.path import existsRESULTS_DIR = 'results1'exists(RESULTS_DIR) or makedirs(RESULTS_DIR)
async def save_data(data):name = data.get('name')data_path = f'{RESULTS_DIR}/{name}.json'json.dump(data, open(data_path, 'w', encoding='utf-8'), ensure_ascii=False, indent=2)

然后在 main  方法中添加 save_data 方法的调用

import asyncioasync def main():await init()try:for page in range(1, TOTAL_PAGE + 1 ):await scrape_index(page)detail_urls = await parse_index()for detail_url in detail_urls:await scrape_detail(detail_url)detail_data = await parse_detail()await save_data(detail_data)logging.info('data %s', detail_data)finally:await browser.close()
if __name__ == '__main__':asyncio.run(main())

问题排查

代码运行过程中, 可能由于 Pyppeteer 本身实现方面的问题,因此在连续运行 20 秒之后,报错

Pyppeteer.errors.NetworkError: Protocol Error (Runtime.evaluate): Session closed.

问题的解决方法是修改源码, 问题描述详见 : https://github.com/miyakogi/pyppeteer/issues/178

我这里没遇到,应该是在后面的版本修复了

无头模式

如果需要无头模式,将最开始的

HEADLES = False  改成 HEADLES = True 就可以了

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/51537.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

半监督学习方法的种类

半监督学习方法结合了少量标记数据和大量未标记数据,利用未标记数据来改进模型的性能。以下是几种常见的半监督学习方法: 1. **自训练(Self-training)**:模型先用少量标记数据进行训练,然后使用这个模型来…

NVIDIA正偷偷复活卡皇泰坦,性能秒杀5090Ti

PC 硬件圈的瓜年年有,但最近似乎格外的多噢! 首先针对 13、14 代酷睿 CPU 不稳定问题,Intel 终于做出了正式回应: 他们在对退回的 CPU 进行大量分析后得出,确认是过高的运行电压和微代码算法错误导致了不稳定情况。 …

2-48 基于matlab的EM算法聚类可视化程序

基于matlab的EM算法聚类可视化程序,通过期望最大化算法(EM)优化类别间距,使得类别间距最大、类内间距最小。输出聚类前后结果及收敛曲线。程序已调通,可直接运行。 2-48 期望最大化算法(EM) 聚类…

微信小程序教程002:代码结构介绍和新建小程序页面

文章目录 代码介绍1、小程序代码构成2、小程序页面组成部分3、JSON配置文件的作用3.1 app.json文件3.2 project.config.json文件3.3 sitemap.json文件3.4 页面的.json文件 新建小程序页面WXML和WXSS介绍1、什么是WXML2、什么是WXSS 小程序的JS文件1、JS文件2、小程序中JS文件分…

使用abpcli创建项目时提示数据库迁移失败

问题描述 使用abpcli创建项目时提示数据库迁移失败! 解决方案: 1、检查数据库连接字符串 {"ConnectionStrings": {"Default": "serverlocalhost;port3306;databaseAcmeBookStore;userroot;passwordyour_password;"} }2、…

你真的会提问吗?如何有效提问?

学会提问,其实就是学会了一种优秀的思维方式,提问的过程,本身就是思考的过程。学会提问,也是我们提升领导力非常重要的一步。 一个又一个优秀的提问,会把我们引向人生巅峰;而低质量的提问,只会…

MySQL数据库的DQL的高级数据查询语句

目录 非等值联查: 等值联查: eg:5张表联查 连接查询——left/right/inner join on eg: 连接查询——union Eg: 不去重的并集——union all 子查询(内部查询) 1、where型子查询 2、from型子查询&a…

微信小程序之用户登录

用户登录是小程序的一个常用功能,当用户在浏览文章想要收藏时,在线上购买商品时,只有用户登录自己账号以后,才可以进一步使用这些功能。此文论述了小程序用户登录功能的设计流程 一、设计思路 1、界面 小程序界面效果如下所示&…

【Golang 面试 - 基础题】每日 5 题(六)

✍个人博客:Pandaconda-CSDN博客 📣专栏地址:http://t.csdnimg.cn/UWz06 📚专栏简介:在这个专栏中,我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话,欢迎点赞👍收藏…

vue3pinia

pinia 一.pinia的理解及其搭配pinia环境二.使用二.修改数据(三种方式)三.storeToRefs 目录是store Store 包含了状态(state)、获取器(getters)和操作(actions),相当于组件中的&#…

web后端--Spring事务管理

事务也要日志配置 !!!!debug前面记得加空格 logging:level:org.springframework.jdbc.support.JdbcTransactionManager: debugrollbackFor 默认情况下,只有出现RunTimeException才会回滚事务,rollbackfor属性用于控制出现何种异常类型,回滚…

Linux shell编程笔记0

一、shell概述 shell是一个命令行解释器,它接收应用程序/用户命令,然后调用操作系统内核。 shell还是一个功能强大的编程语言,易编写、易调试、灵活性强。 二、shell脚本入门 1.进入编辑模式进入到目录下 vi 文件名称如果是system下的文件…

解决MyBatis查询oracle的NCLOB类型都是内存地址字符串

在项目的配置类文件夹中放入如下通用类OracleResultSetInterceptor import org.apache.ibatis.executor.resultset.ResultSetHandler; import org.apache.ibatis.plugin.*;import java.io.IOException; import java.io.Reader; import java.io.StringWriter; import java.sql.…

电子水尺的工作原理

TH-SC24电子水尺,也被称为感应式防汛水尺或水位在线监测仪,是一种专门用于监测河流水域水位变化的高科技设备。它在防汛工作中发挥着至关重要的作用,能够实时、准确地提供水位数据,为防汛决策和应急响应提供有力支持。   工作原…

连续两年入选!得帆信息强势上榜2024 Gartner ICT技术成熟度曲线

近日,国际权威咨询机构Gartner发布了《Hype Cycle for ICT in China, 2024》(2024年中国ICT技术成熟度曲线)报告。得帆信息连续两年入选低代码应用平台(LCAP)标杆供应商(Sample Vendor)。 每年&…

初创小程序公司怎么选服务器合作商

初创小程序公司怎么选服务器合作商?在移动互联网的浪潮中,小程序以其轻量、便捷、即用即走的特点,成为了众多初创企业快速触达用户、展现创意与服务的理想平台。然而,对于初创小程序公司而言,如何在纷繁复杂的服务器市…

ABAP 无意义的FORM 规范

发现一个极为奇怪的现象,大多数ABAP程序员会在FORM名称前加前缀frm_。 请问这是规范吗,整齐好看吗,又好看在哪里呢。这是哪个师傅教的,意义是什么?而且大多数人就来个frm_get_data与frm_del_data,然后这两…

基于springboot+vue+uniapp的居民健康监测小程序

开发语言:Java框架:springbootuniappJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包&#…

SQL入门通识:轻松掌握数据库查询语言

文章目录 什么是SQL?SQL的基本概念SQL的基本操作查询(SELECT)插入(INSERT)更新(UPDATE)删除(DELETE) 结合操作:联表查询和排序联表查询(JOIN&…

(CVPR-2024)通过多阶段框架和定制的多解码器架构提高扩散模型的训练效率

通过多阶段框架和定制的多解码器架构提高扩散模型的训练效率 Paper Title:Improving Training Efficiency of Diffusion Models via Multi-Stage Framework and Tailored Multi-Decoder Architecture Paper是密歇根大学发表在CVPR 2024的工作 Paper地址 Code地址 Abstract 扩散…