├── .env.example ├── .eslintrc ├── .github └── workflows │ ├── deploy-github-pages.yml │ └── deploy-to-vercel.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.assets └── image-20250413185708120.png ├── README.md ├── babel.config.js ├── docs ├── TODO.md ├── challenges │ ├── 91porn视频播放链接加密.yml │ ├── AkamaiBot管理器.yml │ ├── Barracuda WAF.yml │ ├── Citrix Bot Management.yml │ ├── Cloudbric WAF.yml │ ├── Cloudflare5秒盾.yml │ ├── DataDome.yml │ ├── F5Shape安全.yml │ ├── Fastly Bot Management.yml │ ├── Google reCAPTCHA v3.yml │ ├── ImpervaBot防护.yml │ ├── Kasada.yml │ ├── LeetCode.yml │ ├── Palo Alto Prisma Cloud.yml │ ├── PerimeterX.yml │ ├── README.md │ ├── Radware Bot Manager.yml │ ├── Sophos Web App Firewall.yml │ ├── Sucuri Firewall.yml │ ├── hCaptcha.yml │ ├── jsjiami JS最牛加密-V7.yml │ ├── kaitorishouten买取商店价格加密.yml │ ├── meta.yml │ ├── sojson加密结果逆向.yml │ ├── 中国五矿集团有限公司供应链管理平台.yml │ ├── 佛冈通请求头x-itouchtv-ca-key加密.yml │ ├── 力哥爱英语开发者工具打开检测.yml │ ├── 加速乐.yml │ ├── 商丘市教育体育局鼠标移动设置Cookie.yml │ ├── 国家标准全文公开系统.yml │ ├── 小报童请求头sign.yml │ ├── 微店登录参数分析.yml │ ├── 成都市中小学教师继续教育网登录密码加密.yml │ ├── 爱给网站音频播放链接加密.yml │ ├── 瑞数.yml │ ├── 网易易盾.yml │ ├── 腾讯天御验证码.yml │ └── 阿里滑块.yml └── images │ ├── logo.png │ └── logo文生图提示词.md ├── eslint.config.js ├── favicon.png ├── index.html ├── package-lock.json ├── package.json ├── public ├── CC11001100-wechat-qrcode.png ├── favicon.png └── index.html ├── src ├── App.css ├── App.tsx ├── assets │ ├── CC11001100-wechat-qrcode.png │ ├── favicon.png │ └── logo.png ├── components │ ├── AboutPage │ │ ├── AboutCard.tsx │ │ ├── ContactCard.tsx │ │ ├── index.tsx │ │ └── styles.ts │ ├── ChallengeContributePage │ │ ├── components │ │ │ ├── BackupHistoryModal.tsx │ │ │ ├── Base64UrlInput.tsx │ │ │ ├── BasicInfo.tsx │ │ │ ├── DescriptionFields.tsx │ │ │ ├── DifficultySelector.tsx │ │ │ ├── FormContainer.tsx │ │ │ ├── FormHeader.tsx │ │ │ ├── ResponsiveContainer.tsx │ │ │ ├── ScrollButtons.tsx │ │ │ ├── SolutionForm.tsx │ │ │ ├── SolutionItem.tsx │ │ │ ├── SolutionsSection.tsx │ │ │ ├── TagsSelector.tsx │ │ │ ├── UrlInput.tsx │ │ │ ├── YamlActionButtons.tsx │ │ │ ├── YamlImportSection.tsx │ │ │ ├── YamlPreviewContent.tsx │ │ │ ├── YamlPreviewSection.tsx │ │ │ └── yaml-import │ │ │ │ ├── FileImportTab.tsx │ │ │ │ ├── TextImportTab.tsx │ │ │ │ └── UrlImportTab.tsx │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useAllTags.ts │ │ │ ├── useAsyncOperation.ts │ │ │ ├── useBase64UrlEncoder.d.ts │ │ │ ├── useBase64UrlEncoder.ts │ │ │ ├── useEventListener.ts │ │ │ ├── useFormPersistence.ts │ │ │ ├── useFormScrolling.ts │ │ │ ├── useFormStyles.ts │ │ │ ├── useFormValidator.ts │ │ │ ├── useMarkdownEditor.ts │ │ │ ├── useTagsSelector.ts │ │ │ ├── useYamlBuilder.ts │ │ │ ├── useYamlGeneration.ts │ │ │ ├── useYamlImport.ts │ │ │ └── useYamlParser.ts │ │ ├── index.tsx │ │ ├── styles.ts │ │ ├── types.ts │ │ └── utils │ │ │ ├── i18nUtils.ts │ │ │ ├── markdownStyleUtils.ts │ │ │ ├── textUtils.ts │ │ │ ├── urlUtils.ts │ │ │ ├── validators.ts │ │ │ ├── yamlChallengeUpdater.ts │ │ │ ├── yamlCommentProcessor.ts │ │ │ ├── yamlFormatter.ts │ │ │ ├── yamlParser.ts │ │ │ ├── yamlUpdater.ts │ │ │ └── yamlUtils.ts │ ├── ChallengeDetailPage │ │ ├── ChallengeActions.tsx │ │ ├── ChallengeDescription.tsx │ │ ├── ChallengeExpiredAlert.tsx │ │ ├── ChallengeHeader.tsx │ │ ├── ChallengeMetadata.tsx │ │ ├── ChallengePagination.tsx │ │ ├── ChallengeSolutions.tsx │ │ ├── ChallengeTags.tsx │ │ ├── index.tsx │ │ └── utils.ts │ ├── ChallengeFilters.tsx │ ├── ChallengeListPage │ │ ├── ChallengeControls.tsx │ │ ├── ChallengeData.ts │ │ ├── ChallengeFilters.tsx │ │ ├── ChallengeListItem.tsx │ │ ├── SimpleChallengeList.tsx │ │ ├── exports.ts │ │ └── index.tsx │ ├── FileViewer.js │ ├── GitHubRibbon.tsx │ ├── GitHubStarCounter.tsx │ ├── HomePage │ │ ├── ChallengeSection.tsx │ │ ├── FeatureSection.tsx │ │ ├── HeroSection.tsx │ │ ├── index.tsx │ │ └── styles.ts │ ├── IdTag.tsx │ ├── NavBar.tsx │ ├── PageTitle.tsx │ ├── PlatformTag.tsx │ ├── SearchBox.tsx │ ├── StarRating.tsx │ └── TopicTag.tsx ├── gh-fork-ribbon.css ├── i18n.ts ├── i18n │ └── utils.ts ├── index.css ├── locales │ ├── en.ts │ └── zh.ts ├── main.tsx ├── plugins │ └── VirtualFileSystemPlugin │ │ ├── challengeProcessor │ │ ├── collector.ts │ │ ├── index.ts │ │ ├── processor.ts │ │ └── types.ts │ │ ├── fileUtils.ts │ │ ├── imageProcessor │ │ ├── index.ts │ │ ├── markdownProcessor.ts │ │ ├── types.ts │ │ └── utils.ts │ │ ├── index.ts │ │ └── types.ts ├── react-app-env.d.ts ├── services │ └── SearchService.ts ├── styles.tsx ├── styles │ ├── dropdown.css │ ├── github-ribbon-fix.css │ ├── index.css │ ├── markdown.css │ └── star-rating.css ├── types │ ├── challenge.ts │ ├── challenge.ts │ ├── vfs.ts │ └── web-vitals.d.ts └── vite-env.d.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json ├── vercel.json └── vite.config.ts /.env.example: -------------------------------------------------------------------------------- 1 | # 环境变量配置示例 2 | # 此文件用于说明项目需要的环境变量 3 | VITE_BAIDU_ANALYTICS_ID=your_baidu_analytics_site_id -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREP/crawler-leetcode/c8cc9ba6b648113632ab633ff19b80de04c2fdbe/.eslintrc -------------------------------------------------------------------------------- /.github/workflows/deploy-github-pages.yml: -------------------------------------------------------------------------------- 1 | name: 部署GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main # 或者是master,取决于你的主分支名称 7 | 8 | jobs: 9 | build-and-deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: 检出代码 13 | uses: actions/checkout@v3 14 | with: 15 | persist-credentials: false # token将用于部署,因此我们不持久化凭证 16 | 17 | - name: 设置Node.js环境 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: '18' # 使用更新的Node.js版本 21 | cache: 'npm' 22 | 23 | - name: 安装依赖 24 | run: npm ci 25 | 26 | - name: 构建网站 27 | run: npm run build # 在package.json中定义为tsc && vite build 28 | 29 | - name: 部署到GitHub Pages 30 | uses: JamesIves/github-pages-deploy-action@v4 31 | with: 32 | branch: gh-pages # 部署到的分支 33 | folder: dist # Vite构建输出目录 34 | clean: true # 清理gh-pages分支上的旧文件 35 | token: ${{ secrets.GITHUB_TOKEN }} # GitHub自动提供的访问令牌 36 | 37 | - name: 部署完成通知 38 | run: 'echo "✅ 网站已成功部署到GitHub Pages!访问地址: https://jsrep.github.io/crawler-leetcode/"' -------------------------------------------------------------------------------- /.github/workflows/deploy-to-vercel.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Vercel 2 | 3 | on: 4 | push: 5 | branches: 6 | - main # 或者你的默认分支名 7 | workflow_dispatch: # 允许手动触发 8 | 9 | env: 10 | VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} 11 | VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} 12 | 13 | jobs: 14 | deploy: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 2 21 | 22 | - name: Setup Node.js 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: '20' 26 | 27 | - name: Install dependencies 28 | run: npm install 29 | 30 | - name: Install Vercel CLI 31 | run: npm install --global vercel@latest 32 | 33 | - name: Pull Vercel Environment Information 34 | run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} 35 | 36 | - name: Build Project Artifacts 37 | run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} 38 | 39 | - name: Deploy Project Artifacts to Vercel 40 | run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # 环境变量文件 16 | .env 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | # Editor directories and files 23 | .vscode/* 24 | !.vscode/extensions.json 25 | .idea 26 | .DS_Store 27 | *.suo 28 | *.ntvs* 29 | *.njsproj 30 | *.sln 31 | *.sw? 32 | .vercel 33 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # 项目根目录创建 .npmrc 2 | strict-peer-dependencies=false 3 | legacy-peer-deps=true -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 JavaScript Reverse Engineering Practice 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.assets/image-20250413185708120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREP/crawler-leetcode/c8cc9ba6b648113632ab633ff19b80de04c2fdbe/README.assets/image-20250413185708120.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeetCode 爬虫挑战 2 | 3 | [![部署GitHub Pages](https://github.com/JSREP/crawler-leetcode/actions/workflows/deploy-github-pages.yml/badge.svg)](https://github.com/JSREP/crawler-leetcode/actions/workflows/deploy-github-pages.yml) 4 | 5 | 这个仓库收集了各种网站的爬虫挑战案例,展示了不同类型的反爬虫技术和解决方案。项目使用React+TypeScript开发,通过GitHub Pages进行部署。 6 | 7 | **在线访问**: [https://jsrep.github.io/crawler-leetcode/](https://jsrep.github.io/crawler-leetcode/) (需要VPN访问) 8 | 9 | ![image-20250413185708120](./README.assets/image-20250413185708120.png) 10 | 11 | ## 项目结构 12 | 13 | ``` 14 | crawler-leetcode/ 15 | ├── .github/ # GitHub相关配置 16 | │ └── workflows/ # GitHub Actions工作流配置 17 | ├── docs/ # 文档和挑战定义 18 | │ └── challenges/ # 爬虫挑战YAML定义文件 19 | ├── public/ # 静态资源 20 | ├── src/ # 源代码 21 | │ ├── components/ # React组件 22 | │ ├── pages/ # 页面组件 23 | │ ├── plugins/ # 项目插件 24 | │ ├── utils/ # 工具函数 25 | │ └── App.tsx # 应用入口 26 | ├── package.json # 项目依赖 27 | └── vite.config.ts # Vite配置 28 | ``` 29 | 30 | ## 爬虫挑战 31 | 32 | 所有爬虫挑战都定义在 `docs/challenges/` 目录中,使用YAML格式描述挑战的特点、难度和解决方案。详细的贡献指南请参考 [挑战贡献指南](docs/challenges/README.md)。 33 | 34 | 目前包含的挑战类型: 35 | 36 | - 验证码挑战(如reCAPTCHA、hCaptcha) 37 | - 浏览器指纹识别 38 | - JavaScript混淆与加密 39 | - API限流与保护 40 | - WebAssembly保护 41 | - 设备指纹和行为分析 42 | 43 | ## 本地开发 44 | 45 | ```bash 46 | # 克隆项目 47 | git clone https://github.com/JSREP/crawler-leetcode.git 48 | cd crawler-leetcode 49 | 50 | # 安装依赖 51 | npm install 52 | 53 | # 启动开发服务器 54 | npm run dev 55 | 56 | # 构建项目 57 | npm run build 58 | 59 | # 预览构建结果 60 | npm run preview 61 | ``` 62 | 63 | ## 自动部署 64 | 65 | 本项目配置了GitHub Actions自动部署流程,当代码推送到主分支时,会自动构建并部署到GitHub Pages: 66 | 67 | 1. 检出代码 68 | 2. 设置Node.js环境 69 | 3. 安装依赖 70 | 4. 构建项目 71 | 5. 部署到gh-pages分支 72 | 73 | 你可以在 `.github/workflows/deploy-github-pages.yml` 文件中查看完整的工作流配置。 74 | 75 | ## 贡献指南 76 | 77 | 1. Fork本仓库 78 | 2. 创建新分支 (`git checkout -b feature/new-challenge`) 79 | 3. 提交更改 (`git commit -m 'Add new challenge: XXX'`) 80 | 4. 推送到分支 (`git push origin feature/new-challenge`) 81 | 5. 创建Pull Request 82 | 83 | 欢迎贡献新的爬虫挑战案例、改进文档或代码! 84 | 85 | ## 许可证 86 | 87 | MIT 88 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-env', 4 | '@babel/preset-react', 5 | '@babel/preset-typescript' 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | 作者这个字段,是要能够自动补全的,单击输入的就要把平台上所有的作者都下拉展开(按照出现次数倒序排列),然后根据用户的输入自动筛选补全,当然用户也可以不选择已有的作者,选择输入新的作者都是可以的,下拉仅仅只是为了补全,减少用户输入的工作量 2 | 3 | 4 | 5 | 6 | 7 | 8 | 检查docs/challenges下所有的yaml文件: 9 | 1. 如果缺少name_en或者name_en为空,则将name字段的值翻译为英文作为此字段的值; 10 | 2. 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/challenges/91porn视频播放链接加密.yml: -------------------------------------------------------------------------------- 1 | id: 102 2 | id-alias: 91porn-videos 3 | platform: Web 4 | name: 91porn视频播放链接加密 5 | name_en: "" 6 | difficulty-level: 3 7 | description-markdown: 91porn视频播放链接加密,评3星主要在于抵制不良诱惑 8 | base64-url: aHR0cHM6Ly85MXBvcm4uY29tL3ZpZXdfdmlkZW8ucGhwP3ZpZXdrZXk9NTMyYWMxNzE3ZjI4NzY5YWUxM2EmYz02aGh5aiZ2aWV3dHlwZT0mY2F0ZWdvcnk9 9 | is-expired: false 10 | tags: 11 | - js-reverse 12 | - video 13 | solutions: [] 14 | create-time: 2025-04-13 05:07:16 15 | update-time: 2025-04-13 05:07:16 16 | -------------------------------------------------------------------------------- /docs/challenges/AkamaiBot管理器.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 5 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: akamai-bot 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - ai-detection 16 | - behavior-analysis 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: Akamai Bot Manager 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: Akamai Bot Manager 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 4 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | Akamai的AI驱动爬虫检测系统,可识别自动化工具的行为模式。 40 | 41 | 特点:利用机器学习分析用户行为,识别异常访问模式。 42 | 43 | 破解难点:需要模拟真实用户的浏览行为和交互方式,突破AI行为分析。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly93d3cuYWthbWFpLmNvbS9wcm9kdWN0cy9ib3QtbWFuYWdlcg== 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:04 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:04 -------------------------------------------------------------------------------- /docs/challenges/Barracuda WAF.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 26 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: barracuda-waf 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - signature-based 16 | - ip-reputation 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: Barracuda WAF 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: Barracuda WAF 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 3 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | Barracuda基于签名的Web应用防火墙。 40 | 41 | 特点:利用预定义的签名库识别已知爬虫工具,结合IP信誉评估系统阻止恶意来源。 42 | 43 | 破解难点:需要绕过签名检测和IP信誉系统,模拟正常客户端的行为特征。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly93d3cuYmFycmFjdWRhLmNvbS8= 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:25 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:25 -------------------------------------------------------------------------------- /docs/challenges/Citrix Bot Management.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 25 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: citrix-bot 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - api-protection 16 | - behavior-analysis 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: Citrix Bot Management 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: Citrix Bot Management 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 4 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | Citrix的API防护解决方案,包含爬虫行为分析模块。 40 | 41 | 特点:专注于API保护,通过行为分析识别异常访问模式,防止API滥用。 42 | 43 | 破解难点:需要模拟合法API调用模式,避免触发行为分析系统的告警。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly93d3cuY2l0cml4LmNvbS8= 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:24 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:24 -------------------------------------------------------------------------------- /docs/challenges/Cloudbric WAF.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 30 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: cloudbric-waf 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - log-analysis 16 | - behavior-profiling 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: Cloudbric WAF 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: Cloudbric WAF 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 3 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | Cloudbric基于日志分析和行为建模的WAF解决方案。 40 | 41 | 特点:通过深度日志分析和用户行为建模,识别异常访问模式。 42 | 43 | 破解难点:需要模拟正常用户的行为模式,避免触发行为分析系统的异常告警。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly93d3cuY2xvdWRicmljLmNvbS8= 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:29 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:29 -------------------------------------------------------------------------------- /docs/challenges/Cloudflare5秒盾.yml: -------------------------------------------------------------------------------- 1 | # 2 | # 爬虫挑战合集元数据配置文件 3 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 4 | # 当数据结构发生不兼容变更时需递增版本号 5 | version: 1 6 | 7 | # 爬虫挑战合集定义 8 | challenges: 9 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 10 | - id: 4 11 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 12 | id-alias: cloudflare-5s 13 | # 挑战标签系统(数组格式,选填) 14 | # 用于分类和筛选,支持多个标签 15 | tags: 16 | - js-challenge 17 | - ip-reputation 18 | 19 | # 挑战目标网站类型(枚举值,必填) 20 | # 允许值: Web / Android / iOS 21 | platform: Web 22 | 23 | # 挑战名称(必填) 24 | # 作为列表和详情页的标题,建议控制在30个字符以内 25 | name: Cloudflare 5秒盾 26 | 27 | # 挑战英文名称(选填) 28 | # 当用户选择英文语言时显示,不提供时将使用中文名称 29 | name_en: Cloudflare 5s Challenge 30 | 31 | # 挑战难度评级(整数类型,必填) 32 | # 取值范围: 1-5,1表示最简单,5表示最难 33 | # 前端展示时会转换为星级显示 34 | difficulty-level: 4 35 | 36 | # Markdown格式详细描述(必选) 37 | # 当需要复杂排版时使用 38 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 39 | description-markdown: | 40 | Cloudflare的JS挑战防护,要求客户端执行JavaScript代码后才能继续访问。 41 | 42 | 特点:利用浏览器执行JS代码验证客户端身份,通常需要等待5秒钟。 43 | 44 | 破解难点:JS代码经过混淆,每次请求动态生成,需要正确执行JS计算才能获取有效Cookie。 45 | 46 | # 挑战目标网站URL的base64编码 47 | base64-url: aHR0cHM6Ly93d3cuY2xvdWRmbGFyZS5jb20vd2Vic2l0ZS1zZWN1cml0eS9ib3QtbWFuYWdlbWVudC8= 48 | 49 | # 链接有效性状态(布尔值) 50 | # 标记挑战链接是否失效,true表示已失效 51 | # 失效挑战会在前端显示警告标志 52 | is-expired: false 53 | 54 | # 创建时间(ISO 8601格式) 55 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 56 | # 时区默认为UTC+8 57 | create-time: 2025-03-01 00:00:03 58 | 59 | # 最后更新时间(ISO 8601格式) 60 | # 记录挑战最后修改时间,格式与create-time相同 61 | # 当任何字段变更时需同步更新此时间 62 | update-time: 2025-03-01 00:00:03 -------------------------------------------------------------------------------- /docs/challenges/DataDome.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 9 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: datadome 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - real-time-detection 16 | - js-obfuscation 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: DataDome 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: DataDome 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 4 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | 实时爬虫检测系统,结合机器学习与行为分析技术。 40 | 41 | 特点:毫秒级实时响应,基于机器学习的爬虫检测,JS混淆保护。 42 | 43 | 破解难点:需要解决JS混淆与实时检测,模拟人类行为特征。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly9kYXRhZG9tZS5jby9wcm9kdWN0L2JvdC1tYW5hZ2VtZW50Lw== 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:08 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:08 -------------------------------------------------------------------------------- /docs/challenges/F5Shape安全.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 10 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: f5-shape 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - anti-automation 16 | - behavior-analysis 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: F5 Shape Security 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: F5 Shape Security 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 4 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | F5的反自动化解决方案,专门防护撞库和爬虫攻击。 40 | 41 | 特点:专注于防止自动化攻击,如撞库、账号盗用和数据爬取。 42 | 43 | 破解难点:需要绕过复杂的行为分析系统,模拟真实用户交互。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly93d3cuZjUuY29tL3Byb2R1Y3RzL3NoYXBlLXNlY3VyaXR5 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:09 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:09 -------------------------------------------------------------------------------- /docs/challenges/Fastly Bot Management.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 24 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: fastly-bot 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - edge-computing 16 | - rate-limiting 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: Fastly Bot Management 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: Fastly Bot Management 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 3 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | Fastly边缘计算节点实现的爬虫流量管理。 40 | 41 | 特点:在边缘节点进行爬虫检测和流量控制,减轻源站压力,提供精准的速率限制。 42 | 43 | 破解难点:需要处理分布式边缘节点的检测机制,同时应对请求速率限制。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly93d3cuZmFzdGx5LmNvbS9wcm9kdWN0cy9ib3QtbWl0aWdhdGlvbg== 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:23 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:23 -------------------------------------------------------------------------------- /docs/challenges/Google reCAPTCHA v3.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 20 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: recaptcha-v3 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - risk-analysis 16 | - no-captcha 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: Google reCAPTCHA v3 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: Google reCAPTCHA v3 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 4 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | Google的无感验证系统,通过用户行为计算风险分数。 40 | 41 | 特点:在后台分析用户行为给出风险评分,无需用户主动交互,体验更流畅。 42 | 43 | 破解难点:需要模拟完整的用户交互过程,包括鼠标移动、页面滚动等多维度行为特征。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS9yZWNhcHRjaGEv 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:19 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:19 -------------------------------------------------------------------------------- /docs/challenges/ImpervaBot防护.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 7 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: imperva-bot 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - ai-detection 16 | - traffic-analysis 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: Imperva Bot Protection 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: Imperva Bot Protection 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 4 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | Imperva的高级爬虫防护,通过AI分析流量特征识别自动化工具。 40 | 41 | 特点:深度学习分析流量模式,识别恶意爬虫行为。 42 | 43 | 破解难点:需要模拟真实用户的访问模式和行为特征,绕过AI检测系统。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly93d3cuaW1wZXJ2YS5jb20vcHJvZHVjdHMvYm90LW1hbmFnZW1lbnQv 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:06 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:06 -------------------------------------------------------------------------------- /docs/challenges/Kasada.yml: -------------------------------------------------------------------------------- 1 | id: 110 2 | id-alias: Kasada 3 | platform: Web 4 | name: Kasada防护 5 | name_en: "" 6 | difficulty-level: 3 7 | description-markdown: Kasada防护 8 | base64-url: aHR0cHM6Ly93d3cua2FzYWRhLmlvL3Byb2R1Y3Qv 9 | is-expired: false 10 | tags: 11 | - js-reverse 12 | solutions: [] 13 | create-time: 2025-04-13 08:00:31 14 | update-time: 2025-04-13 08:00:31 15 | -------------------------------------------------------------------------------- /docs/challenges/LeetCode.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | challenges: 3 | - id: 101 4 | id-alias: leetcode-crawler 5 | tags: 6 | - js-reverse 7 | - graphql 8 | - rate-limit 9 | - anti-crawler 10 | platform: Web 11 | name: LeetCode抓取挑战 12 | name_en: LeetCode Crawler Challenge 13 | difficulty-level: 3 14 | description-markdown: | 15 | LeetCode是一个广受欢迎的在线编程学习平台,提供了大量的编程题目和讨论区。抓取LeetCode的题目、提交、讨论等内容面临多种反爬机制。 16 | 17 | ## 特点 18 | 19 | - **GraphQL API**:LeetCode使用GraphQL API作为主要数据接口,需要构造正确的查询语句 20 | - **登录验证**:部分内容需要登录才能访问,包括完整的题目描述、提交记录等 21 | - **速率限制**:对请求频率有严格限制,过快的请求会被临时封禁 22 | - **防盗链**:图片等资源有referer检查,直接请求可能会被拒绝 23 | - **Cookie验证**:依赖多个Cookie字段进行会话验证和CSRF防护 24 | 25 | ## 破解难点 26 | 27 | - 需要正确分析和构造GraphQL查询参数 28 | - 登录流程包含多重验证,需要模拟浏览器行为 29 | - 需要实现有效的请求频率控制和错误重试机制 30 | - 一些题目内容通过JavaScript动态加载和渲染 31 | - 接口返回格式可能随时变化,需要及时调整抓取策略 32 | 33 | ## 典型抓取场景 34 | 35 | 1. 抓取题目列表和题目详情 36 | 2. 获取用户提交历史和代码 37 | 3. 爬取题目讨论和解答 38 | 4. 提取题目标签和分类信息 39 | 5. 获取竞赛历史和排名数据 40 | 41 | 需要注意的是,LeetCode的API可能会定期更新,抓取策略也需要相应调整。建议在抓取时遵循平台的robots.txt规则和使用条款。 42 | base64-url: aHR0cHM6Ly9sZWV0Y29kZS5jb20v 43 | is-expired: false 44 | solutions: 45 | - title: 使用Python和requests-html抓取LeetCode 46 | url: https://github.com/yourusername/leetcode-crawler 47 | source: GitHub 48 | author: Anonymous 49 | create-time: 2023-11-15 08:30:00 50 | update-time: 2023-11-15 08:30:00 -------------------------------------------------------------------------------- /docs/challenges/Palo Alto Prisma Cloud.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 27 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: paloalto-bot 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - machine-learning 16 | - api-security 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: Palo Alto Prisma Cloud 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: Palo Alto Prisma Cloud 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 4 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | Palo Alto的云安全解决方案,包含AI驱动的爬虫检测。 40 | 41 | 特点:利用机器学习技术分析API调用模式,识别自动化工具的异常行为。 42 | 43 | 破解难点:需要绕过AI行为分析系统,同时处理API安全防护措施。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly93d3cucGFsb2FsdG9uZXR3b3Jrcy5jb20vcHJpc21hL2Nsb3Vk 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:26 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:26 -------------------------------------------------------------------------------- /docs/challenges/PerimeterX.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 22 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: perimeterx 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - behavior-analysis 16 | - device-fingerprint 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: PerimeterX 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: PerimeterX 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 5 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | 高级行为分析防护系统,通过设备指纹和用户行为模式识别爬虫。 40 | 41 | 特点:采用深度行为分析技术,结合设备指纹识别,能够精确区分人类与机器行为。 42 | 43 | 破解难点:需要全方位模拟人类行为特征,同时处理复杂的设备指纹识别挑战。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly93d3cucGVyaW1ldGVyeC5pby8= 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:21 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:21 -------------------------------------------------------------------------------- /docs/challenges/README.md: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集贡献指南 2 | 3 | 本目录收集了各种网站的爬虫挑战,每个挑战都描述了一个特定网站的反爬机制和技术细节。如果你希望贡献新的挑战,请按照以下指南操作。 4 | 5 | ## 目录结构 6 | 7 | ``` 8 | docs/challenges/ 9 | ├── README.md # 本文件:贡献指南 10 | ├── meta.yml # 示例和元数据格式说明 11 | ├── Challenge Name 1.yml # 挑战1的YAML文件 12 | ├── Challenge Name 2.yml # 挑战2的YAML文件 13 | └── ... # 更多挑战文件 14 | ``` 15 | 16 | ## 如何贡献新挑战 17 | 18 | 1. 每个爬虫挑战需要保存为一个单独的YAML文件 19 | 2. 文件名应该与挑战名称一致,例如:`Akamai Bot Manager.yml` 20 | 3. 确保你的YAML文件符合以下格式规范 21 | 22 | ## YAML文件结构 23 | 24 | 每个挑战YAML文件必须包含以下基本格式: 25 | 26 | ```yaml 27 | version: 1 28 | challenges: 29 | - id: <唯一ID> 30 | # 其他字段... 31 | ``` 32 | 33 | ### 必填字段 34 | 35 | - `version`: 数据结构版本,目前固定为1 36 | - `challenges`: 包含单个挑战信息的数组(即使只有一个挑战) 37 | - `id`: 挑战的唯一ID(整数) 38 | - `id-alias`: ID的别名,用于URL和引用(字符串) 39 | - `platform`: 平台类型,目前支持:Web、Android、iOS 40 | - `name`: 挑战名称(中文) 41 | - `difficulty-level`: 难度级别(1-5,整数) 42 | - `description-markdown` 或 `description-markdown-path`: 挑战描述(选其一) 43 | - `base64-url`: 目标网站URL的base64编码 44 | - `is-expired`: 链接是否已失效(布尔值) 45 | - `create-time`: 创建时间(格式:YYYY-MM-DD HH:mm:ss) 46 | - `update-time`: 更新时间(格式:YYYY-MM-DD HH:mm:ss) 47 | 48 | ### 可选字段 49 | 50 | - `name_en`: 挑战英文名称 51 | - `tags`: 标签数组,如 ['js-reverse', 'wasm', 'jsvmp'] 52 | - `description-markdown_en` 或 `description-markdown-path_en`: 英文描述 53 | - `solutions`: 解决方案数组,每个解决方案包含title、url、source和author字段 54 | 55 | ## 挑战描述建议 56 | 57 | 描述应包含以下内容: 58 | 1. 简单介绍目标网站的功能和特点 59 | 2. 详细描述其反爬机制和技术特点 60 | 3. 可能的难点和解决思路 61 | 4. 尽量客观描述,避免主观评价 62 | 5. 可以使用Markdown语法增强可读性 63 | 64 | ## 完整示例 65 | 66 | ```yaml 67 | version: 1 68 | challenges: 69 | - id: 999 70 | id-alias: example-akamai 71 | tags: 72 | - browser-fingerprint 73 | - behavior-analysis 74 | platform: Web 75 | name: Akamai Bot Manager 76 | name_en: Akamai Bot Manager 77 | difficulty-level: 5 78 | description-markdown: | 79 | Akamai Bot Manager是一种高级反爬虫解决方案,使用多层防护机制识别和阻止自动化流量。 80 | 81 | 特点: 82 | - 设备指纹识别:收集浏览器、系统和硬件特征 83 | - 行为分析:监控鼠标移动、点击模式和导航行为 84 | - 机器学习:基于历史数据识别异常行为 85 | 86 | 破解难点: 87 | - 需要准确模拟真实用户的浏览器环境 88 | - 必须生成逼真的人类行为模式 89 | - 要应对动态变化的检测算法 90 | base64-url: aHR0cHM6Ly93d3cuYWthbWFpLmNvbS8= 91 | is-expired: false 92 | create-time: 2025-03-01 00:00:01 93 | update-time: 2025-03-01 00:00:01 94 | ``` 95 | 96 | ## 提交流程 97 | 98 | 1. Fork本仓库 99 | 2. 创建新分支: `git checkout -b add-new-challenge` 100 | 3. 添加你的挑战YAML文件到`docs/challenges/`目录 101 | 4. 提交修改: `git commit -m "Add new challenge: XXX"` 102 | 5. 推送到你的Fork: `git push origin add-new-challenge` 103 | 6. 创建Pull Request 104 | 105 | ## 注意事项 106 | 107 | - 确保提供的URL是合法的,且不涉及违法内容 108 | - 描述应客观准确,不包含主观评价或不恰当内容 109 | - 日期格式必须符合:`YYYY-MM-DD HH:mm:ss` 110 | - 在提交前请验证YAML格式是否正确 111 | 112 | 感谢你对爬虫挑战合集的贡献! 113 | -------------------------------------------------------------------------------- /docs/challenges/Radware Bot Manager.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 23 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: radware-bot 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - signature-detection 16 | - js-challenge 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: Radware Bot Manager 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: Radware Bot Manager 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 4 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | Radware的爬虫管理系统,结合签名检测和JS挑战。 40 | 41 | 特点:通过特征签名识别已知爬虫工具,同时使用JS挑战验证客户端执行能力。 42 | 43 | 破解难点:需要绕过特征检测,同时解决JS挑战获取有效会话凭证。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly93d3cucmFkd2FyZS5jb20vcHJvZHVjdHMvYm90LW1hbmFnZXI= 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:22 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:22 -------------------------------------------------------------------------------- /docs/challenges/Sophos Web App Firewall.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 28 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: sophos-waf 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - signature-detection 16 | - anomaly-detection 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: Sophos Web App Firewall 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: Sophos Web App Firewall 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 3 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | Sophos的Web应用防火墙,支持异常流量检测。 40 | 41 | 特点:结合特征签名和行为异常检测,识别自动化工具的访问模式。 42 | 43 | 破解难点:需要绕过特征检测,同时避免触发异常行为监测系统。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly93d3cuc29waG9zLmNvbS9wcm9kdWN0cy93ZWItYXBwbGljYXRpb24tZmlyZXdhbGw= 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:27 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:27 -------------------------------------------------------------------------------- /docs/challenges/Sucuri Firewall.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 29 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: sucuri-waf 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - dns-level 16 | - virtual-patching 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: Sucuri Firewall 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: Sucuri Firewall 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 3 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | Sucuri的DNS级Web应用防火墙,提供虚拟补丁防护。 40 | 41 | 特点:通过DNS级别进行流量过滤,提供虚拟补丁技术快速应对新威胁。 42 | 43 | 破解难点:需要绕过DNS级防护和虚拟补丁防护机制,处理动态更新的安全规则。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly9zdWN1cmkubmV0L3dlYnNpdGUtZmlyZXdhbGw= 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:28 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:28 -------------------------------------------------------------------------------- /docs/challenges/hCaptcha.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 21 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: hcaptcha 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - image-classification 16 | - proof-of-work 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: hCaptcha 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: hCaptcha 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 4 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | 基于图像分类的验证系统,部分版本要求解决PoW挑战。 40 | 41 | 特点:通过要求用户进行图像分类、识别特定物体来验证人机身份,部分版本使用工作量证明机制。 42 | 43 | 破解难点:需要结合计算机视觉技术处理图像识别问题,同时应对可能的工作量证明挑战。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly93d3cuaGNhcHRjaGEuY29tLw== 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:20 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:20 -------------------------------------------------------------------------------- /docs/challenges/meta.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 0 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: example 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | # 建议使用有意义的标签,例如防护机制类型、技术特点等 15 | tags: 16 | - js-reverse 17 | - wasm 18 | - jsvmp 19 | 20 | # 挑战目标网站类型(枚举值,必填) 21 | # 允许值: Web / Android / iOS / WeChat-MiniProgram / Electron / Windows-Native / Mac-Native / Linux-Native 22 | platform: Web 23 | 24 | # 挑战名称(必填) 25 | # 作为列表和详情页的标题,建议控制在30个字符以内 26 | name: 示例挑战 27 | 28 | # 挑战英文名称(选填) 29 | # 当用户选择英文语言时显示,不提供时将使用中文名称 30 | name_en: Example Challenge 31 | 32 | # 挑战难度评级(整数类型,必填) 33 | # 取值范围: 1-5,1表示最简单,5表示最难 34 | # 前端展示时会转换为星级显示 35 | difficulty-level: 1 36 | 37 | # Markdown格式详细描述(必选) 38 | # 当需要复杂排版时使用 39 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 40 | # 建议包含:简介、特点、破解难点等内容 41 | description-markdown: | 42 | 这是一个示例挑战,用于演示YAML格式的正确写法。 43 | 44 | 特点:包含了JavaScript混淆、WebAssembly和JS虚拟机保护技术。 45 | 46 | 破解难点:需要分析混淆的JS代码,理解WebAssembly运行机制,处理JS虚拟机保护。 47 | 48 | # 英文版Markdown格式详细描述(选填) 49 | # 当用户选择英文语言时显示,与description-markdown-path_en字段二选一使用 50 | # 不提供英文描述时将使用中文描述 51 | description-markdown_en: | 52 | This is an example challenge to demonstrate the correct YAML format. 53 | 54 | Features: Includes JavaScript obfuscation, WebAssembly and JS virtual machine protection. 55 | 56 | Challenges: Requires analyzing obfuscated JS code, understanding WebAssembly mechanisms, and handling JS virtual machine protection. 57 | 58 | # Markdown文件路径(推荐,但与上面的字段二选一) 59 | # 指向包含完整挑战描述的Markdown文件,支持图片等复杂内容 60 | # 路径应为相对于项目根目录的相对路径 61 | # 与description-markdown字段二选一使用 62 | # description-markdown-path: contents/example/description.md 63 | 64 | # 英文版Markdown文件路径(选填) 65 | # 指向包含英文版完整挑战描述的Markdown文件 66 | # 与description-markdown_en字段二选一使用 67 | # 当用户选择英文语言时显示此内容 68 | # description-markdown-path_en: contents/example/description_en.md 69 | 70 | # 挑战目标网站URL的base64编码(必填) 71 | # 使用base64编码是为了避免直接暴露URL 72 | # 可以使用在线工具将URL转换为base64格式 73 | base64-url: aHR0cHM6Ly9leGFtcGxlLmNvbS8= 74 | 75 | # 链接有效性状态(布尔值,必填) 76 | # 标记挑战链接是否失效,true表示已失效 77 | # 失效挑战会在前端显示警告标志 78 | is-expired: false 79 | 80 | # 解决方案集合(数组,选填) 81 | # 每个解决方案应包含以下字段: 82 | # title: 解决方案标题(必填) 83 | # url: 解决方案链接(完整URL) 84 | # source: 来源平台(如GitHub、博客等) 85 | # author: 贡献者名称(可选) 86 | solutions: 87 | - title: 示例解决方案 88 | url: https://example.com/solution 89 | source: GitHub 90 | author: 贡献者 91 | 92 | # 创建时间(ISO 8601格式,必填) 93 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 94 | # 时区默认为UTC+8 95 | create-time: 2025-03-01 20:42:17 96 | 97 | # 最后更新时间(ISO 8601格式,必填) 98 | # 记录挑战最后修改时间,格式与create-time相同 99 | # 当任何字段变更时需同步更新此时间 100 | update-time: 2025-03-01 20:42:17 101 | 102 | # 是否忽略该挑战 103 | # 设置为true时,该挑战不会在列表中显示,并且编译构建时也会忽略不会被打包 104 | ignored: false -------------------------------------------------------------------------------- /docs/challenges/力哥爱英语开发者工具打开检测.yml: -------------------------------------------------------------------------------- 1 | id: 108 2 | id-alias: "" 3 | platform: Web 4 | name: 力哥爱英语开发者工具打开检测 5 | name_en: "" 6 | difficulty-level: 1 7 | description-markdown: 按F12和打开开发者工具之后都有防护 8 | base64-url: aHR0cHM6Ly9pZW5nbGlzaDUyMS5jb20v 9 | is-expired: false 10 | tags: 11 | - js-reverse 12 | solutions: [] 13 | create-time: 2025-04-13 07:45:34 14 | update-time: 2025-04-13 07:45:34 15 | -------------------------------------------------------------------------------- /docs/challenges/加速乐.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 33 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: jsl 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | # 示例: ["js-reverse", "wasm", "jsvmp"] 15 | tags: 16 | - js-reverse 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: 加速乐 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: JSL 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 2 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | 特点:采用 三次请求+动态Cookie 机制(AAEncode+OB混淆),每次访问需解密JS生成有效Cookie15。 40 | 41 | 破解难点:动态加密算法、多层Cookie校验、JS混淆代码难以逆向。 42 | 43 | # 英文版Markdown格式详细描述(选填) 44 | # 当用户选择英文语言时显示,与description-markdown-path_en字段二选一使用 45 | # 不提供英文描述时将使用中文描述 46 | description-markdown_en: "JSL" 47 | 48 | # Markdown文件路径(推荐) 49 | # 指向包含完整挑战描述的Markdown文件,支持图片等复杂内容 50 | # 路径应为相对于项目根目录的相对路径 51 | # 与description-markdown字段二选一使用 52 | # description-markdown-path: contents/example/description.md 53 | 54 | # 英文版Markdown文件路径(选填) 55 | # 指向包含英文版完整挑战描述的Markdown文件 56 | # 与description-markdown_en字段二选一使用 57 | # 当用户选择英文语言时显示此内容 58 | # description-markdown-path_en: contents/example/description_en.md 59 | 60 | # 挑战目标网站URL的base64编码 61 | base64-url: aHR0cHM6Ly93d3cuanNsLmNvbS5jbi8= 62 | 63 | # 链接有效性状态(布尔值) 64 | # 标记挑战链接是否失效,true表示已失效 65 | # 失效挑战会在前端显示警告标志 66 | is-expired: false 67 | 68 | # 解决方案集合(数组) 69 | # 每个解决方案应包含以下字段: 70 | # title: 解决方案标题(必填) 71 | # url: 解决方案链接(完整URL) 72 | # source: 来源平台(如GitHub、博客等) 73 | # author: 贡献者名称(可选) 74 | # solutions: 75 | # - title: JS逆向解决方案 76 | # url: http://fake.com 77 | # source: fake 78 | # author: fake 79 | 80 | # 创建时间(ISO 8601格式) 81 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 82 | # 时区默认为UTC+8 83 | create-time: 2025-04-12 10:00:00 84 | 85 | # 最后更新时间(ISO 8601格式) 86 | # 记录挑战最后修改时间,格式与create-time相同 87 | # 当任何字段变更时需同步更新此时间 88 | update-time: 2025-04-12 10:00:00 89 | -------------------------------------------------------------------------------- /docs/challenges/商丘市教育体育局鼠标移动设置Cookie.yml: -------------------------------------------------------------------------------- 1 | id: 106 2 | id-alias: "" 3 | platform: Web 4 | name: 商丘市教育体育局鼠标移动设置Cookie 5 | name_en: "" 6 | difficulty-level: 1 7 | description-markdown: |+ 8 | 9 | 第一次访问网站的时候,让移动鼠标,移动鼠标的时候会设置cookie。 10 | 11 | 如果不是第一次访问,隐身模式访问即可。 12 | 13 | 14 | 15 | base64-url: aHR0cHM6Ly9qeXR5ai5zaGFuZ3FpdS5nb3YuY24vendnay9mZHpkZ2tuci96ZmNnMzFzcXNqeXR5ai96YmdnMzFzcXNqeXR5ag== 16 | is-expired: false 17 | tags: 18 | - js-reverse 19 | solutions: [] 20 | create-time: 2025-04-13 07:29:54 21 | update-time: 2025-04-13 07:29:54 22 | -------------------------------------------------------------------------------- /docs/challenges/瑞数.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 32 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: river 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | # 示例: ["js-reverse", "wasm", "jsvmp"] 15 | tags: 16 | - js-reverse 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: 瑞数动态安全(River Security) 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: Test Challenge 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 3 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | 瑞数动态安全(River Security) 40 | 41 | 特点:基于行为分析和轨迹检测,要求用户手动滑动滑块,结合AI识别是否为自动化工具8。 42 | 43 | 破解难点:检测鼠标轨迹、加速度、浏览器指纹等,普通模拟滑动难以通过。 44 | 45 | # 英文版Markdown格式详细描述(选填) 46 | # 当用户选择英文语言时显示,与description-markdown-path_en字段二选一使用 47 | # 不提供英文描述时将使用中文描述 48 | description-markdown_en: "Test Challenge: A sample challenge for breaking through JavaScript anti-crawling mechanisms" 49 | 50 | # Markdown文件路径(推荐) 51 | # 指向包含完整挑战描述的Markdown文件,支持图片等复杂内容 52 | # 路径应为相对于项目根目录的相对路径 53 | # 与description-markdown字段二选一使用 54 | # description-markdown-path: contents/example/description.md 55 | 56 | # 英文版Markdown文件路径(选填) 57 | # 指向包含英文版完整挑战描述的Markdown文件 58 | # 与description-markdown_en字段二选一使用 59 | # 当用户选择英文语言时显示此内容 60 | # description-markdown-path_en: contents/example/description_en.md 61 | 62 | # 挑战目标网站URL的base64编码 63 | base64-url: aHR0cHM6Ly93d3cucmlzdW4uY29tL3Byb2R1Y3RzL3dhZi8= 64 | 65 | # 链接有效性状态(布尔值) 66 | # 标记挑战链接是否失效,true表示已失效 67 | # 失效挑战会在前端显示警告标志 68 | is-expired: false 69 | 70 | # 解决方案集合(数组) 71 | # 每个解决方案应包含以下字段: 72 | # title: 解决方案标题(必填) 73 | # url: 解决方案链接(完整URL) 74 | # source: 来源平台(如GitHub、博客等) 75 | # author: 贡献者名称(可选) 76 | solutions: 77 | - title: JS逆向解决方案 78 | url: http://fake.com 79 | source: fake 80 | author: fake 81 | 82 | # 创建时间(ISO 8601格式) 83 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 84 | # 时区默认为UTC+8 85 | create-time: 2025-03-01 20:42:17 86 | 87 | # 最后更新时间(ISO 8601格式) 88 | # 记录挑战最后修改时间,格式与create-time相同 89 | # 当任何字段变更时需同步更新此时间 90 | update-time: 2025-03-01 20:42:17 91 | -------------------------------------------------------------------------------- /docs/challenges/网易易盾.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 6 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: netease-yidun 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - device-fingerprint 16 | - slider-captcha 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: 网易易盾 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: NetEase Yidun 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 3 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | 网易推出的综合防护方案,包含滑块验证、设备指纹检测等技术。 40 | 41 | 特点:多种验证方式结合,设备指纹识别与行为分析相结合。 42 | 43 | 破解难点:需要绕过多层验证机制,处理动态变化的验证逻辑。 44 | 45 | base64-url: aHR0cHM6Ly9kdW4uMTYzLmNvbS8= 46 | 47 | # 链接有效性状态(布尔值) 48 | # 标记挑战链接是否失效,true表示已失效 49 | # 失效挑战会在前端显示警告标志 50 | is-expired: false 51 | 52 | # 创建时间(ISO 8601格式) 53 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 54 | # 时区默认为UTC+8 55 | create-time: 2025-03-01 00:00:05 56 | 57 | # 最后更新时间(ISO 8601格式) 58 | # 记录挑战最后修改时间,格式与create-time相同 59 | # 当任何字段变更时需同步更新此时间 60 | update-time: 2025-03-01 00:00:05 -------------------------------------------------------------------------------- /docs/challenges/腾讯天御验证码.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 8 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: tencent-captcha 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | tags: 15 | - no-captcha 16 | - behavior-analysis 17 | 18 | # 挑战目标网站类型(枚举值,必填) 19 | # 允许值: Web / Android / iOS 20 | platform: Web 21 | 22 | # 挑战名称(必填) 23 | # 作为列表和详情页的标题,建议控制在30个字符以内 24 | name: 腾讯天御验证码 25 | 26 | # 挑战英文名称(选填) 27 | # 当用户选择英文语言时显示,不提供时将使用中文名称 28 | name_en: Tencent Tianyu Captcha 29 | 30 | # 挑战难度评级(整数类型,必填) 31 | # 取值范围: 1-5,1表示最简单,5表示最难 32 | # 前端展示时会转换为星级显示 33 | difficulty-level: 3 34 | 35 | # Markdown格式详细描述(必选) 36 | # 当需要复杂排版时使用 37 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 38 | description-markdown: | 39 | 腾讯推出的智能验证方案,通过行为分析实现无感验证。 40 | 41 | 特点:无需用户主动操作,通过后台行为分析判断是否为机器人。 42 | 43 | 破解难点:需要模拟真实用户的行为特征,通过行为分析系统的判断。 44 | 45 | # 挑战目标网站URL的base64编码 46 | base64-url: aHR0cHM6Ly9jbG91ZC50ZW5jZW50LmNvbS9wcm9kdWN0L3RpYW55dWNhcHRjaGE= 47 | 48 | # 链接有效性状态(布尔值) 49 | # 标记挑战链接是否失效,true表示已失效 50 | # 失效挑战会在前端显示警告标志 51 | is-expired: false 52 | 53 | # 创建时间(ISO 8601格式) 54 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 55 | # 时区默认为UTC+8 56 | create-time: 2025-03-01 00:00:07 57 | 58 | # 最后更新时间(ISO 8601格式) 59 | # 记录挑战最后修改时间,格式与create-time相同 60 | # 当任何字段变更时需同步更新此时间 61 | update-time: 2025-03-01 00:00:07 -------------------------------------------------------------------------------- /docs/challenges/阿里滑块.yml: -------------------------------------------------------------------------------- 1 | # 爬虫挑战合集元数据配置文件 2 | # 用于在数据结构变更时进行版本兼容性校验,必须为整数 3 | # 当数据结构发生不兼容变更时需递增版本号 4 | version: 1 5 | 6 | # 爬虫挑战合集定义 7 | challenges: 8 | # 单个爬虫挑战定义,每个挑战都有一个唯一的id标识,id是必须的,ID必须是一个整数,并且全局唯一 9 | - id: 31 10 | # 可以给ID设置一个别名,用于在列表中显示,ID别名也可以用于访问详情页 11 | id-alias: ali-slider 12 | # 挑战标签系统(数组格式,选填) 13 | # 用于分类和筛选,支持多个标签 14 | # 示例: ["js-reverse", "wasm", "jsvmp"] 15 | tags: 16 | - js-reverse 17 | - jsvmp 18 | 19 | # 挑战目标网站类型(枚举值,必填) 20 | # 允许值: Web / Android / iOS 21 | platform: Web 22 | 23 | # 挑战名称(必填) 24 | # 作为列表和详情页的标题,建议控制在30个字符以内 25 | name: 阿里云盾滑块验证 26 | 27 | # 挑战英文名称(选填) 28 | # 当用户选择英文语言时显示,不提供时将使用中文名称 29 | name_en: Ali Slider 30 | 31 | # 挑战难度评级(整数类型,必填) 32 | # 取值范围: 1-5,1表示最简单,5表示最难 33 | # 前端展示时会转换为星级显示 34 | difficulty-level: 4 35 | 36 | # Markdown格式详细描述(必选) 37 | # 当需要复杂排版时使用 38 | # 与description-markdown-path字段二选一使用,必须选其中一种方式提供描述 39 | description-markdown: | 40 | 特点:基于行为分析和轨迹检测,要求用户手动滑动滑块,结合AI识别是否为自动化工具8。 41 | 42 | 破解难点:检测鼠标轨迹、加速度、浏览器指纹等,普通模拟滑动难以通过。 43 | 44 | ### 案例网站 45 | - [https://login.taobao.com/havanaone/login/login.htm](https://login.taobao.com/havanaone/login/login.htm) 46 | - [https://home.51cto.com/index/?from_service=icv&reback=https://www.51cto.com/](https://home.51cto.com/index/?from_service=icv&reback=https://www.51cto.com/) 47 | - [https://wf.pub/reader?ft=https%3A%2F%2Fkjzl.wfpub.cn%2Fap%2FgetPdf%3Fid%3D75%26type%3Dperio%26title%3D%E7%A7%91%E6%8A%80%E7%BA%B5%E8%A7%88%202022%E5%B9%B4-122%E6%9C%9F&pageNeed=2&rangeNeed=0%20https://36kr.com/%20%20%E7%99%BB%E5%BD%95](https://wf.pub/reader?ft=https%3A%2F%2Fkjzl.wfpub.cn%2Fap%2FgetPdf%3Fid%3D75%26type%3Dperio%26title%3D%E7%A7%91%E6%8A%80%E7%BA%B5%E8%A7%88%202022%E5%B9%B4-122%E6%9C%9F&pageNeed=2&rangeNeed=0%20https://36kr.com/%20%20%E7%99%BB%E5%BD%95) 48 | - [https://newrank.cn/user/login](https://newrank.cn/user/login) 49 | - [https://www.xiachufang.com/auth/login/](https://www.xiachufang.com/auth/login/) 50 | - [https://www.alizhaopin.com/login_personal.htm](https://www.alizhaopin.com/login_personal.htm) 51 | - [https://account.wps.cn/](https://account.wps.cn/) 52 | - [https://sspai.com/](https://sspai.com/) 53 | - [https://data.xiguaji.com/](https://data.xiguaji.com/) 54 | description-markdown_en: Ali Slider 55 | 56 | # Markdown文件路径(推荐) 57 | # 指向包含完整挑战描述的Markdown文件,支持图片等复杂内容 58 | # 路径应为相对于项目根目录的相对路径 59 | # 与description-markdown字段二选一使用 60 | # description-markdown-path: contents/example/description.md 61 | 62 | # 英文版Markdown文件路径(选填) 63 | # 指向包含英文版完整挑战描述的Markdown文件 64 | # 与description-markdown_en字段二选一使用 65 | # 当用户选择英文语言时显示此内容 66 | # description-markdown-path_en: contents/example/description_en.md 67 | 68 | # 挑战目标网站URL的base64编码 69 | base64-url: aHR0cHM6Ly9kbHAuZ2hzLmNuL3Ivdz9jbWQ9Y29tLnF3aW5ncy5hcHBzLmdocy5nd2Ntcy5zZWFyY2gmdHlwZT0x 70 | 71 | # 链接有效性状态(布尔值) 72 | # 标记挑战链接是否失效,true表示已失效 73 | # 失效挑战会在前端显示警告标志 74 | is-expired: false 75 | 76 | # 解决方案集合(数组) 77 | # 每个解决方案应包含以下字段: 78 | # title: 解决方案标题(必填) 79 | # url: 解决方案链接(完整URL) 80 | # source: 来源平台(如GitHub、博客等) 81 | # author: 贡献者名称(可选) 82 | # solutions: 83 | # - title: JS逆向解决方案 84 | # url: http://fake.com 85 | # source: fake 86 | # author: fake 87 | 88 | # 创建时间(ISO 8601格式) 89 | # 记录挑战首次添加时间,格式: YYYY-MM-DD HH:mm:ss 90 | # 时区默认为UTC+8 91 | create-time: 2025-04-12 10:00:00 92 | 93 | # 最后更新时间(ISO 8601格式) 94 | # 记录挑战最后修改时间,格式与create-time相同 95 | # 当任何字段变更时需同步更新此时间 96 | update-time: 2025-04-13 10:36:24 97 | # 测试热更新 98 | # 再次测试 99 | # 测试热更新3 100 | # 最终测试热更新 101 | -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREP/crawler-leetcode/c8cc9ba6b648113632ab633ff19b80de04c2fdbe/docs/images/logo.png -------------------------------------------------------------------------------- /docs/images/logo文生图提示词.md: -------------------------------------------------------------------------------- 1 | # Logo 生成提示词 2 | 3 | ## 中文提示词 4 | 5 | 设计一个简约而现代的爬虫技术挑战平台标志。核心元素为一个几何化的蜘蛛图形,具有编程代码线条风格。蜘蛛身体部分可融入代码符号或网络节点元素,腿部设计成数据连接线或代码路径。标志使用渐变色,从深蓝色过渡到科技紫色。整体设计需要简洁、扁平、专业,便于在不同尺寸下识别,适合作为网站和应用程序的图标。图像背景应为透明,尺寸为512x512像素,矢量风格,确保边缘清晰锐利。 6 | 7 | ## 英文提示词 8 | 9 | Design a minimalist and modern logo for a Web Crawler Challenge Platform. The core element should be a geometric spider figure with coding line art style. The spider's body can incorporate code symbols or network node elements, with legs designed as data connection lines or code paths. Use a gradient color scheme transitioning from deep blue to tech purple. The overall design should be clean, flat, professional, and easily recognizable at different sizes, suitable for website and application icons. The image background should be transparent, size 512x512 pixels, vector style, ensuring sharp and clean edges. 10 | 11 | ## 风格参考关键词 12 | 13 | - 极简设计 14 | - 几何图形 15 | - 线条艺术 16 | - 编程符号 17 | - 网络节点 18 | - 扁平化2.0 19 | - 渐变色 20 | - 矢量图标 21 | - 高识别度 22 | - 可扩展性强 23 | - 透明背景 24 | - 数据可视化元素 25 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREP/crawler-leetcode/c8cc9ba6b648113632ab633ff19b80de04c2fdbe/favicon.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 爬虫技术挑战平台 8 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crawler-leetcode", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "homepage": "https://jsrep.github.io/crawler-leetcode", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "tsc && vite build", 10 | "lint": "eslint .", 11 | "preview": "vite preview", 12 | "predeploy": "npm run build", 13 | "deploy": "gh-pages -d dist" 14 | }, 15 | "dependencies": { 16 | "@ant-design/icons": "^5.6.1", 17 | "@types/markdown-it": "^14.1.2", 18 | "@types/react-beautiful-dnd": "^13.1.8", 19 | "@types/react-responsive": "^8.0.8", 20 | "antd": "^5.16.4", 21 | "fuse.js": "^7.1.0", 22 | "i18next": "^24.2.2", 23 | "i18next-browser-languagedetector": "^8.0.4", 24 | "markdown-it": "^14.1.0", 25 | "react": "^18.2.0", 26 | "react-beautiful-dnd": "^13.1.1", 27 | "react-dom": "^18.2.0", 28 | "react-ga4": "^2.1.0", 29 | "react-i18next": "^15.4.1", 30 | "react-markdown": "^10.1.0", 31 | "react-markdown-editor-lite": "^1.3.4", 32 | "react-responsive": "^10.0.1", 33 | "react-router-dom": "^6.23.1", 34 | "react-syntax-highlighter": "^15.6.1", 35 | "rehype-raw": "^7.0.0", 36 | "web-vitals": "^4.2.4", 37 | "yaml": "^2.3.4" 38 | }, 39 | "devDependencies": { 40 | "@faker-js/faker": "^9.6.0", 41 | "@types/js-yaml": "^4.0.9", 42 | "@types/node": "^22.14.0", 43 | "@types/react": "^18.2.67", 44 | "@types/react-dom": "^18.2.22", 45 | "@types/react-syntax-highlighter": "^15.5.13", 46 | "@typescript-eslint/eslint-plugin": "5.62.0", 47 | "@typescript-eslint/parser": "5.62.0", 48 | "@vitejs/plugin-react": "^4.3.4", 49 | "chokidar": "^4.0.3", 50 | "eslint": "^8.57.0", 51 | "eslint-plugin-react": "^7.37.4", 52 | "gh-pages": "^6.3.0", 53 | "js-yaml": "^4.1.0", 54 | "typescript": "^4.9.5", 55 | "vite": "^6.2.6" 56 | }, 57 | "browserslist": { 58 | "production": [ 59 | ">0.2%", 60 | "not dead", 61 | "not op_mini all" 62 | ], 63 | "development": [ 64 | "last 1 chrome version", 65 | "last 1 firefox version", 66 | "last 1 safari version" 67 | ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /public/CC11001100-wechat-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREP/crawler-leetcode/c8cc9ba6b648113632ab633ff19b80de04c2fdbe/public/CC11001100-wechat-qrcode.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREP/crawler-leetcode/c8cc9ba6b648113632ab633ff19b80de04c2fdbe/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 爬虫 LeetCode 13 | 14 | 15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | width: 100%; 3 | min-height: 100vh; 4 | display: flex; 5 | flex-direction: column; 6 | } 7 | 8 | .app-container { 9 | flex: 1; 10 | width: 100%; 11 | margin: 0 auto; 12 | padding: 0 0; 13 | } 14 | 15 | .page-content { 16 | padding: 2rem 0; 17 | } 18 | 19 | .ant-layout-header { 20 | background: #fff; 21 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 22 | } 23 | 24 | .ant-menu-horizontal { 25 | line-height: 64px; 26 | } 27 | 28 | /* 导航菜单项样式 */ 29 | .ant-menu-item { 30 | font-size: 16px; 31 | font-weight: 500; 32 | padding: 0 16px !important; 33 | } 34 | 35 | .ant-menu-item-selected { 36 | color: #42b983 !important; 37 | } 38 | 39 | .ant-menu-item-selected::after { 40 | background-color: #42b983 !important; 41 | height: 3px !important; 42 | } 43 | 44 | /* 导航栏容器宽度 */ 45 | .navbar-container { 46 | max-width: 1000px; 47 | margin: 0 auto; 48 | } 49 | 50 | /* 防止菜单项显示为省略号 */ 51 | .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item, 52 | .ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu { 53 | padding: 0 20px; 54 | } 55 | 56 | .ant-card { 57 | margin-bottom: 1rem; 58 | } 59 | 60 | .logo { 61 | height: 6em; 62 | padding: 1.5em; 63 | will-change: filter; 64 | transition: filter 300ms; 65 | } 66 | .logo:hover { 67 | filter: drop-shadow(0 0 2em #646cffaa); 68 | } 69 | .logo.react:hover { 70 | filter: drop-shadow(0 0 2em #61dafbaa); 71 | } 72 | 73 | @keyframes logo-spin { 74 | from { 75 | transform: rotate(0deg); 76 | } 77 | to { 78 | transform: rotate(360deg); 79 | } 80 | } 81 | 82 | @media (prefers-reduced-motion: no-preference) { 83 | a:nth-of-type(2) .logo { 84 | animation: logo-spin infinite 20s linear; 85 | } 86 | } 87 | 88 | .card { 89 | padding: 2em; 90 | } 91 | 92 | .read-the-docs { 93 | color: #888; 94 | } 95 | 96 | /* 移动端响应式样式 */ 97 | @media (max-width: 768px) { 98 | /* 导航栏调整 */ 99 | .navbar-container { 100 | width: 100%; 101 | padding: 0 16px; 102 | } 103 | 104 | /* 移动端内容区域调整 */ 105 | .content-wrapper { 106 | padding: 10px 0 !important; 107 | } 108 | 109 | /* 移动端卡片样式调整 */ 110 | .ant-card { 111 | border-radius: 8px; 112 | } 113 | 114 | /* 移动端表格调整 */ 115 | .ant-table { 116 | font-size: 12px; 117 | } 118 | 119 | /* 移动端表单样式 */ 120 | .ant-form-item-label { 121 | padding-bottom: 4px; 122 | } 123 | 124 | /* 移动端按钮样式 */ 125 | .ant-btn { 126 | font-size: 14px; 127 | height: 32px; 128 | padding: 0 15px; 129 | } 130 | 131 | /* 防止溢出 */ 132 | .mobile-container { 133 | overflow-x: hidden; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import {HashRouter, BrowserRouter, Route, Routes} from 'react-router-dom' 2 | import './App.css' 3 | import NavBar from './components/NavBar' 4 | import HomePage from './components/HomePage' 5 | import ChallengeListPage from './components/ChallengeListPage' 6 | import ChallengeDetailPage from './components/ChallengeDetailPage' 7 | import ChallengeContributePage from './components/ChallengeContributePage' 8 | import AboutPage from './components/AboutPage' 9 | import GitHubRibbon from './components/GitHubRibbon' 10 | import PageTitle from './components/PageTitle' 11 | import './gh-fork-ribbon.css'; 12 | import './styles/github-ribbon-fix.css'; 13 | 14 | // 根据环境选择路由器 15 | const Router = import.meta.env.VERCEL ? BrowserRouter : HashRouter; 16 | 17 | const App = () => { 18 | return ( 19 | 20 |
21 | 22 | 23 | 24 |
25 | 26 | }/> 27 | }/> 28 | }/> 29 | }/> 30 | }/> 31 | }/> 32 | 33 |
34 |
35 |
36 | ) 37 | } 38 | 39 | export default App -------------------------------------------------------------------------------- /src/assets/CC11001100-wechat-qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREP/crawler-leetcode/c8cc9ba6b648113632ab633ff19b80de04c2fdbe/src/assets/CC11001100-wechat-qrcode.png -------------------------------------------------------------------------------- /src/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREP/crawler-leetcode/c8cc9ba6b648113632ab633ff19b80de04c2fdbe/src/assets/favicon.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JSREP/crawler-leetcode/c8cc9ba6b648113632ab633ff19b80de04c2fdbe/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/AboutPage/AboutCard.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Card, Typography } from 'antd'; 3 | import { useTranslation } from 'react-i18next'; 4 | import { cardStyle, textStyle } from './styles'; 5 | 6 | const { Text } = Typography; 7 | 8 | /** 9 | * 关于爬虫LeetCode的描述卡片组件 10 | */ 11 | const AboutCard: React.FC = () => { 12 | const { t } = useTranslation(); 13 | 14 | return ( 15 | 21 | 22 | {t('about.description')} 23 | 24 | 25 | ); 26 | }; 27 | 28 | export default AboutCard; -------------------------------------------------------------------------------- /src/components/AboutPage/ContactCard.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Card, Space, Tag, Typography, Image, Divider } from 'antd'; 3 | import { useTranslation } from 'react-i18next'; 4 | import { GithubOutlined, StarFilled, WechatOutlined } from '@ant-design/icons'; 5 | import { cardStyle, githubIconStyle, githubLinkStyle, starTagStyle, textStyle } from './styles'; 6 | import wechatQrcode from '../../assets/CC11001100-wechat-qrcode.png'; 7 | 8 | const { Text, Link } = Typography; 9 | 10 | interface ContactCardProps { 11 | repoStars: number | string | null; 12 | } 13 | 14 | /** 15 | * 联系我们卡片组件 16 | */ 17 | const ContactCard: React.FC = ({ repoStars }) => { 18 | const { t } = useTranslation(); 19 | 20 | return ( 21 | 27 | 28 | 29 | {t('about.contact.email')}:CC11001100@qq.com 30 | 31 | 32 | 33 | 34 | 39 | JSREP/crawler-leetcode 40 | 41 | 42 | {repoStars !== null ? ( 43 | } 45 | style={starTagStyle} 46 | > 47 | {repoStars} 48 | 49 | ) : ( 50 | {t('about.contact.loading')} 51 | )} 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 扫码加微信,拉你进逆向技术讨论群 60 | 61 | 加好友时请备注【逆向】,方便我知道你是从这里来的~ 62 | 微信二维码 69 | 70 | 71 | 72 | ); 73 | }; 74 | 75 | export default ContactCard; -------------------------------------------------------------------------------- /src/components/AboutPage/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { Typography } from 'antd'; 3 | import { useTranslation } from 'react-i18next'; 4 | import AboutCard from './AboutCard'; 5 | import ContactCard from './ContactCard'; 6 | import { containerStyle, contentStyle, titleDividerStyle, titleStyle } from './styles'; 7 | 8 | const { Title } = Typography; 9 | 10 | /** 11 | * 关于页面组件 12 | * 包含"关于爬虫LeetCode"和"联系我们"两个部分 13 | */ 14 | const AboutPage = () => { 15 | const { t } = useTranslation(); 16 | const [repoStars, setRepoStars] = useState(null); 17 | 18 | // 获取GitHub仓库star数量 19 | useEffect(() => { 20 | const fetchRepoStars = async () => { 21 | try { 22 | const response = await fetch('https://api.github.com/repos/JSREP/crawler-leetcode'); 23 | if (response.ok) { 24 | const data = await response.json(); 25 | setRepoStars(data.stargazers_count); 26 | } else { 27 | console.error(`Failed to fetch: ${response.status}`); 28 | setRepoStars('获取失败'); 29 | } 30 | } catch (error) { 31 | console.error('Error:', error); 32 | setRepoStars('获取失败'); 33 | } 34 | }; 35 | fetchRepoStars(); 36 | }, []); 37 | 38 | return ( 39 |
40 | 41 | {t('about.title')} 42 | <div style={titleDividerStyle} /> 43 | 44 | 45 |
46 | {/* 关于爬虫LeetCode */} 47 | 48 | 49 | {/* 联系我们 */} 50 | 51 |
52 |
53 | ); 54 | }; 55 | 56 | export default AboutPage; -------------------------------------------------------------------------------- /src/components/AboutPage/styles.ts: -------------------------------------------------------------------------------- 1 | import { CSSProperties } from 'react'; 2 | 3 | // 页面容器样式 4 | export const containerStyle: CSSProperties = { 5 | padding: 24, 6 | background: '#f8f9fa', 7 | minHeight: '100vh' 8 | }; 9 | 10 | // 标题和分割线样式 11 | export const titleStyle: CSSProperties = { 12 | textAlign: 'center', 13 | marginBottom: 48 14 | }; 15 | 16 | // 标题下方的装饰线样式 17 | export const titleDividerStyle: CSSProperties = { 18 | height: 4, 19 | background: 'linear-gradient(to right, #42b983, #3eaf7c)', 20 | width: 100, 21 | margin: '10px auto 0' 22 | }; 23 | 24 | // 内容区域样式 25 | export const contentStyle: CSSProperties = { 26 | maxWidth: 1000, 27 | margin: '0 auto' 28 | }; 29 | 30 | // 卡片公共样式 31 | export const cardStyle: CSSProperties = { 32 | boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)', 33 | transition: 'all 0.3s', 34 | marginBottom: 32 35 | }; 36 | 37 | // 卡片文本样式 38 | export const textStyle: CSSProperties = { 39 | color: '#4a5568', 40 | lineHeight: 1.8, 41 | fontSize: '16px', 42 | display: 'block' 43 | }; 44 | 45 | // GitHub图标样式 46 | export const githubIconStyle: CSSProperties = { 47 | color: '#4a5568', 48 | fontSize: '18px' 49 | }; 50 | 51 | // GitHub链接样式 52 | export const githubLinkStyle: CSSProperties = { 53 | color: '#42b983', 54 | fontSize: '16px' 55 | }; 56 | 57 | // Star标签样式 58 | export const starTagStyle: CSSProperties = { 59 | background: '#42b983', 60 | color: '#fff', 61 | borderRadius: 20, 62 | marginLeft: 8 63 | }; -------------------------------------------------------------------------------- /src/components/ChallengeContributePage/components/BackupHistoryModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Modal, List, Button, Space, Empty, Typography, Divider } from 'antd'; 3 | import { ReloadOutlined, HistoryOutlined, DeleteOutlined } from '@ant-design/icons'; 4 | 5 | const { Text, Title } = Typography; 6 | 7 | interface BackupHistoryModalProps { 8 | visible: boolean; 9 | onClose: () => void; 10 | backupOptions: { label: string; value: string }[]; 11 | onRecover: (timestamp: string) => void; 12 | } 13 | 14 | /** 15 | * 备份历史模态框组件 16 | * 用于显示和恢复历史备份 17 | */ 18 | const BackupHistoryModal: React.FC = ({ 19 | visible, 20 | onClose, 21 | backupOptions, 22 | onRecover 23 | }) => { 24 | return ( 25 | 28 | 29 | 备份历史 30 | 31 | } 32 | open={visible} 33 | onCancel={onClose} 34 | footer={[ 35 | 38 | ]} 39 | width={600} 40 | > 41 | {backupOptions.length === 0 ? ( 42 | 43 | ) : ( 44 | <> 45 | 46 | 您可以恢复以下任一备份。点击"恢复"按钮将用所选备份替换当前表单数据。 47 | 48 | 49 | 50 | 51 | ( 55 | } 62 | onClick={() => onRecover(item.value)} 63 | > 64 | 恢复 65 | 66 | ]} 67 | > 68 | } 70 | title={备份时间: {item.label}} 71 | description="点击恢复按钮将用此备份替换当前表单数据。此操作不可撤销。" 72 | /> 73 | 74 | )} 75 | /> 76 | 77 | )} 78 | 79 | ); 80 | }; 81 | 82 | export default BackupHistoryModal; -------------------------------------------------------------------------------- /src/components/ChallengeContributePage/components/BasicInfo.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Form, Input, InputNumber, Radio, FormInstance } from 'antd'; 3 | import { SectionProps } from '../types'; 4 | 5 | interface BasicInfoProps { 6 | form: FormInstance; 7 | } 8 | 9 | /** 10 | * 挑战基本信息表单部分 11 | */ 12 | const BasicInfo: React.FC = ({ form }) => { 13 | return ( 14 | <> 15 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | 32 | 37 | 38 | Web 39 | Android 40 | iOS 41 | 42 | 43 | 44 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | ); 60 | }; 61 | 62 | export default BasicInfo; -------------------------------------------------------------------------------- /src/components/ChallengeContributePage/components/DescriptionFields.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Suspense, lazy, useState, useEffect } from 'react'; 3 | import { Form, Spin } from 'antd'; 4 | import { SectionProps } from '../types'; 5 | // 导入已抽取的验证规则 6 | import { descriptionValidators } from '../utils/validators'; 7 | // 导入钩子 8 | import { useMarkdownEditor } from '../hooks'; 9 | import { markdownEditorStyles, customEditorStyles, getEditorConfig } from '../utils/markdownStyleUtils'; 10 | 11 | // 懒加载Markdown编辑器组件 12 | const MdEditor = lazy(() => 13 | import('react-markdown-editor-lite').then(module => { 14 | // 同时导入样式 15 | import('react-markdown-editor-lite/lib/index.css'); 16 | return { default: module.default }; 17 | }) 18 | ); 19 | 20 | // 编辑器加载中占位组件 21 | const EditorLoading = () => ( 22 |
23 | 24 |
25 | ); 26 | 27 | /** 28 | * 描述字段组件 29 | * 提供中英文双语Markdown编辑功能 30 | */ 31 | const DescriptionFields: React.FC = ({ form }) => { 32 | // 状态管理 33 | const [isEditorReady, setIsEditorReady] = useState(false); 34 | 35 | // 使用自定义hook管理编辑器状态 36 | const { 37 | markdownRenderer, 38 | editorChineseRef, 39 | editorEnglishRef, 40 | styleRef, 41 | handleChineseEditorChange, 42 | handleEnglishEditorChange, 43 | handleImageUpload 44 | } = useMarkdownEditor({ 45 | form, 46 | chineseFieldName: 'descriptionMarkdown', 47 | englishFieldName: 'descriptionMarkdownEn' 48 | }); 49 | 50 | // 添加自定义样式 51 | useEffect(() => { 52 | // 插入自定义样式到head中 53 | const styleElement = document.createElement('style'); 54 | styleElement.innerHTML = markdownEditorStyles + customEditorStyles; 55 | document.head.appendChild(styleElement); 56 | 57 | // 标记编辑器准备就绪 58 | setIsEditorReady(true); 59 | 60 | // 组件卸载时移除样式 61 | return () => { 62 | if (styleElement && document.head.contains(styleElement)) { 63 | document.head.removeChild(styleElement); 64 | } 65 | }; 66 | }, []); 67 | 68 | // 编辑器配置 69 | const editorConfig = React.useMemo(() => getEditorConfig(), []); 70 | 71 | return ( 72 | <> 73 | 78 | }> 79 | {isEditorReady && ( 80 | markdownRenderer.render(text)} 84 | onChange={handleChineseEditorChange} 85 | placeholder="请使用Markdown格式输入题目描述,支持图片、代码块等。可以直接粘贴图片!" 86 | config={editorConfig} 87 | onImageUpload={handleImageUpload} 88 | /> 89 | )} 90 | 91 | 92 | 93 | 97 | }> 98 | {isEditorReady && ( 99 | markdownRenderer.render(text)} 103 | onChange={handleEnglishEditorChange} 104 | placeholder="请使用Markdown格式输入英文题目描述(可选),英文版将在用户切换语言时显示。可以直接粘贴图片!" 105 | config={editorConfig} 106 | onImageUpload={handleImageUpload} 107 | /> 108 | )} 109 | 110 | 111 | 112 | ); 113 | }; 114 | 115 | export default DescriptionFields; -------------------------------------------------------------------------------- /src/components/ChallengeContributePage/components/DifficultySelector.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useState, useEffect } from 'react'; 3 | import { Form, Select, FormInstance } from 'antd'; 4 | import StarRating from '../../StarRating'; 5 | 6 | const { Option } = Select; 7 | 8 | interface DifficultySelectorProps { 9 | form: FormInstance; 10 | value?: number; 11 | onChange?: (value: number) => void; 12 | } 13 | 14 | /** 15 | * 难度选择组件 16 | */ 17 | const DifficultySelector: React.FC = ({ form, value, onChange }) => { 18 | const [currentDifficulty, setCurrentDifficulty] = useState(value || 1); 19 | 20 | // 监听表单中难度级别的变化 21 | useEffect(() => { 22 | const formDifficulty = form.getFieldValue('difficultyLevel'); 23 | if (formDifficulty && formDifficulty !== currentDifficulty) { 24 | setCurrentDifficulty(formDifficulty); 25 | } 26 | }, [form, currentDifficulty]); 27 | 28 | // 难度级别变化处理 29 | const handleDifficultyChange = (value: number) => { 30 | setCurrentDifficulty(value); 31 | form.setFieldsValue({ difficultyLevel: value }); 32 | 33 | if (onChange) { 34 | onChange(value); 35 | } 36 | }; 37 | 38 | // 空函数,只用于让星星显示为可点击状态 39 | const handleStarClick = () => {}; 40 | 41 | return ( 42 | 47 | 106 | 107 | ); 108 | }; 109 | 110 | export default DifficultySelector; -------------------------------------------------------------------------------- /src/components/ChallengeContributePage/components/ResponsiveContainer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useEffect, useState } from 'react'; 3 | import { styles } from '../styles'; 4 | 5 | interface ResponsiveContainerProps { 6 | children: React.ReactNode; 7 | mobileBreakpoint?: number; 8 | } 9 | 10 | /** 11 | * 响应式容器组件 12 | * 根据屏幕尺寸自动调整布局样式 13 | */ 14 | const ResponsiveContainer: React.FC = ({ 15 | children, 16 | mobileBreakpoint = 768 // 默认移动端断点为768px 17 | }) => { 18 | const [isMobile, setIsMobile] = useState(false); 19 | 20 | // 监听窗口尺寸变化 21 | useEffect(() => { 22 | const checkScreenSize = () => { 23 | setIsMobile(window.innerWidth < mobileBreakpoint); 24 | }; 25 | 26 | // 初始检查 27 | checkScreenSize(); 28 | 29 | // 添加resize事件监听 30 | window.addEventListener('resize', checkScreenSize); 31 | 32 | // 清理 33 | return () => { 34 | window.removeEventListener('resize', checkScreenSize); 35 | }; 36 | }, [mobileBreakpoint]); 37 | 38 | // 根据屏幕尺寸选择样式 39 | const containerStyle = isMobile 40 | ? { ...styles.container, ...styles.mobileContainer } 41 | : styles.container; 42 | 43 | return ( 44 |
45 | {children} 46 |
47 | ); 48 | }; 49 | 50 | export default ResponsiveContainer; -------------------------------------------------------------------------------- /src/components/ChallengeContributePage/components/ScrollButtons.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Button, Tooltip } from 'antd'; 3 | import { VerticalAlignTopOutlined, VerticalAlignBottomOutlined } from '@ant-design/icons'; 4 | 5 | /** 6 | * 滚动控制按钮组件,用于快速跳转到页面顶部或底部 7 | */ 8 | const ScrollButtons: React.FC = () => { 9 | // 滚动到页面顶部 10 | const scrollToTop = () => { 11 | window.scrollTo({ 12 | top: 0, 13 | behavior: 'smooth' 14 | }); 15 | }; 16 | 17 | // 滚动到页面底部 18 | const scrollToBottom = () => { 19 | window.scrollTo({ 20 | top: document.documentElement.scrollHeight, 21 | behavior: 'smooth' 22 | }); 23 | }; 24 | 25 | // 按钮样式 26 | const buttonStyle = { 27 | width: '40px', 28 | height: '40px', 29 | borderRadius: '50%', 30 | display: 'flex', 31 | justifyContent: 'center', 32 | alignItems: 'center', 33 | boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)', 34 | border: 'none' 35 | }; 36 | 37 | return ( 38 |
47 | 48 |
66 | ); 67 | }; 68 | 69 | export default ScrollButtons; -------------------------------------------------------------------------------- /src/components/ChallengeContributePage/components/SolutionItem.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Button, Space, Tooltip } from 'antd'; 3 | import { EditOutlined, DeleteOutlined, DragOutlined } from '@ant-design/icons'; 4 | import { Solution } from '../types'; 5 | import { memo } from 'react'; 6 | 7 | interface SolutionItemProps { 8 | solution: Solution; 9 | index: number; 10 | onEdit: (index: number) => void; 11 | onRemove: (index: number) => void; 12 | isDraggable?: boolean; 13 | } 14 | 15 | /** 16 | * 参考资料项目组件 17 | */ 18 | const SolutionItem: React.FC = memo(({ 19 | solution, 20 | index, 21 | onEdit, 22 | onRemove, 23 | isDraggable = false 24 | }) => { 25 | const containerStyle = { 26 | marginBottom: '16px', 27 | padding: '8px', 28 | border: '1px solid #f0f0f0', 29 | borderRadius: '4px', 30 | transition: 'box-shadow 0.2s, transform 0.1s', 31 | background: '#fff', 32 | boxShadow: isDraggable ? '0 1px 2px rgba(0,0,0,0.1)' : 'none', 33 | cursor: isDraggable ? 'grab' : 'default', 34 | position: 'relative' as const, 35 | }; 36 | 37 | return ( 38 |
39 | 40 |
41 | {isDraggable && ( 42 | 43 | 51 | 52 | )} 53 |
54 | 标题: {solution.title} 55 | {solution.source && <> | 来源: {solution.source}} 56 | {solution.author && <> | 作者: {solution.author}} 57 |
58 |
59 | 60 | 63 | 66 | 67 |
68 |
69 | {solution.url} 70 |
71 |
72 | ); 73 | }); 74 | 75 | export default SolutionItem; -------------------------------------------------------------------------------- /src/components/ChallengeContributePage/components/UrlInput.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { FormInstance } from 'antd'; 3 | import Base64UrlInput from './Base64UrlInput'; 4 | 5 | interface UrlInputProps { 6 | form: FormInstance; 7 | onChange?: (value: string) => void; 8 | } 9 | 10 | /** 11 | * URL输入组件 12 | * 包装Base64UrlInput,提供URL输入和编码功能 13 | */ 14 | const UrlInput: React.FC = ({ form, onChange }) => { 15 | return ( 16 | 20 | ); 21 | }; 22 | 23 | export default UrlInput; -------------------------------------------------------------------------------- /src/components/ChallengeContributePage/components/YamlActionButtons.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Button, message } from 'antd'; 3 | import { CopyOutlined, DownloadOutlined } from '@ant-design/icons'; 4 | 5 | interface YamlActionButtonsProps { 6 | yamlOutput: string; 7 | onCopyYaml: () => void; 8 | onDownloadYaml: () => void; 9 | } 10 | 11 | /** 12 | * YAML操作按钮组件 13 | */ 14 | const YamlActionButtons: React.FC = ({ 15 | yamlOutput, 16 | onCopyYaml, 17 | onDownloadYaml 18 | }) => { 19 | // 处理点击复制按钮 20 | const handleCopyClick = () => { 21 | if (!yamlOutput) { 22 | message.warning('请先生成YAML', 2); 23 | return; 24 | } 25 | onCopyYaml(); 26 | }; 27 | 28 | // 处理点击下载按钮 29 | const handleDownloadClick = () => { 30 | if (!yamlOutput) { 31 | message.warning('请先生成YAML', 2); 32 | return; 33 | } 34 | onDownloadYaml(); 35 | }; 36 | 37 | return ( 38 | <> 39 | 45 | 46 | 52 | 53 | ); 54 | }; 55 | 56 | export default YamlActionButtons; -------------------------------------------------------------------------------- /src/components/ChallengeContributePage/components/YamlPreviewContent.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 3 | import { tomorrow } from 'react-syntax-highlighter/dist/esm/styles/prism'; 4 | import { Alert } from 'antd'; 5 | 6 | interface YamlPreviewContentProps { 7 | yamlOutput: string; 8 | } 9 | 10 | /** 11 | * YAML内容预览组件 12 | */ 13 | const YamlPreviewContent: React.FC = ({ yamlOutput }) => { 14 | if (!yamlOutput) { 15 | return ( 16 | 22 | ); 23 | } 24 | 25 | return ( 26 |
33 | 44 | {yamlOutput} 45 | 46 |
47 | ); 48 | }; 49 | 50 | export default YamlPreviewContent; -------------------------------------------------------------------------------- /src/components/ChallengeContributePage/components/yaml-import/FileImportTab.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Upload, message } from 'antd'; 3 | import { FileTextOutlined } from '@ant-design/icons'; 4 | import { RcFile } from 'antd/es/upload'; 5 | import * as jsYaml from 'js-yaml'; 6 | 7 | interface FileImportTabProps { 8 | onImport: (content: string) => void; 9 | setLoading: (loading: boolean) => void; 10 | } 11 | 12 | /** 13 | * 文件导入标签页组件 14 | */ 15 | const FileImportTab: React.FC = ({ onImport, setLoading }) => { 16 | // 验证YAML内容 17 | const validateYaml = (content: string): boolean => { 18 | try { 19 | jsYaml.load(content); 20 | return true; 21 | } catch (error) { 22 | console.error('YAML解析失败:', error); 23 | message.error('YAML格式错误,请检查内容'); 24 | return false; 25 | } 26 | }; 27 | 28 | // 处理文件导入 29 | const handleFileImport = (file: RcFile) => { 30 | setLoading(true); 31 | const reader = new FileReader(); 32 | 33 | reader.onload = (e: ProgressEvent) => { 34 | try { 35 | const content = e.target?.result as string; 36 | if (validateYaml(content)) { 37 | onImport(content); 38 | message.success('YAML文件导入成功'); 39 | } 40 | } catch (error) { 41 | console.error('读取文件失败:', error); 42 | message.error('读取文件失败'); 43 | } finally { 44 | setLoading(false); 45 | } 46 | }; 47 | 48 | reader.onerror = () => { 49 | message.error('文件读取失败'); 50 | setLoading(false); 51 | }; 52 | 53 | reader.readAsText(file); 54 | 55 | // 阻止自动上传 56 | return false; 57 | }; 58 | 59 | return ( 60 |
61 | 67 |

68 | 69 |

70 |

点击或拖拽文件到此区域上传

71 |

72 | 支持 .yml 或 .yaml 格式文件 73 |

74 |
75 |
76 |

77 | 点击或拖拽YAML文件到上方区域。支持单个挑战或包含多个挑战的集合文件(将导入第一个挑战)。 78 |

79 |
80 |
81 | ); 82 | }; 83 | 84 | export default FileImportTab; -------------------------------------------------------------------------------- /src/components/ChallengeContributePage/components/yaml-import/TextImportTab.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Input, Space, message } from 'antd'; 3 | import * as jsYaml from 'js-yaml'; 4 | 5 | const { TextArea } = Input; 6 | 7 | interface TextImportTabProps { 8 | yamlText: string; 9 | setYamlText: (text: string) => void; 10 | onImport: (content: string) => void; 11 | setLoading: (loading: boolean) => void; 12 | } 13 | 14 | /** 15 | * 文本粘贴导入标签页组件 16 | */ 17 | const TextImportTab: React.FC = ({ 18 | yamlText, 19 | setYamlText, 20 | onImport, 21 | setLoading 22 | }) => { 23 | // 验证YAML内容 24 | const validateYaml = (content: string): boolean => { 25 | try { 26 | jsYaml.load(content); 27 | return true; 28 | } catch (error) { 29 | console.error('YAML解析失败:', error); 30 | message.error('YAML格式错误,请检查内容'); 31 | return false; 32 | } 33 | }; 34 | 35 | // 处理文本导入 36 | const handleTextImport = () => { 37 | if (!yamlText.trim()) { 38 | message.error('请粘贴YAML内容'); 39 | return; 40 | } 41 | 42 | setLoading(true); 43 | try { 44 | if (validateYaml(yamlText)) { 45 | onImport(yamlText); 46 | message.success('粘贴的YAML导入成功'); 47 | } 48 | } finally { 49 | setLoading(false); 50 | } 51 | }; 52 | 53 | React.useEffect(() => { 54 | // 将handleTextImport方法暴露给父组件 55 | const handleEvent = (event: CustomEvent) => { 56 | if (event.detail?.action === 'import-text') { 57 | handleTextImport(); 58 | } 59 | }; 60 | 61 | window.addEventListener('yaml-import-action', handleEvent as EventListener); 62 | 63 | return () => { 64 | window.removeEventListener('yaml-import-action', handleEvent as EventListener); 65 | }; 66 | }, [yamlText]); 67 | 68 | return ( 69 |
70 | 71 |