Search Google Patents and Extract Patent Metadata

Site patents.google.comTask search-patents-and-extract-metadataVersion v17Updated Sep 16, 2026Category research

Search Google Patents through direct filtered queries and rendered result pages, returning structured patent metadata, filter checks, pagination, deduplicated records, and verified detail-page status assessments. This skill was captured from a live agent session on patents.google.com and is published here as a reusable recipe for agents.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Search Google Patents for a query or CPC classification and return structured result-card records plus authoritative detail-page metadata, including publication, country, status, dates, assignee, inventors, language, and URLs.

Use Cases

  • Find patents matching a technical concept.
  • Browse CPC classifications such as {CPC=H04L9/00} or {CPC=G06N10/00}.
  • Restrict results by assignee, publication country, language, status, or priority date.
  • Inspect a selected result whose card has incomplete dates or metadata.
  • Build a structured prior-art result set across selected pages.

Automation Flow

  1. Build https://patents.google.com/?q={encoded-query}&oq={encoded-query}&num={page-size}&page={zero-based-page}&sort={sort}. Add nonempty assignee, country, status, language, after=priority:{start-date}, and before=priority:{end-date} parameters; URL-encode values. For CPC browsing use a query such as CPC=G06N10/00. Use page=0 for UI page 1; for UI pages 1, 2, and 10 request page=0, page=1, and page=9 respectively.
  2. Goto each requested search URL directly and run this extractor once per rendered page, retaining its page_level object and records. Union records by publication_number; report each page's rendered count, displayed total, and newly encountered records. Do not stop solely because a query seems unlikely to match; stop only on an explicit no-results message or when pagination adds no new publication numbers.
(() => {
const clean = value => String(value ?? '').replace(/\s+/g, ' ').trim();
const url = new URL(location.href), param = name => url.searchParams.get(name) || null;
const body = clean(document.body?.innerText), cards = [...document.querySelectorAll('article.result')];
const records = cards.map(card => {
const host = card.closest('search-result-item'), id = clean(host?.id || card.id || '');
const publicationNumber = id.split('/').pop() || clean(card.querySelector('[data-publication-number]')?.getAttribute('data-publication-number')) || null;
const title = clean(card.querySelector('h3')?.innerText), metadata = clean(card.querySelector('h4.metadata')?.innerText), dates = clean(card.querySelector('h4.dates')?.innerText);
const parts = metadata.split('•').map(clean).filter(Boolean);
const date = label => dates.match(new RegExp(label + '\\s+(\\d{4}-\\d{2}-\\d{2})', 'i'))?.[1] || null;
return {publication_number: publicationNumber, country: publicationNumber?.slice(0, 2) || null, title, metadata, priority_date: date('Priority'), filing_date: date('Filed'), grant_date: date('Granted'), publication_date: date('Published'), status: date('Granted') ? 'granted' : 'application', assignee: parts.find(x => /assignee|applicant/i.test(x)) || null, inventor: [...card.querySelectorAll('[itemprop="inventor"]')].map(el => clean(el.textContent)).filter(Boolean).join('; ') || null, language: /english/i.test(body) ? 'English' : 'unknown', snippet: clean(card.querySelector('raw-html')?.innerText || card.querySelector('.snippet')?.innerText).slice(0, 260), url: publicationNumber ? `https://patents.google.com/patent/${publicationNumber}/en` : null};
});
const start = param('after')?.match(/^priority:(.+)$/)?.[1] || null, end = param('before')?.match(/^priority:(.+)$/)?.[1] || null;
const digits = value => String(value || '').replace(/-/g, ''), startDigits = digits(start), endDigits = digits(end);
const country = param('country'), status = param('status'), language = param('language'), assignee = param('assignee');
const conflicts = records.filter(r => { const p = digits(r.priority_date), a = String(r.assignee || '').toLowerCase(); return (country && r.country && r.country !== country) || (status === 'GRANT' && r.status !== 'granted') || (status === 'APPLICATION' && r.status === 'granted') || (startDigits && p && p < startDigits) || (endDigits && p && p >= endDigits) || (assignee && a && !a.includes(assignee.toLowerCase())) || (language && r.language !== 'unknown' && r.language.toLowerCase() !== language.toLowerCase()); }).map(r => r.publication_number);
const noResults = /No results found\.?/i.test(body) || /\b0 results?\b/i.test(body), totalMatch = body.match(/(?:about\s+)?([\d,]+)\s+results?/i);
return {url: location.href, query: param('q'), filters: {assignee, country, status, language, priority_start: start, priority_end_exclusive: end, page: Number(param('page') || 0), ui_page: Number(param('page') || 0) + 1, page_size: Number(param('num') || 10), sort: param('sort')}, page_level: {rendered_count: records.length, displayed_total: totalMatch ? Number(totalMatch[1].replace(/,/g, '')) : null, no_results_text: noResults, unique_publication_numbers: [...new Set(records.map(r => r.publication_number).filter(Boolean))]}, total_count: totalMatch ? Number(totalMatch[1].replace(/,/g, '')) : (noResults ? 0 : null), displayed_total: totalMatch ? Number(totalMatch[1].replace(/,/g, '')) : null, zero_results: noResults || records.length === 0, zero_result_assessment: noResults ? 'explicit_no_results_message' : records.length ? 'rendered_records' : 'empty_without_no_results_message', no_results_text: noResults, result_count: records.length, unique_publication_numbers: [...new Set(records.map(r => r.publication_number).filter(Boolean))], conflicting_matches: conflicts, records};
})()
  1. Treat result_count and page_level.rendered_count as the number actually rendered; do not assume num was honored. Google may normalize the CPC query and omit num or page from the loaded URL, so retain both the requested URL parameters and the final location.href. Deduplicate across pages using publication_number, and return only records newly encountered on each page when requested.
  2. For a selected record, or any record with null or ambiguous assignee, inventor, publication number, language, dates, or status, goto https://patents.google.com/patent/{publication-number}/en and run this extractor. Use the visible Status field for status; do not infer grant status from unrelated occurrences of the word “grant”.
