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())
Pyppeteer爬取实战
最新推荐文章于 2024-04-03 10:35:27 发布