优化 Python 爬虫速度的方法:
一、网络层面
- 并发请求
- 多线程:
- Python 标准库中的
threading
模块可以用于实现多线程。例如,在爬取多个网页时,可以为每个网页的请求创建一个线程。但是,Python 中的GIL
(全局解释器锁)会限制多线程在 CPU 密集型任务中的性能提升。不过对于网络 I/O 密集型的爬虫任务,多线程仍然可以带来显著的速度提升。 - 以下是一个简单的示例,使用多线程来并发地获取多个网页的内容:
- Python 标准库中的
- 多线程:
import threading
import requests
def get_page_content(url):
try:
response = requests.get(url)
print(f"成功获取{url}的内容,长度为{len(response.text)}")
except requests.RequestException as e:
print(f"获取{url}内容出错:{e}")
urls = ["https://www.example1.com", "https://www.example2.com", "https://www.example3.com"]
threads = []
for url in urls:
thread = threading.Thread(target=get_page_content, args=(url,))
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
- 多进程:
multiprocessing
模块可以利用多个 CPU 核心来并行执行任务。对于 CPU 密集型的操作(如解析大量数据)和网络请求都有很好的加速效果。它通过创建子进程来避免GIL
的限制。- 示例代码如下:
import multiprocessing
import requests
def get_page_content(url):
try:
response = requests.get(url)
print(f"成功获取{url}的内容,长度为{len(response.text)}")
except requests.RequestException as e:
print(f"获取{url}内容出错:{e}")
if __name__ == "__main__":
urls = ["https://www.example1.com", "https://www.example2.com", "https://www.example3.com"]
pool = multiprocessing.Pool(processes=len(urls))
pool.map(get_page_content, urls)
pool.close()
pool.join()
- 异步编程(asyncio):
asyncio
是 Python 用于异步编程的库。它可以让程序在等待 I/O 操作(如网络请求)完成的过程中去执行其他任务。配合aiohttp
库可以高效地实现异步爬虫。- 以下是一个简单的示例:
import asyncio
import aiohttp
async def get_page_content(session, url):
try:
async with session.get(url) as response:
text = await response.text()
print(f"成功获取{url}的内容,长度为{len(text)}")
except aiohttp.ClientError as e:
print(f"获取{url}内容出错:{e}")
async def main():
urls = ["https://www.example1.com", "https://www.example2.com", "https://www.example3.com"]
async with aiohttp.ClientSession() as session:
tasks = []
for url in urls:
task = asyncio.ensure_future(get_page_content(session, url))
tasks.append(task)
await asyncio.gather(*tasks)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
- 设置合适的超时时间
- 在请求网页时,设置合理的超时时间可以避免爬虫在某个响应缓慢的网站上浪费过多时间。例如,在
requests
库中,可以使用timeout
参数。 - 示例:
- 在请求网页时,设置合理的超时时间可以避免爬虫在某个响应缓慢的网站上浪费过多时间。例如,在
import requests
try:
response = requests.get("https://www.example.com", timeout = 5) # 设置超时时间为5秒
print(response.text)
except requests.Timeout:
print("请求超时")
except requests.RequestException as e:
print(f"请求出错:{e}")
- 使用连接池
- 对于频繁请求的情况,使用连接池可以减少建立连接的开销。
urllib3
库提供了连接池功能。例如,在requests
库内部实际上也使用了urllib3
的连接池。 - 以下是一个简单的
urllib3
连接池示例:
- 对于频繁请求的情况,使用连接池可以减少建立连接的开销。
import urllib3
http = urllib3.PoolManager()
response = http.request('GET', 'https://www.example.com')
print(response.data.decode('utf - 8'))
二、数据处理层面
- 优化数据解析
- 选择高效的解析库:
- 对于 HTML/XML 解析,
lxml
库通常比 Python 标准库中的xml.etree.ElementTree
和html.parser
更快。 - 例如,使用
lxml
解析 HTML 的示例:
- 对于 HTML/XML 解析,
- 选择高效的解析库:
from lxml import html
import requests
response = requests.get("https://www.example.com")
tree = html.fromstring(response.text)
# 提取网页中的标题元素
title = tree.xpath('//title/text()')[0]
print(title)
- 减少不必要的解析操作:
- 只提取需要的数据。例如,如果只需要网页中的链接,就不要解析整个网页的文本内容和样式等其他无关信息。在
lxml
中,可以通过精准的 XPath 或 CSS 选择器来定位需要的数据。 - 例如,只提取网页中的所有
<a>
标签的href
属性:
- 只提取需要的数据。例如,如果只需要网页中的链接,就不要解析整个网页的文本内容和样式等其他无关信息。在
from lxml import html
import requests
response = requests.get("https://www.example.com")
tree = html.fromstring(response.text)
links = tree.xpath('//a/@href')
print(links)
- 缓存数据
- 本地缓存:
- 对于一些不经常更新的网页内容,可以将其缓存到本地文件或数据库中。下次需要访问相同内容时,直接从本地读取,而不是再次发起网络请求。
- 例如,使用
pickle
模块将爬取的数据缓存到本地文件:
- 本地缓存:
import requests
import pickle
url = "https://www.example.com"
cache_file = "cache.pkl"
try:
with open(cache_file, 'rb') as f:
cached_data = pickle.load(f)
except FileNotFoundError:
response = requests.get(url)
cached_data = response.text
with open(cache_file, 'wb') as f:
pickle.dump(cached_data, f)
print(cached_data)
- 内存缓存:
- 对于在同一次运行中可能会多次访问的数据,可以使用
functools.lru_cache
(最近最少使用缓存)来缓存函数的返回结果。如果爬虫中有一个函数用于获取某个网页特定部分的数据,并且会被多次调用,可以使用这个装饰器。 - 示例:
- 对于在同一次运行中可能会多次访问的数据,可以使用
import requests
import functools
@functools.lru_cache(maxsize=128)
def get_page_section(url, section_selector):
response = requests.get(url)
# 假设使用lxml进行解析,这里只是示例
from lxml import html
tree = html.fromstring(response.text)
section = tree.xpath(section_selector)
return section
result = get_page_section("https://www.example.com", "//div[@class='content']")