通过以下几种方式进一步提高效率,尤其是在爬取大量数据时,提升性能和稳定性:
1. 使用 asyncio + aiohttp 实现异步爬取
Python 的 asyncio 库和 aiohttp 库结合使用,可以在网络 I/O 操作(如发送 HTTP 请求和接收响应)时实现异步操作,这样就能充分利用计算机的资源,避免在等待请求响应时阻塞线程。
与传统的线程模型相比,asyncio 的性能更高,因为它不会像线程那样消耗大量的内存和上下文切换的开销,适合 I/O 密集型任务,如网络爬取。
使用 asyncio 和 aiohttp 异步爬取示例:
复制编辑
import aiohttp
import asyncio
from aiohttp import ClientSession
import async_timeout
# 用于存储爬取到的数据
collected_data = []
# 爬取商品详情页的异步函数
async def fetch_product_data(session: ClientSession, url: str):
try:
async with async_timeout.timeout(10): # 设置请求超时,避免挂起
async with session.get(url, headers={"User-Agent": "Mozilla/5.0"}) as response:
if response.status == 200:
html = await response.text()
# 解析页面(根据实际页面结构)
product_data = {
"name": "商品名称", # 从 html 中提取的商品信息
"price": "商品价格"
}
collected_data.append(product_data)
except Exception as e:
print(f"爬取 {url} 时出错:{e}")
# 异步爬取多个商品详情页的函数
async def scrape_products(product_urls: list):
async with aiohttp.ClientSession() as session:
tasks = [fetch_product_data(session, url) for url in product_urls]
await asyncio.gather(*tasks)
# 商品详情页 URL 列表
product_urls = [
"https://item.jd.com/100012343234.html",
"https://item.jd.com/100019283746.html",
# 更多商品 URL
]
# 执行异步爬取
asyncio.run(scrape_products(product_urls))
# 打印爬取到的商品数据
print(collected_data)
代码解析:
- 异步请求:使用
aiohttp实现异步 HTTP 请求。通过async with语法,可以在发送请求时不阻塞线程。 - 超时控制:使用
async_timeout.timeout限制每个请求的超时时间,避免网络问题导致任务长时间挂起。 - 并发任务:通过
asyncio.gather方法并发执行多个任务,实现异步爬取多个商品详情页。 - 爬取速度:异步爬取相比多线程更高效,因为它不会在等待响应时阻塞线程,也不需要频繁地进行上下文切换。
2. 使用 requests + 多进程 (multiprocessing)
如果你有大量的商品详情页需要爬取,并且你的系统有多个核心,可以使用 Python 的 multiprocessing 模块将任务分配到多个进程中。多进程可以充分利用多核 CPU,尤其适合 CPU 密集型任务。
在多进程模型下,每个进程都有自己独立的内存空间,因此不会出现线程安全问题。通过使用进程池 (Pool) 来管理进程,可以避免频繁创建和销毁进程带来的性能损失。
使用多进程的示例代码:
复制编辑
import requests
from multiprocessing import Pool
# 定义爬取商品详情页数据的函数
def fetch_product_data(product_url):
try:
response = requests.get(product_url, headers={"User-Agent": "Mozilla/5.0"})
if response.status_code == 200:
# 解析商品详情页的数据
product_data = {
"name": "商品名称", # 假设我们通过解析页面得到的数据
"price": "商品价格" # 通过解析页面获取价格
}
return product_data
except Exception as e:
print(f"爬取 {product_url} 时出错:{e}")
return None
# 批量爬取商品详情页的函数
def scrape_products(product_urls):
with Pool(processes=4) as pool: # 使用 4 个进程进行并行处理
results = pool.map(fetch_product_data, product_urls)
# 清洗掉 None 值(失败的请求)
return [result for result in results if result is not None]
# 商品详情页 URL 列表
product_urls = [
"https://item.jd.com/100012343234.html",
"https://item.jd.com/100019283746.html",
# 更多商品 URL
]
# 执行多进程爬取
collected_data = scrape_products(product_urls)
# 打印爬取到的商品数据
print(collected_data)
3. 使用代理池和用户代理
- 代理池:为了避免 IP 被封禁,可以使用代理池技术,定期更换代理 IP。
- 用户代理(User-Agent) :每次请求时使用不同的
User-Agent字符串,以模拟来自不同浏览器和设备的请求,避免被检测为爬虫。
使用代理池的代码示例:
复制编辑
import random
import requests
# 代理池(示例)
proxies = [
"http://123.123.123.123:8888",
"http://123.123.123.124:8888",
# 更多代理
]
# 获取随机代理
def get_random_proxy():
return random.choice(proxies)
# 使用代理发送请求
def fetch_product_data_with_proxy(product_url):
proxy = get_random_proxy()
try:
response = requests.get(product_url, headers={"User-Agent": "Mozilla/5.0"}, proxies={"http": proxy, "https": proxy})
if response.status_code == 200:
# 解析商品详情页的数据
product_data = {
"name": "商品名称",
"price": "商品价格"
}
return product_data
except Exception as e:
print(f"爬取 {product_url} 时出错:{e}")
return None
总结:
- 异步爬取(
asyncio+aiohttp) :适合 I/O 密集型任务,可以大幅提高爬取速度。 - 多进程(
multiprocessing) :适合 CPU 密集型任务,可以利用多核 CPU,适合处理大量数据。 - 代理池和用户代理:提高防封机制,避免爬取过程中出现封禁问题。