目录

生成器
生成器是一种特殊的迭代器,它可以在需要时才产生值,而不是一次性将所有值加载到内存中。
传统列表 vs 生成器对比
def memory_comparison():
import sys
# 列表推导式 - 占用大量内存
list_comp = [x**2 for x in range(1000000)]
print(f"列表占用内存: {sys.getsizeof(list_comp)} bytes")
# 生成器表达式 - 内存友好
gen_exp = (x**2 for x in range(1000000))
print(f"生成器占用内存: {sys.getsizeof(gen_exp)} bytes")
return list_comp, gen_exp
# 运行对比
list_data, gen_data = memory_comparison()
列表推导式
代码中列表推导式一次性生成了100万个元素的平方,并将所有结果存储在内存中sys.getsizeof函数显示列表对象占用的内存大小,通常会非常大因为它包含了所有元素的实际数据。
生成器表达式
生成器表达式是惰性求值的,它不会一次性生成所有元素,而是按需计算每个元素sys.getsizeof显示的内存占用非常小,仅为生成器对象本身的开销,不包含元素数据。
yield机制深度解析
yield关键字是生成器的核心,它不仅可以产生值,还能接收值,实现双向通信。
def advanced_generator():
"""演示yield的双向通信能力"""
print("生成器启动")
value = None
while True:
# yield既可以发送值,也可以接收值
received = yield value
if received is None:
value = "默认值"
elif received == "stop":
break
else:
value = f"处理后的值: {received}"
print("生成器结束")
def test_bidirectional_generator():
gen = advanced_generator()
# 启动生成器,执行到第一个yield,输出初始值None
print(next(gen)) # 输出: None
# 发送值给生成器,接收处理后的返回值
print(gen.send("hello")) # 输出: 处理后的值: hello
print(gen.send("world")) # 输出: 处理后的值: world
# 发送停止信号,结束生成器
try:
gen.send("stop")
except StopIteration:
print("生成器已停止")
test_bidirectional_generator()
调用advanced_generator()返回一个生成器对象,但函数体并未执行。使用next(gen)启动生成器,执行到第一个yield表达式暂停,返回yield后面的值(此处为None)。生成器此时处于暂停状态,等待外部发送值。
yield能接收外部通过send()方法传入的值。在received = yield value中,yield value将value发送给调用者,同时暂停生成器。当调用者使用gen.send(some_value)时,some_value被赋值给received,生成器继续执行。
生成器的高级用法
def fibonacci_generator(n):
"""斐波那契数列生成器"""
a, b = 0, 1
count = 0
while count < n:
yield a
a, b = b, a + b
count += 1
def pipeline_example():
"""生成器管道处理示例"""
def read_data():
"""模拟数据源"""
for i in range(100):
yield f"data_{i}"
def filter_data(data_stream):
"""过滤数据"""
for item in data_stream:
if int(item.split('_')[1]) % 2 == 0:
yield item
def transform_data(data_stream):
"""转换数据"""
for item in data_stream:
yield item.upper()
def process_data(data_stream):
"""处理数据"""
for item in data_stream:
yield f"processed_{item}"
# 构建处理管道
pipeline = process_data(
transform_data(
filter_data(
read_data()
)
)
)
# 处理前10个结果
for i, result in enumerate(pipeline):
if i >= 10:
break
print(result)
pipeline_example()
生成器通过yield实现惰性求值,适合处理大数据和流式数据
而模拟的数据处理管道,包含数据读取、过滤、转换和最终处理四个阶段。每个阶段都是一个生成器函数,接收上游生成器作为输入,逐条处理数据并 yield 结果。通过嵌套调用,实现了数据的流式处理,避免了中间数据的全部存储,极大节省内存。最终打印处理后的前10条数据,展示了管道的效果。
协程的演进:从yield到async/await
基于yield的协程
协程(Coroutine)是一种比线程更轻量级的“用户态”并发技术。它允许程序在执行过程中主动暂停(挂起)和恢复,从而实现多任务的切换和并发执行。与线程不同协程的切换由程序控制,不依赖操作系统调度,因此开销更小效率更高。在async/await语法出现之前,Python使用yield来实现协程。
def old_style_coroutine():
"""传统yield协程示例"""
print("协程启动")
while True:
try:
value = yield
print(f"接收到值: {value}")
# 模拟异步操作
import time
time.sleep(0.1)
print(f"处理完成: {value}")
except GeneratorExit:
print("协程关闭")
break
def coroutine_scheduler():
"""简单的协程调度器"""
coroutines = []
# 创建多个协程
for i in range(3):
coro = old_style_coroutine()
next(coro) # 启动协程
coroutines.append(coro)
# 轮询执行协程
for i in range(5):
for j, coro in enumerate(coroutines):
try:
coro.send(f"task_{j}_step_{i}")
except StopIteration:
pass
# 关闭所有协程
for coro in coroutines:
coro.close()
coroutine_scheduler()
yield协程实现:利用yield表达式实现函数的暂停与恢复。通过value = yield接收外部传入的数据,实现协程与调用者的双向通信。使用GeneratorExit异常捕获协程关闭信号,优雅释放资源。
协程调度器设计:创建多个协程实例,并通过next()启动,执行到第一个yield暂停。通过循环轮询,依次向每个协程发送任务数据,实现简单的“协作式多任务”调度。调度器负责驱动协程执行,模拟异步任务的并发处理。最后调用close()关闭所有协程,确保协程资源正确释放。
模拟异步操作:使用time.sleep(0.1)模拟耗时操作,展示协程在处理任务时的暂停与恢复。传统yield协程本身是同步阻塞的,真正的异步需要结合事件循环或异步库。
现代async/await语法
import asyncio
import time
async def modern_coroutine(name, delay):
"""现代async/await协程"""
print(f"协程 {name} 开始执行")
# 异步等待
await asyncio.sleep(delay)
print(f"协程 {name} 执行完成,耗时 {delay} 秒")
return f"结果来自 {name}"
async def coroutine_orchestration():
"""协程编排示例"""
# 并发执行多个协程
tasks = [
modern_coroutine("任务1", 1),
modern_coroutine("任务2", 2),
modern_coroutine("任务3", 0.5)
]
start_time = time.time()
# 等待所有任务完成
results = await asyncio.gather(*tasks)
end_time = time.time()
print(f"所有任务完成,总耗时: {end_time - start_time:.2f} 秒")
print("结果:", results)
# 运行协程
asyncio.run(coroutine_orchestration())
现代async/await协程特点:使用async def定义异步函数,返回协程对象。通过await挂起协程,等待异步操作完成(如asyncio.sleep),期间事件循环调度其他任务执行。利用asyncio.gather实现多个协程的并发执行,极大提升I/O密集型任务效率。事件循环(asyncio.run)负责调度和管理协程生命周期。协程函数可以直接返回结果,方便后续处理。
协程的错误处理和超时控制
import asyncio
async def robust_coroutine_example():
"""健壮的协程示例,包含错误处理和超时控制"""
async def risky_operation(should_fail=False):
await asyncio.sleep(1)
if should_fail:
raise ValueError("模拟错误")
return "操作成功"
async def timeout_operation():
await asyncio.sleep(5) # 模拟长时间操作
return "超时操作完成"
# 错误处理
try:
result = await risky_operation(should_fail=True)
print(result)
except ValueError as e:
print(f"捕获错误: {e}")
# 超时控制
try:
result = await asyncio.wait_for(timeout_operation(), timeout=2.0)
print(result)
except asyncio.TimeoutError:
print("操作超时")
# 任务取消
task = asyncio.create_task(timeout_operation())
await asyncio.sleep(1)
task.cancel()
try:
await task
except asyncio.CancelledError:
print("任务被取消")
asyncio.run(robust_coroutine_example())
错误处理:使用try-except捕获协程中抛出的异常,保证程序不会因未处理异常崩溃。示例中risky_operation模拟可能失败的异步操作,抛出ValueError,调用处捕获并处理。
超时控制:利用asyncio.wait_for为协程设置超时时间超过指定时间未完成则抛出asyncio.TimeoutError。通过捕获超时异常,可以优雅地处理长时间未响应的异步任务。
任务取消:使用asyncio.create_task创建独立任务,允许在运行中主动取消。调用task.cancel()发送取消请求,协程内部抛出asyncio.CancelledError异常。通过捕获CancelledError,可以执行清理操作或记录日志。
异步生成器与异步迭代器
异步生成器
import asyncio
import aiofiles
async def async_file_reader(filename):
"""异步文件读取生成器"""
try:
async with aiofiles.open(filename, 'r') as file:
async for line in file:
# 模拟处理时间
await asyncio.sleep(0.01)
yield line.strip()
except FileNotFoundError:
print(f"文件 {filename} 不存在")
async def async_number_generator(start, end, delay=0.1):
"""异步数字生成器"""
for i in range(start, end):
await asyncio.sleep(delay)
yield i
async def test_async_generators():
"""测试异步生成器"""
# 测试异步数字生成器
print("异步数字生成器:")
async for num in async_number_generator(1, 6, 0.2):
print(f"生成数字: {num}")
# 创建测试文件
with open("test.txt", "w") as f:
f.write("line 1\nline 2\nline 3\n")
# 测试异步文件读取
print("\n异步文件读取:")
async for line in async_file_reader("test.txt"):
print(f"读取行: {line}")
asyncio.run(test_async_generators())
异步生成器基础:使用async def定义异步生成器函数,结合yield关键字实现异步迭代。通过async for语法异步遍历生成器,支持非阻塞式数据消费。
异步数字生成器:async_number_generator通过异步延迟模拟异步数据产生过程。适合模拟实时数据流或需要异步等待的数字序列生成。
异步文件读取生成器:利用aiofiles库实现异步文件操作,避免阻塞事件循环。通过async for逐行读取文件内容,结合await asyncio.sleep模拟处理延迟。具备异常处理机制,捕获文件不存在等错误,增强健壮性。
异步迭代器实现
import asyncio
class AsyncRange:
"""异步范围迭代器"""
def __init__(self, start, end, delay=0.1):
self.start = start
self.end = end
self.delay = delay
self.current = start
def __aiter__(self):
return self
async def __anext__(self):
if self.current >= self.end:
raise StopAsyncIteration
await asyncio.sleep(self.delay)
value = self.current
self.current += 1
return value
class AsyncDataProcessor:
"""异步数据处理器"""
def __init__(self, data_source):
self.data_source = data_source
def __aiter__(self):
return self
async def __anext__(self):
try:
data = await self.data_source.__anext__()
# 模拟数据处理
await asyncio.sleep(0.05)
return f"processed_{data}"
except StopAsyncIteration:
raise
async def test_async_iterators():
# 测试AsyncRange
print("AsyncRange迭代器:")
async for value in AsyncRange(1, 5, 0.2):
print(f"值: {value}")
# 测试AsyncDataProcessor
print("\nAsyncDataProcessor:")
data_source = AsyncRange(10, 15, 0.1)
processor = AsyncDataProcessor(data_source)
async for processed_data in processor:
print(f"处理结果: {processed_data}")
asyncio.run(test_async_iterators())
异步迭代器协议:自定义异步迭代器需实现__aiter__()返回自身或异步迭代器对象。实现__anext__()异步方法,返回下一个元素或抛出StopAsyncIteration结束迭代。通过async for语法自动调用__aiter__()和__anext__(),实现异步遍历。
AsyncRange类:模拟同步range的异步版本,支持异步延迟控制。每次调用__anext__()时,异步等待指定delay后返回当前值。迭代结束时抛出StopAsyncIteration,通知async for循环终止。
AsyncDataProcessor类:接收一个异步迭代器作为数据源,内部通过调用数据源的__anext__()获取数据。对获取的数据进行异步处理(模拟延迟),返回处理后的结果。通过捕获StopAsyncIteration实现迭代终止的传递。
异步组合与链式处理:AsyncDataProcessor将异步迭代器作为输入,实现了异步数据流的链式处理。这种设计模式适合构建复杂的异步数据处理管道,支持灵活组合和扩展。
实战案例:异步爬虫框架设计
核心code
import asyncio
import aiohttp
import time
from urllib.parse import urljoin, urlparse
from collections import deque
import logging
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class AsyncSpider:
"""异步爬虫框架核心类"""
def __init__(self, max_concurrent=10, delay=1.0):
self.max_concurrent = max_concurrent
self.delay = delay
self.session = None
self.semaphore = asyncio.Semaphore(max_concurrent)
self.visited_urls = set()
self.failed_urls = set()
async def __aenter__(self):
"""异步上下文管理器入口"""
connector = aiohttp.TCPConnector(limit=100, limit_per_host=30)
timeout = aiohttp.ClientTimeout(total=30)
self.session = aiohttp.ClientSession(
connector=connector,
timeout=timeout,
headers={'User-Agent': 'AsyncSpider/1.0'}
)
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
"""异步上下文管理器出口"""
if self.session:
await self.session.close()
async def fetch(self, url):
"""异步获取单个URL"""
async with self.semaphore:
try:
await asyncio.sleep(self.delay)
async with self.session.get(url) as response:
if response.status == 200:
content = await response.text()
self.visited_urls.add(url)
logger.info(f"成功获取: {url}")
return {
'url': url,
'status': response.status,
'content': content,
'headers': dict(response.headers)
}
else:
logger.warning(f"HTTP错误 {response.status}: {url}")
self.failed_urls.add(url)
return None
except Exception as e:
logger.error(f"获取失败 {url}: {str(e)}")
self.failed_urls.add(url)
return None
async def fetch_multiple(self, urls):
"""异步获取多个URL"""
tasks = [self.fetch(url) for url in urls]
results = await asyncio.gather(*tasks, return_exceptions=True)
# 过滤掉异常和None结果
valid_results = [r for r in results if r is not None and not isinstance(r, Exception)]
return valid_results
class AsyncCrawler:
"""异步爬虫调度器"""
def __init__(self, spider: AsyncSpider):
self.spider = spider
self.url_queue = deque()
self.results = []
def add_urls(self, urls):
"""添加URL到队列"""
for url in urls:
if url not in self.spider.visited_urls:
self.url_queue.append(url)
async def crawl_batch(self, batch_size=50):
"""批量爬取URL"""
batch_urls = []
for _ in range(min(batch_size, len(self.url_queue))):
if self.url_queue:
batch_urls.append(self.url_queue.popleft())
if batch_urls:
results = await self.spider.fetch_multiple(batch_urls)
self.results.extend(results)
return results
return []
async def crawl_all(self, batch_size=50):
"""爬取所有队列中的URL"""
while self.url_queue:
batch_results = await self.crawl_batch(batch_size)
logger.info(f"完成批次,获取 {len(batch_results)} 个结果")
# 可以在这里添加结果处理逻辑
yield batch_results
class DataProcessor:
"""数据处理器"""
@staticmethod
async def extract_links(html_content, base_url):
"""从HTML中提取链接"""
# 简化的链接提取(实际项目中建议使用BeautifulSoup或lxml)
import re
link_pattern = r'href=[\'"]([^\'"]+)[\'"]'
links = re.findall(link_pattern, html_content)
absolute_links = []
for link in links:
absolute_url = urljoin(base_url, link)
if urlparse(absolute_url).netloc: # 只保留完整URL
absolute_links.append(absolute_url)
return absolute_links
@staticmethod
async def save_results(results, filename):
"""异步保存结果到文件"""
import aiofiles
import json
async with aiofiles.open(filename, 'w', encoding='utf-8') as f:
for result in results:
await f.write(json.dumps(result, ensure_ascii=False) + '\n')
async def spider_example():
"""爬虫使用示例"""
# 测试URL列表
test_urls = [
'https://httpbin.org/delay/1',
'https://httpbin.org/json',
'https://httpbin.org/html',
'https://httpbin.org/xml',
'https://httpbin.org/status/200'
]
start_time = time.time()
async with AsyncSpider(max_concurrent=5, delay=0.5) as spider:
crawler = AsyncCrawler(spider)
crawler.add_urls(test_urls)
all_results = []
# 使用异步生成器处理结果
async for batch_results in crawler.crawl_all(batch_size=3):
all_results.extend(batch_results)
# 实时处理每批结果
for result in batch_results:
print(f"处理结果: {result['url']} - 状态: {result['status']}")
# 保存结果
processor = DataProcessor()
await processor.save_results(all_results, 'spider_results.json')
end_time = time.time()
print(f"\n爬取完成!")
print(f"总耗时: {end_time - start_time:.2f} 秒")
print(f"成功: {len(spider.visited_urls)} 个URL")
print(f"失败: {len(spider.failed_urls)} 个URL")
print(f"总结果: {len(all_results)} 条")
# 运行爬虫示例
asyncio.run(spider_example())
# 高级特性:递归爬取和去重
class RecursiveCrawler(AsyncCrawler):
"""递归爬虫,支持深度爬取"""
def __init__(self, spider: AsyncSpider, max_depth=2, domain_filter=None):
super().__init__(spider)
self.max_depth = max_depth
self.domain_filter = domain_filter
self.url_depth = {}
def add_url_with_depth(self, url, depth=0):
"""添加URL并记录深度"""
if (url not in self.spider.visited_urls and
url not in self.url_depth and
depth <= self.max_depth):
if self.domain_filter:
if urlparse(url).netloc not in self.domain_filter:
return
self.url_queue.append(url)
self.url_depth[url] = depth
async def recursive_crawl(self):
"""递归爬取"""
while self.url_queue:
current_url = self.url_queue.popleft()
current_depth = self.url_depth.get(current_url, 0)
# 爬取当前URL
result = await self.spider.fetch(current_url)
if result and current_depth < self.max_depth:
# 提取新链接
processor = DataProcessor()
new_links = await processor.extract_links(
result['content'],
current_url
)
# 添加新链接到队列
for link in new_links[:5]: # 限制每页最多5个链接
self.add_url_with_depth(link, current_depth + 1)
logger.info(f"从 {current_url} 提取到 {len(new_links)} 个链接")
yield result
async def recursive_spider_example():
"""递归爬虫示例"""
async with AsyncSpider(max_concurrent=3, delay=1.0) as spider:
crawler = RecursiveCrawler(
spider,
max_depth=2,
domain_filter=['httpbin.org']
)
# 添加起始URL
crawler.add_url_with_depth('https://httpbin.org/links/5')
results = []
async for result in crawler.recursive_crawl():
if result:
results.append(result)
print(f"爬取: {result['url']}")
print(f"递归爬取完成,共获取 {len(results)} 个页面")
# 运行递归爬虫
asyncio.run(recursive_spider_example())
AsyncSpider — 异步HTTP请求核心: 利用aiohttp.ClientSession实现高效异步HTTP请求。
通过asyncio.Semaphore限制最大并发数,防止过载目标服务器。支持请求延迟控制,避免请求过于频繁导致封禁。具备完善的异常捕获和状态码判断,自动记录成功与失败的URL。提供fetch单个请求和fetch_multiple批量请求接口,支持并发任务调度。
AsyncCrawler — 异步爬虫调度器:维护URL队列(deque),支持动态添加待爬取URL。
通过crawl_batch实现批量异步爬取,结合fetch_multiple高效获取数据。采用异步生成器crawl_all逐批返回爬取结果,方便实时处理和流式消费。设计灵活,易于扩展为多层爬取或深度爬取。
DataProcessor — 数据处理与持久化:提供简易的HTML链接提取方法,利用正则表达式解析href属性,结合urljoin生成绝对URL。采用aiofiles实现异步文件写入,保证I/O操作非阻塞。支持JSON格式逐条写入,方便后续数据分析和复用。
异步上下文管理与资源释放:AsyncSpider实现异步上下文管理器接口__aenter__和__aexit__,确保aiohttp.ClientSession正确创建与关闭,避免资源泄漏。
递归爬虫优势:自动发现新链接,实现多层级网页数据采集。深度控制避免爬取过深导致资源耗尽。域名过滤防止爬虫跑出目标站点。异步生成器设计支持流式处理,提升效率。
259

被折叠的 条评论
为什么被折叠?



