Pyqt 4 - QWebView.load(url) утечки памяти (не из Python)

По сути, я извлекаю серию ссылок из своей базы данных и хочу просмотреть их для конкретных ссылок, которые я ищу. Затем я повторно отправляю эти ссылки в свою очередь ссылок, на которую ссылаются несколько моих QWebView, и они продолжают собирать их для обработки / хранения.

Моя проблема в том, что, поскольку это работает... скажем, на 200 или 500 ссылок, оно начинает использовать все больше и больше оперативной памяти.

Я подробно рассмотрел это, используя heapy, memory_profiler и objgraph, чтобы выяснить, что вызывает утечку памяти... Объекты кучи питона остаются примерно одинаковыми с точки зрения количества и размера с течением времени. Это заставило меня думать, что объекты C++ не удаляются. Конечно, используя memory_profiler, объем оперативной памяти увеличивается только при вызове строк кода self.load(self.url). Я пытался это исправить, но безрезультатно.

Код:

from PyQt4.QtCore import QUrl
from PyQt4.QtWebKit import QWebView, QWebSettings
from PyQt4.QtGui import QApplication
from lxml.etree import HTMLParser

# My functions
from util import dump_list2queue, parse_doc

class ThreadFlag:
    def __init__(self, threads, jid, db):
        self.threads = threads
        self.job_id = jid
        self.db_direct = db
        self.xml_parser = HTMLParser()

class WebView(QWebView):
    def __init__(self, thread_flag, id_no):
        super(QWebView, self).__init__()
        self.loadFinished.connect(self.handleLoadFinished)
        self.settings().globalSettings().setAttribute(QWebSettings.AutoLoadImages, False)
        # This is actually a dict with a few additional details about the url we want to pull
        self.url = None
        # doing one instance of this to avoid memory leaks
        self.qurl = QUrl()
        # id of the webview instance
        self.id = id_no
        # Status webview instance, green mean it isn't working and yellow means it is.
        self.status = 'GREEN'
        # Reference to a single universal object all the webview instances can see.
        self.thread_flag = thread_flag

    def handleLoadFinished(self):
        try:
            self.processCurrentPage()
        except Exception as e:
            print e

        self.status = 'GREEN'

        if not self.fetchNext():
            # We're finished!
            self.loadFinished.disconnect()
            self.stop()
        else:
            # We're not finished! Do next url.
            self.qurl.setUrl(self.url['url'])
            self.load(self.qurl)

    def processCurrentPage(self):
        self.frame = str(self.page().mainFrame().toHtml().toUtf8())

        # This is the case for the initial web pages I want to gather links from.
        if 'name' in self.url:
            # Parse html string for links I'm looking for.
            new_links = parse_doc(self.thread_flag.xml_parser, self.url, self.frame)
            if len(new_links) == 0: return 0
            fkid = self.url['pkid']
            new_links = map(lambda x: (fkid, x['title'],x['url'], self.thread_flag.job_id), new_links)


            # Post links to database, db de-dupes and then repull ones that made it.
            self.thread_flag.db_direct.post_links(new_links)
            added_links = self.thread_flag.db_direct.get_links(self.thread_flag.job_id,fkid)

            # Add the pulled links to central queue all the qwebviews pull from
            dump_list2queue(added_links, self._urls)
            del added_links
        else:
            # Process one of the links I pulled from the initial set of data that was originally in the queue.
            print "Processing target link!"

    # Get next url from the universal queue!
    def fetchNext(self):
        if self._urls and self._urls.empty():
            self.status = 'GREEN'
            return False
        else:
            self.status = 'YELLOW'
            self.url = self._urls.get()
            return True

    def start(self, urls):
        # This is where the reference to the universal queue gets made.
        self._urls = urls
        if self.fetchNext():
            self.qurl.setUrl(self.url['url'])
            self.load(self.qurl)

# uq = central url queue shared between webview instances
# ta = array of webview objects
# tf - thread flag (basically just a custom universal object that all the webviews can access).

# This main "program" is started by another script elsewhere.
def main_program(uq, ta, tf):

    app = QApplication([])
    webviews = ta
    threadflag = tf

    tf.app = app

    print "Beginning the multiple async web calls..."

    # Create n "threads" (really just webviews) that each will make asynchronous calls.
    for n in range(0,threadflag.threads):
        webviews.append(WebView(threadflag, n+1))
        webviews[n].start(uq)

    app.exec_()

Вот что говорят мои инструменты памяти (они все примерно одинаковы во всей программе)

  1. RAM: resource.getrusage (resource.RUSAGE_SELF).ru_maxrss / 1024

2491 (MB)

  1. Objgraph наиболее распространенных типов:

дескриптор метода 9959

функция 8342

слабый реф 6440

кортеж 6418

Дикт 4982

обертка_дескриптор 4380

getset_descriptor 2314

список 1890

method_descriptor 1445

встроенный_функция_метод 1298

  1. бесформенный:

Перегородка из набора 9879 предметов. Общий размер = 1510000 байт.

Индекс Количество% Размер% Совокупный% Вид (класс / диктат класса)

 0   2646  27   445216  29    445216  29 str

 1    563   6   262088  17    707304  47 dict (no owner)

 2   2267  23   199496  13    906800  60 __builtin__.weakref

 3   2381  24   179128  12   1085928  72 tuple

 4    212   2   107744   7   1193672  79 dict of guppy.etc.Glue.Interface

 5     50   1    52400   3   1246072  83 dict of guppy.etc.Glue.Share

 6    121   1    40200   3   1286272  85 list

 7    116   1    32480   2   1318752  87 dict of guppy.etc.Glue.Owner

 8    240   2    30720   2   1349472  89 types.CodeType

 9     42   0    24816   2   1374288  91 dict of class

1 ответ

Ваша программа действительно развивается из-за кода C++, но это не фактическая утечка с точки зрения создания объектов, на которые больше нет ссылок. По крайней мере частично происходит то, что ваш QWebView содержит QWebPage, который содержит QWebHistory(). Каждый раз, когда вы вызываете self.load, история становится немного длиннее.

Обратите внимание, что QWebHistory имеет функцию clear().

Документация доступна: http://pyqt.sourceforge.net/Docs/PyQt4/qwebview.html

Другие вопросы по тегам