ScreenshotNeo

BlogHow-to

How to Capture HTML Tables in Chrome

Copy simple tables, extract complex grids with DevTools, export to CSV, and capture tables that resist normal copy and paste.

By the ScreenshotNeo team30 September 20268 min read

How to Capture HTML Tables in Chrome

To capture an HTML table in Chrome, start by selecting the visible cells and pasting them into Excel or Google Sheets. If that fails, use Chrome DevTools to read the rendered DOM and copy tab-separated text or CSV. For div-based grids, virtual scrolling, merged cells, pagination, or repeated work, use a table-capture extension or a screenshot API.

1. Copy and paste the visible table

This is the fastest method for a short, semantic <table> with ordinary rows and columns.

  1. Drag across the cells you need.
  2. Press Ctrl+C on Windows/Linux or Cmd+C on macOS.
  3. Paste into Excel, Google Sheets, or another spreadsheet.
  4. Check the first and last columns, headers, dates, and numbers.

Copy and paste can omit hidden rows, combine merged cells unexpectedly, or capture only the rows currently rendered by a virtualized grid. If the result is incomplete, continue with DevTools.

2. Extract a table with Chrome DevTools

DevTools gives you a no-install way to inspect the page and run JavaScript against the table already rendered in Chrome. Chrome documents that the Sources panel can create and run JavaScript Snippets, and the Console opens with Ctrl+Shift+J on Windows, Linux, and ChromeOS.

Right-click the table, choose Inspect, then open the Console. You can also use Sources → Snippets for a script you plan to reuse. See the Chrome DevTools Sources documentation and DevTools keyboard shortcuts.

Read the first table as tab-separated text

const table = document.querySelector('table');
if (!table) throw new Error('No table element found');

const text = [...table.querySelectorAll('tr')]
  .map(row => [...row.querySelectorAll('th, td')]
    .map(cell => cell.innerText.replace(/\s+/g, ' ').trim())
    .join('\t'))
  .join('\n');

copy(text);
console.log(text);

The copy() helper places the result on the clipboard in DevTools. Paste it into a spreadsheet.

Choose a specific table

Inspect the table in the Elements panel and copy a selector such as #results, table.data, or a more specific ancestor selector.

const table = document.querySelector('#results');
if (!table) throw new Error('Table #results was not found');

const rows = [...table.rows].map(row =>
  [...row.cells].map(cell => cell.innerText.replace(/\s+/g, ' ').trim())
);

copy(rows.map(row => row.join('\t')).join('\n'));
console.log(`Copied ${rows.length} rows`);

Export a table as CSV

CSV requires escaping commas, quotation marks, and line breaks. This script handles those characters and copies the result.

const table = document.querySelector('table');
if (!table) throw new Error('No table element found');

const csvEscape = value => {
  const text = value.replace(/\s+/g, ' ').trim();
  return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
};

const rows = [...table.querySelectorAll('tr')].map(row =>
  [...row.querySelectorAll('th, td')]
    .map(cell => csvEscape(cell.innerText))
    .join(',')
);

const csv = rows.join('\n');
copy(csv);
console.log(csv);

To save the output, paste it into a text editor and save it with a .csv extension, or paste directly into a spreadsheet and use its export command.

const table = document.querySelector('table');
const rows = [...table.querySelectorAll('tr')].map(row =>
  [...row.querySelectorAll('th, td')].map(cell => ({
    text: cell.innerText.trim(),
    href: cell.querySelector('a')?.href ?? null,
    value: cell.getAttribute('data-value') ?? null
  }))
);
console.log(JSON.stringify(rows, null, 2));

Use data-value when a cell displays a formatted value such as “$1,234” but stores a raw number in an attribute. Inspect the page first; attribute names differ by site.

3. Handle tables that are not real HTML tables

Many dashboards are built from repeated div elements with CSS Grid or Flexbox. In that case, document.querySelector('table') returns nothing.

The DOM structure determines whether copy and paste, DevTools, or a picker works best.
The DOM structure determines whether copy and paste, DevTools, or a picker works best.
  1. Inspect one visible row and identify the repeating row selector.
  2. Identify the cell selector inside each row.
  3. Run a script against those selectors and verify the row count.
const rowSelector = '.grid-row';
const cellSelector = '.grid-cell';
const rows = [...document.querySelectorAll(rowSelector)].map(row =>
  [...row.querySelectorAll(cellSelector)]
    .map(cell => cell.innerText.replace(/\s+/g, ' ').trim())
);

if (!rows.length) throw new Error('No rows matched; inspect the selectors');
copy(rows.map(row => row.join('\t')).join('\n'));
console.log(`Copied ${rows.length} rows`);

Selectors are site-specific. A table picker extension can be quicker for div-based grids, especially when it supports selecting a repeating row pattern.

4. Capture virtualized, lazy-loaded, and infinite-scroll tables

Virtualized tables keep only visible rows in the DOM. A script may report 30 rows even when the table contains thousands.

  • Scroll through the table slowly so additional rows render.
  • Run the extraction after scrolling and compare the count with the expected total.
  • If the page has a page-size control, select the largest practical page size first.
  • For infinite scroll, repeat extraction after each scroll and de-duplicate rows by a stable ID.
  • Check the final row for truncation and confirm that sorting or filtering did not change during capture.

