Created
July 31, 2026 15:35
-
-
Save htlin222/37372cc3ec8e5dd2ca4d1ddd3174620b to your computer and use it in GitHub Desktop.
Convert Zotero CSV exports to Marp markdown slides with PubMed abstracts and ChatGPT-summarized bullet points. Usage: `python script.py -f collection.csv`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """ | |
| Zotero CSV to Marp Slide Generator | |
| Converts Zotero bibliography exports (CSV format) into Marp-compatible markdown presentations. | |
| Fetches PubMed abstracts via DOI, summarizes with ChatGPT into bullet points. | |
| Dependencies: openai, pandas, biopython, requests | |
| Usage: python script.py -f zotero_export.csv | |
| Output: output.md (marp-compatible presentation) | |
| """ | |
| import os | |
| import re | |
| import subprocess | |
| import sys | |
| import openai | |
| import pandas as pd | |
| import requests | |
| from Bio import Entrez | |
| def respond(prompt): | |
| openai.api_key = "YOUR_API_KEY" | |
| completions = openai.Completion.create( | |
| engine="text-davinci-002", | |
| prompt=prompt, | |
| max_tokens=1000, | |
| n=1, | |
| stop=None, | |
| temperature=0.5, | |
| ) | |
| message = completions.choices[0].text | |
| return message | |
| def doi_to_pmid(doi): | |
| """Convert DOI to PubMed ID via NCBI esearch API.""" | |
| base_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi" | |
| params = {"db": "pubmed", "term": f"{doi}[DOI]", "retmode": "json"} | |
| response = requests.get(base_url, params=params) | |
| response_json = response.json() | |
| if "esearchresult" in response_json and "idlist" in response_json[ | |
| "esearchresult"]: | |
| pmids = response_json["esearchresult"]["idlist"] | |
| if pmids: | |
| return pmids[0] | |
| return None | |
| def get_abstract_from_pmid(pmid): | |
| """Fetch abstract text from PubMed ID.""" | |
| # NCBI 要求提供聯絡信箱;設環境變數 NCBI_EMAIL 即可 | |
| Entrez.email = os.environ.get("NCBI_EMAIL", "your-email@example.com") | |
| handle = Entrez.efetch(db="pubmed", id=pmid, retmode="xml") | |
| records = Entrez.read(handle) | |
| try: | |
| abstract = records["PubmedArticle"][0]["MedlineCitation"]["Article"][ | |
| "Abstract"]["AbstractText"][0] | |
| except (KeyError, IndexError): | |
| abstract = "Abstract not available" | |
| return abstract | |
| def get_abstract(doi): | |
| """Retrieve abstract from DOI.""" | |
| pmid = doi_to_pmid(doi) | |
| print(pmid) | |
| if pmid: | |
| return get_abstract_from_pmid(pmid) | |
| else: | |
| print("No DOI Found") | |
| return None | |
| def clean_text(string): | |
| """Format bullet points with slide separators every 3 items.""" | |
| lines = string.split("\n") | |
| filtered_lines = [line for line in lines if line.startswith("- ")] | |
| grouped_lines = [] | |
| for i in range(0, len(filtered_lines), 3): | |
| grouped_lines.extend(filtered_lines[i:i + 3]) | |
| if i + 3 < len(filtered_lines): | |
| grouped_lines.append("\n\n---\n\n") | |
| return "\n".join(grouped_lines) | |
| def extract_plain_text_line_by_line(string: str) -> str: | |
| """Extract plain text from HTML-formatted notes.""" | |
| lines = re.split(r"<\/[^>]+>", string) | |
| lines = [ | |
| re.sub(r"<[^>]+>", "", line).strip() for line in lines | |
| if re.sub(r"<[^>]+>", "", line).strip() | |
| ] | |
| combined = "- " + "\n- ".join(lines) | |
| combined = clean_text(combined) | |
| return combined | |
| if __name__ == "__main__": | |
| if len(sys.argv) != 3: | |
| print("Usage: script_name.py -f filename.csv") | |
| sys.exit(1) | |
| flag, filename = sys.argv[1], sys.argv[2] | |
| if flag != "-f": | |
| print("Usage: script_name.py -f filename.csv") | |
| sys.exit(1) | |
| df = pd.read_csv(filename) | |
| doi_list = df["DOI"].dropna().unique().tolist() | |
| markdown_content = "# Title Page\n\n---\n\n" | |
| for doi in doi_list: | |
| title = df[df["DOI"] == doi]["Title"].iloc[0] | |
| note = df[df["DOI"] == doi]["Notes"].iloc[0] | |
| if isinstance(note, str) and len(note) > 0: | |
| cleaned_note = (f"## {title}\n\n### Hightlights\n\n" + | |
| extract_plain_text_line_by_line(note) + | |
| "\n\n---\n\n") | |
| else: | |
| cleaned_note = "" | |
| print(title) | |
| abstract = get_abstract(doi) | |
| response = respond( | |
| f"transferred to bullet points in markdown, like '- content\n- content' here are the context:\n{abstract}" | |
| ) | |
| lines = [line for line in response.splitlines() if line.strip()] | |
| bullet_points = "\n".join(lines) | |
| bullet_points = clean_text(bullet_points) | |
| year = df[df["DOI"] == doi]["Publication Year"].iloc[0] | |
| markdown_content += f"{cleaned_note}## {title}\n\n### Summary\n\n{bullet_points}\n\n<!-- {abstract} -->\n\n> {title} ({year}). DOI: {doi}\n\n---\n\n" | |
| markdown_content += "\n\n## Thank You for Your Listening\n" | |
| with open("output.md", "w") as file: | |
| file.write(markdown_content) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment