{
  "source_file": "0930",
  "source_platform": "Dev.to",
  "author": "Mariano Gobea Alcoba",
  "title_listed": "Robust LLM Extractor for Websites in TypeScript!",
  "keywords": "",
  "domain": "大模型算法",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://dev.to/mgobea/robust-llm-extractor-for-websites-in-typescript-3mmj",
  "publish_date": "2026-03-26",
  "corpus_stage": "",
  "notes": "",
  "id": "4400e9eb4ab8",
  "collected_at": "2026-10-02T14:05:45",
  "title": "Robust LLM Extractor for Websites in TypeScript! - DEV Community",
  "body_markdown": "Web scraping and structured data extraction from websites have long been fundamental tasks in data engineering, fueling analytics, competitive intelligence, and various automation workflows. The traditional approach, relying heavily on CSS selectors or XPath expressions, presents a persistent challenge: fragility. Website layouts change frequently, leading to broken selectors, pipeline failures, and significant maintenance overhead. This fragility is exacerbated by the increasing complexity of modern web applications, which often render content dynamically using JavaScript, making static HTML parsing insufficient.\nThe Promise and Pitfalls of LLM-First Extraction\nThe advent of large language models (LLMs) appeared to offer a compelling solution to the brittleness of traditional scraping. The intuition is straightforward: provide an LLM with raw HTML and a natural language instruction or a schema, then request structured JSON output. This paradigm shift promised to abstract away the intricate details of DOM structure, offering a more resilient approach to data extraction.\nHowever, practical application of LLMs for web data extraction reveals several significant challenges that can make the \"LLM-first\" approach more painful than anticipated:\nToken Budget Exhaustion and Noise Reduction\nRaw HTML, especially from modern web pages, is replete with superfluous content for extraction purposes. Navigation bars, footers, headers, advertisements, tracking scripts, inline styles, and comment blocks collectively constitute a substantial portion of the page's HTML, often representing 80% or more of the total token count. Feeding this undifferentiated mass to an LLM quickly exhausts token budgets, leading to higher API costs and potentially truncated or less accurate outputs due to context window limitations. Effective noise reduction is therefore not merely an optimization but a prerequisite for feasible LLM-based extraction.\nMalformed JSON Output\nDespite sophisticated instruction following capabilities, LLMs are not infallible JSON generators. They frequently produce malformed JSON, particularly when dealing with complex, nested schemas or lengthy outputs. A single missing bracket, an unescaped quote, or an extraneous comma can render the entire output unparsable by standard JSON libraries, leading to pipeline crashes and lost data. The problem is compounded when extracting arrays of objects, where a single invalid item can corrupt the entire array structure. Robust error recovery is crucial to mitigate this common failure mode.\nURL Hygiene and Normalization\nWeb pages are a mosaic of relative URLs, URL fragments, query parameters, and tracking identifiers. When extracting links or image sources, these must be canonicalized and normalized into absolute, clean URLs. An LLM might extract a relative path, an uncleaned URL with tracking parameters, or a URL with a fragment identifier, necessitating post-processing that is often overlooked in initial LLM integration attempts. Consistent URL normalization is essential for data integrity and usability.\nRepetitive Boilerplate\nIntegrating LLMs into a data extraction pipeline typically involves a series of common, repetitive steps: fetching HTML (often requiring browser automation), cleaning the HTML, converting it to a more LLM-friendly format (like Markdown), constructing the LLM prompt, invoking the LLM, parsing its output, handling potential errors, and finally validating the structured data against a schema. Rebuilding this entire pipeline for every new extraction task leads to significant developer overhead and inconsistencies across projects.\nLightfeed Extractor: An Opinionated Solution for Robustness\nRecognizing these systemic challenges, Lightfeed Extractor emerged as a TypeScript library designed to encapsulate the complete pipeline from raw HTML to validated, structured data. Its architecture focuses on robustness, type safety, and efficient resource utilization, aiming to turn the promise of LLM-based extraction into a reliable production reality.\nCore Component: Intelligent HTML Pre-processing and Markdown Conversion\nThe most critical initial step for efficient LLM processing is transforming raw, verbose HTML into a concise, content-focused representation. Lightfeed Extractor employs a multi-stage approach for this:\nDOM Segmentation and Noise Reduction\nThe library leverages robust DOM parsing capabilities, typically through\njsdom\nfor server-side processing or Playwright's browser context for live DOM interaction. The objective is to identify and isolate the main content block while aggressively pruning irrelevant elements.\nDOM Construction\n: The raw HTML is parsed into a navigable Document Object Model (DOM) tree. This allows for semantic analysis and structural manipulation, which is difficult with regex-based approaches.\nMain Content Identification\n: Sophisticated heuristics, often inspired by readability algorithms (e.g., similar to Mozilla's Readability.js), are applied to locate the primary content region of the page. These algorithms typically analyze factors such as element density, text length within blocks, tag frequency, and the presence of semantic HTML5 tags like\n<article>\n,\n<main>\n, and\n<section>\n. The goal is to intelligently discern editorial content from surrounding UI elements.\nNoise Pruning\n: Once the main content candidate is identified, extraneous elements are systematically removed from the DOM. This includes:\nStructural elements commonly found outside main content:\n<nav>\n,\n<header>\n,\n<footer>\n,\n<aside>\n.\nNon-content elements:\n<script>\n,\n<style>\n,\n<iframe>\n,\n<noscript>\n.\nCommon identifiers for advertisements, social sharing widgets, comment sections (if not desired), and other non-essential interactive components, often identifiable via CSS classes or IDs.\nEmpty elements or elements with minimal text content that are likely layout placeholders.\nThis process significantly reduces the overall token count, allowing more substantial content to fit within the LLM's context window and focusing the LLM's attention on relevant data.\nSemantic Enhancement and URL Canonicalization\nAfter noise reduction, the remaining DOM is semantically enhanced and normalized:\nImage Inclusion\n: If desired,\n<img>\ntags are processed. Their\nsrc\nattributes are extracted, and their\nalt\ntext is prioritized.\nLink Normalization\n: All\n<a>\ntags'\nhref\nattributes are canonicalized. This involves:\nResolution of Relative URLs\n: Relative paths (e.g.,\n/products/item-123\n) are resolved against the base URL of the original page to form absolute URLs (e.g.,\nhttps://example.com/products/item-123\n).\nQuery Parameter Cleaning\n: Common tracking parameters (e.g.,\nutm_source\n,\nfbclid\n,\ngclid\n) and other irrelevant query components are stripped to produce cleaner, canonical URLs.\nFragment Removal\n: URL fragments (\n#section\n) are typically removed unless explicitly required, as they do not identify unique resources.\nMarkdown Representation for LLM Efficiency\nThe cleaned and enhanced HTML fragment is then converted into Markdown. Markdown is chosen for its conciseness and its natural alignment with how LLMs process textual information. Libraries like\nturndown\nor custom renderers facilitate this conversion, ensuring that headings, lists, links, and paragraphs are represented efficiently. This final Markdown output is a significantly condensed, yet semantically rich, representation of the original web page's core content, optimized for LLM consumption.\nimport\n{\nJSDOM\n}\nfrom\n'\njsdom\n'\n;\nimport\nTurndownService\nfrom\n'\nturndown\n'\n;\nimport\n{\nURL\n}\nfrom\n'\nurl\n'\n;\ninterface\nHtmlProcessorOptions\n{\nbaseUrl\n:\nstring\n;\nincludeImages\n?:\nboolean\n;\nstripTrackingParams\n?:\nboolean\n;\n}\nexport\nclass\nHtmlToMarkdownProcessor\n{\nprivate\nturndownService\n:\nTurndownService\n;\nprivate\noptions\n:\nHtmlProcessorOptions\n;\nconstructor\n(\noptions\n:\nHtmlProcessorOptions\n)\n{\nthis\n.\noptions\n=\noptions\n;\nthis\n.\nturndownService\n=\nnew\nTurndownService\n({\nheadingStyle\n:\n'\natx\n'\n,\ncodeBlockStyle\n:\n'\nfenced\n'\n,\nhr\n:\n'\n---\n'\n,\n});\n// Custom rules for turndown if needed, e.g., to handle specific elements\n// or to modify how links/images are rendered.\nthis\n.\nturndownService\n.\naddRule\n(\n'\nanchor\n'\n,\n{\nfilter\n:\n[\n'\na\n'\n],\nreplacement\n:\n(\ncontent\n,\nnode\n)\n=>\n{\nconst\nanchor\n=\nnode\nas\nHTMLAnchorElement\n;\nlet\nhref\n=\nanchor\n.\nhref\n;\nif\n(\nhref\n)\n{\ntry\n{\nconst\nresolvedUrl\n=\nnew\nURL\n(\nhref\n,\nthis\n.\noptions\n.\nbaseUrl\n);\nif\n(\nthis\n.\noptions\n.\nstripTrackingParams\n)\n{\n// Simple stripping example\nresolvedUrl\n.\nsearchParams\n.\nforEach\n((\n_value\n,\nkey\n)\n=>\n{\nif\n(\nkey\n.\nstartsWith\n(\n'\nutm_\n'\n)\n||\nkey\n.\nstartsWith\n(\n'\ngclid\n'\n))\n{\nresolvedUrl\n.\nsearchParams\n.\ndelete\n(\nkey\n);\n}\n});\n}\n// Ensure absolute URL\nhref\n=\nresolvedUrl\n.\ntoString\n();\n}\ncatch\n(\ne\n)\n{\n// Handle malformed URLs if necessary\n}\n}\nreturn\n`[\n${\ncontent\n}\n](\n${\nhref\n||\n''\n}\n)`\n;\n}\n});\nif\n(\n!\nthis\n.\noptions\n.\nincludeImages\n)\n{\nthis\n.\nturndownService\n.\nremove\n(\n'\nimg\n'\n);\n}\nelse\n{\nthis\n.\nturndownService\n.\naddRule\n(\n'\nimage\n'\n,\n{\nfilter\n:\n[\n'\nimg\n'\n],\nreplacement\n:\n(\ncontent\n,\nnode\n)\n=>\n{\nconst\nimg\n=\nnode\nas\nHTMLImageElement\n;\nlet\nsrc\n=\nimg\n.\nsrc\n;\nif\n(\nsrc\n)\n{\ntry\n{\nconst\nresolvedUrl\n=\nnew\nURL\n(\nsrc\n,\nthis\n.\noptions\n.\nbaseUrl\n);\nsrc\n=\nresolvedUrl\n.\ntoString\n();\n}\ncatch\n(\ne\n)\n{\n// Handle malformed URLs\n}\n}\nreturn\n`![\n${\nimg\n.\nalt\n||\n''\n}\n](\n${\nsrc\n||\n''\n}\n)`\n;\n}\n});\n}\n}\npublic\nprocess\n(\nhtml\n:\nstring\n):\nstring\n{\nconst\ndom\n=\nnew\nJSDOM\n(\nhtml\n);\nconst\ndocument\n=\ndom\n.\nwindow\n.\ndocument\n;\n// A simplistic main content extraction for demonstration.\n// In a real library, this would involve sophisticated algorithms.\nlet\nmainContentElement\n=\ndocument\n.\nquerySelector\n(\n'\nmain\n'\n)\n||\ndocument\n.\nbody\n;\n// Remove common noisy elements from the main content (or entire document)\n[\n'\nnav\n'\n,\n'\nheader\n'\n,\n'\nfooter\n'\n,\n'\naside\n'\n,\n'\nscript\n'\n,\n'\nstyle\n'\n,\n'\niframe\n'\n].\nforEach\n(\ntag\n=>\n{\ndocument\n.\nquerySelectorAll\n(\ntag\n).\nforEach\n(\nel\n=>\nel\n.\nremove\n());\n});\n// Further heuristic-based removal could target ad divs, social buttons, etc.\nreturn\nthis\n.\nturndownService\n.\nturndown\n(\nmainContentElement\n.\ninnerHTML\n);\n}\n}\n// Example usage:\nconst\nhtmlContent\n=\n`\n  <!DOCTYPE html>\n  <html>\n  <head><title>Product Page</title></head>\n  <body>\n    <header><h1>Site Header</h1><nav>...</nav></header>\n    <main>\n      <article>\n        <h2>Product Name</h2>\n        <p>This is a great product. <a href=\"/details?id=123&utm_source=test\">More info</a></p>\n        <img src=\"/assets/product.jpg\" alt=\"Product Image\">\n      </article>\n      <aside>Related products...</aside>\n    </main>\n    <footer><p>Copyright</p></footer>\n    <script>alert('hello');</script>\n  </body>\n  </html>\n`\n;\nconst\nprocessor\n=\nnew\nHtmlToMarkdownProcessor\n({\nbaseUrl\n:\n'\nhttps://example.com\n'\n,\nincludeImages\n:\ntrue\n,\nstripTrackingParams\n:\ntrue\n,\n});\nconst\nmarkdown\n=\nprocessor\n.\nprocess\n(\nhtmlContent\n);\nconsole\n.\nlog\n(\nmarkdown\n);\n/* Expected output (simplified):\n## Product Name\n\nThis is a great product. [More info](https://example.com/details?id=123)\n\n![Product Image](https://example.com/assets/product.jpg)\n*/\nEnter fullscreen mode\nExit fullscreen mode\nType-Safe Data Contracts with Zod\nOne of the most significant advancements for robust data extraction is the integration of Zod for defining and validating output schemas. Zod is a TypeScript-first schema declaration and validation library, offering powerful compile-time type inference and runtime validation.\nDefining Schemas for LLM Output\nWith Lightfeed Extractor, developers define the expected structure of the extracted data using Zod. This schema serves as both a contract for the LLM and a validation mechanism for its output.\nimport\n{\nz\n}\nfrom\n'\nzod\n'\n;\n// Define the schema for a single product\nconst\nproductSchema\n=\nz\n.\nobject\n({\nname\n:\nz\n.\nstring\n().\ndescribe\n(\n'\nThe name of the product.\n'\n),\nprice\n:\nz\n.\nnumber\n().\npositive\n().\ndescribe\n(\n'\nThe price of the product, as a positive number.\n'\n),\ncurrency\n:\nz\n.\nstring\n().\nlength\n(\n3\n).\ndescribe\n(\n'\nThe 3-letter currency code (e.g., USD, EUR).\n'\n),\ndescription\n:\n\"\nz.string().optional().describe('A brief description of the product.'),\n\"\nfeatures\n:\nz\n.\narray\n(\nz\n.\nstring\n()).\nmin\n(\n1\n).\noptional\n().\ndescribe\n(\n'\nA list of key features.\n'\n),\nimageUrl\n:\nz\n.\nstring\n().\nurl\n().\noptional\n().\ndescribe\n(\n'\nThe absolute URL to the product image.\n'\n),\n});\n// Define the schema for the entire extraction, which might be an array of products\nconst\nextractionSchema\n=\nz\n.\nobject\n({\nproducts\n:\nz\n.\narray\n(\nproductSchema\n).\nmin\n(\n1\n).\ndescribe\n(\n'\nAn array of extracted product details.\n'\n),\n});\ntype\nExtractedProducts\n=\nz\n.\ninfer\n<\ntypeof\nextractionSchema\n>\n;\n// This schema is then passed to the extractor, along with the Markdown content.\nEnter fullscreen mode\nExit fullscreen mode\nThe\n.describe()\nmethod is particularly useful as it adds metadata that can be directly incorporated into the LLM's prompt, guiding its output generation more effectively.\nRuntime Validation and Developer Experience\nUpon receiving the LLM's raw JSON output, Lightfeed Extractor attempts to parse it and then rigorously validates the resulting JavaScript object against the defined Zod schema.\nCompile-time Safety\n: Developers benefit from TypeScript's static type checking, ensuring that code interacting with the extracted data aligns with the schema.\nRuntime Validation\n: Zod performs deep validation, checking types, required fields, string formats (e.g.,\nurl()\n,\nemail()\n), array lengths (\nmin()\n,\nmax()\n), and custom validation rules. This catches discrepancies that LLMs might introduce, even if the JSON is syntactically valid.\nDetailed Error Reporting\n: If validation fails, Zod provides granular, human-readable error messages, pinpointing exactly which part of the data structure is invalid and why. This is invaluable for debugging LLM prompts or identifying problematic website structures.\nResilient Data Recovery and Partial Extraction\nPerhaps one of the most distinguishing features of Lightfeed Extractor is its emphasis on resilience and graceful degradation in the face of malformed LLM output. Instead of failing outright, the library attempts to salvage valid data.\nStrategies for Malformed JSON\nWhen\nJSON.parse()\nfails, the library employs a multi-pronged strategy to recover parsable JSON:\nPrefix/Suffix Trimming\n: LLMs sometimes wrap JSON in conversational text or markdown code blocks (e.g.,\n\n```\njson\n). Regular expressions are used to trim such extraneous content, isolating the actual JSON string between the first opening\n{\nand the last closing\n}\n(or\n[\nand\n]\n).\nHeuristic-based Repair\n: For common JSON syntax errors, the library can apply heuristic repairs. This might involve:\nMissing Commas\n: Detecting and inserting commas between objects in an array or between key-value pairs in an object when they are clearly missing.\nUnclosed Strings/Brackets\n: Attempting to complete obviously unclosed quotes or brackets.\nTrailing Commas\n: Removing redundant trailing commas, which are invalid in strict JSON.\nInvalid Escapes\n: Correcting common escape sequence errors.\nThis level of repair often relies on dedicated JSON repair libraries or custom parsing logic, balancing aggressive correction with the risk of unintended data alteration.\nIterative Refinement (Advanced)\n: For persistent issues, an advanced strategy involves sending the malformed output back to the LLM with explicit instructions to correct its JSON, referencing the original prompt and schema. This \"self-correction\" mechanism can significantly improve extraction success rates but incurs additional LLM token costs and latency.\nThe Importance of Granular Validation\nEven if the JSON is syntactically correct, individual data points might not conform to the schema (e.g., a price field containing a string \"N/A\" instead of a number). Lightfeed Extractor handles this through granular validation, especially crucial for extracting lists of items:\nArray Item Validation\n: If the top-level schema expects an array of objects (e.g.,\nz.array(productSchema)\n), the library iterates through each item in the LLM's parsed output array.\nIndividual Item Validation\n: Each item is validated against its specific Zod sub-schema (\nproductSchema\n).\nCollection of Valid Data\n: Valid items are collected into the final result. Invalid items are skipped, and their validation errors are typically logged or returned as part of a comprehensive error report, alongside the partially extracted data.\nThis approach ensures that if an LLM successfully extracts 19 out of 20 products, those 19 valid products are still returned, preventing complete pipeline failure due to a single malformed entry.\ntypescript\nimport { z } from 'zod';\n\nconst itemSchema = z.object({\n  id: z.string(),\n  value: z.number().positive(),\n});\n\ntype Item = z.infer<typeof itemSchema>;\n\nfunction parseAndValidateWithRecovery(\n  rawLLMOutput: string,\n  schema: z.ZodSchema<any>\n): { data: any | null; errors: z.ZodError | string | null; partialData?: any[] } {\n  let parsedJson: any;\n  let error: string | null = null;\n\n  try {\n    // Attempt standard parse\n    parsedJson = JSON.parse(rawLLMOutput);\n  } catch (e: any) {\n    // Fallback: Attempt heuristic repair\n    console.warn('JSON parse failed. Attempting heuristic repair...');\n    try {\n      // Simplified repair: e.g., trim markdown code blocks and attempt parse again\n      const jsonRegex = /\n\n```json\\s*(\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\])\\s*```\n\n/m;\n      let cleanedOutput = rawLLMOutput;\n      const match = rawLLMOutput.match(jsonRegex);\n      if (match && match[1]) {\n        cleanedOutput = match[1];\n      } else {\n        // More robust repair could involve a dedicated library\n        // For demonstration, a basic attempt to find first '{' and last '}'\n        const firstBracket = cleanedOutput.indexOf('{');\n        const lastBracket = cleanedOutput.lastIndexOf('}');\n        if (firstBracket !== -1 && lastBracket !== -1 && lastBracket > firstBracket) {\n          cleanedOutput = cleanedOutput.substring(firstBracket, lastBracket + 1);\n        } else {\n            const firstArrayBracket = cleanedOutput.indexOf('[');\n            const lastArrayBracket = cleanedOutput.lastIndexOf(']');\n            if (firstArrayBracket !== -1 && lastArrayBracket !== -1 && lastArrayBracket > firstArrayBracket) {\n              cleanedOutput = cleanedOutput.substring(firstArrayBracket, lastArrayBracket + 1);\n            }\n        }\n      }\n      parsedJson = JSON.parse(cleanedOutput);\n      console.warn('JSON repaired successfully.');\n    } catch (repairError) {\n      error = `Failed to parse JSON even after repair: ${e.message} and ${repairError}`;\n      return { data: null, errors: error };\n    }\n  }\n\n  // Validate against the schema\n  const validationResult = schema.safeParse(parsedJson);\n\n  if (validationResult.success) {\n    return { data: validationResult.data, errors: null };\n  } else {\n    // Handle partial data recovery for arrays\n    if (schema instanceof z.ZodArray && Array.isArray(parsedJson)) {\n      const itemSchema = (schema as z.ZodArray<any>).element;\n      const partialData: any[] = [];\n      const itemErrors: z.ZodError[] = [];\n\n      parsedJson.forEach((item, index) => {\n        const itemValidationResult = itemSchema.safeParse(item);\n        if (itemValidationResult.success) {\n          partialData.push(itemValidationResult.data);\n        } else {\n          itemErrors.push(new z.ZodError([\n            { ...itemValidationResult.error.errors[0], path: [`${index}`].concat(itemValidationResult.error.errors[0].path) }\n          ]));\n        }\n      });\n\n      if (partialData.length > 0) {\n        console.warn(`Partial data extracted successfully: ${partialData.length} items out of ${parsedJson.length}.`);\n        return { data: parsedJson, errors: new z.ZodError(itemErrors.flatMap(e => e.errors)), partialData: partialData };\n      }\n    }\n\n    console.error('Schema validation failed:', validationResult.error);\n    return { data: null, errors: validationResult.error };\n  }\n}\n\n// Example usage:\nconst malformedJsonOutput = `\nThis is some introductory text.\n\\`\\`\\`json\n[\n  {\"id\": \"A1\", \"value\": 100},\n  {\"id\": \"B2\", \"value\": \"invalid\"}, // Malformed value\n  {\"id\": \"C3\", \"value\": 300},\n  {\"id\": \"D4\", \"value\": -50} // Invalid positive number\n]\n\\`\\`\\`\nSome trailing text.\n`;\n\nconst schemaForArray = z.array(itemSchema);\n\nconst result = parseAndValidateWithRecovery(malformedJsonOutput, schemaForArray);\nconsole.log('\\nResult:', result);\n/* Expected output will show errors for B2 and D4, but partialData will contain A1 and C3 */\nEnter fullscreen mode\nExit fullscreen mode\nOrchestration with LangChain-Compatible LLMs\nLightfeed Extractor is designed to be agnostic to the underlying LLM provider, achieving this through compatibility with LangChain's flexible interfaces. This allows seamless integration with various models: OpenAI (GPT-3.5, GPT-4), Google Gemini, Anthropic Claude, or locally hosted models via Ollama.\nPrompt Engineering for Structured Output\nEffective prompt engineering is paramount for consistent LLM output. The library constructs prompts that include:\nThe cleaned Markdown content of the web page.\nThe Zod schema (often serialized into a JSON schema or a structured natural language description).\nClear instructions to generate JSON conforming strictly to the provided schema, without conversational preamble or postscript.\nInstructions for handling missing data (e.g., return\nnull\nor omit optional fields if data is not found).\nIntegration Flexibility\nBy leveraging LangChain's\nRunnable\ninterface or similar abstractions, Lightfeed Extractor can easily swap LLM implementations without altering the core logic. This provides developers with the flexibility to choose models based on cost, performance, and specific task requirements.\nLeveraging Playwright for Enhanced Web Interaction\nWhile\njsdom\nis effective for static HTML parsing, many modern websites rely heavily on JavaScript for rendering content. Furthermore, many sites employ anti-bot measures. Lightfeed Extractor addresses these challenges by integrating Playwright for browser automation.\nDynamic Content and Anti-Bot Measures\nJavaScript Rendering\n: Playwright launches a real browser instance (Chromium, Firefox, or WebKit), allowing it to execute JavaScript, render dynamic content, and interact with single-page applications (SPAs) just like a human user would. This ensures that the HTML provided to the LLM is the fully rendered content.\nAnti-Bot Patches\n: Playwright can be configured with \"stealth\" techniques to mimic human browsing behavior, making it more difficult for websites to detect and block automated access. This includes setting realistic user agents, viewport sizes, avoiding common bot-like network patterns, and handling CAPTCHAs (though the latter often requires external services).\nResource Optimization\nPlaywright also enables optimization strategies:\nResource Blocking\n: Specific resources (images, fonts, stylesheets, scripts from known ad/tracker domains) can be intercepted and blocked at the network level. This reduces bandwidth consumption, speeds up page loading, and minimizes the amount of \"noise\" HTML that needs to be processed later, further conserving token budgets.\nHeadless Operation\n: For server environments, Playwright operates in headless mode, meaning no graphical browser window is displayed, making it suitable for scalable deployment.\nArchitectural Considerations and Operational Insights\nThe Lightfeed Extractor embodies a robust pipeline design, orchestrating various components into a cohesive workflow.\nPipeline Flow\nInput\n: A URL or raw HTML string, accompanied by a Zod schema.\nBrowser Automation (Optional)\n: If a URL is provided, Playwright navigates to the page, waits for full rendering, and extracts the raw HTML.\nHTML Pre-processing\n: The raw\nOriginally published in Spanish at\nwww.mgatc.com/blog/robust-llm-extractor-websites-typescript/",
  "metadata": {
    "title": "Robust LLM Extractor for Websites in TypeScript! - DEV Community",
    "description": "Web scraping and structured data extraction from websites have long been fundamental tasks in data... Tagged with llm, dataextraction, webscraping, typescript.",
    "keywords": "llm, dataextraction, webscraping, typescript, software, coding, development, engineering, inclusive, community",
    "author": null,
    "og:type": "article",
    "og:url": "https://dev.to/mgobea/robust-llm-extractor-for-websites-in-typescript-3mmj",
    "og:title": "Robust LLM Extractor for Websites in TypeScript!",
    "og:description": "Web scraping and structured data extraction from websites have long been fundamental tasks in data...",
    "og:site_name": "DEV Community",
    "og:image": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fycppj6wouc639vhjkfgt.png",
    "twitter:site": "@thepracticaldev",
    "twitter:creator": "@",
    "twitter:title": "Robust LLM Extractor for Websites in TypeScript!",
    "twitter:description": "Web scraping and structured data extraction from websites have long been fundamental tasks in data...",
    "twitter:card": "summary_large_image",
    "twitter:widgets:new-embed-design": "on",
    "twitter:image:src": "https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fycppj6wouc639vhjkfgt.png"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/4400e9eb4ab8/code/body-000.txt",
      "chars": 3857,
      "language": "text"
    },
    {
      "file": "attachments/4400e9eb4ab8/code/body-001.txt",
      "chars": 973,
      "language": "text"
    },
    {
      "file": "attachments/4400e9eb4ab8/code/body-002.txt",
      "chars": 3903,
      "language": "text"
    }
  ],
  "images": [],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://www.mgatc.com/blog/robust-llm-extractor-websites-typescript/",
      "label": "www.mgatc.com/blog/robust-llm-extractor-websites-typescript/",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:50:06Z",
  "source_url": "https://dev.to/mgobea/robust-llm-extractor-for-websites-in-typescript-3mmj",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 23858,
  "expected_body_images": 0,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/4400e9eb4ab8/pages/source.html",
  "local_html": "attachments/4400e9eb4ab8/pages/article.html",
  "body_base_path": "attachments/4400e9eb4ab8/pages/",
  "body_markdown_path": "attachments/4400e9eb4ab8/pages/article.md"
}