【问题标题】:Python multi-thread crawler memory continues to growPython多线程爬虫内存持续增长
【发布时间】:2015-03-18 01:53:02
【问题描述】:

我用 Python3 写了一个简单的脚本。它枚举 POST 请求的所有可能输入。我遇到的问题是创建完所有线程后内存一直在增长,最后会因为内存不足而被系统杀死。我使用 Pimpler 检查了 myThread 类。结果表明,myThread 的所有实例的内存使用量并没有迅速增加。我不知道是什么导致了这种内存泄漏。

import requests
import threading
import time

class myThread(threading.Thread):
    def __init__(self, threadID, name, st, ed):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.st = st
        self.ed = ed

    def run(self):
        print("Starting "+self.name)
        get_range(self.st, self.ed)
        print("Exiting " + self.name)

def get_by_id(n):
    payload =  {"id":n}
    url = "http://www.example.com"  # This is for example
    headers = { 'Content-Type': 'application/x-www-form-urlencoded',
                'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
                'Accept-Encoding':"gzip, deflate",
              }
    try:
        r = requests.post(url, data=payload, headers=headers)
    except Exception as e:
        return -2
    if r.status_code is not 200:
        return -2
    if "Cannot find" in r.text:
        return -1
    else:
        with open(os.path.join("./pages", n), 'w') as f:
            f.write(r.text)
        return 1

def get_range(a, b):
    for i in range(a, b):
        r = get_by_id(str(i))

if __name__ == "__main__":
    threads = []
    for x in range(20):
        threads.append(myThread(x, "Thread-"+str(x), 800000000000+x*4000, 800000000000+(x+1)*4000))
        threads[-1].start()
        time.sleep(0.3)
    for t in threads:
        t.join()
    print("Exiting Main")

以下是删除所有可能导致内存问题的文件操作后的代码。

import requests
import threading
import time

class myThread(threading.Thread):
    def __init__(self, threadID, name, st, ed):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.st = st
        self.ed = ed

    def run(self):
        print("Starting "+self.name)
        get_range(self.st, self.ed)
        print("Exiting " + self.name)

def get_by_id(n):
    payload =  {"id":n}
    url = "http://www.example.com"  # This is for example
    headers = { 'Content-Type': 'application/x-www-form-urlencoded',
                'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
                'Accept-Encoding':"gzip, deflate",
              }
    try:
        r = requests.post(url, data=payload, headers=headers)
    except Exception as e:
        return -2
    if r.status_code is not 200:
        return -2
    if "Cannot find" in r.text:
        return -1
    else:
        return 1

def get_range(a, b):
    for i in range(a, b):
        r = get_by_id(str(i))

if __name__ == "__main__":
    threads = []
    for x in range(20):
        threads.append(myThread(x, "Thread-"+str(x), 800000000000+x*4000, 800000000000+(x+1)*4000))
        threads[-1].start()
        time.sleep(0.3)
    for t in threads:
        t.join()
    print("Exiting Main")

【问题讨论】:

    标签: python multithreading web-crawler


    【解决方案1】:

    问题是打开,为了写入文件,必须在操作系统中创建一个文件处理程序。每次调用 open 命令时,您都在创建一个新的文件处理程序。相反,您应该打开文件处理程序一次,然后将其作为参数传递给 get_by_id。然后每个线程只有一个文件处理程序。

    或者,您可以使用 file.close() 来释放操作系统资源。当文件超出范围时,这可能最终由于垃圾收集而发生,但在这种情况下依赖 GC 是非常糟糕的做法。无论如何,在循环中创建不必要的对象是不好的做法。因此,请执行以下操作:

    import requests
    import threading
    import time
    
    class myThread(threading.Thread):
    def __init__(self, threadID, name, st, ed):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.st = st
        self.ed = ed
    
    def run(self):
        print("Starting "+self.name)
        get_range(self.st, self.ed)
        print("Exiting " + self.name)
    
    def get_by_id(n, f):
    payload =  {"id":n}
    url = "http://www.example.com"  # This is for example
    headers = { 'Content-Type': 'application/x-www-form-urlencoded',
                'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
                'Accept-Encoding':"gzip, deflate",
              }
    try:
        r = requests.post(url, data=payload, headers=headers)
    except Exception as e:
        return -2
    if r.status_code is not 200:
        return -2
    if "Cannot find" in r.text:
        return -1
    else:
        f.write(r.text)
        return 1
    
    def get_range(a, b):
    with open(os.path.join("./pages", n), 'w') as f:
    for i in range(a, b):
        r = get_by_id(str(i), f)
    f.close();
    
    if __name__ == "__main__":
    threads = []
    for x in range(20):
        threads.append(myThread(x, "Thread-"+str(x), 800000000000+x*4000, 800000000000+(x+1)*4000))
        threads[-1].start()
        time.sleep(0.3)
    for t in threads:
        t.join()
    print("Exiting Main")
    

    【讨论】:

    • 但是,with 可以确保文件已关闭,不是吗?
    • 我没有意识到 with 会调用 file.close()。但即便如此,这只指示操作系统现在可以关闭文件处理程序,它仍然可能需要一些可观的时间。只打开一个文件处理程序要好得多。你试过吗?它有什么好处吗?
    • 我将所有with 语句更改为f=open()f.close()。但是,这并不能解决我的问题。即使我删除了所有文件操作,内存仍在增加。
    【解决方案2】:

    我相信问题出在这里

        else:
        with open(os.path.join("./pages", n), 'w') as f:
            f.write(r.text)
        return 1
    

    您正在打开一个越来越大的文件,并且由于打开文件会将其写入内存,因此您正在使用越来越多的内存。

    仅供参考:内存泄漏不会显示为分配给程序,而只是由于程序未能释放内存而导致的不可用内存,但让它超出范围。因此,我知道这不是内存泄漏。

    更新: 我决定自己测试一下这个程序。它们都没有导致内存呈指数增长,尽管它们在准备写入时确实增加了,但在写入后下降了。我能给出的唯一建议是更新到请求模块的最新版本,如果这不起作用,请更新 python 本身。

    【讨论】:

    • 但实际情况是这种“else”分支很少发生,而且每次都会写入一个以“n”值命名的新文件,我认为打开文件不会占用越来越多的内存。
    • @brick:好吧,我能得到更多信息吗? IE 你在哪里存储找到的链接。对于网络爬虫来说,它必须使用越来越多的某种内存,无论是内存还是磁盘空间。
    • 我实际上不应该将此脚本称为爬虫。我所做的是枚举某个 url 的所有可能输入,因此我不需要像普通爬虫那样保存新发现的链接的任何地方。这很奇怪。
    • 是的,当响应成功且状态码为 200 并且r.text 中没有“找不到”时,它会存储响应r.text。这就是else 分支的作用。这种打击并不经常发生。另外,每次发生这种情况时,都会打开一个以id命名的新文件来记录r.text,然后再关闭。
    • @brick:我发现了问题所在。它是with open(os.path.join("./pages", n), 'w') as f:,您永远不会关闭文件,而是在 ram 中留下处理程序和文件的副本。由于它是一个处理程序,因此不能使用 with-as 语句;而是使用f=open(os.path.join("./pages", n), 'w') 然后f.write(r.text) 最后f.close()。这应该可以解决您的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-29
    • 1970-01-01
    • 1970-01-01
    • 2012-10-19
    • 1970-01-01
    相关资源
    最近更新 更多