GAE Python LXML - 超过软私有内存限制

0 投票
2 回答
678 浏览
提问于 2025-04-17 16:45

我正在获取一个经过GZ压缩的LXML文件,并试图将产品信息写入数据库模型。之前我遇到了本地内存的问题,后来在StackOverflow上得到了帮助(问题链接)。现在我已经把一切都搞定并部署了,但在服务器上却出现了以下错误:

Exceeded soft private memory limit with 158.164 MB after servicing 0 requests total

我尝试了所有我知道的方法来减少内存使用,目前使用的代码如下。这个GZ压缩文件大约7MB,而解压后是80MB。在本地运行时代码一切正常。我尝试过以HTTP请求和定时任务的方式运行,但没有什么区别。现在我在想有没有什么办法可以让它更高效。

在StackOverflow上,有一些类似的问题提到了前端和后端的配置,但我对此不太熟悉。我正在使用GAE的免费版本,这个任务需要每周运行一次。任何关于如何更好地推进的建议都非常感谢。

from google.appengine.api.urlfetch import fetch
import gzip, base64, StringIO, datetime, webapp2
from lxml import etree
from google.appengine.ext import db

class GetProductCatalog(webapp2.RequestHandler):
  def get(self):
    user = XXX
    password = YYY
    url = 'URL'

    # fetch gziped file
    catalogResponse = fetch(url, headers={
        "Authorization": "Basic %s" % base64.b64encode(user + ':' + password)
    }, deadline=10000000)

    # the response content is in catalogResponse.content
    # un gzip the file
    f = StringIO.StringIO(catalogResponse.content)
    c = gzip.GzipFile(fileobj=f)
    content = c.read()

    # create something readable by lxml
    xml = StringIO.StringIO(content)

    # delete unnecesary variables
    del f
    del c
    del content

    # parse the file
    tree = etree.iterparse(xml, tag='product')

    for event, element in tree:
        if element.findtext('manufacturer') == 'New York':
            if Product.get_by_key_name(element.findtext('sku')):
                    coupon = Product.get_by_key_name(element.findtext('sku'))
                    if coupon.last_update_prov != datetime.datetime.strptime(element.findtext('lastupdated'), "%d/%m/%Y"):
                        coupon.restaurant_name = element.findtext('name')
                        coupon.restaurant_id = ''
                        coupon.address_street = element.findtext('keywords').split(',')[0]
                        coupon.address_city = element.findtext('manufacturer')
                        coupon.address_state = element.findtext('publisher')
                        coupon.address_zip = element.findtext('manufacturerid')
                        coupon.value = '$' + element.findtext('price') + ' for $' + element.findtext('retailprice')
                        coupon.restrictions = element.findtext('warranty')
                        coupon.url = element.findtext('buyurl')
                        if element.findtext('instock') == 'YES':
                            coupon.active = True
                        else:
                            coupon.active = False
                        coupon.last_update_prov = datetime.datetime.strptime(element.findtext('lastupdated'), "%d/%m/%Y")
                        coupon.put()
                    else:
                        pass
            else:
                    coupon = Product(key_name = element.findtext('sku'))
                    coupon.restaurant_name = element.findtext('name')
                    coupon.restaurant_id = ''
                    coupon.address_street = element.findtext('keywords').split(',')[0]
                    coupon.address_city = element.findtext('manufacturer')
                    coupon.address_state = element.findtext('publisher')
                    coupon.address_zip = element.findtext('manufacturerid')
                    coupon.value = '$' + element.findtext('price') + ' for $' + element.findtext('retailprice')
                    coupon.restrictions = element.findtext('warranty')
                    coupon.url = element.findtext('buyurl')
                    if element.findtext('instock') == 'YES':
                        coupon.active = True
                    else:
                        coupon.active = False

                    coupon.last_update_prov = datetime.datetime.strptime(element.findtext('lastupdated'), "%d/%m/%Y")
                    coupon.put()
        else:
            pass

        element.clear()

更新

根据保罗的建议,我实现了后端。经过一些麻烦后,它终于顺利运行了——下面是我使用的代码。

我的backends.yaml文件如下:

backends:
- name: mybackend
  instances: 10
  start: mybackend.app
  options: dynamic

而我的app.yaml文件如下:

handlers:
- url: /update/mybackend
  script: mybackend.app
  login: admin

2 个回答

0

关于后端:看你提供的例子,感觉你的请求只是由前端处理的。

要让它由后端处理,可以试着调用这个任务:http://mybackend.my_app_app_id.appspot.com/update/mybackend

另外,我觉得你可以把start: mybackend.app这一行从你的backends.yaml文件中去掉。

2

后端就像前端的实例,但它们不能自动扩展,你需要根据需要手动停止和启动它们(或者设置成动态的,这样可能是更好的选择)。

后端最多可以使用1024MB的内存,所以应该能满足你的需求。

https://developers.google.com/appengine/docs/python/backends/overview

App Engine的后端是你的应用程序的一种实例,它们不受请求时间限制,并且可以使用比普通实例更多的内存(最多1GB)和CPU(最多4.8GHz)。这些后端是为需要更快性能、大量可用内存和持续或长时间运行的后台进程而设计的。后端有多种大小和配置,计费是根据运行时间,而不是CPU使用量。

后端可以配置为常驻或动态。常驻后端会持续运行,这样你可以依赖它们的内存状态,并进行复杂的初始化。动态后端在收到请求时才会启动,闲置时会关闭;它们非常适合间歇性或由用户活动驱动的工作。想了解常驻和动态后端之间的区别,可以查看后端类型以及启动和关闭的讨论。

听起来这正是你需要的。免费的使用级别也适合你的任务。

撰写回答