{"id":9628,"date":"2025-07-20T04:54:19","date_gmt":"2025-07-20T04:54:19","guid":{"rendered":"https:\/\/www.newsbeep.com\/uk\/9628\/"},"modified":"2025-07-20T04:54:19","modified_gmt":"2025-07-20T04:54:19","slug":"scrape-smarter-not-harder-let-mcp-and-ai-write-your-next-scraper-for-you","status":"publish","type":"post","link":"https:\/\/www.newsbeep.com\/uk\/9628\/","title":{"rendered":"Scrape Smarter, Not Harder: Let MCP and AI Write Your Next Scraper for You"},"content":{"rendered":"<p>In past episodes of\u00a0<a href=\"https:\/\/substack.thewebscraping.club\/t\/the-lab?ref=hackernoon.com\" target=\"_blank\" rel=\"noopener noreferrer ugc nofollow\">The Lab series<\/a>\u00a0on\u00a0<a href=\"https:\/\/substack.thewebscraping.club\/?ref=hackernoon.com\" target=\"_blank\" rel=\"noopener noreferrer ugc nofollow\">The Web Scraping Club<\/a>, we used\u00a0Firecrawl\u00a0to scrape the content of this newsletter, a vector DB like\u00a0Pinecone\u00a0to store the articles&#8217; markdown, and the OpenAI API to retrieve information and append the context to our prompt to get better answers.<\/p>\n<p>This approach has limitations. The context window size (the amount of information we can include in the prompt) is limited, so the answers were not always great.<\/p>\n<p>Since the AI landscape will change in a few months, it seems that using RAG and Pinecone is old school. Today, the new buzzword is\u00a0<a href=\"https:\/\/www.anthropic.com\/news\/model-context-protocol?ref=hackernoon.com\" target=\"_blank\" rel=\"noopener noreferrer ugc nofollow\">Model Context Protocol (MCP)<\/a>, so I\u2019ve conducted this small experiment to see how MCP can be used for web scraping.<\/p>\n<p>What is a Model Context Protocol?<\/p>\n<p>The\u00a0Model Context Protocol (MCP)\u00a0is an open standard, initially developed by Anthropic, that enables large language models (LLMs) to interact with external tools and data through a standardized interface. In essence, MCP provides a\u00a0universal connector\u00a0between AI models and the systems where data lives. Instead of building custom integrations for every data source or scraping tool, developers can expose their data or functionality via an MCP server. AI assistants (the MCP clients) can consistently request those resources, and this standardized, two-way connection enriches raw content with metadata, context, and instructions so that AI models can more effectively interpret information.<\/p>\n<p>But how does it work?<\/p>\n<p>The MCP follows a client-server architecture: The host application (e.g., an IDE or AI assistant) runs an MCP\u00a0client\u00a0(Claude, Cursor, etc.) that connects via the MCP protocol to one or more MCP\u00a0servers. Each server interfaces with a specific data source or tool (e.g., a database, file system, or web scraper) on your local machine or network, providing structured context or actions to the LLM.\u200b<\/p>\n<p>An MCP server can expose\u00a0resources\u00a0(read-only context data analogous to GET endpoints) and\u00a0tools\u00a0(actions or functions the model can invoke analogous to POST endpoints) to the LLM.<\/p>\n<p>Anthropic open-sourced MCP in early 2024 to encourage industry-wide adoption. The goal was to establish a\u00a0common framework for AI-to-tool communication, reducing reliance on proprietary APIs and making AI integrations more modular and interoperable.<\/p>\n<p>This structured context handling improves on plain prompts. The model no longer just receives a blob of text; instead, it can load data via resources and call tools with well-defined inputs\/outputs. In summary, MCP provides a standardized, secure way to feed context into AI models and receive actions\/results, which is especially powerful in complex tasks like web scraping.<\/p>\n<p>Why Use MCP?<\/p>\n<p>As we\u2019ve seen in previous posts using RAG, one challenge was adding the downloaded article to the prompt since the context size is limited. We tried different solutions, like splitting the articles into chunks on Pinecone and then saving only a summary made with OpenAI to reduce its length.<\/p>\n<p>Using MCP, especially in this case, where we\u2019re using it inside Cursor IDE, we can create a tool that extracts the HTML and saves it to a file that can be read and chunked by Cursor and used to generate the XPaths.<\/p>\n<p>Another interesting aspect of using MCP is that we mix programming and prompt engineering. In fact, creating an MCP tool is like creating a function in our code: once we crafted the prompt in the IDE, this is evaluated and calls our tools with the needed argument (like the URL to fetch) guessed from our prompt. The tool itself, instead, is a programmed function, so there\u2019s not the fuzziness of a prompt, but given an input, we know the output we can expect. This is great because it reduces the uncertainty of the whole process and, at the same time, opens up a myriad of use cases for enriching the context of your chat with the LLM.<\/p>\n<p>Last but not least, MCP is a standard, and theoretically, once we have a server up and running, we could plug it into any model and tool that supports it. However, the fact that OpenAI does not currently support it is not a good sign for the protocol&#8217;s diffusion.<\/p>\n<p>What we\u2019re going to use MCP<\/p>\n<p>We\u2019ll use two key technologies to implement our solution: the\u00a0MCP Python SDK\u00a0and\u00a0Camoufox.<\/p>\n<p>MCP Python SDK.\u00a0The MCP Python SDK is the official library for building MCP servers (and clients) in Python. It implements the full MCP specification, handling all the underlying protocol messaging so you can focus on defining the resources and tools you need.<\/p>\n<p>With this SDK, you can create an MCP server in just a few lines of code. It provides a high-level\u00a0FastMCP\u00a0server class that manages connections and lets you register functions as tools or resources via decorators. For example, you can annotate a function\u00a0@mcp.tool()\u00a0to expose it to the model as an actionable tool.<\/p>\n<p>The SDK packages the function\u2019s result and sends it back to the AI client in a consistent format. In summary, setting up an MCP server that can feed context to LLMs or perform tasks on their behalf is straightforward.<\/p>\n<p>Camoufox for HTML retrieval.\u00a0In web scraping, getting the raw HTML from target pages (especially those with anti-scraping measures or heavy JavaScript) is half the battle. I decided to use Camoufox, which is an open-source stealth browsing tool designed for such scenarios, to be almost sure to be able to get the HTML from every page. This is especially true because the MCP runs locally on my machine, so I won\u2019t need any proxy. On top of its stealth capabilities, I wanted to use Camoufox to build an MCP logic from 0. If you want to save time, you can use\u00a0<a href=\"https:\/\/github.com\/browserbase\/mcp-server-browserbase?ref=hackernoon.com\" target=\"_blank\" rel=\"noopener noreferrer ugc nofollow\">the BrowserBase MCP server<\/a>\u00a0or\u00a0<a href=\"https:\/\/github.com\/hyperbrowserai\/mcp?ref=hackernoon.com\" target=\"_blank\" rel=\"noopener noreferrer ugc nofollow\">the Hyperbrowser one<\/a>. They have some prebuilt tools like extracting data and interacting with the page, making life easier for us.<\/p>\n<p>Technical Implementation of an MCP Server for writing a Camoufox scraper<\/p>\n<p>Now, we\u2019ll build the MCP server that helps us write a Camoufox scraper in three steps. Each of the steps of the process has its own tool:<\/p>\n<p>fetch_page_content\u00a0will be a tool that opens Camoufox and stores the HTML of the target page on a file.<\/p>\n<p>generate_xpaths\u00a0will be a tool that reads the HTML file and, given a template of output data, creates the selectors, again saving them to a file. That\u2019s why we want it flexible enough to handle different page types (for example, a product listing page vs. a product detail page in e-commerce).<\/p>\n<p>write_camoufox_scraper\u00a0will be a tool that reads the selectors and a template of Camoufox spider (Camoufox_template.py) and creates a new one based on that and the selectors just created.<\/p>\n<p>The server code (xpath_server.py) will be saved in the repository under folder 79.MCP\/MCPFiles.<\/p>\n<p>Step 1: Set Up the Environment<\/p>\n<p>First, make sure you have\u00a0Python 3.10+\u00a0installed and install the necessary packages. We\u2019ll need the MCP SDK and Camouf, which you can install via pip.<\/p>\n<p>pip install mcp camoufox <\/p>\n<p>The\u00a0mcp\u00a0package includes the MCP Python SDK and CLI tool.\u00a0Camoufox\u00a0may require an additional step to fetch its browser binary (for example, running\u00a0python -m camoufox fetch\u00a0to download the stealth Firefox \u2013 refer to Camoufox docs for details). Once these are installed, you\u2019re ready to write the server code.<\/p>\n<p>Step 2: Initialize the MCP Server<\/p>\n<p>Create a new Python script (in the repository, it will be\u00a0xpath_server.py). In this script, we&#8217;ll initialize an MCP server and define our tools. Using the MCP SDK is straightforward:<\/p>\n<p>from mcp.server.fastmcp import FastMCP<br \/>\nimport asyncio<br \/>\nfrom camoufox.async_api import AsyncCamoufox<br \/>\nimport time<br \/>\nimport os<\/p>\n<p>mcp = FastMCP(&#8220;Scrapy XPath Generator&#8221;)<\/p>\n<p>&#8230;..<br \/>\ncoded tools<br \/>\n&#8230;.<\/p>\n<p>if __name__ == &#8220;__main__&#8221;:<br \/>\n\t# Initialize and run the server<br \/>\n\tmcp.run(transport=&#8217;stdio&#8217;)<\/p>\n<p>That\u2019s enough for setting up an empty MCP server.<\/p>\n<p>Step 3: Implement HTML Retrieval via Camoufox<\/p>\n<p>We want a tool that, given a URL, fetches the page\u2019s HTML content. We\u2019ll use Camoufox to do this in a stealthy way. Let\u2019s define an MCP\u00a0tool\u00a0called\u00a0fetch_page_content:<\/p>\n<p>HTML_FILE_PATH = &#8220;\/Users\/pierluigivinciguerra\/TWSC\/Code\/TheWebScrapingClub\/79.MCP\/downloaded_page.html&#8221;  # File to store the downloaded HTML<br \/>\nCAMOUFOX_FILE_PATH = &#8220;\/Users\/pierluigivinciguerra\/TWSC\/Code\/TheWebScrapingClub\/79.MCP\/camoufox_template.py&#8221;<\/p>\n<p>@mcp.tool()<br \/>\nasync def fetch_page_content(url: str) -&gt; str:<br \/>\n\tglobal latest_html<\/p>\n<p>\t&#8220;&#8221;&#8221;Fetch page HTML using Camoufox stealth browser.&#8221;&#8221;&#8221;<br \/>\n\tprint(f&#8221;[DEBUG] Fetching URL: {url}&#8221;)<br \/>\n\ttry:<br \/>\n\t\tasync with AsyncCamoufox(humanize=True) as browser:<br \/>\n\t\t\tpage = await browser.new_page()<br \/>\n\t\t\tawait page.goto(url)<br \/>\n\t\t\ttime.sleep(10)<br \/>\n\t\t\tlatest_html = await page.content()<br \/>\n\t\t\twith open(HTML_FILE_PATH, &#8220;w&#8221;, encoding=&#8221;utf-8&#8243;) as f:<br \/>\n\t\t\t\tf.write(latest_html)<br \/>\n\t\t\tprint(&#8220;[DEBUG] HTML stored for later use&#8221;)<br \/>\n\t\t\treturn &#8220;HTML fetched and stored successfully.&#8221;<br \/>\n\texcept Exception as e:<br \/>\n\t\tprint(f&#8221;[ERROR] {e}&#8221;)<br \/>\n\t\treturn f&#8221;Error fetching page: {str(e)}&#8221;<\/p>\n<p>Except for the decorator, it\u2019s a simple Camoufox scraper that opens the URL we\u2019ll pass in the prompt inside Cursor and stores the HTML code in a file.<\/p>\n<p>Step 4: Generate XPath Selectors Based on the template<\/p>\n<p>The second tool we need analyzes the HTML and produces XPath selectors for the data we want to scrape. We\u2019ll create a tool,\u00a0generate_xpaths,\u00a0that takes a template identifier (to indicate what kind of page or extraction pattern to use) as an argument.<\/p>\n<p>Based on the template, this function will output the appropriate XPath expressions. For example, we might have one template for a Product Listing Page (PLP, a page with multiple products listed) and another for a Product Detail Page (PDP, a page with a single product\u2019s details).<\/p>\n<p>@mcp.tool()<br \/>\ndef generate_xpaths(template: str) -&gt; dict:<br \/>\n\t&#8220;&#8221;&#8221;Write XPATH selectors for the requested fields using the downloaded HTML file.&#8221;&#8221;&#8221;<\/p>\n<p>\tif not os.path.exists(HTML_FILE_PATH):<br \/>\n\t\treturn {&#8220;error&#8221;: f&#8221;No HTML file found. Run fetch_page_content() first.&#8221;}<\/p>\n<p>\tif template.lower() == &#8220;plp&#8221;:<br \/>\n\t\tfields = &#8220;product title, product link, product price, product image, product code&#8221;<br \/>\n\telif template.lower() == &#8220;pdp&#8221;:<br \/>\n\t\tfields = &#8220;product title, product price, product description, product image, product color, product size, product code&#8221;<br \/>\n\telse:<br \/>\n\t\treturn {&#8220;error&#8221;: &#8220;Unknown template type&#8221;}<\/p>\n<p>\t# Return the HTML and requested fields so Cursor can analyze them<br \/>\n\treturn {<br \/>\n\t\t&#8220;message&#8221;: &#8220;Print the XPath expressions for the requested fields using the variable latest_html.&#8221;,<br \/>\n\t\t&#8220;requested_fields&#8221;: fields<br \/>\n\t}<\/p>\n<p>In this case, we\u2019re simply defining the desired output structure and the fields for which we want to create the selectors.<\/p>\n<p>Step 5: Generate the new Camoufox scraper<\/p>\n<p>Now that we have the selectors, we want to combine a Camoufox Scraper template (Camoufox_template.py) with the selectors to write a scraper with the desired output structure. I learned that using a Camoufox Scraper template is necessary because, without it, the code generated is usually a hallucination and does not work.<\/p>\n<p>@mcp.tool()<br \/>\ndef write_camoufox_scraper(template: str, url: str) -&gt; dict:<br \/>\n\tprint(f&#8221;[DEBUG] Writing scraper for template: {template} and URL: {url}&#8221;)<br \/>\n\t&#8220;&#8221;&#8221;Reads file Camoufox_template.py and uses it to write a new Camoufox scraper with the requested fields and starting from the url&#8221;&#8221;&#8221;<br \/>\n\twith open(CAMOUFOX_FILE_PATH, &#8220;r&#8221;, encoding=&#8221;utf-8&#8243;) as f:<br \/>\n\t\tlatest_html = f.read()<br \/>\n\treturn{&#8220;message&#8221;: &#8220;Using this template, write a working scraper with the requested fields and starting URL&#8221;}<\/p>\n<p>That\u2019s it; now we just need to configure Cursor to run this MCP server and write a prompt that uses its tools.<\/p>\n<p>Step 6. Using the MCP Server in Cursor<\/p>\n<p>One of the great things about Cursor (an AI-powered IDE) is its built-in support for MCP servers as extensions of the AI\u2019s capabilities. We will integrate our newly created MCP server into Cursor so that the AI assistant can use it to automate web scraping tasks (like generating XPath selectors for us).<\/p>\n<p>To connect the server in Cursor, follow these steps:<\/p>\n<p>Open Cursor Settings\u00a0\u2013 In Cursor IDE, go to the settings menu.<\/p>\n<p>Navigate to MCP Servers\u00a0\u2013 Under the Features section, find\u00a0MCP Servers. (This requires Cursor v0.45.6+ or later, which supports custom MCP server integration.)<\/p>\n<p>Add a New MCP Server\u00a0\u2013 Click the \u201c+ Add New MCP Server\u201d button. This will prompt you to enter details for the server.<\/p>\n<p>Configure the server\u00a0\u2013 Give it a name like \u201cScrapy XPath Server\u201d (any friendly name). For\u00a0Type, select\u00a0&#8220;command&#8221;\u00a0(since we will run a local command to start the server). For\u00a0Command, enter the command to launch your server. For example, if your script is saved at \/path\/to\/xpath_server.py, you might put it in the box.<\/p>\n<p>Save and verify\u00a0\u2013 After entering the details, save the configuration. You should see your MCP server listed in Cursor\u2019s MCP Servers section. Cursor will now launch this server (via the given command) and maintain a connection to it. You might see a green light next to the MCP server\u2019s name.<\/p>\n<p><img decoding=\"async\" style=\"display:block;max-width:100%;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0\" alt=\"\" aria-hidden=\"true\" src=\"data:image\/svg+xml,%3csvg%20xmlns=%27http:\/\/www.w3.org\/2000\/svg%27%20version=%271.1%27%20width=%27866%27%20height=%27303%27\/%3e\"\/><img alt=\"\" src=\"data:image\/gif;base64,R0lGODlhAQABAIAAAAAAAP\/\/\/yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" decoding=\"async\" data-nimg=\"intrinsic\" class=\"image-container undefined\" style=\"position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%\"\/><\/p>\n<p>Once the server is added,\u00a0Cursor\u2019s AI assistant (the \u201cComposer\u201d agent)\u00a0can use the new tools automatically when appropriate.<\/p>\n<p>In practice, when you ask in the composer, \u201cGet the HTML from this URL and then write the XPath selectors using the template PLP\u201d, the assistant will ask you for confirmation to run the\u00a0fetch_page_content\u00a0tool and then the\u00a0generate_xpath\u00a0one.<\/p>\n<p><img decoding=\"async\" style=\"display:block;max-width:100%;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0\" alt=\"\" aria-hidden=\"true\" src=\"data:image\/svg+xml,%3csvg%20xmlns=%27http:\/\/www.w3.org\/2000\/svg%27%20version=%271.1%27%20width=%27531%27%20height=%27582%27\/%3e\"\/><img alt=\"\" src=\"data:image\/gif;base64,R0lGODlhAQABAIAAAAAAAP\/\/\/yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" decoding=\"async\" data-nimg=\"intrinsic\" class=\"image-container undefined\" style=\"position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%\"\/><\/p>\n<p>Our tests<\/p>\n<p>After a few hours of interactions with the prompt to understand how to get the data needed in a reliable way, I\u2019ve found that this one does most of the tasks I asked for when used with Cloud 3.7 Sonnet (confirming that is one of the best LLMs for coding\u00a0<a href=\"https:\/\/www.youtube.com\/watch?v=t8mgoOHMHSk&amp;ref=hackernoon.com\" target=\"_blank\" rel=\"noopener noreferrer ugc nofollow\">as Marco Vinciguerra from ScrapeGraphAI said in our latest interviews<\/a>):<\/p>\n<p>\u201cFetch the URL @https:\/\/www.gucci.com\/it\/it\/ca\/women\/shoes-for-women-c-women-shoes<br \/>Then, write the XPath selectors using the template PLP. Use the downloaded HTML (downloaded_page.html file) to write working XPATH selectors.<br \/>Write the selectors in a file called selectors.txt.<br \/>Then, given the selectors previously created, Write a Camoufox scraper using the template PLP and the initial URL. If in doubt, read the selectors.txt file to understand how many fields and which selectors you have to use. Use the file Camoufox_template.py for building the scraper and customize it with the fields in output read by the PLP template.\u201d<\/p>\n<p>Cursor opens a Camoufox tab, browses to the URL I passed, saves the HTML in a file, reads it, and then generates XPATH selectors and saves them in another file. I opted to save the results of each step inside a file so that the following steps could read them and eventually chunk them automatically rather than passing their content in the context window, which could be too small.<\/p>\n<p>The scraper created is executable the first time, so it\u2019s technically correct, and the results are quite good.<\/p>\n<p><img decoding=\"async\" style=\"display:block;max-width:100%;width:initial;height:initial;background:none;opacity:1;border:0;margin:0;padding:0\" alt=\"\" aria-hidden=\"true\" src=\"data:image\/svg+xml,%3csvg%20xmlns=%27http:\/\/www.w3.org\/2000\/svg%27%20version=%271.1%27%20width=%271305%27%20height=%27785%27\/%3e\"\/><img alt=\"\" src=\"data:image\/gif;base64,R0lGODlhAQABAIAAAAAAAP\/\/\/yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" decoding=\"async\" data-nimg=\"intrinsic\" class=\"image-container undefined\" style=\"position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%\"\/><\/p>\n<p>The scraper, without saying anything about the website, correctly populated all the fields, which is excellent and more than I expected from this test.<\/p>\n<p>I still have some concerns about the solution&#8217;s reliability. I don\u2019t get the same results every time I run the same prompt, which can be frustrating because you don\u2019t know how to fix the process if you don\u2019t know what to expect from it.<\/p>\n<p>The second issue is probably more of a prompt engineering one. The output data structure is not the one I expected, but the one that the assistant found on the Camoufox scraper template. I still haven\u2019t figured out how to solve this, but I\u2019m sure, in some way, it can be done.<\/p>\n<p>I think we&#8217;re done for today. In the next episode, we\u2019ll continue on our journey to make Cursor our web-scraping AI assistant.<\/p>\n","protected":false},"excerpt":{"rendered":"In past episodes of\u00a0The Lab series\u00a0on\u00a0The Web Scraping Club, we used\u00a0Firecrawl\u00a0to scrape the content of this newsletter, a&hellip;\n","protected":false},"author":2,"featured_media":9629,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[20],"tags":[554,733,4308,86,56,54,55],"class_list":["post-9628","post","type-post","status-publish","format-standard","has-post-thumbnail","category-artificial-intelligence","tag-ai","tag-artificial-intelligence","tag-artificialintelligence","tag-technology","tag-uk","tag-united-kingdom","tag-unitedkingdom"],"_links":{"self":[{"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/posts\/9628","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/comments?post=9628"}],"version-history":[{"count":0,"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/posts\/9628\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/media\/9629"}],"wp:attachment":[{"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/media?parent=9628"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/categories?post=9628"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.newsbeep.com\/uk\/wp-json\/wp\/v2\/tags?post=9628"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}