Pyppeteer爬取实战

import logging
import asyncio
from pyppeteer import launch
from pyppeteer.errors import TimeoutError
import json
from os import makedirs
from os.path import exists

# 建立存储目录
RESULTS_DIR = 'results'
exists(RESULTS_DIR) or makedirs(RESULTS_DIR)

# 建立完整URL页面链接
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 = 1920, 1080
# 无头模式,不再弹出浏览器窗口
HEADLESS = True
# 前者代表Pyppeteer所用的浏览器对象,后者代表新建的页面选项卡
browser, tab = None, None

# 设置browser, tab全局变量,能够方便其它方法调用
# headless设置浏览器为无头模式,arg设置窗口宽高和是否隐藏窗口
async def init():
    global browser, tab
    browser = await launch(headless=HEADLESS,
                           arg=['--disable-infobars', f'--window-size={WINDOW_WIDTH},{WINDOW_HEIGHT}'])
    tab = await browser.newPage()
    await tab.setViewport({'width': WINDOW_WIDTH, 'height': WINDOW_HEIGHT})

# 通过爬取方法,对任意URL进行爬取、状态监听以及异常处理
# url: 要爬取的页面url,goto方法调用此URL即可访问对应页面
# selector: 等待渲染出来的节点对应的CSS选择器,
# 调用 waitForSelector 方法传入selector通过options指定 最长等待时间10S
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)

# 爬取列表页的方法,通过scrape_page方法并传入url和selector参数完成对列表页的爬取
# page 代表要爬取的页面的页码
# '.item .name' 选择器,是列表页中电影的名称,意味着电影名称加载出来就代表页面加载成功了
async def scrape_index(page):
    url = INDEX_URL.format(page=page)
    await scrape_page(url, '.item .name')

# 解析列表页,从中提取详情页的URL
# querySelectorAllEval方法('selector', 'pageFunction') 作用找出和选择器匹配的节点,根据pageFunction定义的逻辑从这些节点中抽取出对应的结果返回
# selector 代表选择器, 目前传入的是电影的名称
# pageFunction 代表要执行的JavaScript方法。方法nodes,其返回值是调用map方法得到node,然后再调用node的href属性得到超链接
# querySelectorAllEval方法的返回结果就是当前列表页中所有电影的详情页URL组成的列表
async def parse_index():
    return await tab.querySelectorAllEval('.item .name', 'nodes => nodes.map(node => node.href)')

# 爬取详情页并提取对应的信息
# scrape_page方法(详情页URL, '选择器'),其中h2代表电影名称对应这个节点
async def scrape_detail(url):
    await scrape_page(url, 'h2')


# 解析详情页的方法来提取想要的信息,提取详情页的URL和电影名称、类别、封面、分数和简介等内容
# URL:  链接,直接调用tab对象的url属性即可获取当前页面的URL
# name: 名称,调用querySelectorEval方法(电影名称对应的节点, '调用node的innerText属性提取文本信息即电影名称')
# categories: 类别,调用querySelectorAllEval方法(CSS选择器多个类别节点, '与提取详情页URL时类似使用node方法-使用map方法提取node的innerText得到所有的电影类别')
# cover: 封面,使用CSS选择器.cover直接获取对应的节点
# score: 分数,使用CSS选择为.score直接获取对应的节点
# drama: 简介,对应的CSS选择器为.drama p,直接获取简介对应的节点
# 最后把所有结果汇总成一个字典返回。
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
    }


# 保存数据为JSON文件
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)

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)
                # # 解析详情页的方法来提取想要的信息,提取详情页的URL和电影名称、类别、封面、分数和简介等内容,返回结果字典模式
                detail_data = await parse_detail()
                logging.info('deta %s', detail_data)
                #保存数据
                await save_data(detail_data)
    finally:
        await browser.close()

if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值