MoreRSS

site iconMT | 面条修改

关注于前端开发的web工程师,Self-hosting Homelab 爱好者,email.ml dns.surf开发者。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

MT | 面条的 RSS 预览

在 Docker 沙箱中运行 MCP Server

2025-04-23 22:01:48

MCP 是今年 AI 开发行业很热门的一个协议,但是由于它的 C/S 架构,导致使用者必须在本地运行 MCP Server。

MCP Server 常见的运行方式有 npx(NPM 生态)、uvx(Python 生态)、Docker 等 stdio 方式和 HTTP(SSE/Streaming) 方式。但是 npx 和 uvx 运行命令有着极大的风险。如果不慎执行恶意软件包,可能会导致隐私数据泄露带来极大的安全风险。具体可以看 Invariant 的 MCP Security Notification: Tool Poisoning Attacks 这篇文章。

作为一个软件行业从业者,对安全的关注度极高。让 ChatGPT 整理了一下最近 5 年的 NPM 和 PyPI 供应链攻击事件,让人不寒而栗。

时间 事件 概要与影响范围
2021年2月 “依赖混淆”漏洞披露 安全研究员 Alex Birsan 利用依赖混淆(Dependency Confusion)技术,在 NPM/PyPI 上传与多家企业内部库同名的软件包,成功入侵了包括苹果、微软等35家大厂内部服务器 (PyPI flooded with 1,275 dependency confusion packages)。这一演示引发业内对供应链风险的高度关注。
2021年10月 UAParser.js 库遭劫持 NPM上每周下载量超700万的流行库 ua-parser-js 被攻击者通过维护者账户入侵发布恶意版本 (A Timeline of SSC Attacks, Curated by Sonatype)。受感染版本在安装时植入密码窃取木马加密货币挖矿程序,波及大量开发者系统。
2021年10月 假冒 Roblox 库投毒 攻击者在 NPM 上传多个假冒 Roblox API 的软件包(如 noblox.js-proxy),内含混淆的恶意代码,安装后会植入木马和勒索软件等Payload (A Timeline of SSC Attacks, Curated by Sonatype)。这些包下载数千次,显示出攻击者通过typosquatting手法诱骗游戏开发者。
2021年11月 COA 与 RC 库连续劫持 NPM上热门库 coa(每周下载数百万)和 rc(每周1400万下载)相继被入侵发布恶意版本。受害版本执行与 UAParser.js 案例类似的凭证窃取木马,一度导致全球众多使用 React 等框架的项目构建管线中断 (A Timeline of SSC Attacks, Curated by Sonatype) (A Timeline of SSC Attacks, Curated by Sonatype)。官方调查认定原因均为维护者账户被盗用。
2022年1月 Colors/Faker 开源库“自杀” 著名的颜色格式库 colors.js 和测试数据生成库 faker.js 的作者出于抗议,在最新版本中注入无限循环等破坏性代码,导致包括Meta(Facebook)和亚马逊等公司在内的数千项目崩溃 (A Timeline of SSC Attacks, Curated by Sonatype)(虽非外部攻击,但属于供应链投毒范畴)。
2022年1月 PyPI 1,275个恶意包集中投放 一名用户在1月23日一天内疯狂向 PyPI 发布了 1,275 个恶意软件包 (A Timeline of SSC Attacks, Curated by Sonatype)。这些包大多冒用知名项目或公司的名字(如 xcryptographySagepay 等),安装后收集主机名、IP等指纹信息并通过 DNS/HTTP 回传给攻击者 (PyPI flooded with 1,275 dependency confusion packages) (PyPI flooded with 1,275 dependency confusion packages)。PyPI 管理员在收到报告后一小时内即下架了所有相关包 (PyPI flooded with 1,275 dependency confusion packages)。
2022年3月 Node-ipc “抗议软件”事件 前端构建常用库 node-ipc 的作者在 v10.1.1–10.1.3 版本中加入恶意代码:检测到客户端 IP 属于俄罗斯或白俄罗斯时,就擦除文件系统、用爱心表情覆盖文件 (Corrupted open-source software enters the Russian battlefield | ZDNET) (Corrupted open-source software enters the Russian battlefield | ZDNET)。该库被 Vue CLI 等广泛依赖,导致大量用户系统遭破坏,并被赋予 CVE-2022-23812(CVSS 9.8) (Corrupted open-source software enters the Russian battlefield | ZDNET)。
2022年10月 LofyGang 大规模投毒活动 安全公司发现一个名为“LofyGang”的团伙在 NPM 上分发了将近 200 个恶意包 (LofyGang Distributed ~200 Malicious NPM Packages to Steal Credit Card Data)。这些包通过typosquatting和伪装常用库名称植入木马,窃取开发者的信用卡信息、Discord 账户以及游戏服务登录凭据,累计安装次数达数千次 (LofyGang Distributed ~200 Malicious NPM Packages to Steal Credit Card Data)。这是一起持续一年多的有组织网络犯罪活动。
2022年12月 PyTorch-nightly 依赖链攻击 知名深度学习框架 PyTorch 披露其夜间版在 12月25–30日间遭遇依赖混淆式供应链攻击 (Malicious PyTorch dependency ‘torchtriton’ on PyPI | Wiz Blog):攻击者在 PyPI 上注册了名为 torchtriton 的恶意包,与 PyTorch 夜ly 版所需的私有依赖同名,导致数千名通过 pip 安装 nightly 版的用户中招 (Malicious PyTorch dependency ‘torchtriton’ on PyPI | Wiz Blog)。恶意 torchtriton 包运行后收集系统上的环境变量和秘钥并上传至攻击者服务器,危及用户的云凭证安全。PyTorch 官方紧急发布警告并替换了该命名空间 (Malicious PyTorch dependency ‘torchtriton’ on PyPI | Wiz Blog)。
2023年3月 “W4SP Stealer” 木马泛滥 PyPI 安全研究员陆续发现 PyPI 上出现大量携带 W4SP Stealer 信息窃取木马的恶意包 (W4SP Stealer Discovered in Multiple PyPI Packages Under Various Names)。这些木马别名众多(如 ANGEL Stealer、PURE Stealer 等),但本质均为 W4SP 家族,专门窃取用户密码、加密货币钱包和 Discord 令牌等信息 (W4SP Stealer Discovered in Multiple PyPI Packages Under Various Names)。一次报告就揭示了16个此类恶意包(如 modulesecurityeasycordey 等) (W4SP Stealer Discovered in Multiple PyPI Packages Under Various Names)。PyPI 针对此类木马展开清理,并加强了上传检测。
2023年8月 Lazarus 组织攻击 PyPI ReversingLabs 报告称朝鲜黑客组织 Lazarus 的分支在 PyPI 发布了逾两打(24个以上)伪装热门库的恶意包(代号“VMConnect”行动) (Software Supply Chain Attacks: A (partial) History)。这些包企图针对特定行业(如金融)用户,植入远程访问木马。据称该攻击与此前针对 NuGet 的类似活动相关联,显示出国家级黑客对开源供应链的兴趣。
2024年及以后 持续的供应链威胁 2024年以来,NPM 与 PyPI 上仍不断爆出新的投毒事件。例如2024年初发现假冒VS Code相关NPM包内含远控间谍软件 (A Timeline of SSC Attacks, Curated by Sonatype)、假冒Solana库窃取加密钱包密钥的PyPI包 (A Timeline of SSC Attacks, Curated by Sonatype)等。这表明供应链攻击已成常态化威胁,需要生态系统持续提高警惕和防御能力。

