$npx -y skills add cookjohn/gs-skills --skill gs-exportExport Google Scholar paper(s) to Zotero via BibTeX. Gets citation data from Google Scholar's cite dialog, then pushes to Zotero desktop. Supports single or batch export.
| 1 | # Google Scholar Export to Zotero |
| 2 | |
| 3 | Export Google Scholar paper citation data via BibTeX extraction and push to Zotero desktop. |
| 4 | |
| 5 | ## Arguments |
| 6 | |
| 7 | $ARGUMENTS contains one or more data-cids (space-separated), e.g.: |
| 8 | - `TFS2GgoGiNUJ` — single paper |
| 9 | - `TFS2GgoGiNUJ abc123XYZ def456UVW` — batch export |
| 10 | |
| 11 | ## Steps |
| 12 | |
| 13 | ### Step 1: Get BibTeX for each paper |
| 14 | |
| 15 | For each data-cid, perform 3 tool calls to bypass CORS: |
| 16 | |
| 17 | #### 1a. Fetch cite dialog to get BibTeX link (evaluate_script) |
| 18 | |
| 19 | ```javascript |
| 20 | async () => { |
| 21 | const cid = "DATA_CID_HERE"; |
| 22 | const resp = await fetch( |
| 23 | `https://scholar.google.com/scholar?q=info:${cid}:scholar.google.com/&output=cite`, |
| 24 | { credentials: 'include' } |
| 25 | ); |
| 26 | const html = await resp.text(); |
| 27 | const doc = new DOMParser().parseFromString(html, 'text/html'); |
| 28 | |
| 29 | // Extract export links |
| 30 | const links = Array.from(doc.querySelectorAll('#gs_citi a')).map(a => ({ |
| 31 | format: a.textContent.trim(), |
| 32 | url: a.href |
| 33 | })); |
| 34 | |
| 35 | // Extract citation format texts |
| 36 | const citations = Array.from(doc.querySelectorAll('#gs_citt tr')).map(tr => { |
| 37 | const cells = tr.querySelectorAll('td'); |
| 38 | return { |
| 39 | style: cells[0]?.textContent?.trim() || '', |
| 40 | text: cells[1]?.textContent?.trim() || '' |
| 41 | }; |
| 42 | }); |
| 43 | |
| 44 | const bibtexLink = links.find(l => l.format === 'BibTeX'); |
| 45 | return { cid, bibtexLink: bibtexLink?.url || '', links, citations }; |
| 46 | } |
| 47 | ``` |
| 48 | |
| 49 | #### 1b. Navigate to BibTeX URL (navigate_page) |
| 50 | |
| 51 | Use `mcp__chrome-devtools__navigate_page`: |
| 52 | - url: the `bibtexLink` URL from step 1a (on `scholar.googleusercontent.com`) |
| 53 | |
| 54 | This bypasses CORS restrictions that block fetch() to googleusercontent.com. |
| 55 | |
| 56 | #### 1c. Read BibTeX content (evaluate_script) |
| 57 | |
| 58 | ```javascript |
| 59 | async () => { |
| 60 | return { bibtex: document.body.innerText || document.body.textContent || '' }; |
| 61 | } |
| 62 | ``` |
| 63 | |
| 64 | ### Step 2: Parse BibTeX and push to Zotero |
| 65 | |
| 66 | Save the BibTeX data as JSON, then call the push script: |
| 67 | |
| 68 | ```bash |
| 69 | python "E:/gscholar-skills/.claude/skills/gs-export/scripts/push_to_zotero.py" /tmp/gs_papers.json |
| 70 | ``` |
| 71 | |
| 72 | Before calling the script, construct a JSON file at `/tmp/gs_papers.json` containing paper data parsed from BibTeX. Parse the BibTeX yourself and create the JSON array: |
| 73 | |
| 74 | ```json |
| 75 | [ |
| 76 | { |
| 77 | "pmid": "", |
| 78 | "title": "The title from BibTeX", |
| 79 | "authors": [ |
| 80 | {"lastName": "Smith", "firstName": "John"} |
| 81 | ], |
| 82 | "journal": "Journal Name", |
| 83 | "journalAbbr": "", |
| 84 | "pubdate": "2022", |
| 85 | "volume": "14", |
| 86 | "issue": "4", |
| 87 | "pages": "1054", |
| 88 | "doi": "", |
| 89 | "pdfUrl": "https://example.com/paper.pdf", |
| 90 | "abstract": "", |
| 91 | "keywords": [], |
| 92 | "language": "en", |
| 93 | "pubtype": ["Journal Article"] |
| 94 | } |
| 95 | ] |
| 96 | ``` |
| 97 | |
| 98 | **IMPORTANT**: Set `pdfUrl` from the search result's `fullTextUrl` field (the PDF link extracted by gs-search). The Python script will download the PDF and upload it to Zotero via `/connector/saveAttachment` (Zotero 7.x ignores attachments in saveItems). PDF download may fail for some publishers (403, JS-redirect); these are reported as "PDF skip". |
| 99 | |
| 100 | BibTeX fields mapping: |
| 101 | - `@article{key,` → `itemType: journalArticle` |
| 102 | - `@inproceedings{key,` → `itemType: conferencePaper` |
| 103 | - `@book{key,` → `itemType: book` |
| 104 | - `title={...}` → `title` |
| 105 | - `author={Last1, First1 and Last2, First2}` → `authors` array |
| 106 | - `journal={...}` → `journal` |
| 107 | - `year={...}` → `pubdate` |
| 108 | - `volume={...}` → `volume` |
| 109 | - `number={...}` → `issue` |
| 110 | - `pages={...}` → `pages` |
| 111 | - `publisher={...}` → (included in extra or publisher field) |
| 112 | |
| 113 | ### Step 3: Report |
| 114 | |
| 115 | Single paper: |
| 116 | ``` |
| 117 | Exported to Zotero from Google Scholar: |
| 118 | Title: {title} |
| 119 | Authors: {authors} |
| 120 | Journal: {journal} ({year}) |
| 121 | Data-CID: {dataCid} |
| 122 | ``` |
| 123 | |
| 124 | Batch: |
| 125 | ``` |
| 126 | Exported {count} papers to Zotero from Google Scholar: |
| 127 | 1. {title1} ({journal1}, {year1}) |
| 128 | 2. {title2} ({journal2}, {year2}) |
| 129 | ... |
| 130 | ``` |
| 131 | |
| 132 | ## Batch Export Optimization |
| 133 | |
| 134 | For multiple papers, process sequentially to avoid CAPTCHA: |
| 135 | 1. Get all BibTeX links in one evaluate_script call (fetch all cite dialogs) |
| 136 | 2. Navigate to each BibTeX URL one at a time |
| 137 | 3. Collect all BibTeX entries |
| 138 | 4. Push all to Zotero in a single batch |
| 139 | |
| 140 | ## Notes |
| 141 | |
| 142 | - Single paper export uses 3-4 tool calls: `evaluate_script` (cite dialog) + `navigate_page` (BibTeX URL) + `evaluate_script` (read BibTeX) + `bash python` (Zotero push) |
| 143 | - Batch export: 2N+1 tool calls (N papers: N navigate + N evaluate + 1 bash) |
| 144 | - BibTeX links are on `scholar.googleusercontent.com` — CORS blocks fetch(), so we use navigate_page to bypass |
| 145 | - Reuses `push_to_zotero.py` for Zotero Connector API communication |
| 146 | - Google Scholar BibTeX does NOT include abstract or DOI — these fields will be empty in Zotero |
| 147 | - After export, navigate back to Google Scholar page: `navigate_page` with type `back` |