Some grids expose a total count in the DOM, an accessibility attribute, or a network response. Compare that count with the number you extracted instead of assuming the visible count is complete.

5. Deal with merged cells

rowspan and colspan make a visual table different from a rectangular data set. A basic script returns the cells that exist in each row, so later values can appear shifted.

Inspect a sample containing merged headers before analysis. If you need a rectangular export, expand the spans into a grid with a dedicated table tool, then verify several rows manually. Never infer a column solely from its position when merged cells are present.

6. Use an extension for repeated exports

Extensions can provide a picker, clipboard copying, Google Sheets export, CSV or Excel output, Markdown, and support for div-based layouts. Table Capture advertises clipboard and Google Sheets workflows, div-based table capture, and larger multi-page or scroll-loaded capture in its paid feature set. Capture Web Table describes detection in the top page and iframes, a picker for repeating div lists, local browser processing, and exports such as CSV, Excel, Markdown, and screenshots.

Before installing one, check its current Chrome Web Store listing for permissions, row limits, pricing, and whether multi-page capture is included. Browser extension features can change. For sensitive data, prefer local processing and review the extension’s permissions.

7. Compare the methods

Method Best for Weakness
Copy and paste Small visible semantic tables Unreliable with virtual scrolling, merged cells, and div grids
DevTools Console or Snippet One-off extraction with selector control Requires inspecting the DOM and writing a short script
Extension Repeated work, pickers, and export formats Permissions, changing features, and possible row limits
Screenshot API Repeatable visual capture, PDFs, automation, and remote pages Produces an image or PDF unless you separately extract data

8. Troubleshooting

“No table element found”

Cause: the page uses divs, an iframe, or a shadow root. Fix: inspect the repeating row container, switch into the iframe in DevTools, or use a picker that supports div-based grids.

Only the visible rows were copied

Cause: virtualization. Fix: scroll to render more rows, extract in batches, and compare the final count with the page’s reported total.

Columns are shifted

Cause: rowspan, colspan, hidden cells, or nested layout elements. Fix: inspect merged cells, target only direct cells, and validate a sample against the page.

Numbers are wrong in Excel

Cause: locale-specific separators, currency symbols, or dates were treated as text. Fix: keep the raw text in one column, convert values after import, and specify the correct locale in the spreadsheet.

The script returns stale data

Cause: the table updates asynchronously. Fix: wait for the loading indicator to disappear, confirm the expected heading or row exists, then run the script again.

The page blocks copying

Cause: a copy handler or access control intercepts selection. Fix: use DevTools on data already rendered in the page, an approved export, or the site’s API. Do not bypass authentication or access controls.

Rows from an iframe are missing

Cause: the table is inside a frame. Fix: select the frame’s document in the Elements panel and run the script in that context. Cross-origin frames may prevent direct access; use the site’s supported export instead.

9. Performance, reliability, and privacy

  • Performance: extract only the columns you need and process large results in batches. Calling innerText can trigger layout work; textContent is faster when visual line breaks do not matter.
  • Reliability: record the URL, timestamp, filters, page number, and row count with each export. Save a sample of the source HTML when reproducibility matters.
  • Dynamic pages: wait for a stable selector or network activity to finish before reading rows. Re-run after sorting, filtering, or pagination changes.
  • Privacy: treat copied table data as sensitive. Review extension permissions, avoid sending private pages to third-party services without authorization, and follow site terms, access controls, privacy rules, and applicable law.
  • Cost: DevTools and ordinary copy/paste have no service charge. Extensions may have paid tiers. A screenshot API charges according to its plan and normally returns a visual artifact rather than structured cell data.

10. Or skip the browser setup

If your goal is a repeatable visual capture of a table, ScreenshotNeo provides a single request for a PNG, JPEG, WebP, or PDF. Its cleanup steps accept cookie and consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in headers.

A clean capture removes consent banners, popups, and chat widgets before the table is rendered.
A clean capture removes consent banners, popups, and chat widgets before the table is rendered.

See the ScreenshotNeo API documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

It also supports full-page capture with lazy images loaded, CSS-selector element capture, custom CSS and JavaScript, waits, blocking rules, headers, cookies, user agents, timezone and geolocation, image resizing, caching, signed links, asynchronous jobs, bulk capture, PDFs, and an MCP server for AI agents.

There are 1,000 free screenshots each month with no card. Paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Can Chrome export an HTML table directly to Excel?

Chrome itself does not provide a universal one-click table export. Copy the cells, run a DevTools script that produces CSV, or use an extension with Excel export.

How do I capture a table that will not copy and paste?

Inspect the rendered DOM in DevTools. If it is a real table, extract its rows and cells with JavaScript. If it is a div grid, identify the repeating row and cell selectors or use a picker.

Why does my table have fewer rows than the page?

Virtual scrolling, pagination, filters, and lazy loading can keep only part of the data in the DOM. Scroll, load each page, and verify the extracted count.

Should I capture a screenshot or extract data?

Use DOM extraction for analysis and spreadsheets. Use a screenshot or PDF when visual fidelity, an audit record, or a presentation-ready artifact matters.