Urllib parse in jupyter consuming GBs of memory

Viewed 29

I'm struggling with this one for days

I have code in python that sends get request that returns text file, I'm iterating my requests day by day because amount of data for one day is around 200mb and I don't want to run out of memory.

I'm using gc and deleting all possible variables, but still, according to tracemalloc urllib parse is holding a lot of data in memory and I can't figure out why nor how to free this memory.

import tracemalloc

tracemalloc.start()

import pandas as pd
import json
import gc
from datetime import datetime, timedelta,date
from urllib.request import Request, urlopen  # Python 3
gc.enable()

def daterange(start_date, end_date):
    for n in range(int((end_date - start_date).days)):
        yield start_date + timedelta(n)

start_date = date(2022, 8, 1)
end_date = date(2022, 8, 7)

def func(single_date):
    print(single_date.strftime("%Y-%m-%d"))
    date = single_date.strftime("%Y-%m-%d")
    req = Request('https://website.com/export?from_date='+date+'&to_date='+date+'&project_id=00000')
    req.add_header('Accept', 'text/plain')
    req.add_header( 'Authorization', 'Basic ==')
    content = urlopen(req)
    encoding = content.info().get_content_charset('utf-8')
    data_object = content.read()
    data = data_object.decode(encoding)
    
    df = pd.read_json(data, lines=True, orient='records')
    dfout = pd.concat([df.reset_index()[['event']], pd.json_normalize(df['properties'])], axis=1)

    del df
    del dfout
    del req
    del content
    del data_object
    del data
    del date
    del encoding
    del single_date
    

for single_date in daterange(start_date, end_date):
    gc.collect()
    func(single_date)

snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')

print("[ Top 10 ]")
for stat in top_stats[:10]:
    print(stat)

Tracemalloc output:

[ Top 10 ]
/opt/tljh/user/lib/python3.9/urllib/parse.py:461: size=1391 MiB, count=6, average=232 MiB
/opt/tljh/user/lib/python3.9/urllib/parse.py:486: size=1253 MiB, count=6, average=209 MiB
/opt/tljh/user/lib/python3.9/urllib/parse.py:488: size=132 MiB, count=24, average=5628 KiB
<frozen importlib._bootstrap_external>:647: size=12.0 MiB, count=115719, average=108 B
<frozen importlib._bootstrap>:228: size=5055 KiB, count=24566, average=211 B
/opt/tljh/user/lib/python3.9/tracemalloc.py:505: size=754 KiB, count=13788, average=56 B
/opt/tljh/user/lib/python3.9/tracemalloc.py:498: size=647 KiB, count=13788, average=48 B
/opt/tljh/user/lib/python3.9/tracemalloc.py:193: size=646 KiB, count=13787, average=48 B
/home/jupyter/.local/lib/python3.9/site-packages/pandas/util/_decorators.py:389: size=366 KiB, count=256, average=1463 B
/opt/tljh/user/lib/python3.9/re.py:210: size=277 KiB, count=181, average=1568 B

in the end notebook consumes ~4GB of memory after running through 6 days of data.

Could you please point me in the right direction?

#Update 1

changed code inside function to

def func(single_date):
    print(single_date.strftime("%Y-%m-%d"))
    date = single_date.strftime("%Y-%m-%d")
    req = Request('https://website.com/export?from_date='+date+'&to_date='+date+'&project_id=00000')
    req.add_header('Accept', 'text/plain')
    req.add_header( 'Authorization', 'Basic ==')
    with urlopen(req) as content:
        encoding = content.info().get_content_charset('utf-8')
        data_object = content.read()  # data is now bytes
        data = data_object.decode(encoding)
        df = pd.read_json(data, lines=True, orient='records')
        dfout = pd.concat([df.reset_index()[['event']],  
        pd.json_normalize(df['properties'])], axis=1)

but memory issue remains

#Update 2

also tried similar approach with requests, same result

    with requests.get(url, headers=headers) as r:
            df = pd.read_json(r.text, lines=True, orient='records')
            dfout = pd.concat([df.reset_index()[['event']], pd.json_normalize(df['properties'])], axis=1)

#Update 3

Figured out that issue is originating in pandas

1 Answers

urllib.request.urlopen is designed so it can be used as context manager (like open), for example

from urllib.request import urlopen
with urlopen("http://www.example.com") as content:
    data = content.read()  # data is now bytes
    print(data.decode())

this gives urlopen chance to free resources.

Related