发 Twitter 吐槽了一下,结果吐槽的时候就看到一个推友遇到了一起供应链攻击事件。

Twitter

所幸 @TBXark 推荐了他的 MCP Proxy 项目,可以很方便的将 MCP Server 运行在 Docker 中。他最初的目的是把 MCP Server 运行在服务器上,减少客户端压力和方便移动端调用。 然而由于 Docker 天然的隔离特性,与我期望有沙箱的诉求不谋而合。

MCP Proxy 会在 Docker 中运行 MCP Servers 并转换为 MCP SSE 的协议,这样用户就可以在 MCP 客户端中全部走 SSE 协议调用,这样可以大大减小 npx 和 uvx 直接运行带来的任意文件读取风险。如果部署在境外服务器, 还可以顺带解决网络的问题

但是当前还是可以读取到 /config/config.json 这个 MCP Proxy 的配置文件, 风险可控。同时也给开发者提了需求, config 文件配置 400 权限, npx 和 uvx 命令使用 nobody 用户运行。如果可以实现,将完美解决任意文件读取的问题。

运行 MCP Proxy

MCP Proxy

如果你自己有 VPS 部署了 Docker, 可以使用下面的命令运行 MCP Proxy。

docker run -d -p 9090:9090 -v /path/to/config.json:/config/config.json ghcr.io/tbxark/mcp-proxy:latest

