在这个示例中,我们将快速演示如何使用最少的代码更改在Python中构建一个简单的网页爬虫,并使用Ray Tasks并行化它。
0. 运行环境
本案例的环境为Anaconda建立的名为RayAIR的环境,具体创建过程以下链接的第2节:《机器学习框架Ray -- 2.1 Ray Clusters与Ray AIR的基本使用》
此外,爬虫需要安装beautifulsoup4:
pip install "beautifulsoup4==4.11.1"
完整Anaconda的环境定义为
conda create -n RayAIR python=3.7
conda activate RayAIR
pip install "ray[air]"
pip install xgboost_ray
pip install ipykernel -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install "beautifulsoup4==4.11.1"
后续操作都是在Linux Anaconda的命名为RayAIR环境中,以.ipynb的形式运行。
1.1 网络爬虫
首先,我们将定义一个名为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
1.2 单线程爬取网页
定义一个起始和基本URL,深度为2爬取Ray文档。
base = "https://docs.ray.io/en/latest/"
docs = base + "index.html"
%time len(find_links(docs, base))
单线程爬虫用时1m4s,爬取了594个页面,运行结果如下:
CPU times: user 25 s, sys: 276 ms, total: 25.3 s
Wall time: 1min 4s
594
2. 并行化网络爬虫
可以以多种方式并行爬取页面。最简单的方法是简单地从多个起始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))
爬虫结果:
2023-04-06 04:54:58,567 INFO worker.py:1550 -- Started a local Ray instance. View the dashboard at http://127.0.0.1:8265
594
594
594
101
101
101
CPU times: user 246 ms, sys: 74.9 ms, total: 321 ms
Wall time: 1min 28s
这次并行运行,相比最初的顺序运行要高效得多。
在近似相同的时间内,通过并行化运行爬虫,获得了六组爬虫数据。