D4Vinci/Scrapling · error · TypeError
content argument must be str or bytes, got {type(content)}
Error message
content argument must be str or bytes, got {type(content)} What it means
TypeError from Selector.__init__ when `content` is provided but is neither str nor bytes (and `root` is None). The parser only accepts string or bytes HTML bodies; any other type (None, int, dict, an etree element passed via content instead of root) is rejected.
Source
Thrown at scrapling/parser.py:139
self.url = url
self._raw_body: str | bytes = ""
self.encoding = encoding
self.__keep_cdata = keep_cdata
self.__huge_tree_enabled = huge_tree
self.__keep_comments = keep_comments
# For selector stuff
self.__text: Optional[TextHandler] = None
self.__attributes: Optional[AttributesHandler] = None
self.__tag: Optional[str] = None
self._storage: Optional[StorageSystemMixin] = None
if root is None:
body: str | bytes
if isinstance(content, str):
body = content.strip().replace("\x00", "") or "<html/>"
elif isinstance(content, bytes):
body = content.replace(b"\x00", b"")
else:
raise TypeError(f"content argument must be str or bytes, got {type(content)}")
# https://lxml.de/api/lxml.etree.HTMLParser-class.html
_parser_kwargs: Dict[str, Any] = dict(
recover=True,
remove_blank_text=True,
remove_comments=(not keep_comments),
encoding=encoding,
compact=True,
huge_tree=huge_tree,
default_doctype=True, # Supported by lxml but missing from stubs
strip_cdata=(not keep_cdata),
)
parser = HTMLParser(**_parser_kwargs)
self._root = cast(HtmlElement, fromstring(body or "<html/>", parser=parser, base_url=url or ""))
self._raw_body = content
else:
self._root = cast(HtmlElement, root)View on GitHub (pinned to 5d213a2d47)
Solutions
- Pass the HTML as str or bytes: Selector(content='<div>x</div>') or Selector(content=b'<div>x</div>').
- For lxml elements use the root= parameter, which takes priority.
- Extract the right field first: Selector(content=response.content).
Example fix
# before sel = Selector(content=response) # Response object, not HTML # after sel = Selector(content=response.content) # str/bytes HTML
Defensive patterns
Strategy: type-guard
Validate before calling
assert isinstance(content, (str, bytes)), f"content must be str/bytes, got {type(content).__name__}"
sel = Selector(content=content) Type guard
def is_html_content(value: object) -> bool:
return isinstance(value, (str, bytes)) Try / catch
try:
sel = Selector(content=body)
except TypeError as e:
if "str or bytes" in str(e):
sel = Selector(content=str(body)) # last-resort coercion
else:
raise Prevention
- Pass lxml elements via root=, never content=.
- Extract .content/.text from response objects before constructing a Selector.
- Type-check at the boundary where external data enters your parsing layer.
When it happens
Trigger: Selector(content=123), Selector(content={'html': '...'}), Selector(content=etree_element) (should use root=), or Selector(content=response) passing the whole response object.
Common situations: Passing an already-parsed lxml element through the wrong parameter; JSON APIs returning HTML nested in a dict; passing a Response object where .content was intended; numeric/None defaults leaking from config.
Related errors
- Selector class needs HTML content, or root arguments to work
- Text nodes do not have attributes
- Response meta should be dictionary but got {type(meta).__nam
- Unknown parser argument: "{key}"; maybe you meant {cls.parse
- You must pass a keyword to configure, current keywords: {cls
AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14).
Data as JSON: /api/errors/4ac251e5b76b6334.
Report an issue: GitHub.