如果你没有自己的 VPS, 可以使用 claw.cloud 提供的免费容器服务(每个月 $5 额度, GitHub 注册需满 180 天)。

由于 Claw 有容器大小的限制,我们需要使用下面的环境变量,配置 npx 和 uvx 的缓存目录,防止容器崩溃。

UV_CACHE_DIR=/cache/uv
npm_config_cache=/cache/npm

同时在 /cache 路径下挂载 10G 的存储。 配置参考我的配置: 0.5c CPU, 512M 内存, 10G 硬盘。

最终的配置如下:

Claw

配置 MCP Proxy

MCP Proxy 的配置文件需要挂载在 /config/config.json 路径下,完整配置请参考 https://github.com/TBXark/mcp-proxy?tab=readme-ov-file#configurationonfiguration

以下是我的配置,可以参考。

{
    "mcpProxy": {
        "baseURL": "https://mcp.miantiao.me",
        "addr": ":9090",
        "name": "MCP Proxy",
        "version": "1.0.0",
        "options": {
          "panicIfInvalid": false,
          "logEnabled": true,
          "authTokens": [
            "miantiao.me"
          ]
        }
    },
    "mcpServers": {
        "github": {
            "command": "npx",
            "args": [
                "-y",
                "@modelcontextprotocol/server-github"
            ],
            "env": {
                "GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
            }
        },
        "fetch": {
            "command": "uvx",
            "args": [
                "mcp-server-fetch"
            ]
        },
        "amap": {
            "url": "https://mcp.amap.com/sse?key=<YOUR_TOKEN>"
        }
    }
}

调用 MCP proxy

ChatWise 调用 fetch 为例,直接配置 SSE 协议即可。

fetch

是不是很简单,等 ChatWise 出了移动端这样调用也是完全可用的。

ChatWise

stat

使用 Cloudflare Workers 合并音频文件

2025-04-19 19:09:12

最近把 Hacker News 中文播客 改成了双人对话的形式,由于目前的语音合成模型还不能很好地处理双人对话,所以需要把每个人的音频文件拼接起来。

由于项目之前运行在 Cloudflare Workflow 的 Worker Runtime, 众所周知 Worker Runtime 缺少不少 Node.JS 特性,无法调用 C++ 扩展。而且 Cloudflare Container 还没有正式上线,所以只能使用 Browser Rendering 来实现。

合并音频文件一般都使用 FFMpeg 来做,现在 FFMpeg 也可以通过 WASM 在浏览器内运行了。所以大体的技术方案是:

  1. 使用 Worker Binding 来启动浏览器实例
  2. 浏览器打开音频合并页面,合成语音文件,返回 Blob
  3. 将 Blob 返回给 Worker 后存入 R2

整体代码量不多,但是由于 Browser Rendering 只能远程调用,调试比较麻烦。

最终实现代码:

