The short answer. The fetch worked. Your XPath matched nothing. Three causes, in order of how often they are the real one:
- The XPath came from DevTools and contains
/tbody/, which browsers invent and the raw HTML does not have. - The content is added by JavaScript after page load, and IMPORTXML does not run JavaScript.
- The site changed and the structure your path depended on is gone.
Thirty seconds of View Source distinguishes all three.
Of the two common IMPORTXML errors, this is the more hopeful one. Could not fetch url often means the door is locked. Imported content is empty means you are inside the building and looking in the wrong room.
What the error means precisely
Google fetched the URL. It received HTML. It parsed that HTML into a document. It evaluated your XPath expression against that document, and the expression selected zero nodes.
That is the whole story, and it narrows the problem usefully: the network is fine, the site is not blocking you, and the fix is on your side of the formula. Compare with "could not fetch url", where nothing on your side can help.
Cause 1: the tbody that does not exist
This is the single most common cause and almost nothing explains it clearly.
You right-click the element in Chrome, choose Copy, then Copy XPath, and paste something like:
//*[@id="main"]/div[2]/table/tbody/tr[3]/td[2]
It matches nothing. The reason is tbody. The HTML specification says browsers must insert a tbody element into every table during parsing, whether or not the author wrote one. So the browser's DOM has a tbody, the source HTML very often does not, and DevTools copies the path through the DOM it built rather than the bytes the server sent.
IMPORTXML parses the raw HTML. No browser, no insertion, no tbody, no match.
The fix is to stop caring where in the tree the element sits:
=IMPORTXML(A2, "//table//tr[3]/td[2]")
The double slash descends any number of levels, so it matches whether or not a tbody sits in between. As a general rule, a copied absolute XPath is a liability: it encodes the exact nesting of the page on the day you copied it, and any layout change breaks it. Anchor on something stable instead, a class name, an id, or the text itself.
Cause 2: the content is not in the HTML
Modern sites frequently send a nearly empty document and build the visible page with JavaScript. IMPORTXML fetches HTML and stops. It has no JavaScript engine, so anything a script inserted is invisible to it.
The thirty-second test, and the only one that settles it:
- Open the page.
- Press Ctrl+U (Cmd+Option+U on a Mac) for View Source. Not Inspect Element. This distinction is the entire test.
- Search the source for the text you want to extract.
If the text is there, your XPath is wrong and cause 1 or 3 applies. If it is not there, no XPath will ever find it, because it does not exist in what IMPORTXML receives.
Inspect Element shows the live DOM after scripts have run, which is why it always shows the content and always misleads you here.
What to do when it is JavaScript
Two things sometimes work. Open the browser's Network tab, filter to Fetch/XHR, and reload: often the page is calling a JSON endpoint you can hit directly with IMPORTDATA. Or check whether the site publishes the same data in a <script type="application/ld+json"> block, which is in the source and frequently contains exactly the firmographic fields people are trying to scrape.
If neither exists, the page needs to be rendered before it can be read, and that is outside what any Sheets formula does.
Cause 3: the site changed
A formula that worked for months and now returns empty across every row, with no error, usually means a redesign. Class names change, wrappers get added, an id becomes generated.
This is the failure mode that makes spreadsheet scraping expensive over time. It fails silently. There is no alert, and the cell just goes blank, so you often discover it when someone asks why a report has holes in it.
Test the XPath before you blame it
You can evaluate an XPath in the browser console without touching the sheet. Open DevTools, go to Console, and run:
$x('//table//tr[3]/td[2]')
An empty array means the expression is wrong even against the rendered DOM, which is the more forgiving of the two documents. If it returns nodes here but IMPORTXML still finds nothing, you have confirmed cause 2 without ambiguity: the element exists only after JavaScript.
A quick reference
| Symptom | Cause | Fix |
|---|---|---|
Path from DevTools, contains /tbody/ | Browser-invented element | Use //table//tr instead of the absolute path |
| Text absent from View Source | JavaScript-rendered | Find the JSON endpoint, or render the page elsewhere |
| Worked before, empty now, no error | Site redesign | Rebuild the path against something stable |
$x() returns nothing in console | Expression is genuinely wrong | Anchor on a class, id, or text match |
| Some rows fill, others empty | Pages differ in structure | Multiple paths, or an extractor that is not path-based |
The structural problem underneath all three
Every cause above shares a root: an XPath is a promise about someone else's HTML that they never made to you. It holds until they redeploy.
For one page you check occasionally, that is fine and IMPORTXML is genuinely a good tool. For a lead list where every row is a different company's website, all built differently, it does not hold: there is no single path that describes 400 sites, and maintaining 400 paths is not a job anyone wants.
That is the case for describing what you want rather than where it lives. ReplyLabs takes a column of URLs and a plain description of the field, reads each page, and returns the value with a per-row status so an empty result is visibly a failure rather than a blank cell. Getting website data into a spreadsheet covers the approach, and waterfall enrichment covers what to do when the first attempt comes back empty.