Repository navigation
FetchNode passes the page HTML as convert_to_md's baseurl, inflating model input ~74x #1142
Description
Activity
Hi @julianriegler! I'm Dosu and I'm helping the ScrapeGraphAI, Inc team.
Your diagnosis is spot-on. On
master,handle_web_sourcecallsconvert_to_md(document[0].page_content, parsed_content)whereparsed_contentisdocument[0].page_content[1], so the full HTML gets assigned toh.baseurl[2]. Theuse_soupbranch has the swapped-arguments problem you flagged too —convert_to_md(source, parsed_content)passes the URL as HTML and vice versa [3] — andparsed_contentis indeed unbound whencut=Truesince it's only assigned insideif not self.cut:[4].All of these bugs are addressed in PR #1112, which makes exactly the changes you suggested —
convert_to_md(doc.page_content, source)andconvert_to_md(parsed_content, source)— plus fixes thecleanup_htmlcall (was receiving aResponseobject instead ofresponse.text, and its tuple return wasn't being unwrapped) and the unboundparsed_contentin thecut=Truepath [5]. That PR is still open and not yet merged [6].Separately, PR #1131 (merged) improved
convert_to_mdso that when a real URL is passed, relative links resolve against the full document path instead of just the site root [7]. That improves link resolution once #1112 lands, but doesn't fix the caller-side bug you're reporting sinceconvert_to_mdstill blindly assigns whatever it receives toh.baseurl[2].To reply, just mention @dosu.
Share context across your team and agents. Try Dosu.
Confirming this on the released 2.2.2 package (not just
master), and flagging that the impact is worse than input inflation — on<table>-based pages it causes complete data loss / empty output, so it's a correctness bug, not just a cost/token one.Repro (Hacker News)
from scrapegraphai.graphs import SmartScraperGraph SmartScraperGraph( prompt="Extract the top stories with rank, title, url, points, comments.", source="https://news.ycombinator.com/", config={"llm": {"api_key": "...", "model": "openai/gpt-4o-mini"}}, ).run() # -> {'stories': []}
What happens at each stage
Stage Chars Note Playwright fetch 34,133 full HN HTML, no block convert_to_md(html, url=<the same HTML>)44,827 output starts | [, any page whose markdown begins with a linked/image element ahead of a table layout can silently return empty. Modern div-based pages (e.g.books.toscrape.com) happen to survive.PR #1112 fixes exactly this (
convert_to_md(doc.page_content, source)). Given it turns some sites into silent empty results rather than merely bloated input, it'd be great to get that merged. Happy to help test.Workaround until then, for anyone hitting empty results: patch the caller so the bogus base-URL is dropped when it's HTML —
import scrapegraphai.nodes.fetch_node as fn _orig = fn.convert_to_md fn.convert_to_md = lambda html, url=None: _orig( html, None if (url and (len(url) > 2000 or url.lstrip()[:1] == "<")) else url )
To recap the call site: it passes
parsed_content, which is the raw HTML, whereconvert_to_mdexpects a URL string, so html2text'sbaseurlbecomes the entire document and every relative link gets the full HTML prepended. An open PR already fixes the call site to pass the real source URL; my addition below is the mechanical guard that would have caught this. Beyond that, I'd add a cheap input-hygiene guard that would have caught this mechanically: assertbaseurlparses as a URL and is under a sane length, plus a regression test asserting converted output stays within a small factor of the raw HTML length - a 74x blow-up should trip a test, not a user's token bill. Want me to take a crack at it? Happy to open a PR with working code.
Version: scrapegraphai 2.2.2 (call site unchanged on master), Python 3.14, macOS
What happens
FetchNode.handle_web_sourcecalls (scrapegraphai/nodes/fetch_node.py:398on master):At that point
parsed_contentisdocument[0].page_content, i.e. the full HTML of the page.convert_to_md(html, url)assigns its second argument tohtml2text.HTML2Text().baseurl, sohtml2text prepends the entire HTML document to every relative link it finds.
Reproduction (no LLM needed)
End to end with
SmartScraperGraphon the same page,ParseNodereports 1,948,618 chars ofparsed content instead of ~13.8k. All of it is sent as model input, and the markdown links are
unusable because each href contains the whole document.
With
google/gemini-2.5-flashthat is roughly 0.15 USD per run instead of 0.001 USD, for anidentical answer. On a page with many relative links the factor is ~74x.
Expected
baseurlshould be the source URL, so relative links become absolute.Suggested fix
The
use_soupbranch (fetch_node.py:302) looks affected too:Here
sourceis the URL andparsed_contentthe HTML, so the two arguments appear swapped.In that same branch
parsed_contentis unbound whencutisTrue, because it is only assignedinside
if not self.cut:.