internet-archive-extractor 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: internet-archive-extractor
3
+ Version: 0.0.1
4
+ Summary: Tool for extracting archived web sites from the Internet Archive saving as WARC files.
5
+ Author-email: Victor Harbo Johnston <vijo@cas.au.dk>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/WEB-CHILD/InternetArchiveExtractor
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: certifi
11
+ Requires-Dist: charset-normalizer
12
+ Requires-Dist: idna
13
+ Requires-Dist: numpy
14
+ Requires-Dist: pandas
15
+ Requires-Dist: pysqlite3
16
+ Requires-Dist: python-dateutil
17
+ Requires-Dist: python-magic
18
+ Requires-Dist: pytz
19
+ Requires-Dist: pywaybackup
20
+ Requires-Dist: requests
21
+ Requires-Dist: six
22
+ Requires-Dist: tqdm
23
+ Requires-Dist: tzdata
24
+ Requires-Dist: urllib3
25
+ Requires-Dist: warcio
26
+
27
+ # The project currently only supports converting output from Bitdruids WaybackUp program to be converted to WARC files.
28
+
29
+ ## WaybackupToWarc
30
+
31
+ This project is designed to read a CSV file containing URLs, remove any instances of port 80 from those URLs, and generate WARC-GZ files based on the cleaned data.
32
+ The CSV file can be constructed by using the following tool: [Python Wayback Machine Downloader](https://github.com/bitdruid/python-wayback-machine-downloader)
33
+
34
+ ## Project Structure
35
+
36
+ ```
37
+ WaybackupToWarc
38
+ ├── src
39
+ │ ├── main.py # Main script for processing CSV and generating WARC files
40
+ │ └── utils.py # Utility functions for reading CSV and modifying URLs
41
+ ├── requirements.txt # List of dependencies for the project
42
+ └── README.md # Documentation for the project
43
+ ```
44
+
45
+ ## Requirements
46
+
47
+ To run this project, you need to install the following dependencies:
48
+
49
+ - `warcio`: For creating WARC files.
50
+ - `pandas`: For handling CSV data.
51
+
52
+ You can install the required packages using pip:
53
+
54
+ ```
55
+ pip install -r requirements.txt
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ 1. Place your CSV file in the appropriate directory.
61
+ 2. Update the `src/main.py` file to specify the path to your CSV file.
62
+ 3. Run the main script:
63
+
64
+ ```
65
+ python src/main.py
66
+ ```
67
+
68
+ This will process the CSV file, remove port 80 from the URLs, and generate the corresponding WARC-GZ files.
69
+
70
+ ## Contributing
71
+
72
+ Feel free to submit issues or pull requests if you have suggestions or improvements for the project.
@@ -0,0 +1,46 @@
1
+ # The project currently only supports converting output from Bitdruids WaybackUp program to be converted to WARC files.
2
+
3
+ ## WaybackupToWarc
4
+
5
+ This project is designed to read a CSV file containing URLs, remove any instances of port 80 from those URLs, and generate WARC-GZ files based on the cleaned data.
6
+ The CSV file can be constructed by using the following tool: [Python Wayback Machine Downloader](https://github.com/bitdruid/python-wayback-machine-downloader)
7
+
8
+ ## Project Structure
9
+
10
+ ```
11
+ WaybackupToWarc
12
+ ├── src
13
+ │ ├── main.py # Main script for processing CSV and generating WARC files
14
+ │ └── utils.py # Utility functions for reading CSV and modifying URLs
15
+ ├── requirements.txt # List of dependencies for the project
16
+ └── README.md # Documentation for the project
17
+ ```
18
+
19
+ ## Requirements
20
+
21
+ To run this project, you need to install the following dependencies:
22
+
23
+ - `warcio`: For creating WARC files.
24
+ - `pandas`: For handling CSV data.
25
+
26
+ You can install the required packages using pip:
27
+
28
+ ```
29
+ pip install -r requirements.txt
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ 1. Place your CSV file in the appropriate directory.
35
+ 2. Update the `src/main.py` file to specify the path to your CSV file.
36
+ 3. Run the main script:
37
+
38
+ ```
39
+ python src/main.py
40
+ ```
41
+
42
+ This will process the CSV file, remove port 80 from the URLs, and generate the corresponding WARC-GZ files.
43
+
44
+ ## Contributing
45
+
46
+ Feel free to submit issues or pull requests if you have suggestions or improvements for the project.
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "internet-archive-extractor"
7
+ version = "0.0.1"
8
+ description = "Tool for extracting archived web sites from the Internet Archive saving as WARC files."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "Victor Harbo Johnston", email = "vijo@cas.au.dk" }]
12
+ requires-python = ">=3.8"
13
+ dependencies = [
14
+ "certifi",
15
+ "charset-normalizer",
16
+ "idna",
17
+ "numpy",
18
+ "pandas",
19
+ "pysqlite3",
20
+ "python-dateutil",
21
+ "python-magic",
22
+ "pytz",
23
+ "pywaybackup",
24
+ "requests",
25
+ "six",
26
+ "tqdm",
27
+ "tzdata",
28
+ "urllib3",
29
+ "warcio"
30
+ ]
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/WEB-CHILD/InternetArchiveExtractor"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,106 @@
1
+ from pywaybackup import PyWayBackup
2
+ from wayback_date_object import WaybackDateObject
3
+ import re
4
+
5
+ from utils import import_urls_from_csv
6
+
7
+
8
+ def get_wayback_date_and_archived_url(wayback_url: str):
9
+ """
10
+ Extracts the archive date and archived URL from a Wayback Machine URL.
11
+
12
+ Args:
13
+ wayback_url (str): The URL from the Wayback Machine in the format
14
+ 'https://web.archive.org/web/<timestamp>/<archived_url>'.
15
+
16
+ Returns:
17
+ tuple: A tuple containing:
18
+ - date (WaybackDateObject): The extracted date as a WaybackDateObject.
19
+ - archived_url (str): The original URL archived by the Wayback Machine.
20
+
21
+ Raises:
22
+ AttributeError: If the input URL does not match the expected Wayback Machine format.
23
+ """
24
+ match = re.match(r"https://web\.archive\.org/web/(\d+)/(.*)", wayback_url)
25
+ if match:
26
+ date = WaybackDateObject(match.group(1))
27
+ archived_url = match.group(2)
28
+ return date, archived_url
29
+
30
+ def download_urls_from_csv(csv_file_path: str, url_column_name: str):
31
+ """
32
+ Reads a CSV file containing Internet Archive URLs (eg. https://web.archive.org/web/20251002062751/https://cas.au.dk/erc-webchild),
33
+ retrieves their corresponding Wayback Machine archived URLs and dates, and downloads the archived content for each URL for a period of two weeks around the archived date.
34
+
35
+ Args:
36
+ csv_file_path (str): The file path to the CSV file containing the Internet Archive URLs.
37
+ url_column_name (str): The name of the column in the CSV file that contains the URLs.
38
+
39
+ Returns:
40
+ None
41
+
42
+ Side Effects:
43
+ - Downloads archived content for each URL from the Wayback Machine.
44
+ - Handles and prints TypeError exceptions that may occur during download.
45
+ """
46
+ internet_archive_urls = import_urls_from_csv(csv_file_path, url_column_name)
47
+
48
+ for url in internet_archive_urls:
49
+ wayback_date, archived_url = get_wayback_date_and_archived_url(url)
50
+
51
+ end_date = WaybackDateObject(wayback_date.wayback_format())
52
+ end_date.increment_week()
53
+
54
+ start_date = WaybackDateObject(wayback_date.wayback_format())
55
+ start_date.decrement_week()
56
+
57
+ try:
58
+ download_single_url(archived_url, start_date.wayback_format(), end_date.wayback_format())
59
+ except TypeError as e:
60
+ print()
61
+
62
+
63
+ def download_single_url(url: str, start_date: str, end_date: str):
64
+ """
65
+ Downloads all available snapshots of a given URL from the Internet Archive's Wayback Machine within a specified date range.
66
+
67
+ Args:
68
+ url (str): The URL to download snapshots for.
69
+ start_date (str): The start date (inclusive) in 'YYYYMMDD' format.
70
+ end_date (str): The end date (inclusive) in 'YYYYMMDD' format.
71
+
72
+ Returns:
73
+ None
74
+
75
+ Side Effects:
76
+ - Prints progress and debug information to the console.
77
+ - Downloads and saves the snapshots to disk.
78
+ - Prints the relative paths of the downloaded snapshots.
79
+ """
80
+
81
+ print(f"Downloading {url} from {start_date} to {end_date}")
82
+ backup = PyWayBackup(
83
+ url=url,
84
+ all=True,
85
+ start=start_date,
86
+ end=end_date,
87
+ silent=False,
88
+ debug=True,
89
+ log=True,
90
+ keep=True,
91
+ workers=1,
92
+ reset=False,
93
+ explicit=True
94
+ )
95
+
96
+ backup.run()
97
+ backup_paths = backup.paths(rel=True)
98
+ print(backup_paths)
99
+
100
+
101
+ def main():
102
+ # Currently only doesnt support other files than the one presented here. Just need convertng to useing arguments.
103
+ download_urls_from_csv("./resources/curated_urls.csv", "Internet_Archive_URL")
104
+
105
+ if __name__ == "__main__":
106
+ main()
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: internet-archive-extractor
3
+ Version: 0.0.1
4
+ Summary: Tool for extracting archived web sites from the Internet Archive saving as WARC files.
5
+ Author-email: Victor Harbo Johnston <vijo@cas.au.dk>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/WEB-CHILD/InternetArchiveExtractor
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: certifi
11
+ Requires-Dist: charset-normalizer
12
+ Requires-Dist: idna
13
+ Requires-Dist: numpy
14
+ Requires-Dist: pandas
15
+ Requires-Dist: pysqlite3
16
+ Requires-Dist: python-dateutil
17
+ Requires-Dist: python-magic
18
+ Requires-Dist: pytz
19
+ Requires-Dist: pywaybackup
20
+ Requires-Dist: requests
21
+ Requires-Dist: six
22
+ Requires-Dist: tqdm
23
+ Requires-Dist: tzdata
24
+ Requires-Dist: urllib3
25
+ Requires-Dist: warcio
26
+
27
+ # The project currently only supports converting output from Bitdruids WaybackUp program to be converted to WARC files.
28
+
29
+ ## WaybackupToWarc
30
+
31
+ This project is designed to read a CSV file containing URLs, remove any instances of port 80 from those URLs, and generate WARC-GZ files based on the cleaned data.
32
+ The CSV file can be constructed by using the following tool: [Python Wayback Machine Downloader](https://github.com/bitdruid/python-wayback-machine-downloader)
33
+
34
+ ## Project Structure
35
+
36
+ ```
37
+ WaybackupToWarc
38
+ ├── src
39
+ │ ├── main.py # Main script for processing CSV and generating WARC files
40
+ │ └── utils.py # Utility functions for reading CSV and modifying URLs
41
+ ├── requirements.txt # List of dependencies for the project
42
+ └── README.md # Documentation for the project
43
+ ```
44
+
45
+ ## Requirements
46
+
47
+ To run this project, you need to install the following dependencies:
48
+
49
+ - `warcio`: For creating WARC files.
50
+ - `pandas`: For handling CSV data.
51
+
52
+ You can install the required packages using pip:
53
+
54
+ ```
55
+ pip install -r requirements.txt
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ 1. Place your CSV file in the appropriate directory.
61
+ 2. Update the `src/main.py` file to specify the path to your CSV file.
62
+ 3. Run the main script:
63
+
64
+ ```
65
+ python src/main.py
66
+ ```
67
+
68
+ This will process the CSV file, remove port 80 from the URLs, and generate the corresponding WARC-GZ files.
69
+
70
+ ## Contributing
71
+
72
+ Feel free to submit issues or pull requests if you have suggestions or improvements for the project.
@@ -0,0 +1,12 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/internet_archive_downloader.py
4
+ src/main.py
5
+ src/utils.py
6
+ src/wayback_date_object.py
7
+ src/waybackup_to_warc.py
8
+ src/internet_archive_extractor.egg-info/PKG-INFO
9
+ src/internet_archive_extractor.egg-info/SOURCES.txt
10
+ src/internet_archive_extractor.egg-info/dependency_links.txt
11
+ src/internet_archive_extractor.egg-info/requires.txt
12
+ src/internet_archive_extractor.egg-info/top_level.txt
@@ -0,0 +1,16 @@
1
+ certifi
2
+ charset-normalizer
3
+ idna
4
+ numpy
5
+ pandas
6
+ pysqlite3
7
+ python-dateutil
8
+ python-magic
9
+ pytz
10
+ pywaybackup
11
+ requests
12
+ six
13
+ tqdm
14
+ tzdata
15
+ urllib3
16
+ warcio
@@ -0,0 +1,5 @@
1
+ internet_archive_downloader
2
+ main
3
+ utils
4
+ wayback_date_object
5
+ waybackup_to_warc
@@ -0,0 +1,56 @@
1
+ import sys
2
+ import argparse
3
+ from enum import Enum
4
+ from waybackup_to_warc import combine_csv_files, process_csv_file, COMBINED_CSV_PATH
5
+ from internet_archive_downloader import download_urls_from_csv
6
+
7
+ parser = argparse.ArgumentParser(description="Internet Archive Extractor")
8
+
9
+ parser.add_argument("mode", help="The mode to run the script in: 'download', 'convert' or 'full'.")
10
+ parser.add_argument("input", help="The input file or directory path.")
11
+ parser.add_argument("--output", help="The output file name for the generated WARC file. Only applicable for modes: 'convert' or 'full'.")
12
+ parser.add_argument("--column_name", default="Internet_Archive_URL", help="The column name in the CSV file that contains the URLs for download. Default is 'Internet_Archive_URL'.")
13
+
14
+ class Mode(Enum):
15
+ """
16
+ Enum for the different modes of operation.
17
+ """
18
+ FULL = 1
19
+ DOWNLOAD = 2
20
+ CONVERT = 3
21
+
22
+ args = parser.parse_args()
23
+
24
+ try:
25
+ Mode(args.mode.upper())
26
+ except ValueError:
27
+ try:
28
+ Mode[args.mode.upper()]
29
+ except KeyError:
30
+ print(f"Invalid mode: {args.mode}. Choose from 'download', 'convert' or 'full'.")
31
+ sys.exit(1)
32
+
33
+ def choose_mode():
34
+ if args.mode.upper() == Mode.DOWNLOAD.name:
35
+ print("Download mode selected.")
36
+ download_urls_from_csv(args.input, args.column_name)
37
+ elif args.mode.upper() == Mode.CONVERT.name:
38
+ print("Convert mode selected.")
39
+ combine_csv_files(args.input, COMBINED_CSV_PATH)
40
+ process_csv_file(COMBINED_CSV_PATH, 'output', args.output)
41
+ elif args.mode.upper() == Mode.FULL.name:
42
+ print("Full mode selected.")
43
+
44
+ download_urls_from_csv(args.input, args.column_name)
45
+ combine_csv_files("waybackup_snapshots", COMBINED_CSV_PATH)
46
+ process_csv_file(COMBINED_CSV_PATH, 'output', args.output)
47
+ else:
48
+ print(f"Invalid mode: {args.mode}. Choose from 'download', 'convert' or 'full'.")
49
+ sys.exit(1)
50
+
51
+ def main():
52
+ choose_mode()
53
+
54
+
55
+ if __name__ == "__main__":
56
+ main()
@@ -0,0 +1,45 @@
1
+ def read_csv(file_path):
2
+ import pandas as pd
3
+ return pd.read_csv(file_path, sep=";")
4
+
5
+ def remove_port_80(url):
6
+ if ':80' in url:
7
+ return url.replace(':80', '')
8
+ return url
9
+
10
+ def clean_urls(dataframe):
11
+ dataframe['url_archive'] = dataframe['url_archive'].apply(lambda x: remove_port_80(x))
12
+ dataframe['url_origin'] = dataframe['url_origin'].apply(lambda x: remove_port_80(x))
13
+ return dataframe
14
+
15
+ def create_warc_gz(file_path, dataframe):
16
+ from warcio.archiveiterator import ArchiveIterator
17
+ from warcio.warcwriter import WARCWriter
18
+ import gzip
19
+
20
+ with gzip.open(file_path, 'wb') as stream:
21
+ writer = WARCWriter(stream, gzip=True)
22
+ for index, row in dataframe.iterrows():
23
+ writer.write_webpage(row['url_origin'], row['timestamp'], content_type='text/html')
24
+ writer.write_webpage(row['url_archive'], row['timestamp'], content_type='text/html')
25
+
26
+ def import_urls_from_csv(file_path, column_name):
27
+ """
28
+ Imports URLs from a specified column in a CSV file.
29
+
30
+ Args:
31
+ file_path (str): The path to the CSV file.
32
+ column_name (str): The name of the column containing URLs.
33
+
34
+ Returns:
35
+ list: A list of URLs extracted from the specified column.
36
+
37
+ Raises:
38
+ FileNotFoundError: If the specified file does not exist.
39
+ KeyError: If the specified column does not exist in the CSV file.
40
+ pd.errors.EmptyDataError: If the CSV file is empty.
41
+ """
42
+
43
+ df = read_csv(file_path)
44
+ url_list = df[column_name].tolist()
45
+ return url_list
@@ -0,0 +1,89 @@
1
+ from datetime import datetime, timedelta
2
+
3
+
4
+ class WaybackDateObject:
5
+ def __init__(self, waybackdate_str):
6
+ self.year = waybackdate_str[0:4]
7
+ self.month = waybackdate_str[4:6]
8
+ self.day = waybackdate_str[6:8]
9
+ self.hour = waybackdate_str[8:10]
10
+ self.minute = waybackdate_str[10:12]
11
+ self.second = waybackdate_str[12:14]
12
+
13
+ @classmethod
14
+ def from_values(self, year, month, day, hour, minute, second):
15
+ self.year = year
16
+ self.month = month
17
+ self.day = day
18
+ self.hour = hour
19
+ self.minute = minute
20
+ self.second = second
21
+
22
+ def pretty_print(self):
23
+ """
24
+ Returns a human-readable string representation of the date and time.
25
+ Example: '2003-04-09 19:30:11'
26
+ """
27
+ return f"{self.year}-{self.month}-{self.day} {self.hour}:{self.minute}:{self.second}"
28
+
29
+ def wayback_format(self):
30
+ """
31
+ Returns the date and time in the original Wayback date format.
32
+ Example: '20030409193011'
33
+ """
34
+ return f"{self.year}{self.month}{self.day}{self.hour}{self.minute}{self.second}"
35
+
36
+ def increment_day(self):
37
+ """
38
+ Increments the day by one, adjusting month and year as necessary.
39
+ Note: This method does not account for leap years or varying month lengths.
40
+ """
41
+ day = int(self.day)
42
+ month = int(self.month)
43
+ year = int(self.year)
44
+
45
+ # Days in each month (not accounting for leap years)
46
+ days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
47
+
48
+ day += 1
49
+ if day > days_in_month[month - 1]:
50
+ day = 1
51
+ month += 1
52
+ if month > 12:
53
+ month = 1
54
+ year += 1
55
+
56
+ self.day = f"{day:02d}"
57
+ self.month = f"{month:02d}"
58
+ self.year = str(year)
59
+
60
+ def to_datetime(self):
61
+ """Converts the WaybackDateObject to a Python datetime object."""
62
+ return datetime(
63
+ int(self.year), int(self.month), int(self.day),
64
+ int(self.hour), int(self.minute), int(self.second)
65
+ )
66
+
67
+ def from_datetime(self, dt):
68
+ """Updates the WaybackDateObject from a Python datetime object."""
69
+ self.year = f"{dt.year:04d}"
70
+ self.month = f"{dt.month:02d}"
71
+ self.day = f"{dt.day:02d}"
72
+ self.hour = f"{dt.hour:02d}"
73
+ self.minute = f"{dt.minute:02d}"
74
+ self.second = f"{dt.second:02d}"
75
+
76
+ def increment_week(self):
77
+ """
78
+ Increments the date by 7 days.
79
+ """
80
+ dt = self.to_datetime() + timedelta(days=7)
81
+ self.from_datetime(dt)
82
+
83
+ def decrement_week(self):
84
+ """
85
+ Decrements the date by 7 days.
86
+ """
87
+ dt = self.to_datetime() - timedelta(days=7)
88
+ self.from_datetime(dt)
89
+
@@ -0,0 +1,209 @@
1
+ import glob
2
+ import os
3
+ import sys
4
+ import pandas as pd
5
+ from warcio.archiveiterator import ArchiveIterator
6
+ from warcio.warcwriter import WARCWriter
7
+ from warcio.recordloader import ArcWarcRecord
8
+ from warcio.statusandheaders import StatusAndHeaders
9
+ import csv
10
+ from datetime import datetime
11
+
12
+
13
+ COMBINED_CSV_PATH = "combined_output.csv"
14
+
15
+ def remove_port_80(url):
16
+ if ":80" in url:
17
+ return url.replace(":80", "")
18
+ return url
19
+
20
+ def process_csv(file_path):
21
+ data = pd.read_csv(file_path)
22
+ data['url_archive'] = data['url_archive'].apply(remove_port_80)
23
+ data['url_origin'] = data['url_origin'].apply(remove_port_80)
24
+ return data
25
+
26
+ def write_404_warc_entry(writer, url, warc_date):
27
+ http_headers = StatusAndHeaders('404 Not Found', [('Content-Type', 'text/html')], protocol='HTTP/1.0')
28
+ warc_type = 'response'
29
+ record = writer.create_warc_record(
30
+ url,
31
+ warc_type,
32
+ payload = None,
33
+ http_headers = http_headers,
34
+ warc_headers_dict={'WARC-Date': warc_date} if warc_date else None
35
+ )
36
+ print(f"Writing 404 record for URL: {url}")
37
+ writer.write_record(record)
38
+
39
+ def write_500_warc_entry(writer, url, warc_date):
40
+ http_headers = StatusAndHeaders('500 Internal Server Error', [('Content-Type', 'text/html')], protocol='HTTP/1.0')
41
+ warc_type = 'response'
42
+ record = writer.create_warc_record(
43
+ url,
44
+ warc_type,
45
+ payload = None,
46
+ http_headers = http_headers,
47
+ warc_headers_dict={'WARC-Date': warc_date} if warc_date else None
48
+ )
49
+ print(f"Writing 500 record for URL: {url}")
50
+ writer.write_record(record)
51
+
52
+ def create_warc_gz(data, output_dir, output_filename):
53
+ """
54
+ Creates a compressed WARC (Web ARChive) file (.warc.gz) from a list of data entries.
55
+ Each entry in `data` should be a dictionary containing at least the following keys:
56
+ - 'url_origin': The original URL of the resource.
57
+ - 'file': The local file path to the resource content.
58
+ - 'timestamp': The timestamp of the capture in 'YYYYMMDDHHMMSS' format.
59
+ - 'response': The HTTP response code as a string or integer (e.g., '200', '404', '500').
60
+ The function processes each entry and writes a corresponding WARC record:
61
+ - For HTTP 404 and 500 responses, special WARC records are created using helper functions.
62
+ - For successful responses (HTTP 200), the content is read from the specified file and written as a WARC response record.
63
+ - The content type is inferred from the file extension.
64
+ - If the file does not exist, the entry is skipped and a warning is printed.
65
+ Parameters:
66
+ data (list of dict): List of dictionaries containing resource metadata and file paths.
67
+ output_dir (str): Directory where the output WARC file will be saved.
68
+ output_filename (str): Base name for the output WARC file (without extension).
69
+ Side Effects:
70
+ - Creates the output directory if it does not exist.
71
+ - Writes a compressed WARC file to disk.
72
+ - Prints progress and summary information to stdout.
73
+ Returns:
74
+ None
75
+ """
76
+
77
+ total_counter = 0
78
+ success_counter = 0
79
+ internal_service_error_counter = 0
80
+ not_found_counter = 0
81
+ os.makedirs(output_dir, exist_ok=True)
82
+ warc_path = os.path.join(output_dir, output_filename + '.warc.gz')
83
+ with open(warc_path, 'wb') as stream:
84
+ writer = WARCWriter(stream, gzip=True)
85
+ for row in data:
86
+ total_counter += 1
87
+ response_code = str(row['response']).strip()
88
+ url = row['url_origin']
89
+ file_path = row['file']
90
+
91
+ if total_counter % 5000 == 0:
92
+ print(f"Processing entry number: {total_counter}:")
93
+ # Convert timestamp to ISO 8601 format for WARC-Date
94
+ try:
95
+ dt = datetime.strptime(row['timestamp'].strip(), "%Y%m%d%H%M%S")
96
+ warc_date = dt.strftime("%Y-%m-%dT%H:%M:%SZ")
97
+ except Exception as e:
98
+ print(f"Invalid timestamp {row['timestamp']}: {e}")
99
+ warc_date = None
100
+
101
+ # As 404 AND 500
102
+
103
+ if response_code == '404':
104
+ write_404_warc_entry(writer, url, warc_date)
105
+ not_found_counter += 1
106
+ continue
107
+ elif response_code == '500':
108
+ write_500_warc_entry(writer, url, warc_date)
109
+ internal_service_error_counter += 1
110
+ continue
111
+
112
+
113
+ if not os.path.isfile(file_path):
114
+ print(f"File not found: {file_path}")
115
+ print(f"Timestamp for URL is: {row['timestamp']}")
116
+ print(f"Response code is: {row['response']}")
117
+ # TODO: If response code is 404 a 404 record should be created
118
+ # TODO: If response code is 500 a 500 record should be created etc.
119
+ continue
120
+
121
+ # Set content type based on file extension (simple default)
122
+ content_type = 'text/html'
123
+ ext = os.path.splitext(file_path)[1].lower()
124
+ if ext in ['.jpg', '.jpeg']:
125
+ content_type = 'image/jpeg'
126
+ elif ext == '.png':
127
+ content_type = 'image/png'
128
+ elif ext == '.gif':
129
+ content_type = 'image/gif'
130
+ elif ext == '.css':
131
+ content_type = 'text/css'
132
+ elif ext == '.js':
133
+ content_type = 'application/javascript'
134
+ elif ext == '.pdf':
135
+ content_type = 'application/pdf'
136
+ elif ext == '.txt':
137
+ content_type = 'text/plain'
138
+
139
+
140
+ http_headers = StatusAndHeaders('200 OK', [('Content-Type', content_type)], protocol='HTTP/1.0')
141
+ warc_type = 'response'
142
+
143
+ with open(file_path, 'rb') as payload:
144
+ record = writer.create_warc_record(
145
+ url,
146
+ warc_type,
147
+ payload=payload,
148
+ http_headers=http_headers,
149
+ warc_headers_dict={'WARC-Date': warc_date} if warc_date else None
150
+ )
151
+ writer.write_record(record)
152
+ success_counter += 1
153
+
154
+
155
+ print(
156
+ f"\nWARC creation summary:\n"
157
+ f" Successful records: {success_counter}\n"
158
+ f" Not found (404): {not_found_counter}\n"
159
+ f" Internal errors (500): {internal_service_error_counter}\n"
160
+ f" Total processed: {len(data)}"
161
+ )
162
+
163
+ def read_csv(input_csv):
164
+ with open(input_csv, newline='', encoding='utf-8') as csvfile:
165
+ reader = csv.DictReader(csvfile)
166
+ return list(reader)
167
+
168
+ def process_csv_file(csv_file_path, output_dir, output_filename):
169
+ data = read_csv(csv_file_path)
170
+ create_warc_gz(data, output_dir, output_filename)
171
+
172
+ def combine_csv_files(input_directory, output_file):
173
+ """
174
+ Combines all CSV files in the specified directory into a single CSV file.
175
+ This is used to aggregate the multiple CSV files constructed when downloading content from the Internet Archive into one for further processing into WARC files.
176
+ """
177
+ # Find all CSV files in the directory
178
+ csv_files = glob.glob(os.path.join(input_directory, "*.csv"))
179
+ # Read and concatenate all CSV files
180
+ df_list = [pd.read_csv(f) for f in csv_files]
181
+ combined_df = pd.concat(df_list, ignore_index=True)
182
+ # Write the combined DataFrame to a new CSV file
183
+ combined_df.to_csv(output_file, index=False)
184
+ print(f"Combined {len(csv_files)} files into {output_file}")
185
+
186
+
187
+ def main():
188
+ """
189
+ Main entry point for the script. Expects two command-line arguments: the path to a directory with multiple CSV files and the desired filename for the output warc.gx file.
190
+ - Combines CSV files from the provided path into a single file with the name 'combined_output.csv'.
191
+ - Processes the combined CSV file and generates a warc.gz file using the specified output filename.
192
+ Usage:
193
+ python main.py <csv_file_path> <output_filename>
194
+ """
195
+
196
+ if len(sys.argv) < 3:
197
+ print("Usage: python main.py <csv_file_path> <output_filename>")
198
+ sys.exit(1)
199
+
200
+ csv_file_path = sys.argv[1]
201
+ output_filename = sys.argv[2]
202
+
203
+ combine_csv_files(csv_file_path, COMBINED_CSV_PATH)
204
+ process_csv_file(COMBINED_CSV_PATH, 'output', output_filename)
205
+
206
+
207
+
208
+ if __name__ == "__main__":
209
+ main()