目标
- 掌握异步请求库 aiohttp,实现并发网络请求。
- 使用 beautifulsoup4 解析网页数据。
- 结合 aiofiles 实现异步文件保存,提升效率。
- 完成一个异步网页爬虫项目,爬取多页名言并保存到文件。
知识点讲解
1. 异步请求:aiohttp
aiohttp 是 Python 的异步 HTTP 客户端/服务器框架,适合高并发网络请求。
- 核心方法: aiohttp.ClientSession():创建会话,管理请求。 session.get(url):异步发送 GET 请求。 response.text():异步获取响应内容。
- 异步优势: 并发处理多个请求,减少等待时间。 适合爬取多页或多网站。
- 注意事项: 使用 async/await 语法。 确保正确关闭会话以释放资源。
2. beautifulsoup4:解析网页
与同步版本相同,beautifulsoup4 用于解析 HTML。
- 解析过程仍是同步的,但结合异步请求可整体提速。
- 核心操作:find_all() 提取目标元素,text 获取内容。
3. 异步文件保存:aiofiles
aiofiles 支持异步文件操作,避免阻塞主线程。
- 核心方法: aiofiles.open(file, mode):异步打开文件。 异步写入:使用 await file.write()。
- 优势: 与异步请求配合,保持全程非阻塞。 适合高并发场景下的数据保存。
4. 异步爬虫优化
- 并发控制:使用 asyncio.gather 并行处理多个请求。
- 异常处理:捕获异步请求和解析错误,确保程序健壮。
- 礼貌爬虫:限制并发数量(如最大 5 个),避免压垮服务器。
代码示例
项目:异步爬取名言网站多页数据
我们将爬取
http://quotes.toscrape.com/ 的前 3 页名言和作者信息,异步保存到 CSV 文件。
python
import aiohttp
import aiofiles
import asyncio
from bs4 import BeautifulSoup
import csv
from typing import List, Dict
async def fetch_page(session: aiohttp.ClientSession, url: str) -> str:
"""异步获取网页内容"""
try:
async with session.get(url) as response:
if response.status != 200:
print(f"请求失败:{url},状态码:{response.status}")
return ""
return await response.text()
except aiohttp.ClientError as e:
print(f"请求错误:{url},{e}")
return ""
async def parse_quotes(html: str) -> List[Dict[str, str]]:
"""解析网页中的名言数据"""
if not html:
return []
soup = BeautifulSoup(html, 'html.parser')
quotes = soup.find_all('div', class_='quote')
data = []
for quote in quotes:
try:
text = quote.find('span', class_='text').text.strip()
author = quote.find('small', class_='author').text.strip()
data.append({'text': text, 'author': author})
except AttributeError as e:
print(f"解析错误:{e}")
return data
async def save_to_csv(data: List[Dict[str, str]], filename: str):
"""异步保存数据到 CSV 文件"""
if not data:
print("无数据保存")
return
async with aiofiles.open(filename, 'w', newline='', encoding='utf-8') as file:
writer = csv.DictWriter(file, fieldnames=['text', 'author'])
await file.write('\\ufeff') # 添加 BOM 以确保 Excel 兼容 UTF-8
await writer.writeheader()
for item in data:
await writer.writerow(item)
print(f"数据已保存到 {filename}")
async def crawl_quotes(pages: int = 3):
"""主函数:爬取多页名言数据"""
base_url = "<http://quotes.toscrape.com/page/{}/>"
urls = [base_url.format(i) for i in range(1, pages + 1)]
async with aiohttp.ClientSession() as session:
# 并发获取所有页面
tasks = [fetch_page(session, url) for url in urls]
htmls = await asyncio.gather(*tasks, return_exceptions=True)
# 解析所有页面数据
all_data = []
for html in htmls:
if isinstance(html, str):
data = await parse_quotes(html)
all_data.extend(data)
# 异步保存数据
await save_to_csv(all_data, 'quotes_async.csv')
def main():
"""同步入口"""
asyncio.run(crawl_quotes(pages=3))
if __name__ == "__main__":
main()
代码运行结果
- 运行后,生成 quotes_async.csv,包含前 3 页的名言数据,格式如下:csv
- text,author "The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.","Albert Einstein" ...
- 控制台输出示例:
- 数据已保存到 quotes_async.csv
- 异步请求显著减少总耗时(多页并行处理)。
代码解析
- 异步请求: fetch_page:使用 aiohttp.ClientSession 异步获取网页内容。 asyncio.gather:并发执行多个页面请求。
- 数据解析: parse_quotes:使用 BeautifulSoup 解析 HTML,提取名言和作者。 保持与同步版本一致的逻辑,但处理空响应。
- 异步保存: save_to_csv:使用 aiofiles 异步写入 CSV。 添加 BOM(\ufeff)确保 Excel 正确识别 UTF-8。
- 主逻辑: crawl_quotes:协调请求、解析和保存,爬取指定页数。 使用 ClientSession 管理会话,确保资源释放。
- 异常处理: 捕获网络错误(aiohttp.ClientError)。 处理解析错误(AttributeError)和空数据。
课后练习
- 基础练习: 修改代码,提取每条名言的标签(div.tags a.tag),并保存到 CSV。 将保存格式改为 JSON(使用 aiofiles 异步写入)。
- 进阶练习: 添加并发限制(使用 asyncio.Semaphore,最大 5 个并发)。 动态检测总页数(解析分页导航,如 li.next)。
- 挑战练习: 扩展为爬取其他公开练习网站(如 http://toscrape.com/ 的书籍数据)。 使用 pandas 异步将数据转为 Excel(结合 pandas.to_excel)。
注意事项
- 异步语法:确保熟悉 async/await,避免阻塞操作。
- 服务器压力:大规模爬取时,建议添加延迟或并发限制。
- 调试技巧:打印响应内容或解析结果,检查数据完整性。
- 扩展学习: 探索 scrapy 与异步结合。 学习 httpx,另一个异步 HTTP 库。
性能对比(示例)
- 爬取 3 页: 同步版:约 3-6 秒(取决于网络)。 异步版:约 1-2 秒(并发处理)。
- 异步版在页面数增加时优势更明显。
如果需要进一步优化(如添加并发限制、动态分页或处理动态网页),请告诉我!