pyroid.warc¶
warcio 1.8.2's WARC/ARC reader and datatrove 0.10.1's WARC reading loop, with their API and
results. Guide: pyroid.warc.
A drop-in for warcio 1.8.2's WARC/ARC reader, and datatrove 0.10.1's WARC loop, in Rust.
import pyroid.warc as pw
with open("CC-MAIN-...warc.gz", "rb") as f:
for record in pw.ArchiveIterator(f): # as warcio.ArchiveIterator
if record.rec_type == "response":
html = record.content_stream().read()
for record in pw.ArchiveIterator("x.warc.gz"): # a path: read with the GIL released
...
for ri, doc in pw.iter_documents("x.warc.gz"): # datatrove's WarcReader.read_file loop
doc["text"], doc["id"], doc["url"], doc["date"]
import pyarrow as pa
table = pa.Table.from_batches(pa.record_batch(b) for b in pw.iter_batches("x.warc.gz"))
The reader is warcio 1.8.2's, ported statement by statement: the same records, headers
(StatusAndHeaders), payloads (content_stream(): chunked, gzip, deflate,
deflate_alt and, with brotlicffi >= 1.2.1.0 installed, br), offsets
(get_record_offset/get_record_length), digest checks (check_digests), exceptions
with the same messages (ArchiveLoadFailed, EOFError, zlib.error,
brotlicffi.error, ...), the same sys.stderr writes and warcio.recordloader log
warnings, and the same read(n) and tell() calls on the file object. Gzip members are
inflated with zlib-rs; parsing and decoding run in Rust.
A file object is read with the GIL held, one fh.read(block_size) per block as warcio
does. A path (str or os.PathLike, a pyroid extension) is opened and read in Rust with
the GIL released. An iterator may be used from several threads (its calls run one at a time),
including on free-threaded Python; a file object whose read() calls back into its own
iterator gets a RuntimeError.
Records are pyroid's ports of warcio's classes. pyroid.patch("warcio") makes warcio's own
ArchiveIterator this one, with warcio's StatusAndHeaders, ArchiveLoadFailed and an
ArcWarcRecord subclass: ArchiveIterator takes every class it creates, and its
logger, from class attributes (record_cls, headers_cls, digest_checker_cls,
archive_load_failed, parser_exception, logger).
Differences from warcio:
iter(it)returns the native iterator (warcio returns a generator), and the arguments are read once: changingit.no_record_parselater has no effect, andknown_formatis read-only. Ablock_sizethat is not an int raisesTypeErrorwhen the iterator is made, not at the first read.check_digestsknows md5, sha1, sha224, sha256, sha384 and sha512, under the nameshashlib.newtakes for them (sha1,SHA-1,sha-256, ...). Other algorithms (sha3_256,blake2b, ...) report "unknown hash algorithm name" where hashlib would hash.content_stream()needs the record'sraw_streamto be pyroid's.- Brotli payloads: a damaged stream whose command runs past the end of a metablock that is
not the last one decodes further than libbrotli (more output, or another error later),
where brotlicffi raises
brotlicffi.error.
ArchiveLoadFailed ¶
ArchiveLoadFailed(reason: object)
Bases: Exception
The archive could not be read (warcio's warcio.exceptions.ArchiveLoadFailed).
Raised for an unknown format ("Unknown archive format, first line: ..."), a WARC or ARC
record that does not start as one, a gzip file that is not one member per record, and,
with check_digests="raise", a digest mismatch.
StatusAndHeadersParserException ¶
Bases: Exception
An HTTP status line that does not start with a known protocol (verify_http=True).
Attributes:
-
statusline(Any) –The line, as read.
StatusAndHeaders ¶
StatusAndHeaders(
statusline: str,
headers: Any,
protocol: str = "",
total_len: int = 0,
is_http_request: bool = False,
)
A status line and an ordered list of (name, value) headers.
warcio's StatusAndHeaders: rec_headers (the WARC or ARC headers) and
http_headers of a record. Lookups by name ignore case; headers[...],
get, in and item assignment act like a case-insensitive dict.
Parameters:
-
statusline(str) –The status line after the protocol (
"200 OK"; empty for WARC headers). -
headers(Any) –(name, value)pairs (or a mapping); bytes are decoded as latin-1. -
protocol(str, default:'') –"WARC/1.0","HTTP/1.1","GET", ... -
total_len(int, default:0) –The characters read for the block, line ends included.
-
is_http_request(bool, default:False) –Split the protocol (the verb) off
statusline.
get_header ¶
Return the value of the first header named name (any case), or default_value.
replace_header ¶
Set the last header named name (any case) to value, or append it.
Returns:
-
str | None–The old value, or
Noneif the header was added.
remove_header ¶
get_statuscode ¶
get_statuscode() -> str
Return the status code: the status line up to the first space.
validate_statusline ¶
Check that the status line starts with a positive integer; else replace it.
Returns:
-
bool–Whether it did.
add_range ¶
add_range(
start: int, part_len: int, total_len: int
) -> StatusAndHeaders
Make this a 206 Partial Content response for part_len bytes at start.
compute_headers_buffer ¶
Set headers_buff to to_ascii_bytes.
to_str ¶
DigestChecker ¶
DigestChecker(kind: Any = None)
The digest checks of one record (warcio's DigestChecker).
record.digest_checker is kept up to date as the record's stream is read.
Parameters:
-
kind(Any, default:None) –ArchiveIterator'scheck_digests:"raise"raisesArchiveLoadFailedat the first problem,"log"also writes each problem tosys.stderr.
RawStream ¶
A record's bytes after its headers (record.raw_stream).
warcio's LimitReader over the iterator's reader: it stops at the record's
Content-Length. Reading an old record reads the file where the iterator is now, as
in warcio.
ContentStream ¶
ArcWarcRecord ¶
A WARC or ARC record (warcio's ArcWarcRecord).
Attributes:
-
format(str) –"warc"or"arc"("warc"for ARC records read witharc2warc). -
rec_type(str | None) –WARC-Type("response","request","warcinfo", ...);"response"or"arc_header"for ARC records. -
rec_headers(StatusAndHeaders) –The WARC (or ARC) headers.
-
raw_stream(RawStream) –The bytes after the headers (the HTTP headers already read).
-
http_headers(StatusAndHeaders | None) –The HTTP headers of
response,requestandrevisitrecords with anhttp:orhttps:URI, elseNone. -
content_type(str | None) –The record's
Content-Type. -
length(int | None) –Content-Lengthas an int (0 when it does not parse),Nonewithout one. -
payload_length(int) –The bytes after the HTTP headers (-1 when they were not parsed).
-
digest_checker(DigestChecker) –The record's
DigestChecker.
content_stream ¶
content_stream() -> RawStream | ContentStream
Return the payload, decoded per the HTTP headers.
Transfer-Encoding: chunked is de-chunked, and a Content-Encoding of gzip,
x-gzip, deflate, deflate_alt or (with brotlicffi >= 1.2.1.0) br decompressed. Returns
raw_stream itself when there is nothing to decode, else a new decoder over it on
each call, as warcio.
ArchiveIterator ¶
ArchiveIterator(
fileobj: SupportsRead[bytes] | StrPath,
no_record_parse: bool = False,
verify_http: bool = False,
arc2warc: bool = False,
ensure_http_headers: bool = False,
block_size: int = 16384,
check_digests: bool | Literal["log", "raise"] = False,
)
Iterate over the records of a WARC or ARC file (warcio 1.8.2's ArchiveIterator).
The file may be uncompressed or gzip-compressed with one member per record, as warcio reads them.
Parameters:
-
fileobj(SupportsRead[bytes] | StrPath) –A binary file object, or a path to open (read with the GIL released).
-
no_record_parse(bool, default:False) –Do not parse the HTTP headers.
-
verify_http(bool, default:False) –Raise
StatusAndHeadersParserExceptionfor an HTTP status line that does not start with a known protocol (or verb, for requests). -
arc2warc(bool, default:False) –Present ARC records as WARC records.
-
ensure_http_headers(bool, default:False) –Give records without HTTP headers a
200 OKset. -
block_size(int, default:16384) –The bytes asked for in each
fh.read(). -
check_digests(bool | Literal['log', 'raise'], default:False) –Check
WARC-Block-DigestandWARC-Payload-Digest:Truerecords the results inrecord.digest_checker,"log"also writes problems to stderr,"raise"raisesArchiveLoadFailedat the first.
Raises:
-
ArchiveLoadFailed–The file is not a WARC or ARC file, a record is malformed, or a gzip member holds several records.
-
EOFError–A gzip member or the file ends inside a record's headers.
Subclass to change the classes it creates (what pyroid.patch("warcio") does): the
class attributes record_cls (called as warcio's ArcWarcRecord(format, rec_type,
rec_headers, raw_stream, http_headers, content_type, length, payload_length=...,
digest_checker=...)), headers_cls (as StatusAndHeaders(statusline=..., headers=...,
protocol=..., total_len=...)), digest_checker_cls, archive_load_failed,
parser_exception and logger (warcio.recordloader's by default).
offset
property
¶
offset: int
The offset where the next record starts, once the current one has been read.
err_count
property
¶
err_count: int
Records so far not followed by a blank line (each also warned about on stderr).
member_info
property
¶
(offset, length) of the current record once it has been read to its end.
known_format
property
¶
known_format: Literal['warc', 'arc'] | None
The format of the records read so far (the first record decides).
record
property
¶
record: ArcWarcRecord | None
The current record (None before the first and after the end).
close ¶
close() -> None
Drop the current record and flush the decompressor (the file stays open).
Raises:
-
error–The last gzip member was corrupt (CPython 3.13 and later).
read_to_end ¶
read_to_end(record: object = None) -> None
Read the rest of the current record and the blank lines after it.
WARCIterator ¶
ARCIterator ¶
Document ¶
Batch ¶
Documents of a WARC file as an Arrow record batch (no Arrow library needed).
Columns: id (WARC-Record-ID), url, date (WARC-Target-URI and
WARC-Date, with datatrove's ARC fallbacks), mime_type
(WARC-Identified-Payload-Type), content_type (the HTTP Content-Type), all
large_string; html (large_binary: the decoded payload); and text
(large_string: datatrove's decoding of it).
pyarrow.record_batch(b), pyarrow.table(b) and polars.DataFrame(b) take it
without a copy, through the Arrow PyCapsule interface.
column ¶
Return a column by name or position, as a pyroid.Column (no copy).
Raises:
-
KeyError–No column has that name.
-
IndexError–The position is out of range.
iter_documents ¶
iter_documents(
source: SupportsRead[bytes] | StrPath,
*,
compression: Literal["infer", "gzip"] | None = "infer",
iterator_cls: type[ArchiveIterator] = ...,
) -> Iterator[tuple[int, Document]]
Read the documents datatrove 0.10.1's WarcReader reads from a WARC file.
This is for ri, record in enumerate(ArchiveIterator(f)): doc = process_record(record);
if doc: yield ri, doc, where f is what datatrove opens: response and
conversion records whose WARC-Identified-Payload-Type is text/html or
application/xhtml+xml (or text/plain for conversion), or missing and then
detected by magic.from_buffer; the payload decoded as UTF-8, else as cchardet guesses
(records that decode neither way are skipped). The same documents, the same exceptions at
the same record (KeyError for a record without a URL or date), and the same messages.
ri is the record's index among all records of the file. cchardet
(faust-cchardet) and magic (python-magic) are imported at the first next(), as
datatrove imports them at its first record; if either import fails, datatrove's own loop
runs on ArchiveIterator over CPython's gzip.GzipFile, so the ImportError
comes at the first record. The file is opened at the first next(); up to 64 documents
(16 MiB of payload) are read ahead, and messages and errors are delivered at the document
they belong to.
Parameters:
-
source(SupportsRead[bytes] | StrPath) –A path, or a binary file object.
-
compression(Literal['infer', 'gzip'] | None, default:'infer') –For a path,
"infer"reads names ending in.gz(any case) as gzip and others as uncompressed, as fsspec does;"gzip"andNonesay which. A gzip file is read through an emulation of CPython'sgzip.GzipFile(the bytes warcio sees, and theEOFError/gzip.BadGzipFile/zlib.errorat the same point, as on this CPython version). A file object is read as is unlesscompression="gzip". -
iterator_cls(type[ArchiveIterator], default:...) –Where the
archive_load_failedexception and theloggercome from (and the iterator when an import fails), such as warcio's patched class.
Raises:
-
ValueError–compressionis not"infer","gzip"orNone, or the path's extension names another compression (.zst,.bz2,.xz, ...).
iter_batches ¶
iter_batches(
source: SupportsRead[bytes] | StrPath,
*,
batch_size: int = 1000,
compression: Literal["infer", "gzip"] | None = "infer",
iterator_cls: type[ArchiveIterator] = ...,
) -> Iterator[Batch]
Read a WARC file's documents as Arrow record batches of up to batch_size rows.
The rows are the records iter_documents looks at: response and
conversion records whose WARC-Identified-Payload-Type is one datatrove keeps or
is missing. text is what datatrove's process_record decodes, and null where it
skips the record (magic.from_buffer finds another type, or the payload decodes neither
as UTF-8 nor as cchardet guesses); url and date are null where datatrove raises
KeyError. magic and cchardet are imported only when a row needs them.
source, compression and iterator_cls are as for iter_documents; the
messages of a batch are emitted before it is yielded.
Raises:
-
ValueError–batch_sizeis not a positive int, or asiter_documents.