import json
import logging
from pyppeteer import launch
from pyppeteer.errors import TimeoutError
import asyncio
from os import makedirs
from os.path import exists
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s: %(message)s")
INDEX_URL = "https://dynamic2.scrape.center/page/{page}"
TIMEOUT = 10
TOTAL_PAGE = 10
WINDOW_WIDTH, WINDOW_HEIGHT = 1366, 768
HEADLESS = False
RESULTS_DIR = "results"
exists(RESULTS_DIR) or makedirs(RESULTS_DIR)
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):
"""
通用爬取的方法
:param url: 需要爬取的链接
:param selector: 等待渲染出的节点对应的css选择器
:return: 只是打开当前链接的页面进行加载,没有解析行为
"""
logging.info("scraping %s" % url)
try:
# 运行时页面会先访问这个url,等待节点家族,最多等待10秒,如果加载出来接着往下执行,否则抛出异常,输出到日志
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):
"""
爬取列表页
:param page: 当前的页码
:return:
"""
url = INDEX_URL.format(page=page)
await scrape_page(url, ".item .name")
async def parse_index():
"""
解析列表页,提取详情页的url
querySelectorAll返回节点列表
querySelectorAllEval(selector,pageFunction) # 参数1:css选择器 参数2:要执行的js代码
这里 JavaScript 对应的 pageFunction 输入参数就是 nodes,输出结果是调用了 map 方法得到每个 node,
然后调用 node 的 href 属性即可。这样返回结果就是当前列表页的所有电影的详情页 URL 组成的列表了
:return:返回详情页url组成的列表
"""
return await tab.querySelectorAllEval(".item .name", "nodes => nodes.map(node => node.href)")
async def scrape_detail(url):
"""
详情页爬取
:param url: 当前详情页的URL
:return:
"""
await scrape_page(url, "h2")
async def parse_detail():
"""
解析详情页面
:return: 返回当前页面的字典数据
"""
url = tab.url # 获取当前选项卡的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,
}
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)
detail_data = await parse_detail()
await save_data(detail_data)
logging.info("data %s" % detail_data)
finally:
await browser.close()
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(main())
# 第一步:初始化一个浏览器的方法
# 第二步:创建一个通用爬取页面的方法
# 第三步:创建一个爬取列表页的方法,调用第二步
# 第四步:创建一个单独获取列表页中详情页url的解析方法
# 第五步:创建一个爬取详情页的方法,调用第二步
# 第六步:创建一个解析详情页数据的方法
# 第七步:创建一个保存json数据的方法
# 第八步:整合所有方法
# 对比aiohttp:因为pyppeteer是依托于浏览器加载ajax的,所以无法在tasks列表中直接请求api
# 所以即使使用了asyncio,也是逐个请求返回数据的
# 如果是aiohttp直接请求api,可以用下面的方法,创建tasks任务列表,使用gather启动任务
# session = aiohttp.ClientSession()
# scrape_index_tasks = [asyncio.ensure_future(scrape_index(page=page)) for page in range(1, PAGE_NUMBER + 1)]
# results = await asyncio.gather(*scrape_index_tasks) # 列表页返回json数据
《封号码罗》python爬虫之pyppeteer异步爬虫牛刀小试(九)
最新推荐文章于 2024-07-19 21:30:00 发布