浏览器内音频合并代码

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Audio</title>
  </head>
  <body>
    <script>
      const concatAudioFilesOnBrowser = async (audioFiles) => {
        const script = document.createElement('script')
        script.src = 'https://unpkg.com/@ffmpeg/[email protected]/dist/ffmpeg.min.js'
        document.head.appendChild(script)
        await new Promise((resolve) => (script.onload = resolve))

        const { createFFmpeg, fetchFile } = FFmpeg
        const ffmpeg = createFFmpeg({ log: true })

        await ffmpeg.load()

        // Download and write each file to FFmpeg's virtual file system
        for (const [index, audioFile] of audioFiles.entries()) {
          const audioData = await fetchFile(audioFile)
          ffmpeg.FS('writeFile', `input${index}.mp3`, audioData)
        }

        // Create a file list for ffmpeg concat
        const fileList = audioFiles.map((_, i) => `file 'input${i}.mp3'`).join('\n')
        ffmpeg.FS('writeFile', 'filelist.txt', fileList)

        // Execute FFmpeg command to concatenate files
        await ffmpeg.run(
          '-f',
          'concat',
          '-safe',
          '0',
          '-i',
          'filelist.txt',
          '-c:a',
          'libmp3lame',
          '-q:a',
          '5',
          'output.mp3',
        )

        // Read the output file
        const data = ffmpeg.FS('readFile', 'output.mp3')

        // Create a downloadable link
        const blob = new Blob([data.buffer], { type: 'audio/mp3' })

        // Clean up
        audioFiles.forEach((_, i) => {
          ffmpeg.FS('unlink', `input${i}.mp3`)
        })
        ffmpeg.FS('unlink', 'filelist.txt')
        ffmpeg.FS('unlink', 'output.mp3')

        return blob
      }
    </script>
  </body>
</html>

Worker 调用代码

export async function concatAudioFiles(audioFiles: string[], BROWSER: Fetcher, { workerUrl }: { workerUrl: string }) {
  const browser = await puppeteer.launch(BROWSER)
  const page = await browser.newPage()
  await page.goto(`${workerUrl}/audio`)

  console.info('start concat audio files', audioFiles)
  const fileUrl = await page.evaluate(async (audioFiles) => {
    // 此处 JS 运行在浏览器中
    // @ts-expect-error 浏览器内的对象
    const blob = await concatAudioFilesOnBrowser(audioFiles)

    const result = new Promise((resolve, reject) => {
      const reader = new FileReader()
      reader.onloadend = () => resolve(reader.result)
      reader.onerror = reject
      reader.readAsDataURL(blob)
    })
    return await result
  }, audioFiles) as string

  console.info('concat audio files result', fileUrl.substring(0, 100))

  await browser.close()

  const response = await fetch(fileUrl)
  return await response.blob()
}

const audio = await concatAudioFiles(audioFiles, env.BROWSER, { workerUrl: env.HACKER_NEWS_WORKER_URL })
return new Response(audio)

上面的代码基本是 Cursor 写的,最终效果可以去 Hacker News 代码仓库 看。

stat

使用 AI 让博客排版更上一个台阶

2025-03-23 21:39:55

AI 驱动的博客排版

使用人工智能技术重新定义博客文章的视觉呈现,从简约到精致,从平凡到卓越。

之前 Claude 3.7 发布,用它生成了一个邮箱 App 的设计图,效果还不错。

这周在 Twitter 上看到乔木老师分享的生成网页效果、生成PPT、生成3D教学动画、生成SVG海报的提示词,感觉很有意思。

技术栈更新

这个周末把博客引擎升级了一下,使用 Astro + TailwindCSS + MDX 重构了博客。由于 MDX 有更强的扩展性,所以博客文章正文几乎可以展示任何内容。

于是使用乔木老师分享的提示词,修改了以后,用来生成博客文章的排版。效果出乎意料,从此不再为博客文章的排版而烦恼。

对比图

HackerNews Podcast

修改前:

HackerNews 修改前

修改后:

HackerNews 修改后

RSS Beauty

修改前:

RSS Beauty 修改前

修改后:

RSS Beauty 修改后

Sink

修改前:

Sink 修改前

修改后:

Sink 修改后

URL Longer

