<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Blog on UnWeb</title><link>https://unweb.info/blog/</link><description>Recent content in Blog on UnWeb</description><generator>Hugo</generator><language>en-us</language><copyright>© 2026 MBSoft Systems LLC</copyright><lastBuildDate>Mon, 18 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://unweb.info/blog/rss.xml" rel="self" type="application/rss+xml"/><item><title>How to Scrape JavaScript-Rendered Pages in Python</title><link>https://unweb.info/blog/scrape-javascript-pages-python/</link><pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate><guid>https://unweb.info/blog/scrape-javascript-pages-python/</guid><description>&lt;p>You find a page with the data you need, write a quick scraper, and get back something like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">requests&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">bs4&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">BeautifulSoup&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">requests&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;https://example.com/products&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">soup&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">BeautifulSoup&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">response&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">text&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;html.parser&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">print&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">soup&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_text&lt;/span>&lt;span class="p">())&lt;/span> &lt;span class="c1"># → mostly empty. Navigation chrome. A loading spinner div.&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The page looked full of content in your browser. The scraper returns a skeleton. This is the JavaScript-rendering problem, and it affects a large fraction of the modern web — every React, Vue, Angular, and Next.js site that hydrates on the client, every SPA that fetches content via XHR after the initial page load.&lt;/p></description></item><item><title>Web Scraping with Node.js: Clean Markdown from Any URL</title><link>https://unweb.info/blog/web-scraping-nodejs-unweb/</link><pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate><guid>https://unweb.info/blog/web-scraping-nodejs-unweb/</guid><description>&lt;p>The standard Node.js scraping stack is &lt;code>axios&lt;/code> + &lt;code>cheerio&lt;/code>. It works fine for static sites. It fails quietly on the modern web.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">axios&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;axios&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">cheerio&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">require&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;cheerio&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">response&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kr">await&lt;/span> &lt;span class="nx">axios&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">get&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;https://some-react-app.com/products&amp;#39;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">$&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">cheerio&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">response&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">data&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nx">$&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;body&amp;#39;&lt;/span>&lt;span class="p">).&lt;/span>&lt;span class="nx">text&lt;/span>&lt;span class="p">());&lt;/span> &lt;span class="c1">// &amp;#34;Loading...&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The page looks full of content in Chrome. Your scraper gets a shell. This is because client-side-rendered apps — React, Next.js, Vue — ship an empty &lt;code>&amp;lt;div id=&amp;quot;root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code> and populate it with JavaScript after the browser executes the bundle. &lt;code>axios.get()&lt;/code> fires before any of that runs.&lt;/p></description></item><item><title>Building a Web Research Agent with Python and Claude</title><link>https://unweb.info/blog/web-research-agent-python/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid>https://unweb.info/blog/web-research-agent-python/</guid><description>&lt;p>The standard approach to &amp;ldquo;web research&amp;rdquo; in LLM applications is: fetch the URL, pass the HTML to the model, ask it to extract what you need. This works in demos. In production, it fails on roughly 40% of real-world URLs — the ones that are JavaScript-rendered, paywalled, login-gated, or just structurally noisy enough to confuse extraction.&lt;/p>
&lt;p>The model is not to blame. When you hand Claude a page full of navigation chrome, cookie consent text, related-articles widgets, and advertising tags wrapped around three sentences of actual content, you are asking it to find a needle in a noisy haystack on every single request. Sometimes it works. Often it misses things. The failure mode is silent: the agent reports back, sounds confident, and has extracted from the wrong part of the page.&lt;/p></description></item><item><title>Markdown as Developer Workflow Infrastructure</title><link>https://unweb.info/blog/markdown-developer-workflow/</link><pubDate>Tue, 28 Apr 2026 00:00:00 +0000</pubDate><guid>https://unweb.info/blog/markdown-developer-workflow/</guid><description>&lt;p>A few years ago, Markdown was primarily a lightweight authoring format — a way to write documentation without fighting a rich-text editor. That was the extent of its infrastructure role.&lt;/p>
&lt;p>Something changed when LLMs became central to developer workflows. Markdown is now the exchange format that holds the modern AI development stack together. Prompts are written in Markdown. Context windows are assembled from Markdown. RAG pipelines store and retrieve Markdown chunks. Documentation sites are generated from Markdown. AI coding assistants communicate through Markdown. The MCP protocol uses Markdown as its primary content representation.&lt;/p></description></item><item><title>How to Build a RAG Pipeline with Live Web Data Using Python</title><link>https://unweb.info/blog/rag-pipeline-python-unweb/</link><pubDate>Mon, 27 Apr 2026 00:00:00 +0000</pubDate><guid>https://unweb.info/blog/rag-pipeline-python-unweb/</guid><description>&lt;p>You built a RAG pipeline. You seeded your vector store with 800 documentation pages. Retrieval looks great in your smoke tests. Then you ship it, and three days later a user asks a perfectly reasonable question about your product&amp;rsquo;s API — and your LLM confidently answers with &lt;code>&amp;lt;div id=&amp;quot;root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&lt;/code>.&lt;/p>
&lt;p>That is not a hallucination. That is your pipeline ingesting the skeleton HTML returned by a JavaScript-rendered SPA, embedding it faithfully, and then retrieving it because, token-for-token, it is the closest thing in your index to &amp;ldquo;what does this endpoint return.&amp;rdquo; The embeddings matched. The content was garbage.&lt;/p></description></item><item><title>How to Convert Any Webpage to Markdown from Claude Code with UnWeb MCP</title><link>https://unweb.info/blog/mcp-server-claude-code/</link><pubDate>Wed, 01 Apr 2026 00:00:00 +0000</pubDate><guid>https://unweb.info/blog/mcp-server-claude-code/</guid><description>&lt;p>If you&amp;rsquo;re building RAG pipelines, you&amp;rsquo;ve hit this wall: you need clean text from web pages, but 30% of them are JS-rendered SPAs that return empty markup. You don&amp;rsquo;t find out until your retrieval quality craters.&lt;/p>
&lt;p>The UnWeb MCP server (&lt;code>@mbsoftsystems/unweb-mcp&lt;/code>) gives Claude Code, Cursor, and Windsurf five tools for web-to-markdown conversion — and every response includes a content quality score (0–100) so your agent knows immediately whether the extraction worked.&lt;/p></description></item></channel></rss>