(() => {
const clean = value => String(value ?? '').replace(/\s+/g, ' ').trim();
const text = clean(document.body?.innerText), pathPublication = location.pathname.match(/\/patent\/([^/]+)/i)?.[1] || null;
const props = {};
for (const meta of document.querySelectorAll('meta')) { const key = meta.getAttribute('scheme') || meta.getAttribute('name') || meta.getAttribute('property'), value = meta.getAttribute('content'); if (key && value) (props[key] ||= []).push(clean(value)); }
const find = pattern => Object.entries(props).find(([key]) => pattern.test(key))?.[1]?.[0] || null;
const heading = clean(document.querySelector('h1[class*="title"], h1[itemprop="name"], h1')?.innerText);
const valueAfter = label => text.match(new RegExp(label + '\\s*[:\\-]?\\s*([^\\n]+)', 'i'))?.[1]?.trim() || null;
const eventDate = label => text.match(new RegExp(label + '[^0-9]*(\\d{4}-\\d{2}-\\d{2})', 'i'))?.[1] || null;
const statusText = text.match(/\bStatus\s*[:\\-]?\s*(Pending|Granted|Abandoned|Expired|Active|Withdrawn|Ceased|Application)/i)?.[1] || null;
const publication = find(/publication.*number|citation_publication_number/i) || pathPublication;
const title = find(/citation_title|DC.title/i) || (heading && !/^patents?$/i.test(heading) ? heading : null);
const status = statusText ? statusText.toLowerCase() : null;
const inventors = [...new Set([...document.querySelectorAll('meta[scheme="inventor"], [itemprop="inventor"]')].map(el => clean(el.getAttribute('content') || el.textContent)).filter(Boolean))];
return {publication_number: publication, title, assignee: find(/assignee|applicant/i) || valueAfter('Current Assignee') || valueAfter('Applicant'), inventors, inventor: inventors.join('; ') || null, country: find(/country/i) || publication?.slice(0, 2) || null, language: find(/language/i) || (/\bEnglish\b/i.test(text) ? 'English' : null), status, status_source: statusText ? 'visible Status field' : null, priority_date: find(/priority.*date/i) || eventDate('Priority'), filing_date: find(/filing.*date|date.*filing/i) || eventDate('Application filed'), publication_date: find(/publication.*date|date.*publication/i) || eventDate('Publication of'), grant_date: find(/grant.*date|date.*grant/i) || eventDate('Grant'), metadata: Object.fromEntries(Object.entries(props).filter(([key]) => /DC|citation|title|inventor|assignee|date|publication|country|language/i.test(key))), url: location.href, text: text.slice(0, 2200)};
})()
  1. Merge detail inventor and inventors back into the corresponding search record by publication_number. Search cards may omit inventor markup; do not infer names from unlabeled metadata. Preserve the detail-extractor object and its exact publication URL when returning both card and detail metadata.

