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

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

Pyppeteer爬取实战
爬取目标
电影网站https://spa2.scrape.center/

任 务

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

爬取列表页
示例代码如下:

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,是列表页中电影的名称,意味着电影名称加载出来就代表页面加载成功了,如图所示:

image.png

再定义一个解析列表页的方法,用来提取每部电影的详情页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>
...
...

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

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

数据存储
将爬取数据保存为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()
相关文章
|
2月前
|
JavaScript 前端开发 Go
CSS 与 JS 对 DOM 解析和渲染的影响
【10月更文挑战第16天】CSS 和 JS 会在一定程度上影响 DOM 解析和渲染,了解它们之间的相互作用以及采取适当的优化措施是非常重要的。通过合理的布局和加载策略,可以提高网页的性能和用户体验,确保页面能够快速、流畅地呈现给用户。在实际开发中,要根据具体情况进行权衡和调整,以达到最佳的效果。
|
1月前
|
JavaScript 前端开发 程序员
前端原生Js批量修改页面元素属性的2个方法
原生 Js 的 getElementsByClassName 和 querySelectorAll 都能获取批量的页面元素,但是它们之间有些细微的差别,稍不注意,就很容易弄错!
|
2月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包:原理与实战
【10月更文挑战第12天】深入理解JavaScript中的闭包:原理与实战
|
20天前
Next.js 实战 (二):搭建 Layouts 基础排版布局
本文介绍了作者在Next.js v15.x版本发布后,对一个旧项目的重构过程。文章详细说明了项目开发规范配置、UI组件库选择(最终选择了Ant-Design)、以及使用Ant Design的Layout组件实现中后台布局的方法。文末展示了布局的初步效果,并提供了GitHub仓库链接供读者参考学习。
Next.js 实战 (二):搭建 Layouts 基础排版布局
|
14天前
|
存储 网络架构
Next.js 实战 (四):i18n 国际化的最优方案实践
这篇文章介绍了Next.js国际化方案,作者对比了网上常见的方案并提出了自己的需求:不破坏应用程序的目录结构和路由。文章推荐使用next-intl库来实现国际化,并提供了详细的安装步骤和代码示例。作者实现了国际化切换时不改变路由,并把当前语言的key存储到浏览器cookie中,使得刷新浏览器后语言不会失效。最后,文章总结了这种国际化方案的优势,并提供Github仓库链接供读者参考。
|
15天前
Next.js 实战 (三):优雅的实现暗黑主题模式
这篇文章介绍了在Next.js中实现暗黑模式的具体步骤。首先,需要安装next-themes库。然后,在/components/ThemeProvider/index.tsx文件中新增ThemeProvider组件,并在/app/layout.tsx文件中注入该组件。如果想要加入过渡动画,可以修改代码实现主题切换时的动画效果。最后,需要在需要的位置引入ThemeModeButton组件,实现暗黑模式的切换。
|
1月前
|
设计模式 前端开发 JavaScript
JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式
本文深入探讨了JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式,结合电商网站案例,展示了设计模式如何提升代码的可维护性、扩展性和可读性,强调了其在前端开发中的重要性。
29 2
|
1月前
|
数据采集 JavaScript 搜索推荐
服务器端渲染(SSR)(Nuxt+Next.js)
服务器端渲染(SSR)技术在服务器上生成页面HTML,提升首屏加载速度和SEO效果。Nuxt.js和Next.js分别是基于Vue.js和React.js的流行SSR框架。Nuxt.js提供自动化路由管理、页面级数据获取和布局系统,支持SSR和静态站点生成。Next.js支持SSR、静态生成和文件系统路由,通过`getServerSideProps`和`getStaticProps`实现数据获取。SSR的优点包括首屏加载快、SEO友好和适合复杂页面,但也会增加服务器压力、开发限制和调试难度。选择框架时,可根据项目需求和技术栈决定使用Nuxt.js或Next.js。
|
2月前
|
JavaScript 前端开发 开发者
探索JavaScript原型链:深入理解与实战应用
【10月更文挑战第21天】探索JavaScript原型链:深入理解与实战应用
37 1
|
2月前
|
SQL 前端开发 JavaScript
Nest.js 实战 (十五):前后端分离项目部署的最佳实践
这篇文章介绍了如何使用现代前端框架Vue3和后端Node.js框架Nest.js实现的前后端分离架构的应用,并将其部署到生产环境。文章涵盖了准备阶段,包括云服务器的设置、1Panel面板的安装、数据库的安装、域名的实名认证和备案、SSL证书的申请。在部署Node服务环节,包括了Node.js环境的创建、数据库的配置、用户名和密码的设置、网站信息的填写、静态网站的部署、反向代理的配置以及可能遇到的常见问题。最后,作者总结了部署经验,并希望对读者有所帮助。
210 11