对于初学者,请注意这可能是一项大手术。例如,如果每个页面平均只有 10 个唯一链接,那么如果您想要递归 7 层深度,您将看到超过 1000 万个请求。
另外,我会使用像 BeautifulSoup 这样的 HTML 解析库,而不是正则表达式,这是一种抓取 HTML 的脆弱方法。避免打印到标准输出,这也会减慢工作速度。
至于线程,一种方法是使用工作队列。 Python 的queue class 是线程安全的,因此您可以创建一个工作线程池,用于轮询以从队列中检索 URL。当线程获取 URL 时,它会查找页面上的所有链接并将相关 URL(或页面数据,如果您愿意)附加到全局列表(在 CPython 上是 thread-safe operation - 对于其他实现,使用锁定共享数据结构)。这些 URL 被排入工作队列并继续处理。
线程在级别达到 0 时退出,因为我们使用的是 BFS 而不是使用堆栈的 DFS。这里(可能是安全的)假设是链接的级别比深度多。
并行性来自阻塞等待请求响应的线程,允许 CPU 运行另一个响应到达的线程来执行 HTML 解析和排队工作。
如果您想在多核上运行以帮助并行化工作负载中受 CPU 限制的部分,请read this blog post about the GIL 并查看生成进程。但单单线程就可以让您在很大程度上实现并行化,因为瓶颈受 I/O 限制(等待 HTTP 请求)。
下面是一些示例代码:
import queue
import requests
import threading
import time
from bs4 import BeautifulSoup
def search_links(q, urls, seen):
while 1:
try:
url, level = q.get()
except queue.Empty:
continue
if level <= 0:
break
try:
soup = BeautifulSoup(requests.get(url).text, "lxml")
for x in soup.find_all("a", href=True):
link = x["href"]
if link and link[0] in "#/":
link = url + link[1:]
if link not in seen:
seen.add(link)
urls.append(link)
q.put((link, level - 1))
except (requests.exceptions.InvalidSchema,
requests.exceptions.ConnectionError):
pass
if __name__ == "__main__":
levels = 2
workers = 10
start_url = "https://masdemx.com/category/creatividad/?fbclid=IwAR0G2AQa7QUzI-fsgRn3VOl5oejXKlC_JlfvUGBJf9xjQ4gcBsyHinYiOt8"
seen = set()
urls = []
threads = []
q = queue.Queue()
q.put((start_url, levels))
start = time.time()
for _ in range(workers):
t = threading.Thread(target=search_links, args=(q, urls, seen))
threads.append(t)
t.daemon = True
t.start()
for thread in threads:
thread.join()
print(f"Found {len(urls)} URLs using {workers} workers "
f"{levels} levels deep in {time.time() - start}s")
以下是在我不是特别快的机器上运行的一些示例:
$ python thread_req.py
Found 762 URLs using 15 workers 2 levels deep in 33.625585317611694s
$ python thread_req.py
Found 762 URLs using 10 workers 2 levels deep in 42.211519956588745s
$ python thread_req.py
Found 762 URLs using 1 workers 2 levels deep in 105.16120409965515s
在这个小规模运行中,性能提升了 3 倍。我在较大的运行中遇到了最大请求错误,所以这只是一个玩具示例。