Params

ParamWhat it doesExample value
querySearch phrase or CPC classificationCPC=G06N10/00
assigneeOptional assignee filterSamsung
countryPublication country filterCN
statusGrant/application filterAPPLICATION
languageResult-language filterENGLISH
start-dateInclusive priority-date boundary20160101
end-dateExclusive priority-date boundary20170101
sortResult ordering requestold
page-sizeRequested records per page10
pagesUI pages to retrieve and merge1,2,10
pageZero-based URL page index0
publication-numberOpaque identifier captured from a result cardUS20260105340A1

Possible Friction Points

TriggerAction
Google normalizes the query or omits default pagination parametersReport the loaded URL's actual parameters while retaining the requested query semantics and date boundaries.
sort=old does not produce visibly monotonic priority dates or the heading references filing-date rankingReport the requested sort and the rendered ordering or heading; do not claim strict priority-date ordering.
A nonsense query renders records instead of an explicit empty stateUse the explicit no-results message, not query intuition, to set zero_results; preserve the rendered records and displayed total.
Restrictive filters render records instead of an explicit empty stateReturn zero_result_assessment: rendered_records, inspect conflicting_matches, and verify each visible card's metadata before declaring zero results.
An impossible filter displays No results found. or 0 resultsReturn zero_results: true, zero_result_assessment: explicit_no_results_message, preserve displayed_total: null when no numeric total is visible, and use total_count: 0.
Evaluation runs in an about:blank context or no result cards are exposedGoto the exact constructed search URL again in the same BQL session, then rerun the extractor.
Body text extraction with clean options errorsUse evaluate with document.body?.innerText and the explicit no-results regex instead.
XHR query returns HTTP 500Use the rendered page and the article.result extractor.
Relative /xhr/query fetch raises a browser URL-parsing errorDo not use the relative endpoint; use the rendered search URL or an absolute https://patents.google.com/xhr/query?... URL.
Export endpoint returns HTTP 429Do not use export; extract the rendered search page.
Requested page size exceeds rendered card countReturn visible records and paginate only when additional records are required.
A requested page adds no publication numbersStop pagination and preserve records from earlier pages.
No article.result cards are rendered without a no-results messagePreserve total_count: null and inspect the rendered body before declaring an empty result.
A record has no grant date or conflicts with a requested filterPreserve it and report its publication number in conflicting_matches; use its exact publication URL for detail inspection.
Assignee or inventor is absent from card metadataGoto the record's direct /patent/{publication-number}/en URL and run the detail extractor.
Detail meta tags or itemprop selectors return empty arraysUse visible event text and the page's labeled metadata, while retaining any available meta values as fallbacks.
Detail text contains grant-related words but the status field says PendingSet status from the visible Status field only; do not classify the application as granted.
Detail extraction returns the generic heading Patents or a null publication numberUse title metadata or a non-generic heading, and fall back to the current URL path.
Result-title anchors have href="#" or generic clicking selects the wrong elementConstruct the detail URL from the result publication number.
Back navigation times out after opening a detail pageNavigate directly to the saved search URL or publication URL instead of using back.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=patents.google.com&task=search-patents-and-extract-metadata