使用 Ray 并行化加速你的网络爬虫#
在本例中,我们将快速演示如何使用 Ray Tasks 构建一个简单的 Python 网络爬虫,并通过少量代码修改实现并行化。
要在你的机器上本地运行此示例,请首先使用以下命令安装 ray
和 beautifulsoup
pip install "beautifulsoup4==4.11.1" "ray>=2.2.0"
首先,我们将定义一个名为 find_links
的函数,该函数接受一个起始页面 (start_url
) 作为抓取目标,并以 Ray 文档作为这样的一个起始点。我们的爬虫简单地从该起始 URL 中提取所有包含指定的 base_url
的可用链接(例如,在我们的例子中,我们只希望跟踪 https://docs.rayai.org.cn
上的链接,而非任何外部链接)。随后,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
让我们定义一个起始 URL 和基础 URL,并将 Ray 文档爬取到深度为 2
。
base = "https://docs.rayai.org.cn/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 Tasks 以直接的方式实现这一点。我们只需使用 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 获取结果(通过 get
解析远程调用承诺)。
当然,还有更智能的方法来创建爬虫并高效地并行化它,本示例为你提供了一个起点。