通过使用Ray对您的网络爬虫进行并行加速#

在这个例子中,我们将快速演示如何在Python中构建一个简单的网页爬虫,并通过Ray任务实现并行化,所需的代码更改很少。

要在你的机器上本地运行这个例子,请首先安装raybeautifulsoup,使用以下命令:

pip install "beautifulsoup4==4.11.1" "ray>=2.2.0"

首先,我们将定义一个名为find_links的函数,该函数接受一个起始页面(start_url)进行爬取,我们将Ray文档作为这样的起始点的例子。我们的爬虫简单地从起始URL中提取所有包含给定base_url的可用链接(例如,在我们的例子中,我们只想关注http://docs.ray.io上的链接,而不是任何外部链接)。然后递归调用find_links函数,直到达到某个深度。

为了从网站的HTML元素中提取链接,我们定义了一个名为extract_links的小助手函数,它可以正确处理相对URL,并设置从网站返回的链接数量限制(max_results),以便更轻松地控制爬虫的运行时间。

以下是完整的实现:

import requests
from bs4 import BeautifulSoup

def extract_links(elements, base_url, max_results=100):
    links = []
    for e in elements:
        url = e["href"]
        if "https://" not in url:
            url = base_url + url
        if base_url in url:
            links.append(url)
    return set(links[:max_results])


def find_links(start_url, base_url, depth=2):
    if depth == 0:
        return set()

    page = requests.get(start_url)
    soup = BeautifulSoup(page.content, "html.parser")
    elements = soup.find_all("a", href=True)
    links = extract_links(elements, base_url)

    for url in links:
        new_links = find_links(url, base_url, depth-1)
        links = links.union(new_links)
    return links

让我们定义一个起始网址和基本网址,并以深度2爬取Ray文档。

base = "https://docs.ray.io/en/latest/"
docs = base + "index.html"
%time len(find_links(docs, base))
CPU times: user 19.3 s, sys: 340 ms, total: 19.7 s
Wall time: 25.8 s
591

如您所见,像这样递归地爬取文档根目录返回总计 591 页,并且耗时约 25 秒。

爬取页面可以以多种方式并行化。 最简单的方法可能是从多个起始 URL 开始,并为每个 URL 并行调用 find_links。 我们可以通过 Ray 任务 以直接的方式实现。 我们只需使用 ray.remote 装饰器将 find_links 函数包装在一个名为 find_links_task 的任务中,如下所示:

import ray

@ray.remote
def find_links_task(start_url, base_url, depth=2):
    return find_links(start_url, base_url, depth)

要使用此任务启动并行调用,您只需使用 find_links_tasks.remote(...),而不是直接调用底层 Python 函数。

以下是如何并行运行六个爬虫的示例,前面三个(冗余地)再次爬取 docs.ray.io,另外三个分别爬取 Ray RLlib、Tune 和 Serve 库的主要入口点:

links = [find_links_task.remote(f"{base}{lib}/index.html", base)
         for lib in ["", "", "", "rllib", "tune", "serve"]]
%time for res in ray.get(links): print(len(res))
591
591
105
204
105
CPU times: user 65.5 ms, sys: 47.8 ms, total: 113 ms
Wall time: 27.2 s

这个并行运行大约在与初始的顺序运行相同的时间内爬取了大约四倍的页面数量。 请注意在计时运行中使用ray.get来从Ray中检索结果(remote调用的承诺通过get得到解决)。

当然,还有更聪明的方法来创建爬虫并有效地实现并行化,这个示例为您提供了一个起点。