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

时间:2018-11-20 22:20:34

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

我正在制作一个下载网站内容的功能,然后我在网站中查找链接,对于每个链接,我都递归调用相同的功能,直到第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 个答案:

答案 0 :(得分:0)

您在这里有很多URL,因此这将是一项巨大的操作。例如,如果每个页面平均只有10个链接,则要递归7层,则您正在查看超过1000万个请求。

对于初学者,请使用HTML解析库(例如BeautifulSoup)而不是regex。尽管我尚未测试确切的数量,但这将立即为您带来性能提升。避免打印到标准输出,这也会减慢工作速度。

关于线程,一种方法是使用工作队列。 Python的queue class是线程安全的,因此您可以创建一个工作线程池来轮询以从队列中检索URL。当线程获取URL时,它将在页面上找到所有链接,并将相关的URL(或页面数据,如果需要)附加到全局列表(也是thread-safe operation)上。 URL已排队在工作队列中,过程继续进行。当指定级别达到0时,线程退出。

另一种方法可能是为其所有链接抓取第一个URL,然后在线程池中创建尽可能多的线程,并让它们每个都在单独的链接树上运行。这样可以消除队列争用并减少开销。

无论哪种方式,想法都是线程将阻止等待请求响应,并允许CPU运行其他线程来完成工作,这将弥补线程开销(上下文切换,锁争用)。如果您想在多个内核上运行,请read this blog post about the GIL并查看生成过程。

这是第一种方法的一些示例代码:

import queue
import requests
import threading
import time
from bs4 import BeautifulSoup


def search_links(q, result):
    while 1: 
        try:
            url, level = q.get()
        except queue.Empty:
            continue

        if not level:
            break

        try:
            for x in BeautifulSoup(requests.get(url).text, "lxml").find_all("a", href=True):
                link = x["href"]

                if link and link[0] in "#/":
                    link = url + link[1:]

                result.append(link)
                q.put((link, level - 1))
        except requests.exceptions.InvalidSchema:
            pass


levels = 2
workers = 10
start_url = "https://masdemx.com/category/creatividad/?fbclid=IwAR0G2AQa7QUzI-fsgRn3VOl5oejXKlC_JlfvUGBJf9xjQ4gcBsyHinYiOt8"
urls = []
threads = []
q = queue.Queue()
q.put((start_url, levels))

start = time.time()

for i in range(workers):
    threads.append(threading.Thread(target=search_links, args=(q, urls)))
    threads[-1].daemon = True
    threads[-1].start()

for thread in threads:
    thread.join()

print("Found %d URLs using %d workers %d levels deep in %ds" % (len(urls), workers, levels, time.time() - start))

#for url in urls:
#    print(url)

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

> python thread_req.py
Found 7733 URLs using 1 workers 2 levels deep in 112s
> python thread_req.py
Found 7729 URLs using 10 workers 2 levels deep in 27s
> python thread_req.py
Found 7731 URLs using 20 workers 2 levels deep in 25s

在这种小批量生产中,性能提高了4倍。在较大的运行量中,我遇到了最大的请求错误,因此这只是一个玩具示例。另外值得注意的是,对于队列,您将使用递归或堆栈执行BFS而不是DFS

Try it!