修改前:

URL longer 修改前

修改后:

URL longer 修改后

提示词

源自 乔木老师分享

# 生成文章网页

你是一名专业的网页设计师和前端开发专家,对现代 Web 设计趋势和最佳实践有深入理解,尤其擅长创造具有极高审美价值的用户界面。你的设计作品不仅功能完备,而且在视觉上令人惊叹,能够给用户带来强烈的"Aha-moment"体验。

请根据最后提供的内容,设计一个**美观、现代、易读**的"中文"可视化网页。请充分发挥你的专业判断,选择最能体现内容精髓的设计风格、配色方案、排版和布局。

**设计目标:**

* **视觉吸引力:** 创造一个在视觉上令人印象深刻的网页,能够立即吸引用户的注意力,并激发他们的阅读兴趣。
* **可读性:** 确保内容清晰易读,无论在桌面端还是移动端,都能提供舒适的阅读体验。
* **信息传达:** 以一种既美观又高效的方式呈现信息,突出关键内容,引导用户理解核心思想。
* **情感共鸣:** 通过设计激发与内容主题相关的情感(例如,对于励志内容,激发积极向上的情绪;对于严肃内容,营造庄重、专业的氛围)。

**设计指导(请灵活运用,而非严格遵循):**

* **整体风格:**
  * 可以考虑杂志风格、出版物风格,或者其他你认为合适的现代 Web 设计风格。
  * 配色参考 shadcn ui 的 Zinc 主题配色。
  * 目标是创造一个既有信息量,又有视觉吸引力的页面,就像一本精心设计的数字杂志或一篇深度报道。
* **Hero 模块(可选,但强烈建议):**
  * 如果你认为合适,可以设计一个引人注目的 Hero 模块。
  * 它可以包含标题(h2)、副标题(p)、一段引人入胜的引言。
* **排版:**
  * 精心选择字体组合(衬线和无衬线),以提升中文阅读体验。
  * 利用不同的字号、字重、颜色和样式,创建清晰的视觉层次结构。
  * 可以考虑使用一些精致的排版细节(如首字下沉、悬挂标点)来提升整体质感。
  * Tabler icon 中有很多图标,选合适的点缀增加趣味性。 使用实例 `icon-[tabler--名称]`
* **配色方案:**
  * 选择一套既和谐又具有视觉冲击力的配色方案。
  * 考虑使用高对比度的颜色组合来突出重要元素。
  * 可以探索渐变、阴影等效果来增加视觉深度。
* **布局:**
  * 使用基于网格的布局系统来组织页面元素。
  * 充分利用负空间(留白),创造视觉平衡和呼吸感。
  * 可以考虑使用卡片、分割线、图标等视觉元素来分隔和组织内容。
* **调性:**整体风格精致, 营造一种高级感。

**技术规范:**

* 使用 tailwindCSS 定义样式。
* 不使用 JS , HTML 和 CSS 优先。
* 只生成正文区域,网页已经使用 `prose` 类包裹了, 可以使用 `not-prose` 突破限制。
* 实现完整的深色/浅色模式切换功能。
* 代码结构清晰、语义化,包含适当的注释。
* 实现完整的响应式,必须在所有设备上(手机、平板、桌面)完美展示。

**额外加分项:**

* **微交互:** 添加微妙而有意义的微交互效果来提升用户体验(例如,按钮悬停效果、卡片悬停效果、页面滚动效果)。
* **补充信息:** 可以主动搜索并补充其他重要信息或模块(例如,关键概念的解释、相关人物的介绍等),以增强用户对内容的理解。

**输出要求:**

* 输出一个独立的 MDX 文件,生成的语法符合 MDX 规范和 JSX 规范。
* 修改范围不要超出 mdx 文件。
* 不要在正文中出现标签,发布时间相关的信息。
* 外链增加 `nofollow`, 在新窗口打开, 保留 `title` 属性。
* 代码块不做任何修改,依旧使用 md 格式。
* 确保代码符合 W3C 标准,没有错误或警告。

