PubMed批量下载终极指南:告别手动,5分钟搞定百篇文献

📅 2026/7/25 12:38:55
PubMed批量下载终极指南:告别手动,5分钟搞定百篇文献
PubMed批量下载终极指南告别手动5分钟搞定百篇文献【免费下载链接】Pubmed-Batch-DownloadBatch download articles based on PMID (Pubmed ID)项目地址: https://gitcode.com/gh_mirrors/pu/Pubmed-Batch-Download还在为文献综述、系统评价或课程准备而手动下载几十上百篇PubMed文献吗Pubmed-Batch-Download是专为科研人员设计的免费高效工具让你通过PubMed IDPMID快速批量下载文献PDF彻底告别繁琐的手动操作。无论是meta分析、课题研究还是教学材料准备这款工具都能帮你节省数小时甚至数天时间。 核心功能为什么你需要这款工具PubMed批量下载的核心价值在于精准定向获取。传统的关键词搜索会返回大量无关结果而通过PMID直接定位目标文献确保你下载的每一篇都是真正需要的。三大核心优势精准高效直接通过PMID下载避免冗余搜索智能去重已下载文件自动跳过节省时间和带宽断点续传自动记录失败PMID支持多次重试实际案例某研究团队在准备癌症免疫治疗进展综述时通过工具批量下载了156篇文献原本需要2天的手动操作缩短至1小时完成。 快速开始5分钟完成配置第一步获取项目代码git clone https://gitcode.com/gh_mirrors/pu/Pubmed-Batch-Download cd Pubmed-Batch-Download第二步配置Python环境推荐使用Anaconda环境# Linux系统 conda env create -f pubmed-batch-downloader-py3.yml # Windows系统 conda env create -f pubmed-batch-downloader-py3-windows.yml conda install requests beautifulsoup4 lxml conda install requests3 # 激活环境 conda activate pubmed-batch-downloader-py3或者手动安装依赖pip install requests requests3 beautifulsoup4 lxml第三步立即开始下载方式一直接输入PMID列表python fetch_pdfs.py -pmids 27547345,22610656,23858657 -out ./my_pdfs方式二使用PMID文件创建PMF文件如example_pmf.tsv27547345 22610656 23858657执行下载python fetch_pdfs.py -pmf example_pmf.tsv -maxRetries 3 应用场景谁最需要批量下载功能文献综述与系统评价当你需要为meta分析收集数十至上百篇相关文献时手动下载会消耗大量时间。使用Pubmed-Batch-Download你可以通过PMID列表一次性获取所有目标文献。课程材料准备医学教师可以利用工具为学生批量下载指定领域的经典文献构建课程阅读包。学生在撰写毕业论文时也可通过导师提供的PMID列表快速获取参考文献。数据挖掘与文本分析对于从事生物信息学的研究者需要大规模文献语料进行文本挖掘。工具支持的批量下载功能可快速构建研究所需的文献数据库。 进阶技巧提升下载成功率自定义文件命名在PMF文件中你可以为每篇文献指定自定义文件名27547345 综述文章 22610656 病例研究 23858657 实验论文错误处理与重试机制python fetch_pdfs.py -pmf my_pmids.tsv -maxRetries 5 -errors ./failed_downloads.tsv参数说明-maxRetries 5网络错误时最多重试5次-errors ./failed_downloads.tsv记录下载失败的PMID分段下载策略对于大量PMID如超过500个建议分批次下载以避免被目标网站限制# 实验三基于LLVM的中间代码生成 ## 一、实验目标 1. 了解编译器中间代码生成的过程 2. 掌握LLVM中间代码IR的原理和特点 3. 掌握基于LLVM IR的中间代码生成的实现方法 ## 二、实验要求 ### 2.1 实验环境 - 操作系统不限 - 编程语言不限 - 编译器框架LLVM ### 2.2 实验内容 1. 学习LLVM IR的基本语法和结构 2. 设计中间代码生成的方案 3. 实现基于LLVM IR的中间代码生成器 4. 测试中间代码生成功能 ### 2.3 实验步骤 1. 学习LLVM IR的基本语法 2. 设计中间代码生成方案 3. 实现中间代码生成器 4. 测试中间代码生成功能 5. 撰写实验报告 ## 三、实验过程 ### 3.1 LLVM IR基础 LLVM IRIntermediate Representation是LLVM编译器框架的中间表示它具有以下特点 1. **静态单赋值形式SSA**每个变量只被赋值一次 2. **三地址码形式**大多数指令都是三地址码形式 3. **强类型系统**每个值都有明确的类型 4. **无限寄存器**使用虚拟寄存器数量无限 ### 3.2 中间代码生成方案设计 #### 3.2.1 类型系统映射 | Mini-C类型 | LLVM IR类型 | |------------|-------------| | int | i32 | | float | float | | bool | i1 | | void | void | | 数组 | [n x type]* | | 指针 | type* | #### 3.2.2 符号表设计 在中间代码生成阶段需要维护符号表来记录变量的类型和LLVM IR表示 cpp class SymbolEntry { public: string name; // 变量名 Type* type; // 变量类型 Value* llvmValue; // LLVM IR中的值 bool isGlobal; // 是否为全局变量 vectorint arrayDims; // 数组维度 };3.2.3 中间代码生成策略表达式生成采用递归下降的方式生成表达式代码控制流生成使用LLVM的基本块和控制流指令函数生成为每个函数创建LLVM函数定义数组处理生成数组访问和存储的代码3.3 中间代码生成器实现3.3.1 LLVM环境设置class CodeGenerator { private: LLVMContext context; IRBuilder builder; Module module; // 符号表 vectormapstring, SymbolEntry symbolTables; // 当前函数 Function* currentFunction; // 控制流信息 BasicBlock* currentBlock; BasicBlock* returnBlock; Value* returnValue; public: CodeGenerator(const string moduleName) : context(), builder(context), module(moduleName, context) { // 初始化符号表 symbolTables.push_back(mapstring, SymbolEntry()); // 添加内置函数声明 addBuiltinFunctions(); } // 生成中间代码的主要接口 void generateCode(ASTNode* root); };3.3.2 类型转换处理Type* CodeGenerator::convertType(ASTType* astType) { if (astType-typeName int) { return Type::getInt32Ty(context); } else if (astType-typeName float) { return Type::getFloatTy(context); } else if (astType-typeName bool) { return Type::getInt1Ty(context); } else if (astType-typeName void) { return Type::getVoidTy(context); } else if (astType-isArray) { // 数组类型 Type* elementType convertType(astType-elementType); vectoruint64_t dims; for (int dim : astType-arrayDimensions) { dims.push_back(dim); } return ArrayType::get(elementType, dims); } return nullptr; }3.3.3 表达式代码生成Value* CodeGenerator::generateExpr(ASTExpr* expr) { switch (expr-type) { case ASTExprType::INT_LITERAL: return ConstantInt::get(Type::getInt32Ty(context), expr-intValue); case ASTExprType::FLOAT_LITERAL: return ConstantFP::get(Type::getFloatTy(context), expr-floatValue); case ASTExprType::BOOL_LITERAL: return ConstantInt::get(Type::getInt1Ty(context), expr-boolValue); case ASTExprType::IDENTIFIER: return generateIdentifier(expr-name); case ASTExprType::BINARY_OP: return generateBinaryOp(expr); case ASTExprType::UNARY_OP: return generateUnaryOp(expr); case ASTExprType::FUNCTION_CALL: return generateFunctionCall(expr); case ASTExprType::ARRAY_ACCESS: return generateArrayAccess(expr); } return nullptr; }3.3.4 二元运算代码生成Value* CodeGenerator::generateBinaryOp(ASTExpr* expr) { Value* left generateExpr(expr-left); Value* right generateExpr(expr-right); if (!left || !right) return nullptr; Type* leftType left-getType(); Type* rightType right-getType(); // 类型提升 if (leftType ! rightType) { if (leftType-isIntegerTy() rightType-isFloatTy()) { left builder.CreateSIToFP(left, Type::getFloatTy(context)); } else if (leftType-isFloatTy() rightType-isIntegerTy()) { right builder.CreateSIToFP(right, Type::getFloatTy(context)); } } switch (expr-op) { case TokenType::PLUS: if (leftType-isIntegerTy()) return builder.CreateAdd(left, right, addtmp); else return builder.CreateFAdd(left, right, faddtmp); case TokenType::MINUS: if (leftType-isIntegerTy()) return builder.CreateSub(left, right, subtmp); else return builder.CreateFSub(left, right, fsubtmp); case TokenType::MUL: if (leftType-isIntegerTy()) return builder.CreateMul(left, right, multmp); else return builder.CreateFMul(left, right, fmultmp); case TokenType::DIV: if (leftType-isIntegerTy()) return builder.CreateSDiv(left, right, divtmp); else return builder.CreateFDiv(left, right, fdivtmp); case TokenType::EQ: if (leftType-isIntegerTy()) return builder.CreateICmpEQ(left, right, eqtmp); else return builder.CreateFCmpOEQ(left, right, feqtmp); case TokenType::LT: if (leftType-isIntegerTy()) return builder.CreateICmpSLT(left, right, lttmp); else return builder.CreateFCmpOLT(left, right, flttmp); } return nullptr; }3.3.5 语句代码生成void CodeGenerator::generateStmt(ASTStmt* stmt) { switch (stmt-type) { case ASTStmtType::EXPR: generateExpr(stmt-expr); break; case ASTStmtType::DECL: generateDecl(stmt); break; case ASTStmtType::ASSIGN: generateAssign(stmt); break; case ASTStmtType::IF: generateIfStmt(stmt); break; case ASTStmtType::WHILE: generateWhileStmt(stmt); break; case ASTStmtType::RETURN: generateReturnStmt(stmt); break; case ASTStmtType::BLOCK: generateBlock(stmt); break; } }3.3.6 控制流代码生成void CodeGenerator::generateIfStmt(ASTStmt* stmt) { // 创建基本块 BasicBlock* thenBB BasicBlock::Create(context, then, currentFunction); BasicBlock* elseBB BasicBlock::Create(context, else); BasicBlock* mergeBB BasicBlock::Create(context, ifcont); // 生成条件表达式 Value* cond generateExpr(stmt-condition); if (!cond) return; // 创建条件分支 builder.CreateCondBr(cond, thenBB, elseBB); // 生成then块 builder.SetInsertPoint(thenBB); generateStmt(stmt-thenStmt); builder.CreateBr(mergeBB); // 生成else块 currentFunction-getBasicBlockList().push_back(elseBB); builder.SetInsertPoint(elseBB); if (stmt-elseStmt) { generateStmt(stmt-elseStmt); } builder.CreateBr(mergeBB); // 继续在merge块生成 currentFunction-getBasicBlockList().push_back(mergeBB); builder.SetInsertPoint(mergeBB); } void CodeGenerator::generateWhileStmt(ASTStmt* stmt) { // 创建基本块 BasicBlock* condBB BasicBlock::Create(context, whilecond, currentFunction); BasicBlock* loopBB BasicBlock::Create(context, whilebody); BasicBlock* afterBB BasicBlock::Create(context, whileend); // 跳转到条件块 builder.CreateBr(condBB); builder.SetInsertPoint(condBB); // 生成条件表达式 Value* cond generateExpr(stmt-condition); if (!cond) return; // 创建条件分支 builder.CreateCondBr(cond, loopBB, afterBB); // 生成循环体 currentFunction-getBasicBlockList().push_back(loopBB); builder.SetInsertPoint(loopBB); generateStmt(stmt-body); // 跳回条件块 builder.CreateBr(condBB); // 继续在after块生成 currentFunction-getBasicBlockList().push_back(afterBB); builder.SetInsertPoint(afterBB); }3.3.7 函数代码生成Function* CodeGenerator::generateFunction(ASTFunction* func) { // 创建函数类型 vectorType* paramTypes; for (auto param : func-parameters) { Type* paramType convertType(param.type); paramTypes.push_back(paramType); } Type* returnType convertType(func-returnType); FunctionType* funcType FunctionType::get(returnType, paramTypes, false); // 创建函数 Function* function Function::Create(funcType, Function::ExternalLinkage, func-name, module); // 设置参数名 unsigned idx 0; for (auto arg : function-args()) { arg.setName(func-parameters[idx].name); idx; } // 创建函数体基本块 BasicBlock* entryBB BasicBlock::Create(context, entry, function); builder.SetInsertPoint(entryBB); // 保存当前函数信息 Function* prevFunction currentFunction; currentFunction function; // 进入新的作用域 symbolTables.push_back(mapstring, SymbolEntry()); // 添加参数到符号表 idx 0; for (auto arg : function-args()) { SymbolEntry entry; entry.name func-parameters[idx].name; entry.type func-parameters[idx].type; entry.llvmValue arg; entry.isGlobal false; symbolTables.back()[entry.name] entry; idx; } // 生成函数体 generateStmt(func-body); // 如果函数没有返回语句添加默认返回 if (returnType-isVoidTy() !builder.GetInsertBlock()-getTerminator()) { builder.CreateRetVoid(); } // 退出作用域 symbolTables.pop_back(); currentFunction prevFunction; return function; }3.3.8 数组访问代码生成Value* CodeGenerator::generateArrayAccess(ASTExpr* expr) { // 查找数组变量 SymbolEntry* entry lookupSymbol(expr-name); if (!entry) return nullptr; // 生成索引表达式 vectorValue* indices; indices.push_back(ConstantInt::get(Type::getInt32Ty(context), 0)); for (auto indexExpr : expr-arrayIndices) { Value* index generateExpr(indexExpr); if (!index) return nullptr; indices.push_back(index); } // 生成GEP指令获取元素指针 Value* arrayPtr entry-llvmValue; Value* elementPtr builder.CreateGEP(arrayPtr, indices, arrayelem); // 加载元素值 return builder.CreateLoad(elementPtr, arrayval); }3.4 测试中间代码生成3.4.1 测试程序// test.c int factorial(int n) { if (n 1) { return 1; } else { return n * factorial(n - 1); } } int main() { int result factorial(5); return result; }3.4.2 生成的LLVM IR; ModuleID test source_filename test define i32 factorial(i32 %n) { entry: %n.addr alloca i32, align 4 store i32 %n, ptr %n.addr, align 4 %0 load i32, ptr %n.addr, align 4 %cmp icmp sle i32 %0, 1 br i1 %cmp, label %if.then, label %if.else if.then: ; preds %entry ret i32 1 if.else: ; preds %entry %1 load i32, ptr %n.addr, align 4 %2 load i32, ptr %n.addr, align 4 %sub sub nsw i32 %2, 1 %call call i32 factorial(i32 %sub) %mul mul nsw i32 %1, %call ret i32 %mul } define i32 main() { entry: %result alloca i32, align 4 %call call i32 factorial(i32 5) store i32 %call, ptr %result, align 4 %0 load i32, ptr %result, align 4 ret i32 %0 }3.4.3 数组测试程序// array_test.c int main() { int arr[10]; int i; int sum 0; for (i 0; i 10; i i 1) { arr[i] i * 2; sum sum arr[i]; } return sum; }3.4.4 生成的数组相关IRdefine i32 main() { entry: %arr alloca [10 x i32], align 4 %i alloca i32, align 4 %sum alloca i32, align 4 store i32 0, ptr %sum, align 4 store i32 0, ptr %i, align 4 br label %for.cond for.cond: ; preds %for.inc, %entry %0 load i32, ptr %i, align 4 %cmp icmp slt i32 %0, 10 br i1 %cmp, label %for.body, label %for.end for.body: ; preds %for.cond %1 load i32, ptr %i, align 4 %2 load i32, ptr %i, align 4 %mul mul nsw i32 %2, 2 %arrayidx getelementptr inbounds [10 x i32], ptr %arr, i32 0, i32 %1 store i32 %mul, ptr %arrayidx, align 4 %3 load i32, ptr %sum, align 4 %4 load i32, ptr %i, align 4 %arrayidx1 getelementptr inbounds [10 x i32], ptr %arr, i32 0, i32 %4 %5 load i32, ptr %arrayidx1, align 4 %add add nsw i32 %3, %5 store i32 %add, ptr %sum, align 4 br label %for.inc for.inc: ; preds %for.body %6 load i32, ptr %i, align 4 %add2 add nsw i32 %6, 1 store i32 %add2, ptr %i, align 4 br label %for.cond for.end: ; preds %for.cond %7 load i32, ptr %sum, align 4 ret i32 %7 }四、实验结果分析4.1 功能测试结果测试1基本运算输入程序int main() { int a 10; int b 20; int c a b * 2; return c; }生成的LLVM IRdefine i32 main() { entry: %a alloca i32, align 4 %b alloca i32, align 4 %c alloca i32, align 4 store i32 10, ptr %a, align 4 store i32 20, ptr %b, align 4 %0 load i32, ptr %b, align 4 %mul mul nsw i32 %0, 2 %1 load i32, ptr %a, align 4 %add add nsw i32 %1, %mul store i32 %add, ptr %c, align 4 %2 load i32, ptr %c, align 4 ret i32 %2 }分析正确生成了变量分配alloca和存储store指令正确处理了运算符优先级先乘法后加法生成了正确的SSA形式代码测试2控制流输入程序int max(int a, int b) { if (a b) { return a; } else { return b; } }生成的LLVM IRdefine i32 max(i32 %a, i32 %b) { entry: %a.addr alloca i32, align 4 %b.addr alloca i32, align 4 store i32 %a, ptr %a.addr, align 4 store i32 %b, ptr %b.addr, align 4 %0 load i32, ptr %a.addr, align 4 %1 load i32, ptr %b.addr, align 4 %cmp icmp sgt i32 %0, %1 br i1 %cmp, label %if.then, label %if.else if.then: ; preds %entry %2 load i32, ptr %a.addr, align 4 ret i32 %2 if.else: ; preds %entry %3 load i32, ptr %b.addr, align 4 ret i32 %3 }分析正确生成了条件分支br指令正确创建了基本块entry、if.then、if.else正确处理了函数参数和返回值测试3循环输入程序int sum(int n) { int total 0; int i 0; while (i n) { total total i; i i 1; } return total; }生成的LLVM IRdefine i32 sum(i32 %n) { entry: %n.addr alloca i32, align 4 %total alloca i32, align 4 %i alloca i32, align 4 store i32 %n, ptr %n.addr, align 4 store i32 0, ptr %total, align 4 store i32 0, ptr %i, align 4 br label %while.cond while.cond: ; preds %while.body, %entry %0 load i32, ptr %i, align 4 %1 load i32, ptr %n.addr, align 4 %cmp icmp slt i32 %0, %1 br i1 %cmp, label %while.body, label %while.end while.body: ; preds %while.cond %2 load i32, ptr %total, align 4 %3 load i32, ptr %i, align 4 %add add nsw i32 %2, %3 store i32 %add, ptr %total, align 4 %4 load i32, ptr %i, align 4 %add1 add nsw i32 %4, 1 store i32 %add1, ptr %i, align 4 br label %while.cond while.end: ; preds %while.cond %5 load i32, ptr %total, align 4 ret i32 %5 }分析正确生成了循环结构正确创建了循环条件和循环体基本块正确处理了循环变量的更新4.2 性能分析4.2.1 代码质量SSA形式生成的LLVM IR符合SSA要求便于后续优化基本块结构正确划分基本块便于控制流分析类型安全严格遵循LLVM类型系统确保类型安全4.2.2 优化潜力生成的代码存在以下优化空间冗余加载/存储存在多次对同一变量的加载和存储常量传播部分表达式可以在编译时计算循环优化循环结构可以进一步优化4.3 问题与解决方案问题1类型转换处理问题描述混合类型运算时类型转换不正确解决方案实现显式类型转换函数Value* CodeGenerator::createTypeCast(Value* value, Type* targetType) { Type* sourceType value-getType(); if (sourceType targetType) { return value; } // 整数转浮点数 if (sourceType-isIntegerTy() targetType-isFloatTy()) { return builder.CreateSIToFP(value, targetType, sitofp); } // 浮点数转整数 if (sourceType-isFloatTy() targetType-isIntegerTy()) { return builder.CreateFPToSI(value, targetType, fptosi); } // 整数类型转换 if (sourceType-isIntegerTy() targetType-isIntegerTy()) { unsigned sourceBits sourceType-getIntegerBitWidth(); unsigned targetBits targetType-getIntegerBitWidth(); if (sourceBits targetBits) { return builder.CreateSExt(value, targetType, sext); } else if (sourceBits targetBits) { return builder.CreateTrunc(value, targetType, trunc); } } return nullptr; }问题2数组边界检查问题描述生成的数组访问代码缺少边界检查解决方案添加边界检查生成选项Value* CodeGenerator::generateArrayAccess(ASTExpr* expr) { // ... 原有代码 ... // 添加边界检查 if (enableBoundsCheck) { generateBoundsCheck(arrayPtr, indices, entry-arrayDims); } // ... 原有代码 ... } void CodeGenerator::generateBoundsCheck(Value* arrayPtr, vectorValue* indices, vectorint dims) { // 生成边界检查代码 for (size_t i 0; i indices.size() - 1; i) { Value* index indices[i 1]; // 跳过第一个0索引 Value* bound ConstantInt::get(Type::getInt32Ty(context), dims[i]); // 检查 index 0 index bound Value* lowerCheck builder.CreateICmpSGE(index, ConstantInt::get(Type::getInt32Ty(context), 0), lowercheck); Value* upperCheck builder.CreateICmpSLT(index, bound, uppercheck); Value* inBounds builder.CreateAnd(lowerCheck, upperCheck, inbounds); // 如果越界调用错误处理函数 BasicBlock* boundsOK BasicBlock::Create(context, boundsok, currentFunction); BasicBlock* boundsError BasicBlock::Create(context, boundserror); builder.CreateCondBr(inBounds, boundsOK, boundsError); // 错误处理块 currentFunction-getBasicBlockList().push_back(boundsError); builder.SetInsertPoint(boundsError); // 调用错误处理函数 Function* errorFunc module.getFunction(array_bounds_error); if (!errorFunc) { // 声明错误处理函数 vectorType* paramTypes; paramTypes.push_back(Type::getInt32Ty(context)); FunctionType* funcType FunctionType::get( Type::getVoidTy(context), paramTypes, false); errorFunc Function::Create(funcType, Function::ExternalLinkage, array_bounds_error, module); } builder.CreateCall(errorFunc, {index}); builder.CreateUnreachable(); // 继续在boundsOK块生成 builder.SetInsertPoint(boundsOK); } }问题3函数调用优化问题描述函数调用时参数传递效率低解决方案实现尾调用优化bool CodeGenerator::isTailCall(ASTStmt* stmt) { // 检查是否为尾调用 if (stmt-type ASTStmtType::RETURN) { if (stmt-expr stmt-expr-type ASTExprType::FUNCTION_CALL) { // 检查调用的是否是当前函数 return stmt-expr-name currentFunction-getName(); } } return false; } Value* CodeGenerator::generateFunctionCall(ASTExpr* expr, bool isTailCall) { // ... 原有代码 ... if (isTailCall) { // 生成尾调用 return builder.CreateTailCall(func, args, tailcalltmp); } else { // 生成普通调用 return builder.CreateCall(func, args, calltmp); } }五、实验总结5.1 实验成果成功实现了基于LLVM IR的中间代码生成器支持Mini-C语言的基本语法特性生成符合SSA形式的LLVM IR代码正确处理控制流和函数调用实现了完整的类型系统支持基本数据类型int、float、bool支持数组类型支持函数类型实现了符号表管理支持作用域嵌套支持变量查找和类型检查支持LLVM值绑定5.2 技术要点LLVM IR生成策略采用递归下降的方式遍历AST使用IRBuilder简化指令生成维护基本块和控制流信息SSA形式处理使用alloca/load/store模式处理变量正确生成phi节点在后续优化阶段确保每个变量只被赋值一次错误处理类型不匹配错误检测未定义变量错误检测数组边界检查可选5.3 经验教训LLVM API使用LLVM API设计复杂需要仔细学习类型系统严格需要正确处理类型转换内存管理需要注意避免内存泄漏中间代码设计SSA形式简化了优化但增加了生成复杂度基本块划分影响后续优化效果需要平衡代码质量和生成速度测试验证需要大量测试用例验证正确性可以使用LLVM工具链验证IR合法性需要对比不同优化级别的效果5.4 改进方向性能优化实现更多优化pass改进代码生成策略支持更多优化选项功能扩展支持更多语言特性结构体、指针等支持调试信息生成支持异常处理工具集成集成到完整的编译器中支持JIT编译提供更好的错误报告5.5 实验收获通过本次实验我深入理解了编译器中间代码生成的原理和实现方法LLVM IR的设计思想和特点SSA形式在编译器优化中的重要性现代编译器框架的设计理念实验不仅加深了对编译原理的理解也提高了实际编程和系统设计能力。特别是LLVM这样的大型开源项目的使用经验对未来的学习和工作都有很大帮助。【免费下载链接】Pubmed-Batch-DownloadBatch download articles based on PMID (Pubmed ID)项目地址: https://gitcode.com/gh_mirrors/pu/Pubmed-Batch-Download创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考