JavaScript动态渲染页面爬取——Pyppeteer爬取实战

Pyppeteer爬取实战

  1. 爬取目标

电影网站https://spa2.scrape.center/

  1. 任 务
    • 通过Selenium遍历列表页,获取每部电影的详情页URL
    • 通过Selenium根据上一步获取的详情页URL爬取每部电影的详情页
    • 从详情页中提取每部电影的名称、类别、分数、简介、封面等内容。
  2. 爬取列表页

示例代码如下:

import logging

logging.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

再定义一个初始化Pyppeteer的方法,其中包括启动Pyppeteer、新建一个页面选项卡和设置窗口大小等操作,代码如下:

from pyppeteer import launch

browser, tab = None, None

async def init():
    global browser, tab
    browser = 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})
 

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

async 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)     

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

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

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

1

再定义一个解析列表页的方法,用来提取每部电影的详情页URL,方法定义如下:

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

接下来,串联调用上面的几个方法,代码如下:

import asyncio

async 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.get_event_loop().run_until_complete(main())

运行结果如下:

2024-03-29 18:26:46,375 - INFO : Browser listening on: ws://127.0.0.1:52883/devtools/browser/151b2275-b1ac-4d49-93f9-2879b81ac315
2024-03-29 18:26:47,466 - INFO : scraping <https://spa2.scrape.center/page/1>
2024-03-29 18:26:50,654 - INFO : detail_urls ['<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIx>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIy>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIz>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI0>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI1>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI2>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI3>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI4>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI5>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIxMA==>']
....
....
2024-03-29 18:26:56,923 - INFO : scraping <https://spa2.scrape.center/page/7>
2024-03-29 18:26:57,650 - INFO : detail_urls ['<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI2MQ==>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI2Mg==>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI2Mw==>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI2NA==>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI2NQ==>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI2Ng==>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI2Nw==>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI2OA==>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI2OQ==>', '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWI3MA==>']
2024-03-29 18:26:57,650 - INFO : scraping <https://spa2.scrape.center/page/8>
...
...
  1. 爬取详情页

定义一个爬取详情页的方法,代码如下:

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

定义一个提取详情页信息的方法:

async def parse_detail():
    url = tab.url
    name = 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
    }

在main方法里添加对scrape_detail方法和parse_detail方法的调用。main方法改写如下:

async 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()

重新运行结果如下:

2024-03-29 18:44:02,422 - INFO : data {'url': '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIzMg==>', 'name': '忠犬八公物语 - ハチ公物語', 'categories': ['剧情 '], 'cover': '<https://p0.meituan.net/movie/2d42e00d7ee59ff5bd574f93b8558aa726665.jpg@464w_644h_1e_1c>', 'score': '8.8', 'drama': '1923年,日本秋田县大馆市天降大雪,近藤家纯种秋田犬产仔,赠与县土木科长间濑。后者将其中一犬转赠东京帝国大学教授上野秀次郎(仲代达矢 饰)驯养。上野的独生女千鹤子对此欢欣鼓舞,而上野夫妇却面露难色。后来,千鹤子(石野真子 饰)谈恋爱,竟怀了男友的孩子。男友专程登门造访。上野与之对谈,后者诚惶诚恐,坦言要对其女儿负责,事不宜迟,即日便举行婚礼,上野闻听此言,转怒为喜。千鹤子出嫁后,上野旋即把全部的心血与爱都投注在幼犬身上,并取名为阿八。每日上下班,阿八必在涩谷车站等候,一年四季,风雨无阻,令路人叹为观止,成为地方一道风景,主仆之情感动天地……'}
....
....
2024-03-29 18:44:02,422 - INFO : scraping <https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIzMw==>
2024-03-29 18:44:03,349 - INFO : data {'url': '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIzMw==>', 'name': '海豚湾 - The Cove', 'categories': ['纪录片 '], 'cover': '<https://p0.meituan.net/movie/eb2ea56996f21e7fb47b1a0736c7f177258901.jpg@464w_644h_1e_1c>', 'score': '8.8', 'drama': '日本和歌山县太地,是一个景色优美的小渔村,然而这里却常年上演着惨无人道的一幕。每年,数以万计的海豚经过这片海域,他们的旅程却在太地戛然而止。渔民们将海豚驱赶到靠近岸边的一个地方,来自世界各地的海豚训练师挑选合适的对象,剩下的大批海豚则被渔民毫无理由地赶尽杀绝。这些屠杀,这些罪行,因为种种利益而被政府和相关组织所隐瞒。理查德·贝瑞年轻时曾是一名海豚训练师,他所参与拍摄电影《海豚的故事》备受欢迎。但是,一头海豚的死让理查德的心灵受到强烈的震撼。从此,他致力于拯救海豚的活动。不顾当地政府和村民百般阻挠,他和他的摄影团队想方设法潜入太地的海豚屠杀场,只为将罪行公之于众,拯救人类可爱的朋友……'}
2024-03-29 18:44:03,349 - INFO : scraping <https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIzNA==>
2024-03-29 18:44:04,859 - INFO : data {'url': '<https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIzNA==>', 'name': '英雄本色 - A Better Tomorrow', 'categories': ['剧情 ', '动作 ', '犯罪 '], 'cover': '<https://p0.meituan.net/movie/3e5f5f3aa4b7e5576521e26c2c7c894d253975.jpg@464w_644h_1e_1c>', 'score': '8.8', 'drama': '宋子豪(狄龙 饰)与Mark(周润发 饰)是一个国际伪钞集团之重要人物,也是情如手足的挚友。宋子豪之弟宋子杰(张国荣 饰)刚刚考入警官学校,其女友钟柔(朱宝意 饰)聪明敏慧,对子杰情深一片。一次,宋子豪带手下谭成(李子雄 饰)去台北与当地黑帮作伪钞交易。不料谭成早已勾结台北黑帮,出卖子豪。在一场枪战中宋子豪受伤,被闻讯而来的警察捕获入狱。Mark得知宋子豪被捕,孤身一人赴台北击毙黑帮头目,自己也身负重伤,艰难地逃离现场。与此同时,台湾黑帮唯恐子豪在狱中向警方泄密,派人至香港绑架子豪之父,混战中老父丧命。宋子豪的弟弟子杰得知兄长原为伪钞集团头目,老父为之而死,遂对子豪及黑社会恨之入骨,决心除暴安良。三年后,宋子豪出狱回到香港,决心弃暗投明,但得不到宋子杰的谅解。当年出卖宋子豪的谭成现已成为老大。他假惺惺地要求宋子豪合作,遭到拒绝,便设计加害宋子杰。宋子豪忍无可忍,联合Mark盗取谭成制造伪钞的电脑软盘,随即交给宋子杰。但同时佯称以软盘与谭成做交易,计划在交易时活捉谭成。岂料谭成早有准备,双方在码头展开枪战。这时,宋子杰也驾车赶来,激战中Mark不幸身亡。警察蜂拥而至,包围了码头。为了不连累宋子杰,宋子豪开枪击毙谭成后,从宋子杰身上取下手铐自缚,向警察自首。两兄弟终于言归于好。'}
2024-03-29 18:44:04,859 - INFO : scraping <https://spa2.scrape.center/detail/ZWYzNCN0ZXVxMGJ0dWEjKC01N3cxcTVvNS0takA5OHh5Z2ltbHlmeHMqLSFpLTAtbWIzNQ==>
.....
  1. 数据存储

