Skill: hollyya/my-pubmed-skill (version 1.0.0)

--- name: my-pubmed-skill description: 简易PubMed搜索技能。使用web搜索和API调用检索PubMed文献。当用户要求"搜索PubMed文献"、"查找医学论文"、"医学文献检索"时使用。 --- # My PubMed Search Skill 一个简易的PubMed文献搜索技能，使用多种方法检索PubMed文献。 ## 核心功能 ### 1. Web搜索 - 使用OpenClaw的web_search/web_fetch工具搜索PubMed网站 - 适用于快速浏览和简单搜索 ### 2. API调用 - 使用NCBI E-Utilities API进行结构化检索 - 支持批量获取文献详情 - 支持多种引用格式导出 ### 3. 搜索参数 - **时间范围**: 近一年/近三年/近五年/自定义 - **文章类型**: Review/Clinical Trial/Meta-Analysis等 - **语言**: English/Chinese/Japanese - **可用全文**: Free Full Text/Full Text/Abstract ### 4. 引用格式 - APA - MLA - IEEE - GB/T 7714 - BibTeX ## 使用流程 ### 第一步：明确需求 询问用户： - 研究主题/关键词 - 时间范围（近一年/近三年/不限） - 需要的结果数量 - 引用格式偏好 ### 第二步：执行搜索 根据需求选择方法： 1. **简单搜索** - 使用web_fetch抓取PubMed搜索结果 2. **API搜索** - 运行PowerShell脚本调用PubMed API ```powershell powershell -File scripts/pubmed_search.ps1 "关键词" 10 apa ``` ### 第三步：处理结果 提取以下信息： - 论文标题 - 作者列表 - 发表年份 - PubMed ID (PMID) - 摘要 - 期刊名称 - DOI ### 第四步：格式化输出 使用模板格式化结果： - 表格格式 (`templates/results-table.md`) - 引用格式 (`templates/citation-format.md`) - 搜索选项 (`templates/search-options.md`) ## 脚本使用 ### 基本搜索 ```powershell # 搜索PubMed文献 powershell -File scripts/pubmed_search.ps1 "machine learning" 10 apa ``` ### 参数说明 | 参数 | 说明 | 默认值 | |------|------|--------| | 第1个 | 搜索关键词 | (必填) | | 第2个 | 结果数量 | 5 | | 第3个 | 引用格式 | apa | ### 引用格式 - `apa` - APA格式 - `mla` - MLA格式 - `ieee` - IEEE格式 - `gbt7714` - GB/T 7714格式 ## 脚本文件 - `scripts/pubmed_search.ps1` - 主搜索脚本 - `scripts/test_pubmed_api.ps1` - API测试脚本 ## 模板文件 - `templates/results-table.md` - 表格格式输出模板 - `templates/citation-format.md` - 引用格式模板 - `templates/search-options.md` - 搜索参数模板 ## 参考资料 - `references/pubmed-api.md` - PubMed API使用指南 ## 注意事项 1. PubMed API有访问限制（3次/秒，有API key时10次/秒） 2. 重要文献建议用户前往原文核实 3. 搜索结果仅供参考，需结合专业判断 4. 使用API key可提高速率限制

path: references/pubmed-api.md
content: # PubMed API使用指南

## 官方API

### 1. E-Utilities API
- **URL**: https://eutils.ncbi.nlm.nih.gov/entrez/eutils/
- **无需API key**，但有速率限制（3次/秒）

### 2. PubMed API (需要API key)
- **URL**: https://www.ncbi.nlm.nih.gov/research/pubmed-api/
- **速率限制**: 10次/秒（有API key）

## 基本API调用

### 搜索文献
```bash
# 使用curl
curl "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=CRISPR&retmax=10"
```

### 获取文献详情
```bash
curl "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=12345678&rettype=abstract"
```

## 示例Python代码

```python
import requests
import json

def search_pubmed(query, limit=10):
    url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi"
    params = {
        "db": "pubmed",
        "term": query,
        "retmax": limit,
        "retmode": "json"
    }
    
    response = requests.get(url, params=params)
    if response.status_code == 200:
        return response.json()
    else:
        return None

def fetch_pubmed_details(pmid):
    url = f"https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi"
    params = {
        "db": "pubmed",
        "id": pmid,
        "retmode": "xml"
    }
    
    response = requests.get(url, params=params)
    if response.status_code == 200:
        return response.text
    else:
        return None
```

