【问题标题】:Links of a page and links of that subpages. Recursion/Threads页面的链接和该子页面的链接。递归/线程
【发布时间】:2023-03-03 02:55:02
【问题描述】:

我正在制作一个下载网站内容的函数,然后我在站点中查找链接,并且对于每个链接,我都会递归调用相同的函数,直到第 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)

【问题讨论】:

    标签: python python-3.x multithreading recursion threadpool


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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-17
      • 1970-01-01
      • 1970-01-01
      • 2014-10-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多