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.
- internet_archive_extractor-0.0.1/PKG-INFO +72 -0
- internet_archive_extractor-0.0.1/README.md +46 -0
- internet_archive_extractor-0.0.1/pyproject.toml +33 -0
- internet_archive_extractor-0.0.1/setup.cfg +4 -0
- internet_archive_extractor-0.0.1/src/internet_archive_downloader.py +106 -0
- internet_archive_extractor-0.0.1/src/internet_archive_extractor.egg-info/PKG-INFO +72 -0
- internet_archive_extractor-0.0.1/src/internet_archive_extractor.egg-info/SOURCES.txt +12 -0
- internet_archive_extractor-0.0.1/src/internet_archive_extractor.egg-info/dependency_links.txt +1 -0
- internet_archive_extractor-0.0.1/src/internet_archive_extractor.egg-info/requires.txt +16 -0
- internet_archive_extractor-0.0.1/src/internet_archive_extractor.egg-info/top_level.txt +5 -0
- internet_archive_extractor-0.0.1/src/main.py +56 -0
- internet_archive_extractor-0.0.1/src/utils.py +45 -0
- internet_archive_extractor-0.0.1/src/wayback_date_object.py +89 -0
- internet_archive_extractor-0.0.1/src/waybackup_to_warc.py +209 -0
|
@@ -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,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 @@
|
|
|
1
|
+
|
|
@@ -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()
|