## 速率限制

- **无API key**: 3次/秒
- **有API key**: 10次/秒
- **建议**: 添加延迟，避免触发限制

## 获取API key

1. 注册NCBI账号：https://www.ncbi.nlm.nih.gov/
2. 进入Account Settings → API Key Management
3. 生成API key
4. 在代码中添加：
   ```python
   headers = {"api-key": "your_api_key"}
   ```

## 常见问题

### 1. 403错误
- 检查API key是否正确
- 检查速率限制

### 2. XML解析错误
- 使用XML解析库（如xml.etree.ElementTree）
- 或使用JSON格式（retmode=json）

### 3. 网络连接问题
- 添加超时设置
- 使用代理（如果需要）


path: scripts/pubmed_search.py
content: #!/usr/bin/env python3
"""
PubMed Search Script
使用NCBI E-Utilities API搜索PubMed文献
"""

import requests
import xml.etree.ElementTree as ET
import json
import time
from typing import List, Dict, Optional

class PubMedSearcher:
    """PubMed搜索器"""
    
    BASE_URL = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils"
    
    def __init__(self, [REDACTED_SECRET] = None, email: str = "[REDACTED_SECRET]"):
        """
        初始化搜索器
        
        Args:
            api_key: NCBI API key（可选，用于提高速率限制）
            email: 用于"Polite Pool"的邮箱
        """
        self.[REDACTED_SECRET]
        self.email = email
        self.rate_limit = 10 if api_key else 3  # 每秒请求数
        self.last_request_time = 0
    
    def _rate_limit_wait(self):
        """等待速率限制"""
        current_time = time.time()
        time_since_last = current_time - self.last_request_time
        min_interval = 1.0 / self.rate_limit
        
        if time_since_last < min_interval:
            time.sleep(min_interval - time_since_last)
        
        self.last_request_time = time.time()
    
    def search(self, query: str, limit: int = 10, sort: str = "relevance") -> Dict:
        """
        搜索PubMed文献
        
        Args:
            query: 搜索查询
            limit: 返回结果数量
            sort: 排序方式（relevance/date）
        
        Returns:
            搜索结果字典
        """
        self._rate_limit_wait()
        
        url = f"{self.BASE_URL}/esearch.fcgi"
        params = {
            "db": "pubmed",
            "term": query,
            "retmax": limit,
            "retmode": "json",
            "sort": sort,
            "tool": "openclaw-pubmed-skill",
            "email": self.email
        }
        
        if self.[REDACTED_SECRET]"api_key"] = self.api_key
        
        try:
            response = requests.get(url, params=params, timeout=30)
            response.raise_for_status()
            return response.json()
        except requests.RequestException as e:
            return {"error": str(e)}
    
    def fetch_details(self, pmid: str) -> Dict:
        """
        获取文献详情
        
        Args:
            pmid: PubMed ID
        
        Returns:
            文献详情字典
        """
        self._rate_limit_wait()
        
        url = f"{self.BASE_URL}/efetch.fcgi"
        params = {
            "db": "pubmed",
            "id": pmid,
            "retmode": "xml",
            "tool": "openclaw-pubmed-skill",
            "email": self.email
        }
        
        if self.[REDACTED_SECRET]"api_key"] = self.api_key
        
        try:
            response = requests.get(url, params=params, timeout=30)
            response.raise_for_status()
            return self._parse_xml(response.text)
        except requests.RequestException as e:
            return {"error": str(e)}
    
    def fetch_batch(self, pmids: List[str]) -> List[Dict]:
        """
        批量获取文献详情
        
        Args:
            pmids: PubMed ID列表
        
        Returns:
            文献详情列表
        """
        results = []
        for pmid in pmids:
            details = self.fetch_details(pmid)
            if "error" not in details:
                results.append(details)
        return results
    
    def _parse_xml(self, xml_text: str) -> Dict:
        """解析PubMed XML响应"""
        try:
            root = ET.fromstring(xml_text)
            article = root.find('.//PubmedArticle')
            
            if article is None:
                return {"error": "No article found"}
            
            # 提取标题
            title_elem = article.find('.//ArticleTitle')
            title = title_elem.text if title_elem is not None else "N/A"
            
            # 提取摘要
            abstract_elem = article.find('.//Abstract/AbstractText')
            abstract = abstract_elem.text if abstract_elem is not None else "No abstract available"
            
            # 提取作者
            authors = []
            for author in article.findall('.//Author'):
                last_name = author.find('LastName')
                first_name = author.find('ForeName')
                if last_name is not None:
                    name = f"{last_name.text}"
                    if first_name is not None:
                        name = f"{last_name.text}, {first_name.text}"
                    authors.append(name)
            
            # 提取期刊
            journal_elem = article.find('.//Journal/Title')
            journal = journal_elem.text if journal_elem is not None else "N/A"
            
            # 提取年份
            year_elem = article.find('.//PubDate/Year')
            year = year_elem.text if year_elem is not None else "N/A"
            
            # 提取DOI
            doi_elem = article.find('.//ArticleId[@IdType="doi"]')
            doi = doi_elem.text if doi_elem is not None else "N/A"
            
            # 提取PMID
            pmid_elem = article.find('.//PMID')
            pmid = pmid_elem.text if pmid_elem is not None else "N/A"
            
            return {
                "pmid": pmid,
                "title": title,
                "abstract": abstract,
                "authors": authors,
                "journal": journal,
                "year": year,
                "doi": doi
            }
        except ET.ParseError as e:
            return {"error": f"XML parse error: {e}"}
    
    def format_citation(self, article: Dict, style: str = "apa") -> str:
        """
        格式化引用
        
        Args:
            article: 文章信息字典
            style: 引用格式（apa/mla/ieee/gbt7714）
        
        Returns:
            格式化引用字符串
        """
        authors = article.get("authors", [])
        title = article.get("title", "N/A")
        journal = article.get("journal", "N/A")
        year = article.get("year", "N/A")
        doi = article.get("doi", "N/A")
        
        # 格式化作者
        if len(authors) > 1:
            author_str = f"{authors[0]} et al."
        elif len(authors) == 1:
            author_str = authors[0]
        else:
            author_str = "Unknown"
        
        if style.lower() == "apa":
            return f"{author_str} ({year}). {title}. *{journal}*. https://doi.org/{doi}"
        elif style.lower() == "mla":
            return f"{author_str}. \"{title}.\" *{journal}* ({year})."
        elif style.lower() == "ieee":
            return f"{author_str}, \"{title},\" *{journal}*, {year}."
        elif style.lower() == "gbt7714":
            return f"{author_str}. {title}[J]. {journal}, {year}. DOI: {doi}"
        else:
            return f"{author_str}. {title}. {journal}, {year}."


