Bootstrap

如何优化 Python 爬虫的速度

优化 Python 爬虫速度的方法:

一、网络层面

  1. 并发请求
    • 多线程
      • Python 标准库中的threading模块可以用于实现多线程。例如,在爬取多个网页时,可以为每个网页的请求创建一个线程。但是,Python 中的GIL(全局解释器锁)会限制多线程在 CPU 密集型任务中的性能提升。不过对于网络 I/O 密集型的爬虫任务,多线程仍然可以带来显著的速度提升。
      • 以下是一个简单的示例,使用多线程来并发地获取多个网页的内容:
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())

  1. 设置合适的超时时间
    • 在请求网页时,设置合理的超时时间可以避免爬虫在某个响应缓慢的网站上浪费过多时间。例如,在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}")

  1. 使用连接池
    • 对于频繁请求的情况,使用连接池可以减少建立连接的开销。urllib3库提供了连接池功能。例如,在requests库内部实际上也使用了urllib3的连接池。
    • 以下是一个简单的urllib3连接池示例:

import urllib3

http = urllib3.PoolManager()
response = http.request('GET', 'https://www.example.com')
print(response.data.decode('utf - 8'))

二、数据处理层面

  1. 优化数据解析
    • 选择高效的解析库
      • 对于 HTML/XML 解析,lxml库通常比 Python 标准库中的xml.etree.ElementTreehtml.parser更快。
      • 例如,使用lxml解析 HTML 的示例:
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)

  1. 缓存数据
    • 本地缓存
      • 对于一些不经常更新的网页内容,可以将其缓存到本地文件或数据库中。下次需要访问相同内容时,直接从本地读取,而不是再次发起网络请求。
      • 例如,使用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']")
;