请你像一个真正的设计师一样思考,充分发挥你的专业技能和创造力,打造一个令人惊艳的网页!

待处理内容:@miantiao_me
stat

用播客的方式听 Hacker News

2025-03-03 20:40:37

用播客的方式听 Hacker News

将每日 Hacker News 热门内容转换为中文播客,让你随时随地收听科技资讯

项目背景

Hacker News 是我一直关注的重要资讯来源,它能持续提供新奇有趣的极客资讯。之前我每天都会花半小时左右的时间来浏览。

过年期间,我注意到 DeepSeek 非常火,就尝试使用 Cloudflare Workflow 编写了一个工作流。但生成的内容都很短,而且接口也不稳定。后来尝试 GPT 4.0 系列模型,效果也不理想,于是就暂时搁置了。

Gemini 2.0 发布后,我尝试了一下,发现生成文章的效果还不错。于是我便开发了一个 Web 界面,并加入了 RSS 订阅功能,这样就可以在上班路上用泛用型播客 App 收听 Hacker News 的资讯了。

Hacker News 播客项目预览

项目预览

主要特性

  • 自动抓取 Hacker News 每日热门文章
  • 使用 AI 智能总结文章内容和评论
  • 通过 Edge TTS 生成中文播报
  • 支持网页和播客 App 收听
  • 每日自动更新
  • 提供文章摘要和完整播报文本

技术栈

  • Next.js 应用框架
  • Cloudflare Workers 部署和运行环境
  • Edge TTS 语音合成
  • OpenAI API 内容生成
  • Tailwind CSS 样式处理
  • shadcn UI 组件库

工作流程

  1. 1

    定时抓取 Hacker News 热门文章

    每日自动收集 Hacker News 上最受欢迎的帖子

  2. 2

    使用 AI 生成中文摘要和播报文稿

    通过 Gemini 2.0 AI 将英文内容智能翻译并总结为中文

  3. 3

    通过 Edge TTS 转换为音频

    将生成的文本转换为自然流畅的语音播报

  4. 4

    存储到 Cloudflare R2 和 KV

    将生成的内容和音频存储在高效、低成本的云端存储系统中

  5. 5

    通过 RSS feed 和网页提供访问

    用户可以通过网页浏览或在任何播客应用中订阅收听

未来计划

目前 TTS 使用的是 Edge TTS,只有一个女声。理想情况下,使用男声和女声进行对话的形式可能会更好。豆包的 TTS 音色很不错,但它是收费的。等后续有时间,我会考虑改进这部分。

推荐工具

最后,推荐一下 Cloudflare Workflow,一个很棒的 Workflow 运行平台。

Cloudflare Workflow
Cloudflare Workflow
高效、低成本的云函数工作流平台
stat

RSS.Beauty - 让 RSS 变漂亮!

2024-12-31 22:50:48

RSS.Beauty

让 RSS 订阅源焕发新生的现代工具

拖了快半年的工具终于做完了。

RSS.Beauty 是一个基于 XSLT 技术的 RSS 美化工具,可以将普通的 RSS/Atom 订阅源转换成美观的阅读界面。让信息流变得更加优雅,阅读体验更加舒适。

技术栈

主要特性

精美的阅读界面

完全重新设计的视觉体验,让RSS阅读变得赏心悦目

支持 RSS 2.0 和 Atom 1.0

全面兼容主流的RSS标准,无需担心格式问题

响应式设计

完美适配各种设备,让移动端阅读同样舒适

一键订阅

快速添加到主流RSS阅读器,简化订阅流程

支持自部署

可部署到自己的服务器,保持完全的控制权

快速开始

访问 RSS.Beauty 并输入任意 RSS 订阅源链接即可体验。

开源项目

这是一个开源项目,欢迎贡献代码或提出建议。

RSS.Beauty GitHub 预览
开始使用 RSS.Beauty

让您的 RSS 阅读体验焕然一新

体验 RSS.Beauty 带来的全新阅读感受,将枯燥的信息流变成精美的阅读界面。

