 #  CSS Selector &amp; XPath Tester

 Test [CSS selectors](https://scrapfly.io/blog/posts/css-selector-cheatsheet) and [XPath expressions](https://scrapfly.io/blog/posts/xpath-cheatsheet) on HTML in real-time. See matched elements instantly, generate code for Python/JavaScript, and validate your selectors.

 

 ### Example HTML Templates

  

 

     

 

  Matches: 0 

 

    HTML Input 

  

 



 

 

   Matched Results 

 

  

 

 

 

 ### Actions

     

   

 

 

<html><p>`
            },
            'ecommerce': {
                name: 'E-commerce Product',
                desc: 'Product listing page',
                html: `</p><div class="product-list">
  <div class="product" data-id="123">
    <h2 class="product-name">Laptop Pro 2024</h2>
    <span class="price">$1,299.99</span>
    <div class="rating" data-stars="4.5">
      <span class="stars">★★★★½</span>
      <span class="reviews">(234 reviews)</span>
    </div>
    <a href="/product/123" class="buy-btn">Buy Now</a>
  </div>
  <div class="product" data-id="456">
    <h2 class="product-name">Wireless Mouse</h2>
    <span class="price">$29.99</span>
    <div class="rating" data-stars="4.8">
      <span class="stars">★★★★★</span>
      <span class="reviews">(89 reviews)</span>
    </div>
    <a href="/product/456" class="buy-btn">Buy Now</a>
  </div>
</div><p>`
            },
            'blog': {
                name: 'Blog Post',
                desc: 'Article with metadata',
                html: `<article class="blog-post">
  <header>
    <h1>Web Scraping Best Practices</h1>
    <div class="meta">
      <span class="author">John Doe</span>
      <time datetime="2024-01-15">January 15, 2024</time>
      <span class="category">Tutorial</span>
    </div>
  </header>
  <div class="content">
    <p>Introduction to web scraping...</p>
    <h2>Why Web Scraping?</h2>
    <p>Web scraping allows you to extract data...</p>
  </div>
  <footer>
    <div class="tags">
      <a href="/tag/scraping" class="tag">scraping</a>
      <a href="/tag/python" class="tag">python</a>
    </div>
  </footer>
</article>`
            },
            'table': {
                name: 'Data Table',
                desc: 'Structured table data',
                html: `</p><table class="data-table">
  <thead>
    <tr>
      <th>Name</th>
      <th>Email</th>
      <th>Role</th>
    </tr>
  </thead>
  <tbody>
    <tr data-user-id="1">
      <td class="name">Alice Smith</td>
      <td class="email">alice@example.com</td>
      <td class="role">Admin</td>
    </tr>
    <tr data-user-id="2">
      <td class="name">Bob Johnson</td>
      <td class="email">bob@example.com</td>
      <td class="role">User</td>
    </tr>
    <tr data-user-id="3">
      <td class="name">Carol White</td>
      <td class="email">carol@example.com</td>
      <td class="role">Moderator</td>
    </tr>
  </tbody>
</table><p>`
            },
            'nested': {
                name: 'Nested Structure',
                desc: 'Deeply nested elements',
                html: `</p><div class="container">
  <div class="section">
    <div class="subsection" id="first">
      <ul class="items">
        <li class="item"><span class="label">Item 1</span></li>
        <li class="item"><span class="label">Item 2</span></li>
      </ul>
    </div>
    <div class="subsection" id="second">
      <ul class="items">
        <li class="item active"><span class="label">Item 3</span></li>
        <li class="item"><span class="label">Item 4</span></li>
      </ul>
    </div>
  </div>
</div><p>`
            }
        };

        function initializeEditor() {
            const initialContent = `` || templates.simple.html;

            htmlEditor = CodeMirror(document.getElementById('html_editor'), {
                value: initialContent,
                mode: 'xml',
                lineNumbers: true,
                theme: 'nord'
            });

            htmlEditor.on('change', debounce(evaluateSelector, 300));
        }

        function loadTemplate(templateKey) {
            const template = templates[templateKey];
            if (template && htmlEditor) {
                htmlEditor.setValue(template.html);
                evaluateSelector();
            }
        }

        function renderTemplates() {
            const grid = document.getElementById('template_grid');
            grid.innerHTML = '';

            Object.keys(templates).forEach(key => {
                const template = templates[key];
                const card = document.createElement('div');
                card.className = 'template-card';
                card.onclick = () => loadTemplate(key);

                card.innerHTML = `
                    </p><div class="template-name">${template.name}</div>
                    <div class="template-desc">${template.desc}</div><p>
                `;

                grid.appendChild(card);
            });
        }

        function switchSelectorType(type) {
            currentSelectorType = type;

            document.getElementById('css_btn').classList.toggle('active', type === 'css');
            document.getElementById('xpath_btn').classList.toggle('active', type === 'xpath');

            evaluateSelector();
        }

        function evalCssSelector(html, selector) {
            try {
                const parser = new DOMParser();
                const doc = parser.parseFromString(html, 'text/html');
                const nodes = doc.querySelectorAll(selector);
                return Array.from(nodes);
            } catch (e) {
                throw new Error(`CSS Selector Error: ${e.message}`);
            }
        }

        function evalXPathSelector(html, selector) {
            try {
                const parser = new DOMParser();
                const doc = parser.parseFromString(html, 'text/html');
                const result = doc.evaluate(selector, doc, null, XPathResult.ANY_TYPE, null);

                const nodes = [];
                let node;

                // Handle different XPath result types
                if ([4, 5, 6, 7, 8, 0].includes(result.resultType)) {
                    while ((node = result.iterateNext())) {
                        nodes.push(node);
                    }
                } else if (result.resultType === 1) { // NUMBER_TYPE
                    return [{ type: 'number', value: result.numberValue }];
                } else if (result.resultType === 2) { // STRING_TYPE
                    return [{ type: 'string', value: result.stringValue }];
                } else if (result.resultType === 3) { // BOOLEAN_TYPE
                    return [{ type: 'boolean', value: result.booleanValue }];
                } else if (result.singleNodeValue) {
                    nodes.push(result.singleNodeValue);
                }

                return nodes;
            } catch (e) {
                throw new Error(`XPath Error: ${e.message}`);
            }
        }

        function nodeToString(node) {
            if (node.type) {
                // Primitive type (number, string, boolean)
                return String(node.value);
            }

            if (node instanceof HTMLElement) {
                return node.outerHTML;
            } else if (node.nodeType === 2) { // Attribute
                return node.value;
            } else if (node.nodeType === 3) { // Text
                return node.wholeText;
            } else if (node.nodeType === 8) { // Comment
                return `<!--${node.textContent}-->`;
            }

            return String(node);
        }

        function evaluateSelector() {
            currentSelector = document.getElementById('selector_input').value.trim();

            if (!currentSelector || !htmlEditor) {
                matchedResults = [];
                renderResults();
                return;
            }

            const html = htmlEditor.getValue();

            try {
                if (currentSelectorType === 'css') {
                    matchedResults = evalCssSelector(html, currentSelector);
                } else {
                    matchedResults = evalXPathSelector(html, currentSelector);
                }

                renderResults();
            } catch (e) {
                matchedResults = [];
                renderResults(e.message);
            }
        }

        function renderResults(error = null) {
            const resultsList = document.getElementById('results_list');
            const matchCount = document.getElementById('match_count');

            if (error) {
                resultsList.innerHTML = `</p><div class="error-message"><i class="fas fa-exclamation-triangle mr-2"></i>${error}</div><p>`;
                matchCount.textContent = '0';
                return;
            }

            matchCount.textContent = matchedResults.length;

            if (matchedResults.length === 0) {
                resultsList.innerHTML = '</p><div class="no-matches-message">No matches found</div><p>';
                return;
            }

            resultsList.innerHTML = '';

            matchedResults.forEach((node, index) => {
                const item = document.createElement('div');
                item.className = 'result-item';

                const content = nodeToString(node);

                // Create header with index, node name, and copy button
                const headerDiv = document.createElement('div');
                headerDiv.className = 'result-item-header';

                const indexSpan = document.createElement('span');
                indexSpan.className = 'result-index';
                indexSpan.textContent = index + 1;

                const nodeNameSpan = document.createElement('span');
                nodeNameSpan.className = 'result-node-name';
                nodeNameSpan.textContent = node.nodeName || 'Value';

                const copyBtn = document.createElement('button');
                copyBtn.className = 'result-copy-btn';
                copyBtn.innerHTML = '<i class="fas fa-copy"></i>';
                copyBtn.title = 'Copy this result';
                copyBtn.onclick = (e) => {
                    e.stopPropagation();
                    navigator.clipboard.writeText(content).then(() => {
                        copyBtn.innerHTML = '<i class="fas fa-check"></i>';
                        setTimeout(() => {
                            copyBtn.innerHTML = '<i class="fas fa-copy"></i>';
                        }, 1500);
                    });
                };

                headerDiv.appendChild(indexSpan);
                headerDiv.appendChild(document.createTextNode(' '));
                headerDiv.appendChild(nodeNameSpan);
                headerDiv.appendChild(copyBtn);

                const contentDiv = document.createElement('div');
                contentDiv.className = 'result-content';
                contentDiv.textContent = content.substring(0, 200) + (content.length > 200 ? '...' : '');

                item.appendChild(headerDiv);
                item.appendChild(contentDiv);
                resultsList.appendChild(item);
            });
        }

        function copyAllResults() {
            const results = matchedResults.map(nodeToString).join('\n\n');
            navigator.clipboard.writeText(results).then(() => {
                alert('Results copied to clipboard!');
            });
        }

        function exportPython() {
            const codeDiv = document.getElementById('code_export');
            let code = '';

            if (currentSelectorType === 'css') {
                code = `from lxml import html

# Parse HTML
tree = html.fromstring(html_content)

# CSS Selector (using cssselect)
elements = tree.cssselect('${currentSelector.replace(/'/g, "\\'")}')