将爬取数据保存为JSON文件,实现如下:

import json
from os import makedirs
from os.path import exists

RESULTS_DIR = 'results'

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方法的调用。

async 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()

以上案例可以在[小蜜蜂AI][https://zglg.work]获取更多体验

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ssm009毕业生就业信息统计系统+vue

毕业生就业信息统计系统 摘 要 随着移动应用技术的发展&#xff0c;越来越多的学生借助于移动手机、电脑完成生活中的事务&#xff0c;许多的行业也更加重视与互联网的结合&#xff0c;以提高快捷、高效、安全&#xff0c;可以帮助更多有需求的人。针对传统毕业生就业信息统计…

Spring官方真的不建议使用属性进行依赖注入吗?

使用Spring进行依赖注入时&#xff0c;很多大佬都推荐使用构造方法注入&#xff0c;而非使用在属性上添加 Autowired 注入&#xff0c;而且还说这是Spring官方说的&#xff0c;真的是这样吗&#xff1f; 使用Spring进行依赖主要的方式有很多&#xff0c;主流的使用方式有两种&a…

2核4G云服务器能支持多少人访问?并发数测试

腾讯云轻量应用服务器2核4G5M配置性能测评&#xff0c;腾讯云轻量2核4G5M带宽服务器支持多少人在线访问&#xff1f;并发数10&#xff0c;支持每天5000IP人数访问&#xff0c;腾讯云百科txybk.com整理2核4G服务器支持多少人同时在线&#xff1f;并发数测试、CPU性能、内存性能、…

PID算法控制5840-31ZY编码器直流减速电机旋转特定角度(一)

模块分析 在本工程中&#xff0c;使用stm32做主控芯片输出PWM波&#xff0c;TB6112做电源驱动带动5840-31ZY编码器直流减速电机旋转特定角度 有如下模块 TB6112驱动模块 TB6112是性能优于常见L298N的一款电机驱动芯片&#xff0c;体积更小效率更高发热少 其接线如图&#x…

【3D-GS】Gaussian Splatting SLAM——基于3D Gaussian Splatting的全网最详细的解析

【3D-GS】Gaussian Splatting SLAM——基于3D Gaussian Splatting的定SLAM 3D-GS 与 Nerf 和 Gaussian Splatting1. 开山之作 Nerf2. 扛鼎之作 3D Gaussian Splatting2.1 什么是3D高斯?高斯由1D推广到3D的数学推导2.2 什么是光栅化?2.3 什么是Splatting?2.4 什么是交叉优化?…

互联网医院APP开发攻略:搭建智能医疗平台

互联网医院APP为患者提供了便捷的就医途径&#xff0c;还为医生和医院提供了更加高效的服务和管理手段。接下来&#xff0c;小编将我们本文将就互联网医院APP的开发攻略&#xff0c;以及如何搭建智能医疗平台进行探讨。 1.确定需求和目标 这包括确定服务对象&#xff08;患者、…

Redis分布式锁红锁

Redisson实现分布式锁 lock()上锁解析&#xff1a; 1&#xff0c;hexist判断redis是否有这个锁 2&#xff0c;hset设置锁&#xff0c;hash类型&#xff0c;key为锁名字&#xff0c;value是一对kv&#xff0c;k是当前redisson1的id,v为计数器&#xff0c;表示当前锁持有次数&am…

基于Springboot的学生选课系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的学生选课系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

ML-Decoder: Scalable and Versatile Classification Head

1、引言 论文链接&#xff1a;https://openaccess.thecvf.com/content/WACV2023/papers/Ridnik_ML-Decoder_Scalable_and_Versatile_Classification_Head_WACV_2023_paper.pdf 因为 transformer 解码器分类头[1] 在少类别多标签分类数据集上表现得很好&#xff0c;但由于其查询…

axios+springboot上传图片到本地(vue)

结果&#xff1a; 前端文件&#xff1a; <template> <div> <input type"file" id"file" ref"file" v-on:change"handleFileUpload()"/> <button click"submitFile">上传</button> </div&g…

2024第17届计算机设计大赛开始啦(保研竞赛)

中国大学生计算机设计大赛是面向高校本科生的竞赛&#xff0c;旨在培养创新型、复合型、应用型人才。2024年大赛的主题包括软件应用、微课与教学辅助等11个大类。参赛队由1&#xff5e;3名本科生组成&#xff0c;指导教师不多于2人。在组队和选题方面&#xff0c;强调团结协作和…

Linux——线程概念与线程的创建

目录 一、什么是线程 二、线程的创建 三、重新理解线程 四、进程和线程对比 一、什么是线程 在一个程序里的一个执行路线就叫做线程&#xff08;thread&#xff09;。更准确的定义是&#xff1a;线程是“一个进程内部的控制序列”一切进程至少都有一个执行线程线程在进程内部…

车道线中心线生成方法

车道线中心线生成方法 附赠自动驾驶学习资料和量产经验&#xff1a;链接 基于摄像头传感器输出车道线方程&#xff1a; (1) 其中&#xff1a;、、、为车道线方程系数。 1 车道宽度计算 当车辆直行时&#xff0c;车道宽度计算可根据如下公式计算&#xff1a; …

【BlossomRPC】手把手教你写一个RPC协议

文章目录 新的开始什么是RPC?设计一个RPC需要些什么&#xff1f; 新的开始 经常会遇到一些项目&#xff0c;看着看着就发现看不懂文档了&#xff0c;也就是会出现一些跳过讲解的文章&#xff0c;使得自己很难了解某种中间件的开发全貌&#xff0c;所以想着自己先设计一个比较…

编程实现黄金分割法、平分法和不精确一维搜索等最优化算法

解&#xff1a; 1、黄金分割法 思想&#xff1a; 黄金分割法是通过不断缩短搜索区间的长度来寻求一维函数的极小点&#xff0c;这种方法的基本原理是&#xff1a;在搜索区间[a,b]内按如下规则对称地取两点a1和a2 a1a0.382(b-a); a2a0.618(b-a); 黄金分割法的搜索过程是&#x…

代码随想录算法训练营第二十五天| 回溯算法理论基础、LeetCode77.组合

一、216.组合总和III 题目链接/文章讲解/视频讲解&#xff1a; https://programmercarl.com/0216.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8CIII.html 状态&#xff1a;已解决 1.思路 做过77题&#xff08;上篇博客&#xff09;后&#xff0c;这道题也就不难了&#xff0c;无非是多…

数字化转型导师坚鹏:BLM新质生产力发展方法论

BLM新质生产力发展方法论 ——新质生产力发展之知行果合一 课程背景&#xff1a; 很多学员存在以下问题&#xff1a; 不知道如何理解新质生产力&#xff1f; 不清楚如何发展新质生产力&#xff1f; 不知道新质生产力发展案例&#xff1f; 课程特色&#xff1a; 原创…

echarts统计图占满整个容器

原先的统计图表没有占满容器&#xff0c;感觉整个被压缩了 网上查阅相关资料后发现需要设置grid一个配置项&#xff08;有些数值需要根据实际情况进行调整&#xff09; grid:{top:"0px",left:"0px",right:"0px",bottom:"0px"} 对于gr…

用户登录.java

分析&#xff1a; 1&#xff0c;用String来定义两个变量&#xff0c;记录正确的用户名和密码----->直接赋值得来 2&#xff0c;键盘录入用户名和密码------>new开辟空间得来&#xff0c;存的是地址值 他们直接用比较大小,必定不相同&#xff0c;需要用到String里面的方…

沙箱安全机制

Java安全模型的核心就是Java沙箱(sandbox)&#xff0c; 什么是沙箱&#xff1f; 沙箱是一个 限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM) 特定的运行范围中&#xff0c;并且严格限制代码对本地系统资源访问&#xff0c;通过这样的措施来保证 对代码的 有效隔…