页面的链接和该子页面的链接。递归/线程

Posted

技术标签:

【中文标题】页面的链接和该子页面的链接。递归/线程【英文标题】:Links of a page and links of that subpages. Recursion/Threads 【发布时间】:2019-04-23 10:56:14 【问题描述】:

我正在制作一个下载网站内容的函数,然后我在站点中查找链接,并且对于每个链接,我都会递归调用相同的函数,直到第 7 级。问题是这需要很多时间,所以我希望使用线程池来管理此调用,但我不知道如何准确地将这些任务划分到线程池中。

这是我的实际代码,没有线程池。

import requests
import re

url = 'https://masdemx.com/category/creatividad/?fbclid=IwAR0G2AQa7QUzI-fsgRn3VOl5oejXKlC_JlfvUGBJf9xjQ4gcBsyHinYiOt8'


def searchLinks(url,level):
    print("level: "+str(level))
    if(level==3):
        return 0

    response = requests.get(url)
    enlaces = re.findall(r'<a href="(.*?)"',str(response.text))

    for en in enlaces:
        if (en[0] == "/" or en[0]=="#"):
            en= url+en[1:]
        print(en)
        searchLinks(en,level+1)


searchLinks(url,1)

【问题讨论】:

【参考方案1】:

对于初学者,请注意这可能是一项大手术。例如,如果每个页面平均只有 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() - starts")

以下是在我不是特别快的机器上运行的一些示例:

$ 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 倍。我在较大的运行中遇到了最大请求错误,所以这只是一个玩具示例。

【讨论】:

以上是关于页面的链接和该子页面的链接。递归/线程的主要内容,如果未能解决你的问题,请参考以下文章

子页面的 Wordpress 自定义永久链接

Layuilayui中iframe子页面中的链接点击后在父页面中动态添加tab选项

Wordpress 子菜单链接到页面的一部分

子页面(弹出框)获取父页面中 链接地址,获取传值问题

Layuilayui中iframe子页面中的链接点击后在父页面中动态添加tab选项

无法访问 magnolia cms 中的链接和子页面