# Extract text or attributes
results = [elem.text_content() for elem in elements]

# Or get HTML
results = [html.tostring(elem, encoding='unicode') for elem in elements]`;
            } else {
                code = `from lxml import html

# Parse HTML
tree = html.fromstring(html_content)

# XPath Selector
elements = tree.xpath('${currentSelector.replace(/'/g, "\\'")}')

# Extract results
results = [elem if isinstance(elem, str) else elem.text_content() for elem in elements]

# Or get HTML (for elements)
results = [html.tostring(elem, encoding='unicode') if hasattr(elem, 'tag') else str(elem) for elem in elements]`;
            }

            showCodeExport(code, 'Python');
        }

        function exportJavaScript() {
            let code = '';

            if (currentSelectorType === 'css') {
                code = `// Using Puppeteer
const elements = await page.$$('${currentSelector.replace(/'/g, "\\'")}');
const results = await Promise.all(
  elements.map(el => el.evaluate(node => node.textContent))
);

// Using Cheerio
const $ = cheerio.load(html);
const results = $('${currentSelector.replace(/'/g, "\\'")}')
  .map((i, el) => $(el).text())
  .get();

// Vanilla JavaScript (browser)
const elements = document.querySelectorAll('${currentSelector.replace(/'/g, "\\'")}');
const results = Array.from(elements).map(el => el.textContent);`;
            } else {
                code = `// Using Puppeteer with XPath
