面试题答案
一键面试1. 架构设计
- 异步抓取网页:使用
asyncio
库进行异步I/O操作,创建多个任务并发抓取网页。例如,利用aiohttp
库发送HTTP请求。 - 解析网页数据:在获取网页内容后,使用解析库(如
BeautifulSoup
或lxml
)解析网页生成数据。 - 数据处理与存储:将解析后的数据通过生成器和迭代器进行处理,以实现内存高效,并最终存储到合适的持久化存储中。
2. 数据流转过程
- 抓取阶段:
- 创建
asyncio
事件循环,使用aiohttp
创建多个异步任务去请求网页。每个请求任务完成后返回网页内容。
import aiohttp import asyncio async def fetch(session, url): async with session.get(url) as response: return await response.text() async def fetch_all(urls): async with aiohttp.ClientSession() as session: tasks = [fetch(session, url) for url in urls] return await asyncio.gather(*tasks)
- 创建
- 解析阶段:
- 接收到网页内容后,将其传递给解析函数。解析函数使用生成器逐块生成解析后的数据。
from bs4 import BeautifulSoup def parse(html): soup = BeautifulSoup(html, 'html.parser') # 假设这里解析出的数据是一些段落文本 for p in soup.find_all('p'): yield p.get_text()
- 处理与存储阶段:
- 利用迭代器逐个获取解析生成器生成的数据,并进行处理和存储。例如,将数据存储到文件或数据库中。
def save_data(data_iter): with open('data.txt', 'w') as f: for data in data_iter: f.write(data + '\n')
3. 生成器和迭代器的作用
- 生成器:
- 在解析网页时,生成器按需求生成解析后的数据,而不是一次性将所有解析数据存储在内存中。这样可以显著减少内存占用,特别是在处理大量数据时。例如,
parse
函数中的yield
语句将其变为生成器,每次调用next()
(或在循环中隐式调用)时才生成一个数据块。
- 在解析网页时,生成器按需求生成解析后的数据,而不是一次性将所有解析数据存储在内存中。这样可以显著减少内存占用,特别是在处理大量数据时。例如,
- 迭代器:
- 在处理和存储数据阶段,迭代器用于逐个获取生成器生成的数据。通过迭代器,我们可以控制数据的消费速度,确保内存中不会同时存在过多未处理的数据。例如,
save_data
函数中的for data in data_iter
,data_iter
就是一个迭代器,它逐个获取解析后的数据并写入文件。
- 在处理和存储数据阶段,迭代器用于逐个获取生成器生成的数据。通过迭代器,我们可以控制数据的消费速度,确保内存中不会同时存在过多未处理的数据。例如,
4. 避免内存泄漏问题
- 及时释放资源:
- 在异步请求完成后,及时关闭
aiohttp
的会话,释放连接资源。如在fetch_all
函数中,使用async with aiohttp.ClientSession() as session
,会话在代码块结束时会自动关闭。
- 在异步请求完成后,及时关闭
- 限制内存占用:
- 由于使用生成器和迭代器按需生成和处理数据,内存中不会长时间保存大量未处理的数据。如果处理过程中有中间数据需要缓存,要限制缓存的大小,例如,使用固定大小的队列存储临时数据,当队列满时,及时处理或丢弃旧数据。
- 异常处理:
- 在整个流程中,妥善处理异常。特别是在异步操作和数据处理过程中,如果发生异常,确保所有资源(如网络连接、文件句柄等)被正确关闭和释放,防止资源泄漏导致内存问题。例如,在
fetch
函数中,async with
语句可以确保在请求完成或出现异常时,响应对象被正确关闭。
- 在整个流程中,妥善处理异常。特别是在异步操作和数据处理过程中,如果发生异常,确保所有资源(如网络连接、文件句柄等)被正确关闭和释放,防止资源泄漏导致内存问题。例如,在