【发布时间】:2021-01-04 06:31:19
【问题描述】:
背景 - TLDR:我的项目中有内存泄漏
花了几天时间用 scrapy 浏览内存泄漏文档,但找不到问题所在。 我正在开发一个中等规模的 scrapy 项目,每天大约 40k 个请求。
我正在使用 scrapinghub 的预定运行来托管它。
在scrapinghub,每月只需 9 美元,您就可以获得 1 个具有 1GB RAM 的 VM 来运行您的爬虫。
我在本地开发了一个爬虫并上传到scrapinghub,唯一的问题是运行快结束时,我超出了内存。
本地化设置CONCURRENT_REQUESTS=16 工作正常,但会导致scrapinghub 上的内存超过50%。当我设置CONCURRENT_REQUESTS=4 时,我在 95% 点超过了内存,所以减少到 2 应该可以解决问题,但是我的爬虫变得太慢了。
另一种解决方案是购买 2 个虚拟机以增加 RAM,但我感觉我设置爬虫的方式会导致内存泄漏。
在本例中,该项目将搜索一家在线零售商。
在本地运行时,我的 memusage/max 是 2.7gb 和 CONCURRENT_REQUESTS=16。
我现在将运行我的scrapy结构
- 获取要抓取的总页数
- 使用以下命令循环浏览所有这些页面:www.example.com/page={page_num}
- 在每个页面上,收集有关 48 种产品的信息
- 对于这些产品中的每一个,请转到其页面并获取一些信息
- 使用该信息,为每个产品直接调用 API
- 使用项目管道保存这些(我在本地写入 csv,但不在 scrapinghub 上)
- 管道
class Pipeline(object):
def process_item(self, item, spider):
item['stock_jsons'] = json.loads(item['stock_jsons'])['subProducts']
return item
- 项目
class mainItem(scrapy.Item):
date = scrapy.Field()
url = scrapy.Field()
active_col_num = scrapy.Field()
all_col_nums = scrapy.Field()
old_price = scrapy.Field()
current_price = scrapy.Field()
image_urls_full = scrapy.Field()
stock_jsons = scrapy.Field()
class URLItem(scrapy.Item):
urls = scrapy.Field()
- 主蜘蛛
class ProductSpider(scrapy.Spider):
name = 'product'
def __init__(self, **kwargs):
page = requests.get('www.example.com', headers=headers)
self.num_pages = # gets the number of pages to search
def start_requests(self):
for page in tqdm(range(1, self.num_pages+1)):
url = 'www.example.com/page={page}'
yield scrapy.Request(url = url, headers=headers, callback = self.prod_url)
def prod_url(self, response):
urls_item = URLItem()
extracted_urls = response.xpath(####).extract() # Gets URLs to follow
urls_item['urls'] = [# Get a list of urls]
for url in urls_item['urls']:
yield scrapy.Request(url = url, headers=headers, callback = self.parse)
def parse(self, response) # Parse the main product page
item = mainItem()
item['date'] = DATETIME_VAR
item['url'] = response.url
item['active_col_num'] = XXX
item['all_col_nums'] = XXX
item['old_price'] = XXX
item['current_price'] = XXX
item['image_urls_full'] = XXX
try:
new_url = 'www.exampleAPI.com/' + item['active_col_num']
except TypeError:
new_url = 'www.exampleAPI.com/{dummy_number}'
yield scrapy.Request(new_url, callback=self.parse_attr, meta={'item': item})
def parse_attr(self, response):
## This calls an API Step 5
item = response.meta['item']
item['stock_jsons'] = response.text
yield item
到目前为止我尝试了什么?
-
psutils,帮助不大。
-
trackref.print_live_refs()最后返回以下内容:
HtmlResponse 31 oldest: 3s ago
mainItem 18 oldest: 5s ago
ProductSpider 1 oldest: 3321s ago
Request 43 oldest: 105s ago
Selector 16 oldest: 3s ago
- 随着时间的推移打印前 10 个全局变量
- 随着时间的推移打印前 10 个项目类型
问题
- 如何找到内存泄漏?
- 谁能看到我可能在哪里泄漏了内存?
- 我的 scrapy 结构是否存在根本问题?
如果需要更多信息,请告诉我
要求提供其他信息
- 注意,下面的输出来自我的本地机器,我有足够的内存,所以我正在抓取的网站成为瓶颈。使用 scrapinghub 时,由于 1GB 的限制,疑似内存泄漏成为问题。
如果需要scrapinghub的输出,请告诉我,我认为应该是一样的,但是完成原因的消息是内存超出。
1.从开始的日志行(从INFO:Scrapy xxx开始到蜘蛛打开)。
2020-09-17 11:54:11 [scrapy.utils.log] INFO: Scrapy 2.3.0 started (bot: PLT)
2020-09-17 11:54:11 [scrapy.utils.log] INFO: Versions: lxml 4.5.2.0, libxml2 2.9.10, cssselect 1.1.0, parsel 1.6.0, w3lib 1.22.0, Twisted 20.3.0, Python 3.7.4 (v3.7.4:e09359112e, Jul 8 2019, 14:54:52) - [Clang 6.0 (clang-600.0.57)], pyOpenSSL 19.1.0 (OpenSSL 1.1.1g 21 Apr 2020), cryptography 3.1, Platform Darwin-18.7.0-x86_64-i386-64bit
2020-09-17 11:54:11 [scrapy.crawler] INFO: Overridden settings:
{'BOT_NAME': 'PLT',
'CONCURRENT_REQUESTS': 14,
'CONCURRENT_REQUESTS_PER_DOMAIN': 14,
'DOWNLOAD_DELAY': 0.05,
'LOG_LEVEL': 'INFO',
'NEWSPIDER_MODULE': 'PLT.spiders',
'SPIDER_MODULES': ['PLT.spiders']}
2020-09-17 11:54:11 [scrapy.extensions.telnet] INFO: Telnet Password: # blocked
2020-09-17 11:54:11 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.corestats.CoreStats',
'scrapy.extensions.telnet.TelnetConsole',
'scrapy.extensions.memusage.MemoryUsage',
'scrapy.extensions.logstats.LogStats']
2020-09-17 11:54:12 [scrapy.middleware] INFO: Enabled downloader middlewares:
['scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',
'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',
'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',
'scrapy.downloadermiddlewares.retry.RetryMiddleware',
'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',
'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',
'scrapy.downloadermiddlewares.cookies.CookiesMiddleware',
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',
'scrapy.downloadermiddlewares.stats.DownloaderStats']
2020-09-17 11:54:12 [scrapy.middleware] INFO: Enabled spider middlewares:
['scrapy.spidermiddlewares.httperror.HttpErrorMiddleware',
'scrapy.spidermiddlewares.offsite.OffsiteMiddleware',
'scrapy.spidermiddlewares.referer.RefererMiddleware',
'scrapy.spidermiddlewares.urllength.UrlLengthMiddleware',
'scrapy.spidermiddlewares.depth.DepthMiddleware']
=======
17_Sep_2020_11_54_12
=======
2020-09-17 11:54:12 [scrapy.middleware] INFO: Enabled item pipelines:
['PLT.pipelines.PltPipeline']
2020-09-17 11:54:12 [scrapy.core.engine] INFO: Spider opened
2.结束日志行(INFO:Dumping Scrapy stats to end)。
2020-09-17 11:16:43 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 15842233,
'downloader/request_count': 42031,
'downloader/request_method_count/GET': 42031,
'downloader/response_bytes': 1108804016,
'downloader/response_count': 42031,
'downloader/response_status_count/200': 41999,
'downloader/response_status_count/403': 9,
'downloader/response_status_count/404': 1,
'downloader/response_status_count/504': 22,
'dupefilter/filtered': 110,
'elapsed_time_seconds': 3325.171148,
'finish_reason': 'finished',
'finish_time': datetime.datetime(2020, 9, 17, 10, 16, 43, 258108),
'httperror/response_ignored_count': 10,
'httperror/response_ignored_status_count/403': 9,
'httperror/response_ignored_status_count/404': 1,
'item_scraped_count': 20769,
'log_count/INFO': 75,
'memusage/max': 2707484672,
'memusage/startup': 100196352,
'request_depth_max': 2,
'response_received_count': 42009,
'retry/count': 22,
'retry/reason_count/504 Gateway Time-out': 22,
'scheduler/dequeued': 42031,
'scheduler/dequeued/memory': 42031,
'scheduler/enqueued': 42031,
'scheduler/enqueued/memory': 42031,
'start_time': datetime.datetime(2020, 9, 17, 9, 21, 18, 86960)}
2020-09-17 11:16:43 [scrapy.core.engine] INFO: Spider closed (finished)
- self.num_pages 变量使用什么值?
我正在抓取的网站有大约 2 万个产品,每页显示 48 个。所以它进入网站,查看 20103 个产品,然后除以 48(然后是 math.ceil)得到页数。
- 在更新中间件后添加来自抓取中心的输出
downloader/request_bytes 2945159
downloader/request_count 16518
downloader/request_method_count/GET 16518
downloader/response_bytes 3366280619
downloader/response_count 16516
downloader/response_status_count/200 16513
downloader/response_status_count/404 3
dupefilter/filtered 7
elapsed_time_seconds 4805.867308
finish_reason memusage_exceeded
finish_time 1600567332341
httperror/response_ignored_count 3
httperror/response_ignored_status_count/404 3
item_scraped_count 8156
log_count/ERROR 1
log_count/INFO 94
memusage/limit_reached 1
memusage/max 1074937856
memusage/startup 109555712
request_depth_max 2
response_received_count 16516
retry/count 2
retry/reason_count/504 Gateway Time-out 2
scheduler/dequeued 16518
scheduler/dequeued/disk 16518
scheduler/enqueued 17280
scheduler/enqueued/disk 17280
start_time 1600562526474
【问题讨论】:
-
需要其他信息:1.从开始的日志行(从
INFO: Scrapy xxx started到spider opened)。 2.结束日志行(INFO: Dumping Scrapy stats结束)。 3.self.num_pages变量使用什么值? -
已添加,希望对您有所帮助,感谢您的详细请求 - 使添加变得非常容易@Georgiy
-
你的“我的爬虫变得太慢”是什么意思?在一般情况下,向网站发送请求的频率超过每秒 1 个请求是不正常的。你的蜘蛛在 54 分钟内发送了 42k 个请求(每秒约 13 个请求)!!。
-
嗨@Georgiy,对不起,我会在答案中添加更多上下文,这个输出是在我的本地机器上运行的,有 16 个并发爬虫,请求率为 0.05。在使用抓取集线器时,我必须将其减少到 4,因为我怀疑是内存泄漏,而不是网站节流。
-
TLDR 解决方案:在 Scraping Hub 上抓取云单元,并降低请求率。如果这不起作用,请查看以下答案以深入了解
标签: python memory scrapy scrapinghub