const elements = await page.$x('${currentSelector.replace(/'/g, "\\'")}');
const results = await Promise.all(
  elements.map(el => el.evaluate(node =>
    node.nodeType === 1 ? node.textContent : node.nodeValue
  ))
);

// Vanilla JavaScript (browser)
const result = document.evaluate(
  '${currentSelector.replace(/'/g, "\\'")}',
  document,
  null,
  XPathResult.ANY_TYPE,
  null
);

const results = [];
let node;
while (node = result.iterateNext()) {
  results.push(node.textContent || node.nodeValue);
}`;
            }

            showCodeExport(code, 'JavaScript');
        }

        function showCodeExport(code, language) {
            const codeDiv = document.getElementById('code_export');
            codeDiv.innerHTML = `
                </p><div class="code-export-box mt-3">
                    <div class="code-export-header">
                        <span class="code-export-title"><i class="fas fa-code mr-2"></i>${language} Code</span>
                        <button class="code-export-copy-btn" id="copy-code-btn">
                            <i class="fa-regular fa-copy"></i> Copy
                        </button>
                    </div>
                    <pre class="code-export-pre"><code>${escapeHtml(code)}</code></pre>
                </div><p>
            `;
            codeDiv.classList.remove('d-none');

            // Add click handler for copy button
            document.getElementById('copy-code-btn').addEventListener('click', function() {
                navigator.clipboard.writeText(code).then(() => {
                    this.innerHTML = '<i class="fas fa-check"></i> Copied!';
                    setTimeout(() => {
                        this.innerHTML = '<i class="fa-regular fa-copy"></i> Copy';
                    }, 2000);
                });
            });
        }

        function escapeHtml(text) {
            const div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }

        function downloadResults() {
            const results = matchedResults.map(nodeToString).join('\n\n');
            const blob = new Blob([results], { type: 'text/plain' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = 'selector_results.txt';
            a.click();
            URL.revokeObjectURL(url);
        }

        function clearHTML() {
            if (confirm('Clear HTML editor?')) {
                htmlEditor.setValue('');
                evaluateSelector();
            }
        }

        function debounce(func, wait) {
            let timeout;
            return function(...args) {
                clearTimeout(timeout);
                timeout = setTimeout(() => func.apply(this, args), wait);
            };
        }

        // Initialize on page load
        document.addEventListener('DOMContentLoaded', () => {
            renderTemplates();
            initializeEditor();

            // Auto-detect selector type
            const selectorInput = document.getElementById('selector_input');
            if (selectorInput.value && selectorInput.value.startsWith('/')) {
                switchSelectorType('xpath');
            } else if (selectorInput.value) {
                switchSelectorType('css');
            }

            evaluateSelector();

            // Event listeners
            selectorInput.addEventListener('input', debounce(evaluateSelector, 300));

            document.getElementById('css_btn').addEventListener('click', () => switchSelectorType('css'));
            document.getElementById('xpath_btn').addEventListener('click', () => switchSelectorType('xpath'));
        });
    

                </p><h2 class="mt-5">Learn Parsing</h2>

    <div class="row mt-3">
        <div class="col-md-4">
            <a class="text-decoration-none" href="https://scrapfly.io/blog/posts/parsing-html-with-xpath/" title="Parsing HTML with Xpath">
                <div class="card">
                    <img class="card-img-top" src="https://cdn.scrapfly.io/static/blog/posts/how-to-scrape-without-getting-blocked-tutorial/feature-light.svg" alt="Parsing HTML with Xpath">
                    <div class="card-body" style="min-height: 150px">
                        <p class="card-text">Introduction to xpath in the context of web-scraping. How to extract data from HTML documents using xpath, best practices and available tools.</p>
                    </div>
                </div>
            </a>
        </div>
        <div class="col-md-4">
            <a class="text-decoration-none" href="https://scrapfly.io/blog/posts/parsing-html-with-css/" title="Parsing HTML with CSS Selectors">
                <div class="card">
                    <img class="card-img-top" src="https://cdn.scrapfly.io/static/blog/posts/parsing-html-with-css/feature-light.svg" alt="Parsing HTML with CSS Selectors">
                    <div class="card-body" style="min-height: 150px">
                        <p class="card-text">Introduction to using CSS selectors to parse web-scraped content. Best practices, available tools and common challenges  by interactive examples.</p>
                    </div>
                </div>
            </a>
        </div>
        <div class="col-md-4">
            <a class="text-decoration-none" href="https://scrapfly.io/blog/posts/how-to-avoid-web-scraping-blocking-javascript/" title="Web Scraping with Python and BeautifulSoup">
                <div class="card">
                    <img class="card-img-top" src="https://cdn.scrapfly.io/static/blog/posts/how-to-avoid-web-scraping-blocking-javascript/feature-light.svg" alt="Web Scraping with Python and BeautifulSoup">
                    <div class="card-body" style="min-height: 150px">
                        <p class="card-text">When it comes to web scraping, we're generally interested in two main steps: collecting raw data (such as HTML documents) and parsing this data into something we can digest in our business logic</p>
                    </div>
                </div>
            </a>
        </div>
    </div>

                            
                                    