def main():
    """主函数"""
    import sys
    
    if len(sys.argv) < 2:
        print("Usage: python pubmed_search.py <query> [limit] [style]")
        print("Example: python pubmed_search.py 'machine learning' 5 apa")
        sys.exit(1)
    
    query = sys.argv[1]
    limit = int(sys.argv[2]) if len(sys.argv) > 2 else 5
    style = sys.argv[3] if len(sys.argv) > 3 else "apa"
    
    # 创建搜索器
    searcher = PubMedSearcher()
    
    print(f"🔍 Searching PubMed for: {query}")
    print(f"📊 Limit: {limit} results")
    print("-" * 60)
    
    # 搜索
    search_results = searcher.search(query, limit)
    
    if "error" in search_results:
        print(f"❌ Error: {search_results['error']}")
        sys.exit(1)
    
    # 获取PMID列表
    idlist = search_results.get("esearchresult", {}).get("idlist", [])
    
    if not idlist:
        print("❌ No results found")
        sys.exit(0)
    
    print(f"✅ Found {len(idlist)} results")
    
    # 获取详情
    articles = searcher.fetch_batch(idlist)
    
    for i, article in enumerate(articles, 1):
        print(f"\n📄 Result {i}:")
        print(f"  PMID: {article.get('pmid')}")
        print(f"  Title: {article.get('title')}")
        print(f"  Authors: {', '.join(article.get('authors', []))}")
        print(f"  Journal: {article.get('journal')} ({article.get('year')})")
        print(f"  DOI: {article.get('doi')}")
        
        citation = searcher.format_citation(article, style)
        print(f"  Citation ({style}): {citation}")
        
        # 显示摘要（前100字符）
        abstract = article.get('abstract', '')
        if abstract:
            print(f"  Abstract preview: {abstract[:100]}...")
    
    print("-" * 60)
    print(f"📚 Total results: {len(articles)}")


if __name__ == "__main__":
    main()

path: templates/citation-format.md
content: # 引用格式模板