立即访问 RSS.Beauty

stat

在浏览器中轻松运行 Python 程序

2024-12-21 17:57:34

最近,微软开源了一个名为 MarkItDown 的程序,可以将 Office 文件转换为 Markdown 格式。这个项目一经发布就迅速登上了 GitHub 热门榜。

然而,由于 MarkItDown 是一个 Python 程序,对于非技术用户来说使用起来可能有些困难。为了解决这个问题,我想到了利用 WebAssembly 技术在浏览器中直接运行 Python 代码。

在浏览器内运行 Python 的开源程序是 Pyodide,使用 WebAssembly 移植了 CPython,所以 Python 的语法都是支持的。 Cloudflare 的 Python Worker 也使用的 Pyodide。

Pyodide 是 CPython 的一个移植版本,用于 WebAssembly/Emscripten。

Pyodide 使得在浏览器中使用 micropip 安装和运行 Python 包成为可能。任何在 PyPi 上有可用 wheel 文件的纯 Python 包都被支持。

许多具有 C 扩展的包也已被移植以供 Pyodide 使用。这些包括许多通用包,如 regex、PyYAML、lxml,以及包括 NumPy、pandas、SciPy、Matplotlib 和 scikit-learn 在内的科学 Python 包。Pyodide 配备了强大的 JavaScript ⟺ Python 外部函数接口,使得您可以在代码中自由地混合这两种语言,几乎没有摩擦。这包括对错误处理、async/await 的全面支持,以及更多功能。

在浏览器中使用时,Python 可以完全访问 Web API。

尝试了一下运行 MarkItDown 没想到异常的顺利,看来 WebAssembly 真的是浏览器的未来。

遇到的主要挑战和解决方案:

  1. 文件传输问题:如何将用户选择的文件传递给 Worker 中的 Python 运行时?

    • 解决方案:利用 Pyodide 提供的方案,将浏览器文件转换为 ArrayBuffer,然后写入 Emscripten 文件系统的本地缓存。
  2. 依赖安装问题:PyPI 在中国大陆访问受限。

最终,我们成功实现了一个完全运行在浏览器中的 MarkItDown 工具。欢迎访问 Office File to Markdown 进行体验。

Office File to Markdown

最后放出一下 Worker 中运行 Python 的核心代码:

importScripts('https://testingcf.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js')

// npmmirror 支持 pyodide ,但是不支持 pyodide 下的 zip 包
// importScripts('https://registry.npmmirror.com/pyodide/0.26.4/files/pyodide.js')

async function loadPyodideAndPackages() {
  const pyodide = await loadPyodide()
  globalThis.pyodide = pyodide

  await pyodide.loadPackage('micropip')

  const micropip = pyodide.pyimport('micropip')

  // 需要支持 PEP 691 和跨域, 目前 tuna 支持 PEP 691,但不支持跨域 https://github.com/tuna/issues/issues/2092
  // micropip.set_index_urls([
  //   'https://pypi.your.domains/pypi/simple',
  // ])

  await micropip.install('markitdown==0.0.1a2')
}

const pyodideReadyPromise = loadPyodideAndPackages()

globalThis.onmessage = async (event) => {
  await pyodideReadyPromise

  const file = event.data
  try {
    console.log('file', file)
    const startTime = Date.now()
    globalThis.pyodide.FS.writeFile(`/${file.filename}`, file.buffer)

    await globalThis.pyodide.runPythonAsync(`
from markitdown import MarkItDown

markitdown = MarkItDown()

result = markitdown.convert("/${file.filename}")
print(result.text_content)

with open("/${file.filename}.md", "w") as file:
  file.write(result.text_content)
`)
    globalThis.postMessage({
      filename: `${file.filename}.md`,
      content: globalThis.pyodide.FS.readFile(`/${file.filename}.md`, { encoding: 'utf8' }),
      time: Date.now() - startTime,
    })
  }
  catch (error) {
    globalThis.postMessage({ error: error.message || 'convert error', filename: file.filename })
  }
}
stat