pywaybackup 0.6.3__tar.gz → 0.7.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.
- {pywaybackup-0.6.3/pywaybackup.egg-info → pywaybackup-0.7.1}/PKG-INFO +9 -10
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/README.md +8 -9
- pywaybackup-0.7.1/pywaybackup/SnapshotCollection.py +110 -0
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/pywaybackup/Verbosity.py +13 -8
- pywaybackup-0.7.1/pywaybackup/__version__.py +1 -0
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/pywaybackup/archive.py +111 -124
- pywaybackup-0.6.3/pywaybackup/main.py → pywaybackup-0.7.1/pywaybackup/arguments.py +7 -29
- pywaybackup-0.7.1/pywaybackup/main.py +31 -0
- {pywaybackup-0.6.3 → pywaybackup-0.7.1/pywaybackup.egg-info}/PKG-INFO +9 -10
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/pywaybackup.egg-info/SOURCES.txt +1 -0
- pywaybackup-0.7.1/requirements.txt +3 -0
- pywaybackup-0.6.3/pywaybackup/SnapshotCollection.py +0 -83
- pywaybackup-0.6.3/pywaybackup/__version__.py +0 -1
- pywaybackup-0.6.3/requirements.txt +0 -2
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/.gitignore +0 -0
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/LICENSE +0 -0
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/dev/pip_build.sh +0 -0
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/dev/venv_create.sh +0 -0
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/pywaybackup/__init__.py +0 -0
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/pywaybackup.egg-info/dependency_links.txt +0 -0
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/pywaybackup.egg-info/entry_points.txt +0 -0
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/pywaybackup.egg-info/requires.txt +0 -0
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/pywaybackup.egg-info/top_level.txt +0 -0
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/setup.cfg +0 -0
- {pywaybackup-0.6.3 → pywaybackup-0.7.1}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pywaybackup
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Download snapshots from the Wayback Machine
|
|
5
5
|
Home-page: https://github.com/bitdruid/python-wayback-machine-downloader
|
|
6
6
|
Author: bitdruid
|
|
@@ -14,7 +14,7 @@ License-File: LICENSE
|
|
|
14
14
|
|
|
15
15
|
[](https://pypi.org/project/pywaybackup/)
|
|
16
16
|
[](https://pypi.org/project/pywaybackup/)
|
|
17
|
-

|
|
18
18
|

|
|
19
19
|
[](https://opensource.org/licenses/MIT)
|
|
20
20
|
|
|
@@ -43,6 +43,8 @@ Internet-archive is a nice source for several OSINT-information. This script is
|
|
|
43
43
|
|
|
44
44
|
This script allows you to download content from the Wayback Machine (archive.org). You can use it to download either the latest version or all versions of web page snapshots within a specified range.
|
|
45
45
|
|
|
46
|
+
<!-- ## Info -->
|
|
47
|
+
|
|
46
48
|
### Arguments
|
|
47
49
|
|
|
48
50
|
- `-h`, `--help`: Show the help message and exit.
|
|
@@ -54,8 +56,8 @@ This script allows you to download content from the Wayback Machine (archive.org
|
|
|
54
56
|
|
|
55
57
|
#### Mode Selection (Choose One)
|
|
56
58
|
|
|
57
|
-
- `-c`, `--current`: Download the latest version of each file snapshot.
|
|
58
|
-
- `-f`, `--full`: Download snapshots of all timestamps.
|
|
59
|
+
- `-c`, `--current`: Download the latest version of each file snapshot. You will get a rebuild of the current website with all available files.
|
|
60
|
+
- `-f`, `--full`: Download snapshots of all timestamps. You will get a folder per timestamp with the files available at that time.
|
|
59
61
|
- `-s`, `--save`: Save a page to the Wayback Machine. (beta)
|
|
60
62
|
|
|
61
63
|
#### Optional Arguments
|
|
@@ -73,6 +75,7 @@ Specify the range in years or a specific timestamp either start, end or both. If
|
|
|
73
75
|
|
|
74
76
|
#### Additional
|
|
75
77
|
|
|
78
|
+
- `--no-redirect`: Do not follow redirects of snapshots. Archive.org sometimes redirects to a different snapshot for several reasons. Downloading redirects may lead to timestamp-folders which contain some files with a different timestamp. This does not matter if you only want to download the latest version (`-c`).
|
|
76
79
|
- `--verbosity [LEVEL]`: Set the verbosity: json (print json response), progress (show progress bar) or standard (default).
|
|
77
80
|
- `--retry [RETRY_FAILED]`: Retry failed downloads. You can specify the number of retry attempts as an integer.
|
|
78
81
|
- `--worker [AMOUNT]`: The number of worker to use for downloading (simultaneous downloads). Default is 1. Beware: Using too many worker will lead into refused connections from the Wayback Machine. Duration about 1.5 minutes.
|
|
@@ -85,8 +88,8 @@ Download latest snapshot of all files:<br>
|
|
|
85
88
|
Download latest snapshot of all files with retries:<br>
|
|
86
89
|
`waybackup -u http://example.com -c --retry 3`
|
|
87
90
|
|
|
88
|
-
Download all snapshots sorted per timestamp with a specified range:<br>
|
|
89
|
-
`waybackup -u http://example.com -f -r 5`
|
|
91
|
+
Download all snapshots sorted per timestamp with a specified range and follow redirects:<br>
|
|
92
|
+
`waybackup -u http://example.com -f -r 5 --redirect`
|
|
90
93
|
|
|
91
94
|
Download all snapshots sorted per timestamp with a specified range and save to a specified folder with 3 worker:<br>
|
|
92
95
|
`waybackup -u http://example.com -f -r 5 -o /home/user/Downloads/snapshots --worker 3`
|
|
@@ -94,10 +97,6 @@ Download all snapshots sorted per timestamp with a specified range and save to a
|
|
|
94
97
|
List available snapshots per timestamp without downloading:<br>
|
|
95
98
|
`waybackup -u http://example.com -f -l`
|
|
96
99
|
|
|
97
|
-
## Info
|
|
98
|
-
|
|
99
|
-
The script automatically follows redirects of snapshots.
|
|
100
|
-
|
|
101
100
|
## Contributing
|
|
102
101
|
|
|
103
102
|
I'm always happy for some feature requests to improve the usability of this script.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/pywaybackup/)
|
|
4
4
|
[](https://pypi.org/project/pywaybackup/)
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
|
|
@@ -31,6 +31,8 @@ Internet-archive is a nice source for several OSINT-information. This script is
|
|
|
31
31
|
|
|
32
32
|
This script allows you to download content from the Wayback Machine (archive.org). You can use it to download either the latest version or all versions of web page snapshots within a specified range.
|
|
33
33
|
|
|
34
|
+
<!-- ## Info -->
|
|
35
|
+
|
|
34
36
|
### Arguments
|
|
35
37
|
|
|
36
38
|
- `-h`, `--help`: Show the help message and exit.
|
|
@@ -42,8 +44,8 @@ This script allows you to download content from the Wayback Machine (archive.org
|
|
|
42
44
|
|
|
43
45
|
#### Mode Selection (Choose One)
|
|
44
46
|
|
|
45
|
-
- `-c`, `--current`: Download the latest version of each file snapshot.
|
|
46
|
-
- `-f`, `--full`: Download snapshots of all timestamps.
|
|
47
|
+
- `-c`, `--current`: Download the latest version of each file snapshot. You will get a rebuild of the current website with all available files.
|
|
48
|
+
- `-f`, `--full`: Download snapshots of all timestamps. You will get a folder per timestamp with the files available at that time.
|
|
47
49
|
- `-s`, `--save`: Save a page to the Wayback Machine. (beta)
|
|
48
50
|
|
|
49
51
|
#### Optional Arguments
|
|
@@ -61,6 +63,7 @@ Specify the range in years or a specific timestamp either start, end or both. If
|
|
|
61
63
|
|
|
62
64
|
#### Additional
|
|
63
65
|
|
|
66
|
+
- `--no-redirect`: Do not follow redirects of snapshots. Archive.org sometimes redirects to a different snapshot for several reasons. Downloading redirects may lead to timestamp-folders which contain some files with a different timestamp. This does not matter if you only want to download the latest version (`-c`).
|
|
64
67
|
- `--verbosity [LEVEL]`: Set the verbosity: json (print json response), progress (show progress bar) or standard (default).
|
|
65
68
|
- `--retry [RETRY_FAILED]`: Retry failed downloads. You can specify the number of retry attempts as an integer.
|
|
66
69
|
- `--worker [AMOUNT]`: The number of worker to use for downloading (simultaneous downloads). Default is 1. Beware: Using too many worker will lead into refused connections from the Wayback Machine. Duration about 1.5 minutes.
|
|
@@ -73,8 +76,8 @@ Download latest snapshot of all files:<br>
|
|
|
73
76
|
Download latest snapshot of all files with retries:<br>
|
|
74
77
|
`waybackup -u http://example.com -c --retry 3`
|
|
75
78
|
|
|
76
|
-
Download all snapshots sorted per timestamp with a specified range:<br>
|
|
77
|
-
`waybackup -u http://example.com -f -r 5`
|
|
79
|
+
Download all snapshots sorted per timestamp with a specified range and follow redirects:<br>
|
|
80
|
+
`waybackup -u http://example.com -f -r 5 --redirect`
|
|
78
81
|
|
|
79
82
|
Download all snapshots sorted per timestamp with a specified range and save to a specified folder with 3 worker:<br>
|
|
80
83
|
`waybackup -u http://example.com -f -r 5 -o /home/user/Downloads/snapshots --worker 3`
|
|
@@ -82,10 +85,6 @@ Download all snapshots sorted per timestamp with a specified range and save to a
|
|
|
82
85
|
List available snapshots per timestamp without downloading:<br>
|
|
83
86
|
`waybackup -u http://example.com -f -l`
|
|
84
87
|
|
|
85
|
-
## Info
|
|
86
|
-
|
|
87
|
-
The script automatically follows redirects of snapshots.
|
|
88
|
-
|
|
89
88
|
## Contributing
|
|
90
89
|
|
|
91
90
|
I'm always happy for some feature requests to improve the usability of this script.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from urllib.parse import urlparse
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
class SnapshotCollection:
|
|
5
|
+
|
|
6
|
+
CDX_LIST = []
|
|
7
|
+
SNAPSHOT_COLLECTION = []
|
|
8
|
+
MODE_CURRENT = 0
|
|
9
|
+
|
|
10
|
+
@classmethod
|
|
11
|
+
def create_list_full(cls, cdxResult):
|
|
12
|
+
cls.CDX_LIST = sorted([{"timestamp": snapshot[0], "url": snapshot[1], "status": snapshot[2], "mimetype": snapshot[3], "digest": snapshot[4]} for i, snapshot in enumerate(cdxResult.json()[1:])], key=lambda k: k['timestamp'], reverse=True)
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def create_list_current(cls):
|
|
16
|
+
cls.MODE_CURRENT = 1
|
|
17
|
+
cdxResult_list_filtered = []
|
|
18
|
+
url_set = set()
|
|
19
|
+
for snapshot in cls.CDX_LIST:
|
|
20
|
+
if snapshot["url"] not in url_set:
|
|
21
|
+
cdxResult_list_filtered.append(snapshot)
|
|
22
|
+
url_set.add(snapshot["url"])
|
|
23
|
+
cls.CDX_LIST = cdxResult_list_filtered
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def count_list(cls):
|
|
27
|
+
return len(cls.CDX_LIST)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def create_collection(cls):
|
|
31
|
+
for cdx_entry in cls.CDX_LIST:
|
|
32
|
+
timestamp, url = cdx_entry["timestamp"], cdx_entry["url"]
|
|
33
|
+
url_archive = f"http://web.archive.org/web/{timestamp}{cls._url_get_filetype(url)}/{url}"
|
|
34
|
+
collection_entry = {
|
|
35
|
+
"id": len(cls.SNAPSHOT_COLLECTION),
|
|
36
|
+
"timestamp": timestamp,
|
|
37
|
+
"url_archive": url_archive,
|
|
38
|
+
"url_origin": url,
|
|
39
|
+
"file": False,
|
|
40
|
+
"redirect": False,
|
|
41
|
+
"response": False
|
|
42
|
+
}
|
|
43
|
+
cls.SNAPSHOT_COLLECTION.append(collection_entry)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def snapshot_entry_create_output(cls, collection_entry: dict, output: str) -> str:
|
|
47
|
+
"""
|
|
48
|
+
Create the output path for a snapshot entry of the collection according to the mode.
|
|
49
|
+
|
|
50
|
+
Input:
|
|
51
|
+
- collection_entry: A single snapshot entry of the collection (dict).
|
|
52
|
+
- output: The output directory (str).
|
|
53
|
+
|
|
54
|
+
Output:
|
|
55
|
+
- download_file: The output path for the snapshot entry (str) with filename.
|
|
56
|
+
"""
|
|
57
|
+
timestamp, url = collection_entry["timestamp"], collection_entry["url_origin"]
|
|
58
|
+
domain, subdir, filename = cls._url_split(url)
|
|
59
|
+
if cls.MODE_CURRENT:
|
|
60
|
+
download_dir = os.path.join(output, domain, subdir)
|
|
61
|
+
else:
|
|
62
|
+
download_dir = os.path.join(output, domain, timestamp, subdir)
|
|
63
|
+
download_file = os.path.join(download_dir, filename)
|
|
64
|
+
return download_file
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def snapshot_entry_modify(cls, collection_entry: dict, key: str, value: str):
|
|
68
|
+
"""
|
|
69
|
+
Modify a key-value pair in a snapshot entry of the collection (dict).
|
|
70
|
+
|
|
71
|
+
- Append a new key-value pair if the key does not exist.
|
|
72
|
+
- Modify an existing key-value pair if the key exists.
|
|
73
|
+
"""
|
|
74
|
+
collection_entry[key] = value
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def url_get_timestamp(cls, url):
|
|
78
|
+
"""
|
|
79
|
+
Extract the timestamp from a wayback machine URL.
|
|
80
|
+
"""
|
|
81
|
+
timestamp = url.split("web.archive.org/web/")[1].split("/")[0]
|
|
82
|
+
timestamp = ''.join([char for char in timestamp if char.isdigit()])
|
|
83
|
+
return timestamp
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def _url_get_filetype(cls, url):
|
|
87
|
+
file_extension = os.path.splitext(url)[1][1:]
|
|
88
|
+
urltype_mapping = {
|
|
89
|
+
"jpg": "im_",
|
|
90
|
+
"jpeg": "im_",
|
|
91
|
+
"png": "im_",
|
|
92
|
+
"gif": "im_",
|
|
93
|
+
"svg": "im_",
|
|
94
|
+
"ico": "im_",
|
|
95
|
+
"css": "cs_"
|
|
96
|
+
#"js": "js_"
|
|
97
|
+
}
|
|
98
|
+
urltype = urltype_mapping.get(file_extension, "id_")
|
|
99
|
+
return urltype
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def _url_split(cls, url):
|
|
103
|
+
"""
|
|
104
|
+
Split a URL into domain, subdir and filename.
|
|
105
|
+
"""
|
|
106
|
+
parsed_url = urlparse(url)
|
|
107
|
+
domain = parsed_url.netloc
|
|
108
|
+
subdir = parsed_url.path.strip("/").rsplit("/", 1)[0]
|
|
109
|
+
filename = parsed_url.path.split("/")[-1] or "index.html"
|
|
110
|
+
return domain, subdir, filename
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import tqdm
|
|
2
2
|
import json
|
|
3
|
-
import
|
|
4
|
-
import pywaybackup.SnapshotCollection as sc
|
|
3
|
+
from pywaybackup.SnapshotCollection import SnapshotCollection as sc
|
|
5
4
|
|
|
6
5
|
class Verbosity:
|
|
7
6
|
|
|
@@ -11,8 +10,7 @@ class Verbosity:
|
|
|
11
10
|
pbar = None
|
|
12
11
|
|
|
13
12
|
@classmethod
|
|
14
|
-
def open(cls, args: list
|
|
15
|
-
cls.snapshots = snapshots
|
|
13
|
+
def open(cls, args: list):
|
|
16
14
|
cls.args = args
|
|
17
15
|
if cls.args == "progress":
|
|
18
16
|
cls.mode = "progress"
|
|
@@ -23,16 +21,23 @@ class Verbosity:
|
|
|
23
21
|
|
|
24
22
|
@classmethod
|
|
25
23
|
def close(cls):
|
|
26
|
-
if cls.mode == "
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
if cls.mode == "progress":
|
|
25
|
+
cls.pbar.close()
|
|
26
|
+
if cls.mode == "progress" or cls.mode == "standard":
|
|
27
|
+
successed = len([snapshot for snapshot in sc.SNAPSHOT_COLLECTION if snapshot["file"]])
|
|
28
|
+
failed = len([snapshot for snapshot in sc.SNAPSHOT_COLLECTION if not snapshot["file"]])
|
|
29
|
+
print(f"\nFiles downloaded: {successed}")
|
|
30
|
+
print(f"Files missing: {failed}")
|
|
29
31
|
print("")
|
|
32
|
+
if cls.mode == "json":
|
|
33
|
+
print(json.dumps(sc.SNAPSHOT_COLLECTION, indent=4, sort_keys=True))
|
|
30
34
|
|
|
31
35
|
@classmethod
|
|
32
36
|
def write(cls, message: str = None, progress: int = None):
|
|
33
37
|
if cls.mode == "progress":
|
|
34
38
|
if progress == 0:
|
|
35
|
-
|
|
39
|
+
print("")
|
|
40
|
+
maxval = sc.count_list()
|
|
36
41
|
cls.pbar = tqdm.tqdm(total=maxval, desc="Downloading", unit=" snapshot", ascii="░▒█")
|
|
37
42
|
elif progress == 1:
|
|
38
43
|
cls.pbar.update(1)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.7.1"
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#import threading
|
|
2
1
|
import requests
|
|
3
2
|
import os
|
|
4
3
|
import gzip
|
|
@@ -8,7 +7,8 @@ import http.client
|
|
|
8
7
|
from urllib.parse import urljoin
|
|
9
8
|
from datetime import datetime, timezone
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
from pywaybackup.SnapshotCollection import SnapshotCollection as sc
|
|
11
|
+
|
|
12
12
|
from pywaybackup.Verbosity import Verbosity as v
|
|
13
13
|
|
|
14
14
|
|
|
@@ -42,7 +42,7 @@ def save_page(url: str):
|
|
|
42
42
|
if response_status == 302:
|
|
43
43
|
location = response.getheader("Location")
|
|
44
44
|
v.write("\n-----> Response: 302 (redirect to snapshot)")
|
|
45
|
-
snapshot_timestamp = datetime.strptime(
|
|
45
|
+
snapshot_timestamp = datetime.strptime(sc.url_get_timestamp(location), '%Y%m%d%H%M%S').strftime('%Y-%m-%d %H:%M:%S')
|
|
46
46
|
current_timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')
|
|
47
47
|
timestamp_difference = (datetime.strptime(current_timestamp, '%Y-%m-%d %H:%M:%S') - datetime.strptime(snapshot_timestamp, '%Y-%m-%d %H:%M:%S')).seconds / 60
|
|
48
48
|
timestamp_difference = int(round(timestamp_difference, 0))
|
|
@@ -70,13 +70,14 @@ def save_page(url: str):
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
def print_list(
|
|
73
|
+
def print_list():
|
|
74
74
|
v.write("")
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
count = sc.count_list()
|
|
76
|
+
if count == 0:
|
|
77
|
+
v.write("\nNo snapshots found")
|
|
77
78
|
else:
|
|
78
|
-
__import__('pprint').pprint(
|
|
79
|
-
v.write(f"\n-----> {
|
|
79
|
+
__import__('pprint').pprint(sc.CDX_LIST)
|
|
80
|
+
v.write(f"\n-----> {count} snapshots listed")
|
|
80
81
|
|
|
81
82
|
|
|
82
83
|
|
|
@@ -84,20 +85,21 @@ def print_list(snapshots):
|
|
|
84
85
|
|
|
85
86
|
# create filelist
|
|
86
87
|
# timestamp format yyyyMMddhhmmss
|
|
87
|
-
def query_list(
|
|
88
|
+
def query_list(url: str, range: int, start: int, end: int, explicit: bool, mode: str):
|
|
88
89
|
try:
|
|
89
90
|
v.write("\nQuerying snapshots...")
|
|
90
|
-
|
|
91
|
+
query_range = ""
|
|
91
92
|
if not range:
|
|
92
|
-
if start:
|
|
93
|
-
if end:
|
|
94
|
-
else:
|
|
93
|
+
if start: query_range = query_range + f"&from={start}"
|
|
94
|
+
if end: query_range = query_range + f"&to={end}"
|
|
95
|
+
else:
|
|
96
|
+
query_range = "&from=" + str(datetime.now().year - range)
|
|
95
97
|
cdx_url = f"*.{url}/*" if not explicit else f"{url}"
|
|
96
|
-
cdxQuery = f"https://web.archive.org/cdx/search/xd?output=json&url={cdx_url}{
|
|
98
|
+
cdxQuery = f"https://web.archive.org/cdx/search/xd?output=json&url={cdx_url}{query_range}&fl=timestamp,original,statuscode,mimetype,digest&filter!=statuscode:200"
|
|
97
99
|
cdxResult = requests.get(cdxQuery)
|
|
98
|
-
|
|
99
|
-
if mode == "current"
|
|
100
|
-
v.write(f"\n-----> {
|
|
100
|
+
sc.create_list_full(cdxResult)
|
|
101
|
+
sc.create_list_current() if mode == "current" else None
|
|
102
|
+
v.write(f"\n-----> {sc.count_list()} snapshots found")
|
|
101
103
|
except requests.exceptions.ConnectionError as e:
|
|
102
104
|
v.write(f"\n-----> ERROR: could not query snapshots:\n{e}"); exit()
|
|
103
105
|
|
|
@@ -105,108 +107,68 @@ def query_list(snapshots: sc.SnapshotCollection, url: str, range: int, start: in
|
|
|
105
107
|
|
|
106
108
|
|
|
107
109
|
|
|
108
|
-
|
|
109
|
-
def remove_empty_folders(path, remove_root=True):
|
|
110
|
-
v.write("")
|
|
111
|
-
v.write("Removing empty output folders...")
|
|
112
|
-
count = 0
|
|
113
|
-
if not os.path.isdir(path):
|
|
114
|
-
return
|
|
115
|
-
# remove empty subfolders
|
|
116
|
-
for root, dirs, files in os.walk(path, topdown=False):
|
|
117
|
-
for dir in dirs:
|
|
118
|
-
dir_path = os.path.join(root, dir)
|
|
119
|
-
if not os.listdir(dir_path):
|
|
120
|
-
try:
|
|
121
|
-
os.rmdir(dir_path)
|
|
122
|
-
v.write(f"-----> {dir_path}")
|
|
123
|
-
count += 1
|
|
124
|
-
except OSError as e:
|
|
125
|
-
v.write(f"Error removing {dir_path}: {e}")
|
|
126
|
-
# remove empty root folder
|
|
127
|
-
if remove_root and not os.listdir(path):
|
|
128
|
-
try:
|
|
129
|
-
os.rmdir(path)
|
|
130
|
-
v.write(f"-----> {path}")
|
|
131
|
-
count += 1
|
|
132
|
-
except OSError as e:
|
|
133
|
-
v.write(f"Error removing {path}: {e}")
|
|
134
|
-
if count == 0:
|
|
135
|
-
v.write("No empty folders found")
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
110
|
# example download: http://web.archive.org/web/20190815104545id_/https://www.google.com/
|
|
142
|
-
def download_list(
|
|
111
|
+
def download_list(output, retry, no_redirect, worker):
|
|
143
112
|
"""
|
|
144
113
|
Download a list of urls in format: [{"timestamp": "20190815104545", "url": "https://www.google.com/"}]
|
|
145
114
|
"""
|
|
146
|
-
if
|
|
147
|
-
v.write("\
|
|
115
|
+
if sc.count_list() == 0:
|
|
116
|
+
v.write("\nNothing to download");
|
|
148
117
|
return
|
|
149
|
-
v.write("\nDownloading
|
|
150
|
-
download_list = snapshots.CDX_LIST
|
|
118
|
+
v.write("\nDownloading snapshots...", progress=0)
|
|
151
119
|
if worker > 1:
|
|
152
120
|
v.write(f"\n-----> Simultaneous downloads: {worker}")
|
|
153
|
-
batch_size =
|
|
121
|
+
batch_size = sc.count_list() // worker + 1
|
|
154
122
|
else:
|
|
155
|
-
batch_size =
|
|
156
|
-
|
|
123
|
+
batch_size = sc.count_list()
|
|
124
|
+
sc.create_collection()
|
|
125
|
+
batch_list = [sc.SNAPSHOT_COLLECTION[i:i + batch_size] for i in range(0, len(sc.SNAPSHOT_COLLECTION), batch_size)]
|
|
157
126
|
threads = []
|
|
158
127
|
worker = 0
|
|
159
128
|
for batch in batch_list:
|
|
160
129
|
worker += 1
|
|
161
|
-
thread = threading.Thread(target=download_loop, args=(
|
|
130
|
+
thread = threading.Thread(target=download_loop, args=(batch, output, worker, retry, no_redirect))
|
|
162
131
|
threads.append(thread)
|
|
163
132
|
thread.start()
|
|
164
133
|
for thread in threads:
|
|
165
134
|
thread.join()
|
|
166
|
-
failed_urls = len([url for url in snapshots.SNAPSHOT_COLLECTION if not url["success"]])
|
|
167
|
-
if failed_urls: v.write(f"\n-----> Failed downloads: {failed_urls}")
|
|
168
135
|
|
|
169
|
-
def download_loop(
|
|
136
|
+
def download_loop(snapshot_batch, output, worker, retry, no_redirect, attempt=1, connection=None):
|
|
170
137
|
"""
|
|
171
138
|
Download a list of URLs in a recursive loop. If a download fails, the function will retry the download.
|
|
172
139
|
The "snapshot_collection" dictionary will be updated with the download status and file information.
|
|
173
|
-
Information for each entry is written by "create_entry" and "
|
|
140
|
+
Information for each entry is written by "create_entry" and "snapshot_dict_append" functions.
|
|
174
141
|
"""
|
|
175
|
-
max_attempt = retry + 1
|
|
142
|
+
max_attempt = retry if retry > 0 else retry + 1
|
|
176
143
|
failed_urls = []
|
|
177
144
|
if not connection:
|
|
178
145
|
connection = http.client.HTTPSConnection("web.archive.org")
|
|
179
|
-
if attempt > max_attempt:
|
|
146
|
+
if attempt > max_attempt:
|
|
180
147
|
connection.close()
|
|
181
|
-
v.write(f"\n-----> Worker: {worker} - Failed downloads: {len(
|
|
148
|
+
v.write(f"\n-----> Worker: {worker} - Failed downloads: {len(snapshot_batch)}")
|
|
182
149
|
return
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
attempt += 1
|
|
199
|
-
if failed_urls: download_loop(snapshots, failed_urls, output, worker, retry, attempt, connection)
|
|
200
|
-
|
|
201
|
-
def download(download_entry, connection, status_message):
|
|
150
|
+
for snapshot in snapshot_batch:
|
|
151
|
+
status = f"\n-----> Attempt: [{attempt}/{max_attempt}] Snapshot [{snapshot_batch.index(snapshot)+1}/{len(snapshot_batch)}] - Worker: {worker}"
|
|
152
|
+
download_status = download(output, snapshot, connection, status, no_redirect)
|
|
153
|
+
if not download_status:
|
|
154
|
+
failed_urls.append(snapshot)
|
|
155
|
+
if download_status:
|
|
156
|
+
v.write(progress=1)
|
|
157
|
+
attempt += 1
|
|
158
|
+
if failed_urls:
|
|
159
|
+
if not attempt > max_attempt:
|
|
160
|
+
v.write(f"\n-----> Worker: {worker} - Retry Timeout: 10 seconds")
|
|
161
|
+
time.sleep(15)
|
|
162
|
+
download_loop(failed_urls, output, worker, retry, no_redirect, attempt, connection)
|
|
163
|
+
|
|
164
|
+
def download(output, snapshot_entry, connection, status_message, no_redirect=False):
|
|
202
165
|
"""
|
|
203
166
|
Download a single URL and save it to the specified filepath.
|
|
204
167
|
If there is a redirect, the function will follow the redirect and update the download URL.
|
|
205
168
|
gzip decompression is used if the response is encoded.
|
|
206
169
|
According to the response status, the function will write a status message to the console and append a failed URL.
|
|
207
170
|
"""
|
|
208
|
-
download_url =
|
|
209
|
-
download_file = download_entry["file"]
|
|
171
|
+
download_url = snapshot_entry["url_archive"]
|
|
210
172
|
max_retries = 2
|
|
211
173
|
sleep_time = 45
|
|
212
174
|
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'}
|
|
@@ -216,23 +178,31 @@ def download(download_entry, connection, status_message):
|
|
|
216
178
|
response = connection.getresponse()
|
|
217
179
|
response_data = response.read()
|
|
218
180
|
response_status = response.status
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
181
|
+
response_status_message = parse_response_code(response_status)
|
|
182
|
+
sc.snapshot_entry_modify(snapshot_entry, "response", response_status)
|
|
183
|
+
if not no_redirect:
|
|
184
|
+
if response_status == 302:
|
|
185
|
+
status_message = f"{status_message}\n" + \
|
|
186
|
+
f"REDIRECT -> HTTP: {response.status}"
|
|
187
|
+
while response_status == 302:
|
|
188
|
+
connection.request("GET", download_url, headers=headers)
|
|
189
|
+
response = connection.getresponse()
|
|
190
|
+
response_data = response.read()
|
|
191
|
+
response_status = response.status
|
|
192
|
+
location = response.getheader("Location")
|
|
193
|
+
if location:
|
|
194
|
+
status_message = f"{status_message}\n" + \
|
|
195
|
+
f" -> URL: {location}"
|
|
196
|
+
location = urljoin(download_url, location)
|
|
197
|
+
download_url = location
|
|
198
|
+
sc.snapshot_entry_modify(snapshot_entry, "redirect", True)
|
|
199
|
+
sc.snapshot_entry_modify(snapshot_entry, "redirect_timestamp", sc.url_get_timestamp(location))
|
|
200
|
+
sc.snapshot_entry_modify(snapshot_entry, "redirect_url", location)
|
|
201
|
+
else:
|
|
202
|
+
break
|
|
203
|
+
if response_status == 200:
|
|
204
|
+
sc.snapshot_entry_modify(snapshot_entry, "file", sc.snapshot_entry_create_output(snapshot_entry, output))
|
|
205
|
+
download_file = snapshot_entry["file"]
|
|
236
206
|
os.makedirs(os.path.dirname(download_file), exist_ok=True)
|
|
237
207
|
with open(download_file, 'wb') as file:
|
|
238
208
|
if response.getheader('Content-Encoding') == 'gzip':
|
|
@@ -240,33 +210,50 @@ def download(download_entry, connection, status_message):
|
|
|
240
210
|
file.write(response_data)
|
|
241
211
|
else:
|
|
242
212
|
file.write(response_data)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
f"NOT FOUND -> HTTP: {response.status}\n" + \
|
|
251
|
-
f" -> URL: {download_url}"
|
|
213
|
+
if os.path.isfile(download_file):
|
|
214
|
+
status_message = f"{status_message}\n" + \
|
|
215
|
+
f"SUCCESS -> HTTP: {response_status} - {response_status_message}\n" + \
|
|
216
|
+
f" -> URL: {download_url}\n" + \
|
|
217
|
+
f" -> FILE: {download_file}"
|
|
218
|
+
v.write(status_message)
|
|
219
|
+
return True
|
|
252
220
|
else:
|
|
253
221
|
status_message = f"{status_message}\n" + \
|
|
254
|
-
f"UNEXPECTED -> HTTP: {
|
|
255
|
-
f" -> URL: {download_url}\n"
|
|
256
|
-
|
|
222
|
+
f"UNEXPECTED -> HTTP: {response_status} - {response_status_message}\n" + \
|
|
223
|
+
f" -> URL: {download_url}\n"
|
|
224
|
+
v.write(status_message)
|
|
225
|
+
return True
|
|
226
|
+
# exception returns false and appends the url to the failed list
|
|
227
|
+
except http.client.HTTPException as e:
|
|
228
|
+
status_message = f"{status_message}\n" + \
|
|
229
|
+
f"EXCEPTION -> ({i+1}/{max_retries}), append to failed_urls: {download_url}\n" + \
|
|
230
|
+
f" -> {e}"
|
|
257
231
|
v.write(status_message)
|
|
258
|
-
return
|
|
232
|
+
return False
|
|
233
|
+
# connection refused waits and retries
|
|
259
234
|
except ConnectionRefusedError as e:
|
|
260
235
|
status_message = f"{status_message}\n" + \
|
|
261
236
|
f"REFUSED -> ({i+1}/{max_retries}), reconnect in {sleep_time} seconds...\n" + \
|
|
262
237
|
f" -> {e}"
|
|
263
238
|
v.write(status_message)
|
|
264
239
|
time.sleep(sleep_time)
|
|
265
|
-
except http.client.HTTPException as e:
|
|
266
|
-
status_message = f"{status_message}\n" + \
|
|
267
|
-
f"EXCEPTION -> ({i+1}/{max_retries}), append to failed_urls: {download_url}\n" + \
|
|
268
|
-
f" -> {e}"
|
|
269
|
-
v.write(status_message)
|
|
270
|
-
return False
|
|
271
240
|
v.write(f"FAILED -> download, append to failed_urls: {download_url}")
|
|
272
|
-
return False
|
|
241
|
+
return False
|
|
242
|
+
|
|
243
|
+
RESPONSE_CODE_DICT = {
|
|
244
|
+
200: "OK",
|
|
245
|
+
301: "Moved Permanently",
|
|
246
|
+
302: "Found (redirect)",
|
|
247
|
+
400: "Bad Request",
|
|
248
|
+
403: "Forbidden",
|
|
249
|
+
404: "Not Found",
|
|
250
|
+
500: "Internal Server Error",
|
|
251
|
+
503: "Service Unavailable"
|
|
252
|
+
}
|
|
253
|
+
def parse_response_code(response_code: int):
|
|
254
|
+
"""
|
|
255
|
+
Parse the response code of the Wayback Machine and return a human-readable message.
|
|
256
|
+
"""
|
|
257
|
+
if response_code in RESPONSE_CODE_DICT:
|
|
258
|
+
return RESPONSE_CODE_DICT[response_code]
|
|
259
|
+
return "Unknown response code"
|
|
@@ -1,21 +1,18 @@
|
|
|
1
|
-
import pywaybackup.archive as archive
|
|
2
|
-
import pywaybackup.SnapshotCollection as sc
|
|
3
1
|
import argparse
|
|
4
|
-
import os
|
|
5
|
-
|
|
6
|
-
from pywaybackup.Verbosity import Verbosity as v
|
|
7
|
-
|
|
8
2
|
from pywaybackup.__version__ import __version__
|
|
9
3
|
|
|
10
|
-
def
|
|
4
|
+
def parse():
|
|
5
|
+
|
|
11
6
|
parser = argparse.ArgumentParser(description='Download from wayback machine (archive.org)')
|
|
12
7
|
parser.add_argument('-a', '--about', action='version', version='%(prog)s ' + __version__ + ' by @bitdruid -> https://github.com/bitdruid')
|
|
8
|
+
|
|
13
9
|
required = parser.add_argument_group('required')
|
|
14
10
|
required.add_argument('-u', '--url', type=str, help='URL to use')
|
|
15
11
|
exclusive_required = required.add_mutually_exclusive_group(required=True)
|
|
16
12
|
exclusive_required.add_argument('-c', '--current', action='store_true', help='Download the latest version of each file snapshot (opt range in y)')
|
|
17
13
|
exclusive_required.add_argument('-f', '--full', action='store_true', help='Download snapshots of all timestamps (opt range in y)')
|
|
18
14
|
exclusive_required.add_argument('-s', '--save', action='store_true', help='Save a page to the wayback machine')
|
|
15
|
+
|
|
19
16
|
optional = parser.add_argument_group('optional')
|
|
20
17
|
optional.add_argument('-l', '--list', action='store_true', help='Only print snapshots (opt range in y)')
|
|
21
18
|
optional.add_argument('-e', '--explicit', action='store_true', help='Search only for the explicit given url')
|
|
@@ -23,32 +20,13 @@ def main():
|
|
|
23
20
|
optional.add_argument('-r', '--range', type=int, help='Range in years to search')
|
|
24
21
|
optional.add_argument('--start', type=int, help='Start timestamp format: YYYYMMDDhhmmss')
|
|
25
22
|
optional.add_argument('--end', type=int, help='End timestamp format: YYYYMMDDhhmmss')
|
|
23
|
+
|
|
26
24
|
special = parser.add_argument_group('special')
|
|
25
|
+
special.add_argument('--no-redirect', action='store_true', help='Do not follow redirects by archive.org')
|
|
27
26
|
special.add_argument('--verbosity', type=str, default="standard", choices=["standard", "progress", "json"], help='Verbosity level')
|
|
28
27
|
special.add_argument('--retry', type=int, default=0, metavar="X-TIMES", help='Retry failed downloads (opt tries as int, else infinite)')
|
|
29
28
|
special.add_argument('--worker', type=int, default=1, metavar="AMOUNT", help='Number of worker (simultaneous downloads)')
|
|
30
29
|
|
|
31
30
|
args = parser.parse_args()
|
|
32
|
-
snapshots = sc.SnapshotCollection()
|
|
33
|
-
v.open(args.verbosity, snapshots)
|
|
34
|
-
|
|
35
|
-
if args.current:
|
|
36
|
-
mode = "current"
|
|
37
|
-
elif args.full:
|
|
38
|
-
mode = "full"
|
|
39
|
-
|
|
40
|
-
if args.save:
|
|
41
|
-
archive.save_page(args.url)
|
|
42
|
-
else:
|
|
43
|
-
if args.output is None:
|
|
44
|
-
args.output = os.path.join(os.getcwd(), "waybackup_snapshots")
|
|
45
|
-
archive.query_list(snapshots, args.url, args.range, args.start, args.end, args.explicit, mode)
|
|
46
|
-
if args.list:
|
|
47
|
-
archive.print_list(snapshots)
|
|
48
|
-
else:
|
|
49
|
-
archive.download_list(snapshots, args.output, args.retry, args.worker)
|
|
50
|
-
archive.remove_empty_folders(args.output)
|
|
51
|
-
v.close()
|
|
52
31
|
|
|
53
|
-
|
|
54
|
-
main()
|
|
32
|
+
return args
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import pywaybackup.archive as archive
|
|
2
|
+
import pywaybackup.SnapshotCollection as sc
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from pywaybackup.arguments import parse
|
|
6
|
+
from pywaybackup.Verbosity import Verbosity as v
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
args = parse()
|
|
10
|
+
v.open(args.verbosity)
|
|
11
|
+
|
|
12
|
+
if args.current:
|
|
13
|
+
mode = "current"
|
|
14
|
+
elif args.full:
|
|
15
|
+
mode = "full"
|
|
16
|
+
|
|
17
|
+
if args.save:
|
|
18
|
+
archive.save_page(args.url)
|
|
19
|
+
else:
|
|
20
|
+
if args.output is None:
|
|
21
|
+
args.output = os.path.join(os.getcwd(), "waybackup_snapshots")
|
|
22
|
+
archive.query_list(args.url, args.range, args.start, args.end, args.explicit, mode)
|
|
23
|
+
if args.list:
|
|
24
|
+
archive.print_list()
|
|
25
|
+
else:
|
|
26
|
+
archive.download_list(args.output, args.retry, args.no_redirect, args.worker)
|
|
27
|
+
#archive.remove_empty_folders(args.output)
|
|
28
|
+
v.close()
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pywaybackup
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Download snapshots from the Wayback Machine
|
|
5
5
|
Home-page: https://github.com/bitdruid/python-wayback-machine-downloader
|
|
6
6
|
Author: bitdruid
|
|
@@ -14,7 +14,7 @@ License-File: LICENSE
|
|
|
14
14
|
|
|
15
15
|
[](https://pypi.org/project/pywaybackup/)
|
|
16
16
|
[](https://pypi.org/project/pywaybackup/)
|
|
17
|
-

|
|
18
18
|

|
|
19
19
|
[](https://opensource.org/licenses/MIT)
|
|
20
20
|
|
|
@@ -43,6 +43,8 @@ Internet-archive is a nice source for several OSINT-information. This script is
|
|
|
43
43
|
|
|
44
44
|
This script allows you to download content from the Wayback Machine (archive.org). You can use it to download either the latest version or all versions of web page snapshots within a specified range.
|
|
45
45
|
|
|
46
|
+
<!-- ## Info -->
|
|
47
|
+
|
|
46
48
|
### Arguments
|
|
47
49
|
|
|
48
50
|
- `-h`, `--help`: Show the help message and exit.
|
|
@@ -54,8 +56,8 @@ This script allows you to download content from the Wayback Machine (archive.org
|
|
|
54
56
|
|
|
55
57
|
#### Mode Selection (Choose One)
|
|
56
58
|
|
|
57
|
-
- `-c`, `--current`: Download the latest version of each file snapshot.
|
|
58
|
-
- `-f`, `--full`: Download snapshots of all timestamps.
|
|
59
|
+
- `-c`, `--current`: Download the latest version of each file snapshot. You will get a rebuild of the current website with all available files.
|
|
60
|
+
- `-f`, `--full`: Download snapshots of all timestamps. You will get a folder per timestamp with the files available at that time.
|
|
59
61
|
- `-s`, `--save`: Save a page to the Wayback Machine. (beta)
|
|
60
62
|
|
|
61
63
|
#### Optional Arguments
|
|
@@ -73,6 +75,7 @@ Specify the range in years or a specific timestamp either start, end or both. If
|
|
|
73
75
|
|
|
74
76
|
#### Additional
|
|
75
77
|
|
|
78
|
+
- `--no-redirect`: Do not follow redirects of snapshots. Archive.org sometimes redirects to a different snapshot for several reasons. Downloading redirects may lead to timestamp-folders which contain some files with a different timestamp. This does not matter if you only want to download the latest version (`-c`).
|
|
76
79
|
- `--verbosity [LEVEL]`: Set the verbosity: json (print json response), progress (show progress bar) or standard (default).
|
|
77
80
|
- `--retry [RETRY_FAILED]`: Retry failed downloads. You can specify the number of retry attempts as an integer.
|
|
78
81
|
- `--worker [AMOUNT]`: The number of worker to use for downloading (simultaneous downloads). Default is 1. Beware: Using too many worker will lead into refused connections from the Wayback Machine. Duration about 1.5 minutes.
|
|
@@ -85,8 +88,8 @@ Download latest snapshot of all files:<br>
|
|
|
85
88
|
Download latest snapshot of all files with retries:<br>
|
|
86
89
|
`waybackup -u http://example.com -c --retry 3`
|
|
87
90
|
|
|
88
|
-
Download all snapshots sorted per timestamp with a specified range:<br>
|
|
89
|
-
`waybackup -u http://example.com -f -r 5`
|
|
91
|
+
Download all snapshots sorted per timestamp with a specified range and follow redirects:<br>
|
|
92
|
+
`waybackup -u http://example.com -f -r 5 --redirect`
|
|
90
93
|
|
|
91
94
|
Download all snapshots sorted per timestamp with a specified range and save to a specified folder with 3 worker:<br>
|
|
92
95
|
`waybackup -u http://example.com -f -r 5 -o /home/user/Downloads/snapshots --worker 3`
|
|
@@ -94,10 +97,6 @@ Download all snapshots sorted per timestamp with a specified range and save to a
|
|
|
94
97
|
List available snapshots per timestamp without downloading:<br>
|
|
95
98
|
`waybackup -u http://example.com -f -l`
|
|
96
99
|
|
|
97
|
-
## Info
|
|
98
|
-
|
|
99
|
-
The script automatically follows redirects of snapshots.
|
|
100
|
-
|
|
101
100
|
## Contributing
|
|
102
101
|
|
|
103
102
|
I'm always happy for some feature requests to improve the usability of this script.
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
from urllib.parse import urlparse
|
|
2
|
-
import os
|
|
3
|
-
|
|
4
|
-
class SnapshotCollection:
|
|
5
|
-
|
|
6
|
-
CDX_JSON = []
|
|
7
|
-
CDX_LIST = []
|
|
8
|
-
|
|
9
|
-
SNAPSHOT_COLLECTION = []
|
|
10
|
-
|
|
11
|
-
MODE_CURRENT = 0
|
|
12
|
-
|
|
13
|
-
def __init__(self):
|
|
14
|
-
pass
|
|
15
|
-
|
|
16
|
-
def create_full(self, cdxResult):
|
|
17
|
-
self.CDX_JSON = cdxResult.json()[1:]
|
|
18
|
-
self.CDX_LIST = [{"timestamp": snapshot[0], "url": snapshot[1]} for i, snapshot in enumerate(self.CDX_JSON)]
|
|
19
|
-
self.CDX_LIST = sorted(self.CDX_LIST, key=lambda k: k['timestamp'], reverse=True)
|
|
20
|
-
|
|
21
|
-
def create_current(self):
|
|
22
|
-
self.MODE_CURRENT = 1
|
|
23
|
-
self.CDX_LIST = sorted(self.CDX_LIST, key=lambda k: k['timestamp'], reverse=True)
|
|
24
|
-
cdxResult_list_filtered = []
|
|
25
|
-
url_set = set()
|
|
26
|
-
for snapshot in self.CDX_LIST:
|
|
27
|
-
if snapshot["url"] not in url_set:
|
|
28
|
-
cdxResult_list_filtered.append(snapshot)
|
|
29
|
-
url_set.add(snapshot["url"])
|
|
30
|
-
self.CDX_LIST = cdxResult_list_filtered
|
|
31
|
-
|
|
32
|
-
def create_entry(self, cdx_entry: dict, output: str) -> dict:
|
|
33
|
-
timestamp, url = cdx_entry["timestamp"], cdx_entry["url"]
|
|
34
|
-
url_type = self.__get_url_filetype(url)
|
|
35
|
-
download_url = f"http://web.archive.org/web/{timestamp}{url_type}/{url}"
|
|
36
|
-
domain, subdir, filename = self.__split_url(url)
|
|
37
|
-
if self.MODE_CURRENT: download_dir = os.path.join(output, domain, subdir)
|
|
38
|
-
else: download_dir = os.path.join(output, domain, timestamp, subdir)
|
|
39
|
-
download_file = os.path.join(download_dir, filename)
|
|
40
|
-
cdx_entry = {
|
|
41
|
-
"id": len(self.SNAPSHOT_COLLECTION),
|
|
42
|
-
"url": download_url,
|
|
43
|
-
"file": download_file,
|
|
44
|
-
"timestamp": timestamp,
|
|
45
|
-
"origin_url": url,
|
|
46
|
-
"success": False,
|
|
47
|
-
"retry": 0
|
|
48
|
-
}
|
|
49
|
-
return cdx_entry
|
|
50
|
-
|
|
51
|
-
def count_list(self):
|
|
52
|
-
return len(self.CDX_LIST)
|
|
53
|
-
|
|
54
|
-
def snapshot_collection_write(self, query_entry: dict):
|
|
55
|
-
if query_entry["id"] not in self.SNAPSHOT_COLLECTION:
|
|
56
|
-
self.SNAPSHOT_COLLECTION.append(query_entry)
|
|
57
|
-
|
|
58
|
-
def snapshot_collection_update(self, id: int, key: str, value: str):
|
|
59
|
-
index = next((index for (index, d) in enumerate(self.SNAPSHOT_COLLECTION) if d["id"] == id), None)
|
|
60
|
-
if index is not None:
|
|
61
|
-
self.SNAPSHOT_COLLECTION[index][key] = value
|
|
62
|
-
|
|
63
|
-
def __get_url_filetype(self, url):
|
|
64
|
-
file_extension = os.path.splitext(url)[1][1:]
|
|
65
|
-
urltype_mapping = {
|
|
66
|
-
"jpg": "im_",
|
|
67
|
-
"jpeg": "im_",
|
|
68
|
-
"png": "im_",
|
|
69
|
-
"gif": "im_",
|
|
70
|
-
"svg": "im_",
|
|
71
|
-
"ico": "im_",
|
|
72
|
-
"css": "cs_",
|
|
73
|
-
"js": "js_"
|
|
74
|
-
}
|
|
75
|
-
urltype = urltype_mapping.get(file_extension, "id_")
|
|
76
|
-
return urltype
|
|
77
|
-
|
|
78
|
-
def __split_url(self, url):
|
|
79
|
-
parsed_url = urlparse(url)
|
|
80
|
-
domain = parsed_url.netloc
|
|
81
|
-
subdir = parsed_url.path.strip("/").rsplit("/", 1)[0]
|
|
82
|
-
filename = parsed_url.path.split("/")[-1] or "index.html"
|
|
83
|
-
return domain, subdir, filename
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.6.3"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|