## APA格式
Handelman, G. S., et al. (2018). eDoctor: machine learning and the future of medicine. *Journal of Internal Medicine*, 284(6), 603-619. https://doi.org/10.1111/joim.12822

## GB/T 7714格式
Handelman GS, et al. eDoctor: machine learning and the future of medicine[J]. Journal of Internal Medicine, 2018, 284(6): 603-619. DOI: 10.1111/joim.12822

## MLA格式
Handelman, G. S., et al. "eDoctor: machine learning and the future of medicine." *Journal of Internal Medicine* 284.6 (2018): 603-619.

## IEEE格式
G. S. Handelman et al., "eDoctor: machine learning and the future of medicine," *Journal of Internal Medicine*, vol. 284, no. 6, pp. 603-619, Dec. 2018, doi: 10.1111/joim.12822.

## BibTeX格式
@article{handelman2018,
  title={eDoctor: machine learning and the future of medicine},
  author={Handelman, G. S. and others},
  journal={Journal of Internal Medicine},
  volume={284},
  number={6},
  pages={603--619},
  year={2018},
  doi={10.1111/joim.12822}
}


path: templates/results-table.md
content: # 搜索结果表格模板

## PubMed文献搜索结果

| 序号 | 标题 | 作者 | 年份 | PMID | 期刊 |
|------|------|------|------|------|------|
| 1 | [论文标题1](链接) | 作者列表 | 2024 | PMID | 期刊名 |
| 2 | [论文标题2](链接) | 作者列表 | 2023 | PMID | 期刊名 |
| ... | ... | ... | ... | ... | ... |

---

## 摘要摘要

### 论文1：标题
**PMID**: 12345678
**作者**: Author A, Author B
**期刊**: Journal Name, 2024

**摘要**:
这里是论文的摘要内容...

---

## 检索信息

- **检索词**: {关键词}
- **检索时间**: {日期}
- **结果数量**: {数量}
- **数据库**: PubMed


path: templates/search-options.md
content: # 搜索参数模板

## 可用参数

### 1. 时间范围
- `近一年`: (("2025/01/01"[Date - Publication] : "3000"[Date - Publication]))
- `近三年`: (("2023/01/01"[Date - Publication] : "3000"[Date - Publication]))
- `近五年`: (("2021/01/01"[Date - Publication] : "3000"[Date - Publication]))
- `自定义`: (("开始日期"[Date - Publication] : "结束日期"[Date - Publication]))

### 2. 文章类型
- `Review`: review[pt]
- `Clinical Trial`: clinical trial[pt]
- `Meta-Analysis`: meta-analysis[pt]
- `Randomized Controlled Trial`: randomized controlled trial[pt]
- `Case Reports`: case reports[pt]

### 3. 物种
- `Human`: humans[mesh]
- `Animal`: animals[mesh]
- `Male`: male[mesh]
- `Female`: female[mesh]

### 4. 年龄组
- `Infant`: infant[mesh]
- `Child`: child[mesh]
- `Adult`: adult[mesh]
- `Aged`: aged[mesh]

### 5. 期刊筛选
- `影响因子>10`: journal impact factor > 10
- `核心期刊`: core clinical journal

### 6. 语言
- `English`: english[la]
- `Chinese`: chinese[la]
- `Japanese`: japanese[la]

### 7. 可用全文
- `Free Full Text`: free full text[sb]
- `Full Text`: full text[pt]
- `Abstract`: abstract[pt]

## 组合搜索示例

### 示例1: 近年综述
```
machine learning[Title/Abstract] AND review[pt] AND ("2023/01/01"[Date - Publication] : "3000"[Date - Publication])
```

### 示例2: 临床试验
```
diabetes[Title/Abstract] AND clinical trial[pt] AND humans[mesh]
```

### 示例3: 高影响因子期刊
```
cancer[Title/Abstract] AND ("2024"[Date - Publication] : "3000"[Date - Publication])
```

## 高级搜索语法

| 操作符 | 含义 | 示例 |
|--------|------|------|
| AND | 同时包含 | A AND B |
| OR | 任一包含 | A OR B |
| NOT | 排除 | A NOT B |
| [Title/Abstract] | 标题/摘要 | cancer[Title/Abstract] |
| [MeSH Terms] | MeSH词 | diabetes[MeSH Terms] |
| [Author] | 作者 | smith[Author] |
| [Journal] | 期刊 | nature[Journal] |
| [Date - Publication] | 发表日期 | 2024[Date - Publication] |


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)