Neon-AI commited on
Commit
cc3da48
Β·
verified Β·
1 Parent(s): 6722af6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +183 -17
app.py CHANGED
@@ -1,17 +1,183 @@
1
- import httpx
2
- import pandas as pd
3
- import io
4
-
5
- url = "https://data.commoncrawl.org/cc-index/table/cc-main/warc/crawl=CC-MAIN-2013-20/subset=warc/part-00000-6ac52f25-05a1-4998-adf1-b8c830c08eec.c000.gz.parquet"
6
-
7
- print("Downloading...")
8
- with httpx.stream("GET", url) as r:
9
- data = b""
10
- for chunk in r.iter_bytes():
11
- data += chunk
12
-
13
- print("Reading parquet...")
14
- df = pd.read_parquet(io.BytesIO(data))
15
- print("Columns:", df.columns.tolist())
16
- print("Shape:", df.shape)
17
- print(df.head(3))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Common Crawl URL Extractor
3
+ --------------------------
4
+ Streams every URL from the latest Common Crawl crawl into chunked .gz files.
5
+ Resumable: skips shards whose output file already exists.
6
+
7
+ Output: urls_chunk_000.gz, urls_chunk_001.gz, ... (one per CDX shard)
8
+ Each file contains one URL per line, gzip-compressed.
9
+
10
+ Estimated totals (latest single crawl):
11
+ - ~300 CDX shards
12
+ - ~800 MB per shard (compressed download)
13
+ - ~3–5 billion URLs total
14
+ - ~240 GB total download bandwidth
15
+ """
16
+
17
+ import gzip
18
+ import json
19
+ import os
20
+ import time
21
+ import requests
22
+
23
+ # ── Config ────────────────────────────────────────────────────────────────────
24
+
25
+ OUTPUT_DIR = "/data/cc_urls" # where chunked .gz files are written
26
+ COLLINFO_URL = "https://index.commoncrawl.org/collinfo.json"
27
+ BASE_URL = "https://data.commoncrawl.org/"
28
+ STREAM_CHUNK = 1 * 1024 * 1024 # 1 MB read chunks while streaming
29
+ MAX_RETRIES = 5
30
+ RETRY_DELAY = 10 # seconds between retries
31
+
32
+ # ── Helpers ───────────────────────────────────────────────────────────────────
33
+
34
+ def get_latest_crawl_paths():
35
+ """Fetch the list of CDX shard paths for the most recent crawl."""
36
+ print("Fetching crawl list from collinfo.json ...")
37
+ info = requests.get(COLLINFO_URL, timeout=30).json()
38
+ # collinfo.json is newest-first
39
+ latest_id = info[0]["id"]
40
+ paths_url = f"{BASE_URL}crawl-data/{latest_id}/cc-index.paths.gz"
41
+ print(f"Latest crawl: {latest_id}")
42
+ print(f"Fetching shard list from: {paths_url}")
43
+
44
+ r = requests.get(paths_url, timeout=60)
45
+ r.raise_for_status()
46
+
47
+ # The paths file is a gzip'd plain-text list, one path per line
48
+ raw = gzip.decompress(r.content).decode("utf-8")
49
+ paths = [
50
+ line.strip()
51
+ for line in raw.splitlines()
52
+ if line.strip().endswith(".gz") and "/indexes/cdx-" in line
53
+ ]
54
+ print(f"Found {len(paths)} CDX shards.")
55
+ return latest_id, paths
56
+
57
+
58
+ def shard_output_path(output_dir, shard_index):
59
+ return os.path.join(output_dir, f"urls_chunk_{shard_index:03d}.gz")
60
+
61
+
62
+ def extract_urls_from_shard(shard_url, out_path):
63
+ """
64
+ Stream a CDX shard, extract the URL from each JSON line,
65
+ write compressed output. Returns count of URLs written.
66
+ """
67
+ count = 0
68
+ leftover = b""
69
+
70
+ for attempt in range(1, MAX_RETRIES + 1):
71
+ try:
72
+ r = requests.get(shard_url, stream=True, timeout=120)
73
+ r.raise_for_status()
74
+
75
+ with gzip.open(out_path + ".tmp", "wb") as out_gz:
76
+ # The CDX file is itself gzip-compressed, so decompress on the fly
77
+ decompressor = gzip.GzipFile(fileobj=r.raw)
78
+ while True:
79
+ chunk = decompressor.read(STREAM_CHUNK)
80
+ if not chunk:
81
+ break
82
+ # Process complete lines; carry over incomplete tail
83
+ block = leftover + chunk
84
+ lines = block.split(b"\n")
85
+ leftover = lines[-1] # last may be incomplete
86
+ for line in lines[:-1]:
87
+ line = line.strip()
88
+ if not line:
89
+ continue
90
+ try:
91
+ # CDX line format:
92
+ # <surt-url> <timestamp> <json-blob>
93
+ # We only need the JSON blob (3rd field onward)
94
+ parts = line.split(b" ", 2)
95
+ if len(parts) < 3:
96
+ continue
97
+ obj = json.loads(parts[2])
98
+ url = obj.get("url")
99
+ if url:
100
+ out_gz.write((url + "\n").encode("utf-8"))
101
+ count += 1
102
+ except (json.JSONDecodeError, UnicodeDecodeError):
103
+ continue
104
+
105
+ # Flush leftover
106
+ if leftover.strip():
107
+ try:
108
+ parts = leftover.split(b" ", 2)
109
+ if len(parts) >= 3:
110
+ obj = json.loads(parts[2])
111
+ url = obj.get("url")
112
+ if url:
113
+ out_gz.write((url + "\n").encode("utf-8"))
114
+ count += 1
115
+ except Exception:
116
+ pass
117
+
118
+ # Rename temp file to final on success
119
+ os.replace(out_path + ".tmp", out_path)
120
+ return count
121
+
122
+ except Exception as e:
123
+ print(f" Attempt {attempt}/{MAX_RETRIES} failed: {e}")
124
+ if os.path.exists(out_path + ".tmp"):
125
+ os.remove(out_path + ".tmp")
126
+ if attempt < MAX_RETRIES:
127
+ time.sleep(RETRY_DELAY * attempt)
128
+ else:
129
+ print(f" Giving up on this shard after {MAX_RETRIES} attempts.")
130
+ return 0
131
+
132
+
133
+ # ── Main ──────────────────────────────────────────────────────────────────────
134
+
135
+ def main():
136
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
137
+
138
+ crawl_id, shard_paths = get_latest_crawl_paths()
139
+
140
+ total_urls = 0
141
+ skipped = 0
142
+ failed = 0
143
+ start_time = time.time()
144
+
145
+ print(f"\nStarting extraction β†’ {OUTPUT_DIR}/\n{'─'*60}")
146
+
147
+ for i, shard_path in enumerate(shard_paths):
148
+ out_path = shard_output_path(OUTPUT_DIR, i)
149
+ shard_url = BASE_URL + shard_path
150
+
151
+ # ── Resume: skip already-done shards ──────────────────────────────
152
+ if os.path.exists(out_path):
153
+ skipped += 1
154
+ print(f"[{i+1:03d}/{len(shard_paths)}] SKIP {os.path.basename(shard_path)}")
155
+ continue
156
+
157
+ print(f"[{i+1:03d}/{len(shard_paths)}] Fetching {os.path.basename(shard_path)} ...")
158
+ t0 = time.time()
159
+
160
+ count = extract_urls_from_shard(shard_url, out_path)
161
+ elapsed = time.time() - t0
162
+
163
+ if count > 0:
164
+ total_urls += count
165
+ size_mb = os.path.getsize(out_path) / 1024**2
166
+ print(f" βœ“ {count:,} URLs | {size_mb:.1f} MB | {elapsed:.0f}s")
167
+ else:
168
+ failed += 1
169
+ print(f" βœ— Failed β€” shard skipped")
170
+
171
+ # ── Summary ───────────────────────────────────────────────────────────
172
+ total_elapsed = time.time() - start_time
173
+ print(f"\n{'─'*60}")
174
+ print(f"Crawl: {crawl_id}")
175
+ print(f"Shards: {len(shard_paths)} total | {skipped} skipped | {failed} failed")
176
+ print(f"URLs: {total_urls:,}")
177
+ print(f"Output dir: {OUTPUT_DIR}/")
178
+ print(f"Time: {total_elapsed/3600:.2f} hours")
179
+ print(f"{'─'*60}")
180
+
181
+
182
+ if __name__ == "__main__":
183
+ main()