Extracting compressed .gz and .bz2 s3 files automatically - python

I have a data dump from Wikipedia of about 30 files, each being about ~2.5 GB uncompressed size. I want to extract these files automatically, but as I understand I cannot use Lambda because it has file limitations.
I found another alternate solution of using SQS which will call EC2 instance, which I am working on. However, for that situation to work my script needs to read all zip files(.gz and .bz2) from S3 bucket and folders and extract them.
But on using zipfile module from python, I receive the following error:
zipfile.BadZipFile: File is not a zip file
Is there a solution to this?
This is my code:
import boto3
from io import BytesIO
import zipfile
s3_resource = boto3.resource('s3')
zip_obj = s3_resource.Object(bucket_name="backupwikiscrape", key= 'raw/enwiki-20200920-pages-articles-multistream1.xml-p1p41242.bz2')
buffer = BytesIO(zip_obj.get()["Body"].read())
z = zipfile.ZipFile(buffer)
for filename in z.namelist():
file_info = z.getinfo(filename)
s3_resource.meta.client.upload_fileobj(
z.open(filename),
Bucket='backupwikiextract',
Key=f'{filename}'
)
The above code doesn't seem to be able to extract the above formats. Any suggestions?

Your file is bz2, thus you should use bz2 python library.
To decompress your object:
decompressed_bytes = bz2.decompress(zip_obj.get()["Body"].read())

I'll suggest you to use smart_open, it's much easier. It both handle gz and bz2 files.
from smart_open import open
import boto3
s3_session = boto3.Session()
with open(path_to_my_file, transport_params={'session': s3_session}) as fin:
for line in fin:
print(line)

Related

Is it possible to read a specific file directly from a zip file that is stored on S3?

I have a file called story.txt in a zip file called big.zip that is stored in an S3 bucket called zips-bucket.
I want my Python code to read the content of just story.txt without downloading or even scanning the entire big zip file. Is it possible? How?
No, in your particular case it is not possible. However, S3 offers a functionality called S3 Select that can selectively read a portion of the file if some requirements are met. You can check out the documentation.
Yes, this is possible. You will need to import the smart-open and zipfile modules. Say your compressed file is in s3://zips-bucket/big.zip. Do the following:
import smart_open as so
import zipfile
with so.open('s3://zips-bucket/big.zip', 'rb') as file_data
with zipfile.ZipFile(file_data) as z:
with z.open('story.txt') as zip_file_data:
story_lines = zip_file_data.readlines()
And that should do it!

Read compressed binary file (.grib2.bz2)

I have downloaded one of the files from this list https://opendata.dwd.de/weather/nwp/icon-eu/grib/03/t_2m/ (the actual filenames change every day) which are bz2 compressed.
I can read in the decompressed file using e.g.
import xarray as xr
# cfgrib + dependencies are also required
grib1 = xr.open_dataset("icon-eu_europe_regular-lat-lon_single-level_2020101212_001_ASHFL_S.grib2", engine='cfgrib')
However, I would like to read in the compressed file.
I tried things like
with bz2.open("icon-eu_europe_regular-lat-lon_single-level_2020101818_002_ASWDIFD_S.grib2.bz2", "rb") as f:
xr.open_dataset(f, engine='cfgrib')
but this does not work.
I am looking for any way to programmatically read in the compressed file.
I had the same issue within processing numerical weather prediction data.
What I am doing here is to download the file and hold it as a Binary Object (e.g. with urlopen or requests). Pass this object into the following function:
import bz2, shutil
from io import BytesIO
from pathlib import Path
def bunzip_store(file: BytesIO, local_intermediate_file: Path):
with bz2.BZ2File(file) as fr, local_intermediate_file.open(mode="wb") as fw:
shutil.copyfileobj(fr, fw)
An unzipped file will be stored underlocal_intermediate_file. Now you should be able to open this file.

Python:Retrieve chunk of a zip file and process

