把任何网页变成 LLM-ready 纯文本——9 层自适应降级链,覆盖 Canvas 绑图、字体加密、验证码、文件提取、深度爬取、移动端、微信小程序等全场景。Any webpage → clean, structured text with adaptive multi-layer fallback: Canvas hooking, font decryption, captcha solving, Vision LLM, deep crawling, mobile extraction, and more.
Project description
Universal Extractor
把任何网页变成干净、结构化、LLM-ready 的纯文本。 从静态博客到 Canvas 绑图应用,从字体加密到滑动验证码——9 层自适应降级链,自动找到最优路径。
⚠️ 使用前必读:本项目包含验证码破解、字体解密、MITM 抓包、微信小程序解包等高风险功能。不当使用可能导致违法。请先阅读 🛡️ 法律风险警告。
为什么选择 Universal Extractor?
Scrapling、Jina Reader、Firecrawl 都在 DOM 能直接拿到文字 时才有效。但真实世界的网页远不止如此:
| 场景 | 其他工具 | Universal Extractor |
|---|---|---|
| 静态博客 / 文档站 | ✅ | ✅ Jina 快扫(最快路径) |
| SPA(React / Vue) | ✅ | ✅ Browser DOM |
| WPS / 飞书 / 腾讯文档(Canvas 绑图) | ❌ 空壳 | ✅ Canvas Hook → OCR → Vision LLM |
| 自定义字体加密(起点 / 天眼查 / 大众点评) | ❌ 乱码 | ✅ PUA 检测 → fontTools 解密 |
| 滑动验证码(GeeTest / DataDome) | ⚠️ 需付费 API | ✅ OpenCV 本地求解(免费) |
| 文字顺序验证码 | ❌ 无人支持 | ✅ ddddocr 检测 + 顺序推断 |
| Cloudflare / 反爬保护 | ⚠️ 不稳定 | ✅ TLS 指纹伪装 + Turnstile 求解 |
| 微信公众号 / 小程序 | ❌ | ✅ MITM 代理 + wxapkg 解包 |
| 移动端 App 内容 | ❌ | ✅ ADB 截图 + OCR 滚动提取 |
| PDF / Word / Excel / PPT | ❌ | ✅ → Markdown |
核心思路:不是单一策略,而是一个自适应降级链。URL 一进来先分类,自动算出每层策略的 quality_score,高分先跑,无效层跳过。同一域名第二次抓取时,策略缓存直接命中最优层——越用越快。
架构
Pipeline: Search → Verify → Extract (9-layer fallback) → Validate → Return
Layer ① Jina Reader ← 静态页最快(SPA / Canvas 自动跳过)
Layer ② curl_cffi HTTP ← GBK 编码站优先
Layer ③ Browser DOM ← SPA 必用(预收集 Canvas + CDP 数据复用)
Layer ④ Canvas Hook ← 拦截 Canvas fillText / strokeText
Layer ⑤ CDP Heap Scan ← 扫 __NEXT_DATA__ / __NUXT__ 隐藏数据
Layer ⑥ Screenshot OCR ← Canvas 应用主力(ddddocr + Tesseract)
Layer ⑦ Vision LLM ← 终极兜底(GPT-4o / Claude / Qwen-VL)
Layer ⑧ Captcha Slider ← 检测到滑动验证码时自动求解
Layer ⑨ Captcha Font ← 检测到自定义字体时自动解密
自适应调度:每层不再盲目串行。URL 分类后自动算分排序——比如 kdocs.cn 这样的 Canvas 应用,Jina / curl 自动跳过,OCR 排到最前。
快速开始
安装
# 基础安装
pip install universal-extractor
# 验证码本地求解(推荐)
pip install universal-extractor[captcha]
# 字体解密(对付起点 / 天眼查等)
pip install universal-extractor[captcha-font]
# 文件提取(PDF / Office / EPUB)
pip install universal-extractor[files]
# REST API 服务
pip install universal-extractor[api]
# 全部可选依赖
pip install universal-extractor[captcha-font,openai,anthropic,files,api]
配置 Vision 后端(可选)
至少设置一个环境变量以启用 Layer ⑥⑦ 的云端 OCR:
| 变量 | 后端 | 默认模型 | 模型覆盖变量 |
|---|---|---|---|
OPENAI_API_KEY |
GPT-4o-mini | gpt-4o-mini |
VISION_OPENAI_MODEL |
ANTHROPIC_API_KEY |
Claude Sonnet | claude-sonnet-4-6 |
VISION_ANTHROPIC_MODEL |
DASHSCOPE_API_KEY |
Qwen-VL-Max | qwen-vl-max |
VISION_QWEN_MODEL |
DEEPSEEK_API_KEY |
DeepSeek | deepseek-chat |
VISION_DEEPSEEK_MODEL |
不配 Vision 后端也能用——前 5 层不需要任何 API Key。
最简用法
from universal_extractor import UniversalExtractor
ue = UniversalExtractor(headless=True)
text = ue.extract("https://example.com/article")
print(text)
功能全景
🌐 网页提取
from universal_extractor import Pipeline, PipelineConfig
pipeline = Pipeline(PipelineConfig(
headless=True,
min_completeness=0.5,
search_backends=["duckduckgo", "brave"],
enable_cross_validation=True,
))
result = pipeline.run("三体 小说 全文")
print(result.text) # 提取的正文
print(result.score) # 质量评分 (0.0 ~ 1.0)
print(result.winning_stage) # 命中的阶段名称
📄 文件提取
from universal_extractor import UniversalExtractor
ue = UniversalExtractor()
# PDF / Word / Excel / PPT / EPUB / 图片 / 音频 → Markdown
text = ue.extract_file("report.pdf")
text = ue.extract_auto("presentation.pptx") # 自动判断文件 vs URL
🔍 搜索 + 提取
from universal_extractor import search_urls, search_compare
# 多引擎搜索(DuckDuckGo / Brave / Exa / SearXNG)
urls = search_urls("机器学习入门", max_results=10)
# 搜索 + 对比交叉验证
results = search_compare("新能源汽车政策", backends=["duckduckgo", "brave"])
🕷️ 深度爬取
from universal_extractor import Crawler, CrawlConfig
c = Crawler(headless=True)
# BFS 广度优先(文档站)
c.crawl("https://docs.example.com/", "docs.md", config=CrawlConfig(
strategy="bfs", max_depth=2, max_pages=200,
))
# 链接评分模式(按查询词相关性排序)
c.crawl("https://example.com/", "out.md", config=CrawlConfig(
strategy="best_first",
score_links=True,
score_query="machine learning",
))
# SQLite 持久化队列(进程重启不丢进度)
c.crawl_with_queue("https://example.com/", "out.md")
# 异步流式
async for label, url, data in c.crawl_stream(start_url):
print(f"[{label}] → {len(data['text'])} chars")
📊 结构化提取
from universal_extractor import (
StructuredExtractor,
SchemaExtractionStrategy,
LLMExtractionStrategy,
extract_tables,
)
# Schema 定义 → 结构化输出
extractor = StructuredExtractor()
result = extractor.extract(
url="https://books.toscrape.com/",
strategy=SchemaExtractionStrategy(schema={"title": "str", "price": "float"}),
)
# LLM 智能提取
result = extractor.extract(
url="https://example.com/article",
strategy=LLMExtractionStrategy(
instruction="提取文章中提到的所有人名和职位",
api_key="sk-...",
),
)
# 表格提取(支持分块 LLM 处理超大表格)
tables = extract_tables("https://example.com/data-table")
for t in tables:
print(t.headers, len(t.rows))
🧩 示例驱动提取
from universal_extractor import extract_by_example
# 给几个例子,自动找出规律批量爬取
results = extract_by_example(
url="https://books.toscrape.com/",
examples=["A Light in the Attic", "£51.77"],
)
# → [{"title": "...", "price": "..."}, ...]
📱 移动端提取
from universal_extractor import MobileExtractor
m = MobileExtractor()
# 当前屏幕截图 + OCR
text = m.extract_screen()
# 打开 App → 自动滚动 → 逐屏 OCR → 去重合并
text = m.extract_app(
package="com.example.news",
scroll_count=5,
wait_after_scroll=1.5,
)
# 自定义交互脚本
m.extract_interactive([
{"action": "start_app", "package": "com.example.app"},
{"action": "wait", "seconds": 2},
{"action": "tap", "x": 500, "y": 800},
{"action": "screenshot"},
])
前置条件:Android 设备 + USB 调试 + ADB。不需要 root。
💬 微信小程序 & 公众号
微信公众号(标准 H5,直接提取):
from universal_extractor import WechatMPExtractor
wx = WechatMPExtractor()
article = wx.extract_url("https://mp.weixin.qq.com/s/abc123")
print(article.title, article.author, article.content)
MITM 代理捕获(拦截小程序 API 请求):
pip install universal-extractor[mitm]
ue mitm --ca-export # 导出 CA 证书
ue mitm --setup-guide # 打印代理配置指南
ue mitm --timeout 60 # 捕获微信相关域名
wxapkg 解包(从 PC 微信缓存提取小程序包):
ue wxapkg list # 列出缓存的小程序包
ue wxapkg analyze <path> # 分析 app.json 元数据(无需解包工具)
ue wxapkg unpack <path> -o output/ # 完整解包
🧠 验证码求解
from universal_extractor.captcha import SliderCaptchaSolver, FontCaptchaSolver
# 滑动验证码(OpenCV Canny 边缘检测 + 模板匹配)
slider = SliderCaptchaSolver(captcha_type="geetest")
result = slider.solve(
background_bytes=open("bg.png", "rb").read(),
slider_bytes=open("slider.png", "rb").read(),
)
if result.solved:
print(f"距离: {result.distance}px, 轨迹: {len(result.track)} 点")
# 字体解密(fontTools cmap 表解析 + ddddocr 字模 OCR)
font = FontCaptchaSolver()
mapping = font.crack_from_page(html, page_url="https://...")
decoded = mapping.decode(pua_encrypted_text) #  → "第"
# 文字顺序验证码
result = solver.solve_text_order(
image_bytes=open("captcha.png", "rb").read(),
prompt_text="请依次点击:请 点 击 文 字",
)
# result.clicks → [(120, 45), (310, 80), ...] 按正确顺序排列
| 方案 | 技术栈 | 特点 |
|---|---|---|
| 滑动验证码 | OpenCV Canny + TM_CCOEFF_NORMED 模板匹配 + 4 阶段贝塞尔轨迹 | 本地免费 |
| 字体解密 | fontTools cmap 表 + ddddocr 字模 OCR + PUA 反向映射 | 支持起点 / 天眼查等 |
| 文字顺序验证码 | ddddocr ONNX 目标检测 + 坐标排序 | 本地免费 |
| 云端降级 | CapSolver API | 保底备用 |
🔊 认证 & 登录态
from universal_extractor import AuthManager, Credential
am = AuthManager()
am.save_credential("example.com", Credential(
username="user@example.com",
password="s3cret",
))
# 自动登录 + 保持会话 + 过期自动续期
if not am.session_valid("example.com"):
am.refresh_session("example.com")
🔌 中间件 & 信号
from universal_extractor import Pipeline, signal
# 注册自定义中间件
Pipeline.register_middleware(MyRetryMiddleware())
Pipeline.register_pipeline(MyExportPipeline())
# 事件监听
signal.on("extraction_completed", lambda result: print(result.score))
signal.on("captcha_detected", lambda url, t: send_alert(f"Captcha at {url}"))
# 开发模式(首次缓存 HTTP 响应,后续回放)
pipeline = Pipeline(PipelineConfig(dev_mode=True))
🌍 内容过滤
from universal_extractor import (
PruningContentFilter,
BM25ContentFilter,
LLMContentFilter,
filter_chain,
)
# 三层过滤链:Pruning(去噪)→ BM25(相关性排序)→ LLM(精筛)
filtered = filter_chain(raw_text, query="机器学习", threshold=0.3)
CLI
# 网页提取
ue extract https://example.com/article
ue extract https://example.com --format json
# 文件提取
ue file report.pdf
ue file presentation.pptx --format md -o output.md
# 搜索 + 提取
ue search "机器学习入门"
ue run "三体 小说"
# 深度爬取(BFS / DFS / BestFirst)
ue crawl https://docs.example.com/ -o docs.md --strategy bfs --max-depth 2
ue crawl https://novel.com/catalog -o novel.md --score-links --score-query "主角"
# 批量处理
ue batch urls.txt --format json -o results.json
# 启动 REST API
ue serve # http://127.0.0.1:11235
ue serve --port 8080 --reload
# 移动端提取
ue mobile # 当前屏幕截图 + OCR
ue mobile --package com.example # 打开 App + 滚动提取
# MITM 代理捕获
ue mitm --pattern "api.example.com" --timeout 30
ue mitm --ca-export # 导出 CA 证书
ue mitm --setup-guide # 打印代理配置指南
# 微信小程序
ue wechat "https://mp.weixin.qq.com/s/abc123"
ue wxapkg list
ue wxapkg unpack <path> -o output/
# 通用标志
ue extract URL --dev-mode # 开发模式(缓存 HTTP 响应)
ue extract URL --format json # JSON 输出
ue extract URL --verbose # 详细日志
REST API Server
pip install universal-extractor[api]
ue serve
POST /v1/extract # 提取 URL 或文件
POST /v1/crawl # 深度爬取
GET /v1/task/{id} # 查询任务状态
GET /v1/health # 健康检查
浏览器打开 http://127.0.0.1:11235/docs 查看交互式 Swagger 文档。
MCP Server
让 Claude / Cursor 直接调用提取能力:
{
"mcpServers": {
"universal-extractor": {
"command": "python",
"args": ["-m", "UniversalExtractor.mcp_server"]
}
}
}
Plugin 系统
第三方包只需在 pyproject.toml 声明 entry point 即可注册自定义提取阶段:
[project.entry-points."universal_extractor.stages"]
my_stage = "my_package.extraction:MyCustomStage"
自定义 Stage 实现:
from UniversalExtractor.pipeline import ExtractionStage, StageContext, PipelineStageResult
class MyCustomStage(ExtractionStage):
stage_name = "my_custom_stage"
stage_index = 9
description = "My custom extraction logic"
def can_handle(self, url: str, context: StageContext) -> bool:
return True
def quality_score(self, url: str, context: StageContext) -> int:
return 50 # 0–100,高分优先执行
def extract(self, url: str, context: StageContext) -> PipelineStageResult:
result = PipelineStageResult(stage_name=self.stage_name, stage_index=self.stage_index)
# ... 你的提取逻辑 ...
return result
安装你的包后,Pipeline 启动时自动发现并注册。
模块结构
UniversalExtractor/
├── __init__.py # 懒加载公开 API(100+ 导出符号)
├── extractor.py # URL + 文件自动提取入口
├── pipeline/ # Pipeline 编排引擎
│ ├── pipeline.py # 9 层降级编排 + 中间件 + 信号
│ ├── config.py # 全局配置
│ ├── stage.py # ExtractionStage ABC + StageRegistry
│ ├── middleware.py # 下载中间件链 + 后处理管道
│ ├── context.py # Stage 共享上下文
│ ├── result.py # Pipeline 结果数据类
│ └── stages/ # 提取阶段实现
│ ├── jina.py # ① Jina Reader 快扫
│ ├── curl.py # ② curl_cffi HTTP
│ ├── browser.py # ③ Playwright 浏览器 DOM
│ ├── canvas.py # ④ Canvas fillText Hook
│ ├── cdp.py # ⑤ CDP Heap Scan
│ ├── ocr.py # ⑥ 截图 OCR
│ ├── vision.py # ⑦ Vision LLM
│ ├── captcha_slider.py # ⑧ 滑动验证码求解
│ ├── captcha_font.py # ⑨ 字体解密
│ └── browser_agent.py # ⑩ AI 代理兜底
├── captcha/ # 验证码求解子包
│ ├── slider_solver.py # 滑动验证码(OpenCV + ddddocr)
│ └── font_solver.py # 字体解密(fontTools + ddddocr)
├── extraction/ # 结构化提取
│ ├── structured.py # Schema / Regex / LLM 策略
│ ├── table_extraction.py # 表格提取(含分块 LLM)
│ ├── llm_strategy.py # LLM 提取策略
│ └── schema_strategy.py # Schema 提取策略
├── ocr_providers.py # Vision 后端(GPT-4o / Claude / Qwen / DeepSeek / Tesseract)
├── canvas_hook.py # Canvas fillText / strokeText 拦截 JS
├── screenshot.py # 截帧 / 感知哈希去重 / 垂直拼接
├── scrolling.py # 虚拟滚动(4 层降级 + VirtualScrollConfig)
├── classifier.py # URL 分类 + 7 种能力标签
├── strategy.py # 域名策略缓存
├── adaptive.py # SQLite 自适应学习
├── completeness.py # 7 因子连续完整性评分
├── cross_validator.py # 多源交叉验证
├── search.py # 多引擎搜索(DuckDuckGo / Brave / Exa / SearXNG)
├── filter.py # 3 层内容过滤器(Pruning / BM25 / LLM)
├── crawler.py # 深度爬取(BFS / DFS / BestFirst + 链接评分)
├── request_queue.py # SQLite 持久化请求队列
├── signals.py # 事件总线(发布/订阅)
├── file_ingest.py # 文件 → Markdown(PDF / Word / Excel / PPT / EPUB)
├── auth.py # 登录态管理(加密存储 + 自动续期)
├── mobile.py # 移动端提取(ADB 截图 + OCR)
├── mitm_capture.py # MITM 代理 API 捕获
├── wechat_mp.py # 微信小程序 / 公众号提取
├── wxapkg.py # 微信小程序包 (.wxapkg) 扫描与解包
├── example_api.py # 示例驱动自动提取
├── api_server.py # REST API Server(FastAPI)
├── mcp_server.py # MCP Server
├── http_client.py # HTTP 客户端(curl_cffi + dev mode)
├── rate_limiter.py # 并发安全限速器
├── proxy_manager.py # 代理管理器
├── session_manager.py # 会话管理器
├── observability.py # 性能指标收集
├── weblens.py # 搜 + 筛 + 抓 编排引擎
├── jd_engine.py # 全平台 JD 结构化引擎
└── cli.py # 命令行接口(11 个子命令)
提取能力与覆盖率
| 网站类型 | 生效层 | 提取率 |
|---|---|---|
| 博客 / 新闻 / 文档 | ① DOM | ~98% |
| SPA(React / Vue) | ③ Browser DOM | ~95% |
| 反爬保护站点 | ③ StealthyFetcher + Cloudflare 求解 | ~95% |
| 富文本编辑器 | ③ DOM(6 种编辑器选择器) | ~95% |
| Shadow DOM / iframe | ③ deepText 递归 | ~95% |
| 自定义字体加密(起点 / 天眼查等) | ⑨ FontSolver → ③ DOM 重新提取 | ~90% |
| 普通 Canvas 页面 | ④ Canvas Hook(rAF 轮询) | ~90% |
| GeeTest / DataDome 验证码 | ⑧ CaptchaSlider 求解 → ③ 重新提取 | ~85% |
| Canvas 流式文档(WPS / 飞书 / 腾讯文档) | ⑤⑥⑦ OCR + Vision LLM | ~70% |
同类工具对比
| Firecrawl | Scrapling | Crawl4AI | Universal Extractor | |
|---|---|---|---|---|
| 许可协议 | AGPL-3.0 | BSD-3 | Apache-2.0 | MIT |
| Canvas 绑图页面 | ❌ | ❌ | ❌ | ✅ |
| 字体加密解密 | ❌ | ❌ | ❌ | ✅ |
| 滑动验证码本地求解 | ❌ | ❌ | ❌ | ✅ |
| 文字顺序验证码 | ❌ | ❌ | ❌ | ✅ |
| 文件提取(PDF / Office) | ❌ | ❌ | ❌ | ✅ |
| 深度爬取(BFS / DFS / BestFirst) | ✅ | ❌ | ✅ | ✅ |
| 链接相关性评分 | ❌ | ❌ | ✅ | ✅ |
| 移动端 App 提取 | ❌ | ❌ | ❌ | ✅ |
| 微信小程序提取 | ❌ | ❌ | ❌ | ✅ |
| MITM API 捕获 | ❌ | ❌ | ❌ | ✅ |
| 登录态管理 | ❌ | ❌ | ❌ | ✅ |
| 示例驱动自动提取 | ❌ | ❌ | ❌ | ✅ |
| 中间件链 / 事件系统 | ❌ | ❌ | ❌ | ✅ |
| REST API Server | ✅ | ❌ | ✅ | ✅ |
| MCP Server | ✅ | ✅ | ❌ | ✅ |
| 策略自适应学习 | ❌ | ❌ | ❌ | ✅ |
| 部署方式 | Docker / 云 | pip install | pip install | pip install |
| 结构化提取(Schema / LLM) | ✅ | ❌ | ✅ | ✅ |
| 表格提取(分块 LLM) | ❌ | ❌ | ❌ | ✅ |
| 多引擎搜索 + 交叉验证 | ❌ | ❌ | ❌ | ✅ |
技术栈
| 组件 | 用途 |
|---|---|
| Scrapling 0.4.9 | 浏览器自动化 + TLS 指纹伪装 + Cloudflare Turnstile 求解 |
| Playwright CDP | Chrome DevTools 底层注入 / 内存扫描 / 滚动控制 |
| OpenCV | Canny 边缘检测 + 模板匹配(滑动验证码求解) |
| ddddocr | ONNX 中文 OCR + 目标检测(字体解密 + 顺序验证码) |
| fontTools | TrueType / OpenType cmap 表解析(字体解密) |
| Multi-Vision-LLM | GPT-4o / Claude / Qwen-VL / DeepSeek 云端 OCR |
| Tesseract | 本地 OCR 保底 |
| curl_cffi | TLS 指纹伪装 HTTP 请求 |
| FastAPI | REST API Server |
| SQLite | 策略缓存 + 请求队列持久化 |
使用伦理与责任声明
本项目定位为本地网页内容提取研究工具,设计初衷面向:
- 🟢 合法合规场景:个人学习研究、自有网站内容管理、已获授权的数据采集、学术研究、SEO 分析
- 🟢 合理使用:遵循目标网站
robots.txt,控制请求频率(内置 RateLimiter),不绕过付费墙(paywall) - 🟢 本地优先:所有提取默认在本地完成,不上传用户数据到第三方(除非用户显式配置 Vision LLM 后端)
责任边界
- 本项目不是反爬虫对抗工具。Captcha 求解和 TLS 伪装能力的存在是为了在合法授权场景下保持提取可用性
- 使用者应遵守目标网站的**服务条款(ToS)**和所在地法律法规
- 使用者对自身的使用行为承担全部责任,本项目作者不对任何滥用行为负责
- 如果你不确定自己的使用场景是否合规,请咨询法律专业人士
技术中性声明
Canvas Hook、字体解密、滑动验证码求解等技术属于通用的浏览器自动化和图像处理技术,在以下合法场景中均有应用:
- 无障碍访问(将 Canvas 渲染的文字转为屏幕阅读器可读文本)
- 网页存档与数字保存
- 自动化测试与质量保证
- 内容聚合与搜索引擎索引
技术本身是中性的——如何使用取决于使用者。
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file universal_extractor-0.2.0.tar.gz.
File metadata
- Download URL: universal_extractor-0.2.0.tar.gz
- Upload date:
- Size: 232.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
62cb06ea562e89364461e6bd5e7b60e2dd5f24908e2720b242280781e4512f00
|
|
| MD5 |
7edc3e66d3a20bee3fe970206f4033d6
|
|
| BLAKE2b-256 |
d8aa6297befc3c16ef913d9eecdf5f257211d39055dbd3bccb3c4189a5366e41
|
File details
Details for the file universal_extractor-0.2.0-py3-none-any.whl.
File metadata
- Download URL: universal_extractor-0.2.0-py3-none-any.whl
- Upload date:
- Size: 222.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
68e97126edbf55602f3e9dfb6bf7cab51905b2e344ef59a45bfaf94b97e830dc
|
|
| MD5 |
fada8a23e3c0a6bf3a287e98b6e5ced1
|
|
| BLAKE2b-256 |
6d7d31a07b087ce9b1b41ed792d4461491a2b7257d6ca53ad194b1c99a95a4f4
|