Coverage for haystack/components/converters/html.py: 100%
50 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 13:53 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 13:53 +0000
1# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
2#
3# SPDX-License-Identifier: Apache-2.0
5import os
6from pathlib import Path
7from typing import Any
9from haystack import Document, component, default_from_dict, default_to_dict, logging
10from haystack.components.converters.utils import get_bytestream_from_source, normalize_metadata
11from haystack.dataclasses import ByteStream
12from haystack.lazy_imports import LazyImport
14logger = logging.getLogger(__name__)
16with LazyImport("Run 'pip install trafilatura'") as trafilatura_import:
17 from trafilatura import extract
20@component
21class HTMLToDocument:
22 """
23 Converts an HTML file to a Document.
25 Usage example:
26 ```python
27 from haystack.components.converters import HTMLToDocument
29 converter = HTMLToDocument()
30 results = converter.run(sources=["test/test_files/html/paul_graham_superlinear.html"])
31 documents = results["documents"]
33 print(documents[0].content)
34 # >> 'This is a text from the HTML file.'
35 ```
36 """
38 def __init__(
39 self, extraction_kwargs: dict[str, Any] | None = None, store_full_path: bool = False, encoding: str = "utf-8"
40 ) -> None:
41 """
42 Create an HTMLToDocument component.
44 :param extraction_kwargs: A dictionary containing keyword arguments to customize the extraction process. These
45 are passed to the underlying Trafilatura `extract` function. For the full list of available arguments, see
46 the [Trafilatura documentation](https://trafilatura.readthedocs.io/en/latest/corefunctions.html#extract).
47 :param store_full_path:
48 If True, the full path of the file is stored in the metadata of the document.
49 If False, only the file name is stored.
50 :param encoding:
51 The default encoding to use when converting HTML files. If the encoding is specified in the metadata of a
52 source ByteStream, it overrides this value.
53 """
54 trafilatura_import.check()
56 self.extraction_kwargs = extraction_kwargs or {}
57 self.store_full_path = store_full_path
58 self.encoding = encoding
60 def to_dict(self) -> dict[str, Any]:
61 """
62 Serializes the component to a dictionary.
64 :returns:
65 Dictionary with serialized data.
66 """
67 return default_to_dict(
68 self, extraction_kwargs=self.extraction_kwargs, store_full_path=self.store_full_path, encoding=self.encoding
69 )
71 @classmethod
72 def from_dict(cls, data: dict[str, Any]) -> "HTMLToDocument":
73 """
74 Deserializes the component from a dictionary.
76 :param data:
77 The dictionary to deserialize from.
78 :returns:
79 The deserialized component.
80 """
81 return default_from_dict(cls, data)
83 @component.output_types(documents=list[Document])
84 def run(
85 self,
86 sources: list[str | Path | ByteStream],
87 meta: dict[str, Any] | list[dict[str, Any]] | None = None,
88 extraction_kwargs: dict[str, Any] | None = None,
89 ) -> dict[str, Any]:
90 """
91 Converts a list of HTML files to Documents.
93 :param sources:
94 List of HTML file paths or ByteStream objects.
95 :param meta:
96 Optional metadata to attach to the Documents.
97 This value can be either a list of dictionaries or a single dictionary.
98 If it's a single dictionary, its content is added to the metadata of all produced Documents.
99 If it's a list, the length of the list must match the number of sources, because the two lists will
100 be zipped.
101 If `sources` contains ByteStream objects, their `meta` will be added to the output Documents.
102 :param extraction_kwargs:
103 Additional keyword arguments to customize the extraction process.
105 :returns:
106 A dictionary with the following keys:
107 - `documents`: Created Documents
108 """
110 merged_extraction_kwargs = {**self.extraction_kwargs, **(extraction_kwargs or {})}
112 documents = []
113 meta_list = normalize_metadata(meta=meta, sources_count=len(sources))
115 for source, metadata in zip(sources, meta_list, strict=True):
116 try:
117 bytestream = get_bytestream_from_source(source=source)
118 except Exception as e:
119 logger.warning("Could not read {source}. Skipping it. Error: {error}", source=source, error=e)
120 continue
122 if not bytestream.data:
123 logger.warning("Skipping {source} because it is empty.", source=source)
124 continue
126 try:
127 encoding = bytestream.meta.get("encoding", self.encoding)
128 text = extract(bytestream.data.decode(encoding), **merged_extraction_kwargs)
129 except Exception as conversion_e:
130 logger.warning(
131 "Failed to extract text from {source}. Skipping it. Error: {error}",
132 source=source,
133 error=conversion_e,
134 )
135 continue
137 merged_metadata = {**bytestream.meta, **metadata}
139 if not self.store_full_path and "file_path" in bytestream.meta:
140 file_path = bytestream.meta.get("file_path")
141 if file_path: # Ensure the value is not None for mypy
142 merged_metadata["file_path"] = os.path.basename(file_path)
144 document = Document(content=text, meta=merged_metadata)
145 documents.append(document)
147 return {"documents": documents}