I need to unzip large zip files (Approx. size ~10 GB) and put it back on S3. I have a memory limitation of 512 MB.
I tried this code and getting a MemoryError at line: 9 where it loads the entire file content into memory and hence the this memory error. How to retrieve a chunk of the zip file, unzip it and upload it back to S3?
import json
import boto3
import io
import zipfile
def lambda_handler(event, context):
s3_resource = boto3.resource('s3')
zip_obj = s3_resource.Object(bucket_name="bucket.name", key="test/big.zip")
buffer = io.BytesIO(zip_obj.get()["Body"].read())
z = zipfile.ZipFile(buffer)
for filename in z.namelist():
s3_resource.meta.client.upload_fileobj(
z.open(filename),
Bucket="bucket.name",
Key=f'{"test/" + filename}'
)
Please let me know
I would suggest launch an EC2 instance with a predefined script in UserData of run instance api using Lambda function, so that you can specify location file_name, etc in the script. In the script you can download zip from S3 and unzip using linux commands and then recursively upload entire folder to S3.
You can choose RAM/ROM as per your zip file size, post that you can stop you instance via the same script.

Limit on bz2 file decompression using python?

I have numerous files that are compressed in the bz2 format and I am trying to uncompress them in a temporary directory using python to then analyze. There are hundreds of thousands of files so manually decompressing the files isn't feasible so I wrote the following script.
My issue is that whenever I try to do this, the maximum file size is 900 kb even though a manual decompression has each file around 6 MB. I am not sure if this is a flaw in my code and how I am saving the data as a string to then copy to the file or a problem with something else. I have tried this with different files and I know that it works for files smaller than 900 kb. Has anyone else had a similar problem and knows of a solution?
My code is below:
import numpy as np
import bz2
import os
import glob
def unzip_f(filepath):
'''
Input a filepath specifying a group of Himiwari .bz2 files with common names
Outputs the path of all the temporary files that have been uncompressed
'''
cpath = os.getcwd() #get current path
filenames_ = [] #list to add filenames to for future use
for zipped_file in glob.glob(filepath): #loop over the files that meet the name criterea
with bz2.BZ2File(zipped_file,'rb') as zipfile: #Read in the bz2 files
newfilepath = cpath +'/temp/'+zipped_file[-47:-4] #create a temporary file
with open(newfilepath, "wb") as tmpfile: #open the temporary file
for i,line in enumerate(zipfile.readlines()):
tmpfile.write(line) #write the data from the compressed file to the temporary file
filenames_.append(newfilepath)
return filenames_
path_='test/HS_H08_20180930_0710_B13_FLDK_R20_S*bz2'
unzip_f(path_)
It returns the correct file paths with the wrong sizes capped at 900 kb.
It turns out this issue is due to the files being multi stream which does not work in python 2.7. There is more info here as mentioned by jasonharper and here. Below is a solution just using the Unix command to decompress the bz2 files and then moving them to the temporary directory I want. It is not as pretty but it works.
import numpy as np
import os
import glob
import shutil
def unzip_f(filepath):
'''
Input a filepath specifying a group of Himiwari .bz2 files with common names
Outputs the path of all the temporary files that have been uncompressed
'''
cpath = os.getcwd() #get current path
filenames_ = [] #list to add filenames to for future use
for zipped_file in glob.glob(filepath): #loop over the files that meet the name criterea
newfilepath = cpath +'/temp/' #create a temporary file
newfilename = newfilepath + zipped_file[-47:-4]
os.popen('bzip2 -kd ' + zipped_file)
shutil.move(zipped_file[-47:-4],newfilepath)
filenames_.append(newfilename)
return filenames_
path_='test/HS_H08_20180930_0710_B13_FLDK_R20_S0*bz2'
unzip_f(path_)
This is a known limitation in Python2, where the BZ2File class doesn't support multiple streams.
This can be easily resolved by using bz2file, https://pypi.org/project/bz2file/, which is a backport of Python3 implementation and can be used as a drop-in replacement.
After running pip install bz2file you can just replace bz2 with it:
import bz2file as bz2 and everything should just work :)
The original Python bug report: https://bugs.python.org/issue1625

How to read a jar file inside a zip file using Python

I have a large zip file that contains many jar files. I want to read the content of the jar files.
What I tried is to read the inner jar file into memory, which seem to work (see below). However, I am not sure how large the jar files can be, and am concerned that they won't fit into memory.
Is there streaming solution for this problem?
hello.zip
+- hello.jar
+- Hello.class
#!/usr/local/bin/python3
import os
import io
import zipfile
zip = zipfile.ZipFile('hello.zip', 'r')
for zipname in zip.namelist():
if zipname.endswith('.jar'):
print(zipname)
jarname = zip.read(zipname)
memfile = io.BytesIO(jarname)
jar = zipfile.ZipFile(memfile)
for f in jar.namelist():
print(f)
hello.jar
META-INF/
META-INF/MANIFEST.MF
Hello.class

Categories

Resources